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.