1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
|
Secure Framework
================
This layer provides wrappers around sendto(2) and recvfrom(2), the
secure versions expect an additional 'struct security_association *sa'.
The security association describes the encryption/authentication and
decryption/validation functions that are used to provide confidentiality
and integrity for sent messages.
The implementation heavily draws on the following IPsec related RFCs,
RFC 2401 -- Security Architecture for the Internet Protocol
RFC 2406 -- IP Encapsulating Security Payload (ESP)
RFC 3948 -- UDP Encapsulation of IPsec ESP Packets
The main difference is that in our case the security layer does not work
between a pair of hosts, but assists the application to create secure
tunnels between (logical) application level endpoints. Unlike IPsec, our
payload does not include the TCP or UDP, and IP headers.
Similar to IPsec, this layer does not implement any key exchange and
setup, that step is left up to the application.
Here is a short description of the important structures and functions,
struct security_association {
/* incoming packets */
/* identifier to match an incoming packet with the the correct
* logical connection. Really only here for the convenience of
* the application, it is not used by secure_recvfrom.
* Security identifiers < 256 are considered 'reserved', see
* secure_sendto/secure_recvfrom */
uint32_t recv_spi;
/* The following are used for to detect replay attacks and
* should be initialized to 0 */
uint32_t recv_seq;
unsigned long recv_win;
/* function descriptor and state for packet validation */
const struct secure_auth *validate;
void *validate_context;
/* function descriptor and state for decryption */
const struct secure_encr *decrypt;
void *decrypt_context;
/* outgoing packets */
/* remote connection identifier */
uint32_t peer_spi;
/* sequence number used for outgoing packets, should be
* initialized to 0 */
uint32_t peer_seq;
/* trusted address of the peer, outgoing encrypted packets will
* be sent to this address, incoming packets that are correctly
* validated will update this address */
struct sockaddr_storage peer;
socklen_t peerlen;
/* initialization vector/counter, should be initialized to a
* random value, secure_sendto will properly increment it */
uint8_t send_iv[MAXIVLEN];
/* function descriptor and context for encryption */
const struct secure_encr *encrypt;
void *encrypt_context;
/* function descriptor and context for packet authentication */
const struct secure_auth *authenticate;
void *authenticate_context;
};
This structure contains all the information required to securely send
and receive encrypted and authenticated packets.
ssize_t secure_recvfrom(int s, void *buf, size_t len, int flags,
struct sockaddr *peer, socklen_t *peerlen,
struct security_association **sa,
struct security_association *(*GETSA)(uint32_t spi))
Wrapper around recvfrom to decrypt/validate incoming packets. If the
packet is smaller than 8 bytes, or the first big-endian 32-bit word is
less than 256, the packet is considered unencrypted and passed through
without any futher processing.
Otherwise, the GETSA callback is called with the 32-bit identifier (in
native byte order). If GETSA returns NULL, the packet is dropped and we
return EAGAIN which is (in Linux) identical to a received packet with a
bad UDP checksum.
If GETSA did return a valid security association,
- the sequence number is checked (anti-replay)
- we validate the message checksum
- update the receive window and sa->peer
- decrypt the packet
- check the padding
If any of these steps fail, the packet is dropped and EAGAIN is
returned. 'peer' is set whenever the packet is received, but sa->peer
is only updated when the packet has been successfully validated.
ssize_t secure_sendto(int s, const void *buf, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen,
struct security_association *sa)
Send a packet securely. If sa is NULL or does not have an encryption and
authentication function defined, and the first 32-bit big-endian word in
buf is less than 256 then the data in 'buf' is sent as-is to the address
specified by the 'to' argument. The check if the value is less than 256
is to make sure the packet does not get interpreted by the receiver side
as a valid encrypted packet.
If we do have a valid security association, the payload is padded,
encrypted and authenticated. The result is sent to the address in
sa->peer (i.e. NOT to the 'to' address).
Implemented authentication and encryption modes
===============================================
The framework is pretty flexible and should be able to support many
encryption and authentication algorithms, however the provided ones are
all based on the AES block cipher.
AES-XCBC-MAC-96 authentication
==============================
RFC 3566 -- The AES-XCBC-MAC-96 Algorithm and Its Use With IPsec
AES-CBC based message integrity checksum, requires a 128-bit (16-byte)
key and adds 12 checksum bytes to the packet.
AES-CBC encryption
==================
NIST Special Publication 800-38A -- Recommendation for Block Cipher
Modes of Operation
RFC 3602 -- The AES-CBC Cipher Algorithm and Its Use with IPsec
This is a pretty straightforward and well understood cipher block
chaining mode using AES as the encryption algorithm. CBC encryption
requires the initialization vector to be the same size as the encryption
block (which is 16-bytes for AES) The initialization vector has to be
unpredictable, so we encrypt the IV-counter that was set up by the
generic code in secure_sendto to obtain a pseudo random sequence.
Can be used with 128, 192, and 256 bit keys (16, 24, and 32 bytes).
AES-CCM encryption
==================
NIST Special Publication 800-38C -- Recommendation for Block Cipher
Modes of Operation: The CCM Mode for
Authentication and Confidentiality
RFC 4309 -- Using Advanced Encryption Standard (AES) CCM Mode with
IPsec Encapsulating Security Payload (ESP)
This is a combined encryption and authentication algorithm, so we do not
need a separate authentication function. It also only needs 8 bytes for
the initialization vector. The checksum can be either 8, 12, or 16
bytes. As a result this algorithm has a lower per packet overhead, only
between 16 and 24 bytes instead of the 28 bytes we need for AES-CBC with
AES-XCBC-MAC-96.
We also need less key material because we do not need a separate key for
the message authentication algorithm. Finally, this algorithm lends
itself well for several optimizations, it only uses the AES encrypt
operation, initializing the encryption stream can be done off-line
by the sender before the packet has be be sent and it is highly
parallelizable.
This encryption mode uses 152, 206, or 280-bits of key material
(19, 27, or 35 bytes).
Although there are several advantages, there is one pretty significant
disadvantage. When a combination of the same key and initialization
vector is reused at any time it becomes a trivial operation to obtain
the plaintext of both messages. As such it is not recommended to use
this encryption mode when we have static keys, such as during the
initial handshake (user passwords, the keys in a Coda token, etc).
AES implementation
==================
The used AES implementation is the Rijndael reference implementation (v3.0).
I picked this because it seems to be fairly portable ANSI-C and we do not have
to deal with trying to teach automake/autoconf about various platform specific
assembly implementations and they pose no licensing conflicts wrt. to RPC2's
LGPL license.
As an alternative there is also the very small implementation by Mike Scott,
however that code relies on global variables for the encryption/decryption
state, only supports in-place operations and uses fairly generic naming. So it
could use a bit of cleaning up so that the context can be passed avoid
name-clashes.
To make it simply to replace the AES implementation, the remaining code
expects to be able to include "aes.h", which defines 5 functions to
initialize, setup keys and to encrypt/decrypt a single block.
There are several alternative implementation that can be used,
- A more optimized implementation by Dr. Brian Gladman, his code is dual
licensed as BSD with an advertising clause, or alternatively pure GPL.
Neither of these mix well with RPC2's LGPL license so we probably can't
distribute binaries that are linked using his code. If you really need the
extra performance and make sure you comply with his license terms (as this
code is LGPL that would be the BSD license + advertising clause) and provide
the required copyright notice and disclaimer in any documentation and/or
associated materials that you distribute, you can find his version at,
http://fp.gladman.plus.com/AES/index.htm
- There is also a modified version of the rijndael v3.0 reference code
available as part of the wpa_supplicant sources. It can optionally use
smaller tables which make the code 8KB smaller, and possibly make the code
less vulnerable to timing attacks, however it only supports 128-bit keys.
The modification are by Jouni Malinen and it seems to be dual licensed as
BSD without advertising clause or GPL,
http://hostap.epitest.fi/wpa_supplicant/
- There are more, but from what I've seen most implementations are based on,
* original NIST submission (aka. rijndael v2.2 reference implementation),
* The optimized/cleaned-up Rijndael v3.0 reference implementation,
* Mike Scott's code, when the requirements tend to favour small size,
* Brian Gladman's code, when the requirements are mostly performance.
- If you feel brave you can implement your own, the following is an excellent
article that explains a lot of the implementations details,
http://msdn.microsoft.com/msdnmag/issues/03/11/AES/
AES testvectors
===============
testvectors.h contains several AES testvectors from
http://csrc.nist.gov/CryptoToolkit/aes/rijndael/rijndael-vals.zip
If you want to regenerate or expand the number of tests that are run
during initialization, unzip the testvalues in a subdirectory named
'testvalues' and run gen_testvectors.sh to rebuild. At the top of the
script are some comments and possible settings to vary the memory
overhead/execution time to run these tests.
We run the complete set of included test vectors during every startup.
It only adds a delay of about 0.43 seconds on a 600MHz PIII, and less
than 0.07 seconds on a 3.2GHz P4. But the delay does add a tiny amount
of non-deterministic entropy for the PRNG initialization.
PRNG implementation
===================
A deterministic pseudo random number generator based on ANSI X9.31, with
the NIST recommended usage for using AES as a mixing function. The
algorithm is fairly close to CBC mode encryption.
There is a 16-byte pool of random data that we use as the IV. Then when
we want to get random data we generate an initial seed based on the
current timestamp, some uninitialized data from the stack, and a counter.
This block is then encrypted using AES-CBC where the pool is used as the
IV. This results in a block of 16-bytes of random data. The random block
is then xor-ed with the original seed to get the next block of seed
data. We then refresh the pool of random data by encrypting the seed
block. These steps are repeated until we've returned the number of
random bytes that were requested.
To initialize the pool of random data and the AES128 encryption key, we
get the current timestamp, and read random data from /dev/random (or
/dev/urandom). When /dev/random is unavailable we fall back on several
lower entropy sources such as times(), getpid(), and libc's random().
The first block of random data is discarded, and we run a couple of
statistical tests to see if the resulting random data actually looks
reasonable. Passing these tests does not guarantee that the generated
random numbers are cryptographically strong, but it should detect
serious breakage.
RPC2 secure handshake
=====================
The modified RPC2 handshake is based on the analysis and proposed
implementation of the Andrew Secure RPC Handshake in 'A Logic of
Authentication', by Michael Burrows, Martin Abadi, and Roger Needham.
The handshake uses 4 steps so set up separate server->client and
client->server encryption and authentication keys.
1. client -> server: Na, A
this is a normal RPC2 INIT1 packet, but with the RPC2SEC_CAPABLE
flag set in a header field. Na is a nonce which is used to avoid
replay attacks. A is the client identifier, it can be a username
or an encrypted Coda token.
2. server -> client: {Na, K'ab}Kab
The server sends back the nonce and a random key which will be used
for client -> server traffic encrypted with the shared secret that
was obtained from the client identifier. The client knows this is in
response to it's INIT1 packet because of the value of the nonce.
This packet is encrypted with AES-CBC and authenticated with
AES-XCBC-MAC-96
3. client -> server: {Na, K'ba}K'ab
The client responds with the nonce and a random key that will be
used for any further server -> client traffic, this is encrypted and
authenticated with the random session key we received in step 2.
The packet is encrypted with the encryption algorithm that server
chose and sent in step 2 along with the key. Currently this is
AES-CCM8.
4. server -> client: {Na, Nb}K'ba
The server sends back the nonce and an initial sequence number (Nb)
encrypted with the session key and algorithm it received from the
client in step 3.
|