From: Andre Noll Date: Sun, 16 Mar 2014 16:49:59 +0000 (+0100) Subject: Merge branch 't/aes' X-Git-Tag: v0.5.2~8 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=6eab6671f857009b6d6a2de2c2ec8187f869f705;hp=017aa6503f639c7d0bb7b7be200da5fde4f2f4bd Merge branch 't/aes' Cooking since 2014-02-02. * t/aes: Implement aes_ctr128 and prefer it over RC4. server: Lookup user only once. --- diff --git a/NEWS b/NEWS index b535dd6b..84a42058 100644 --- a/NEWS +++ b/NEWS @@ -5,8 +5,17 @@ NEWS 0.5.2 (to be announced) "orthogonal interior" --------------------------------------------- +The new sync filter, the AES_CTR128 stream cipher and the overhauled +network code are the highlights of this release. + - The new sync filter synchronizes playback between multiple clients. + - Connections between para_server and para_client are now + encrypted by means of AES rather than RC4 if both sides + support it. RC4 is still available as a fallback. This + feature is fully transparent, i.e. no command line options + are necessary, and a client linked against openssl can + speak with a server linked against libgcrypt and vice versa. - Major cleanup of the networking subsystem. - Improved user manual. - Minor fixes to avoid clang warnings. diff --git a/client_common.c b/client_common.c index 6652cc35..3b8ed511 100644 --- a/client_common.c +++ b/client_common.c @@ -336,7 +336,8 @@ static int client_post_select(struct sched *s, struct task *t) case CL_RECEIVED_WELCOME: /* send auth command */ if (!FD_ISSET(ct->scc.fd, &s->wfds)) return 0; - sprintf(buf, AUTH_REQUEST_MSG "%s sideband", ct->user); + sprintf(buf, AUTH_REQUEST_MSG "%s sideband%s", ct->user, + has_feature("aes_ctr128", ct)? ",aes_ctr128" : ""); PARA_INFO_LOG("--> %s\n", buf); ret = write_buffer(ct->scc.fd, buf); if (ret < 0) @@ -352,6 +353,7 @@ static int client_post_select(struct sched *s, struct task *t) /* decrypted challenge/session key buffer */ unsigned char crypt_buf[1024]; struct sb_buffer sbb; + bool use_aes; ret = recv_sb(ct, &s->rfds, &sbb); if (ret <= 0) @@ -370,9 +372,10 @@ static int client_post_select(struct sched *s, struct task *t) goto out; ct->challenge_hash = para_malloc(HASH_SIZE); hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash); - ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); + use_aes = has_feature("aes_ctr128", ct); + ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, use_aes); ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, - SESSION_KEY_LEN); + SESSION_KEY_LEN, use_aes); hash_to_asc(ct->challenge_hash, buf); PARA_INFO_LOG("--> %s\n", buf); ct->status = CL_RECEIVED_CHALLENGE; diff --git a/command.c b/command.c index fc098b78..26126cc9 100644 --- a/command.c +++ b/command.c @@ -786,14 +786,20 @@ static void reset_signals(void) para_sigaction(SIGHUP, SIG_DFL); } -static int parse_auth_request(char *buf, int len, struct user **u) +struct connection_features { + bool sideband_requested; + bool aes_ctr128_requested; +}; + +static int parse_auth_request(char *buf, int len, struct user **u, + struct connection_features *cf) { int ret; char *p, *username, **features = NULL; size_t auth_rq_len = strlen(AUTH_REQUEST_MSG); - bool sideband_requested = false; *u = NULL; + memset(cf, 0, sizeof(*cf)); if (len < auth_rq_len + 2) return -E_AUTH_REQUEST; if (strncmp(buf, AUTH_REQUEST_MSG, auth_rq_len) != 0) @@ -809,18 +815,15 @@ static int parse_auth_request(char *buf, int len, struct user **u) create_argv(p, ",", &features); for (i = 0; features[i]; i++) { if (strcmp(features[i], "sideband") == 0) - sideband_requested = true; + cf->sideband_requested = true; + else if (strcmp(features[i], "aes_ctr128") == 0) + cf->aes_ctr128_requested = true; else { ret = -E_BAD_FEATURE; goto out; } } } - if (sideband_requested == false) { /* sideband is mandatory */ - PARA_ERROR_LOG("client did not request sideband\n"); - ret = -E_BAD_FEATURE; - goto out; - } PARA_DEBUG_LOG("received auth request for user %s\n", username); *u = lookup_user(username); ret = 1; @@ -895,10 +898,11 @@ __noreturn void handle_connect(int fd, const char *peername) int ret; unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; unsigned char challenge_hash[HASH_SIZE]; - char *p, *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; + char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; size_t numbytes; struct command_context cc_struct = {.peer = peername}, *cc = &cc_struct; struct iovec iov; + struct connection_features cf; cc->scc.fd = fd; reset_signals(); @@ -909,7 +913,7 @@ __noreturn void handle_connect(int fd, const char *peername) /* send Welcome message */ ret = write_va_buffer(fd, "This is para_server, version " PACKAGE_VERSION ".\n" - "Features: sideband\n" + "Features: sideband,aes_ctr128\n" ); if (ret < 0) goto net_err; @@ -917,12 +921,14 @@ __noreturn void handle_connect(int fd, const char *peername) ret = recv_buffer(fd, buf, HANDSHAKE_BUFSIZE); if (ret < 0) goto net_err; - ret = parse_auth_request(buf, ret, &cc->u); + ret = parse_auth_request(buf, ret, &cc->u, &cf); if (ret < 0) goto net_err; - p = buf + strlen(AUTH_REQUEST_MSG); - PARA_DEBUG_LOG("received auth request for user %s\n", p); - cc->u = lookup_user(p); + if (!cf.sideband_requested) { /* sideband is mandatory */ + PARA_ERROR_LOG("client did not request sideband\n"); + ret = -E_BAD_FEATURE; + goto net_err; + } if (cc->u) { get_random_bytes_or_die(rand_buf, sizeof(rand_buf)); ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf), @@ -939,7 +945,7 @@ __noreturn void handle_connect(int fd, const char *peername) numbytes = 256; get_random_bytes_or_die((unsigned char *)buf, numbytes); } - PARA_DEBUG_LOG("sending %u byte challenge + rc4 keys (%zu bytes)\n", + PARA_DEBUG_LOG("sending %u byte challenge + session key (%zu bytes)\n", CHALLENGE_SIZE, numbytes); ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false); buf = NULL; @@ -969,8 +975,10 @@ __noreturn void handle_connect(int fd, const char *peername) alarm(0); PARA_INFO_LOG("good auth for %s\n", cc->u->name); /* init stream cipher keys with the second part of the random buffer */ - cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); - cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); + cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, + cf.aes_ctr128_requested); + cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, + SESSION_KEY_LEN, cf.aes_ctr128_requested); ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false); if (ret < 0) goto net_err; diff --git a/crypt.c b/crypt.c index 5445138d..e6a31e0e 100644 --- a/crypt.c +++ b/crypt.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -259,14 +260,40 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, return ret < 0? -E_ENCRYPT : ret; } +struct aes_ctr_128_context { + AES_KEY key; + unsigned char ivec[AES_CRT128_BLOCK_SIZE]; + unsigned char ecount[AES_CRT128_BLOCK_SIZE]; + unsigned int num; +}; + struct stream_cipher { - RC4_KEY key; + bool use_aes; + union { + RC4_KEY rc4_key; + struct aes_ctr_128_context aes; + } context; }; -struct stream_cipher *sc_new(const unsigned char *data, int len) +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes) { + int ret; struct stream_cipher *sc = para_malloc(sizeof(*sc)); - RC4_set_key(&sc->key, len, data); + struct aes_ctr_128_context *aes; + + sc->use_aes = use_aes; + if (!use_aes) { + RC4_set_key(&sc->context.rc4_key, len, data); + return sc; + } + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + aes = &sc->context.aes; + ret = AES_set_encrypt_key(data, AES_CRT128_BLOCK_SIZE * 8 /* bits */, + &aes->key); + assert(ret == 0); + memcpy(aes->ivec, data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); + aes->num = 0; return sc; } @@ -282,10 +309,9 @@ void sc_free(struct stream_cipher *sc) */ #define RC4_ALIGN 8 -void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +static void rc4_crypt(RC4_KEY *key, struct iovec *src, struct iovec *dst) { size_t len = src->iov_len, l1, l2; - RC4_KEY *key = &sc->key; assert(len > 0); assert(len < ((typeof(src->iov_len))-1) / 2); @@ -306,6 +332,28 @@ void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) ((char *)dst->iov_base)[len] = '\0'; } +static void aes_ctr128_crypt(struct aes_ctr_128_context *aes, struct iovec *src, + struct iovec *dst) +{ + size_t len = src->iov_len; + + *dst = (typeof(*dst)) { + /* Add one for the terminating zero byte. */ + .iov_base = para_malloc(len + 1), + .iov_len = len + }; + AES_ctr128_encrypt(src->iov_base, dst->iov_base, len, + &aes->key, aes->ivec, aes->ecount, &aes->num); + ((char *)dst->iov_base)[len] = '\0'; +} + +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + if (sc->use_aes) + return aes_ctr128_crypt(&sc->context.aes, src, dst); + return rc4_crypt(&sc->context.rc4_key, src, dst); +} + void hash_function(const char *data, unsigned long len, unsigned char *hash) { SHA_CTX c; diff --git a/crypt.h b/crypt.h index 1406197d..e9657ff5 100644 --- a/crypt.h +++ b/crypt.h @@ -123,10 +123,12 @@ struct stream_cipher_context { * * \param data The key. * \param len The size of the key. + * \param use_aes True: Use the aes_ctr128 stream cipher, false: Use RC4. * * \return A new stream cipher structure. */ -struct stream_cipher *sc_new(const unsigned char *data, int len); +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes); /** * Encrypt or decrypt a buffer using a stream cipher. diff --git a/crypt_backend.h b/crypt_backend.h index 481a215f..3676fff1 100644 --- a/crypt_backend.h +++ b/crypt_backend.h @@ -8,6 +8,9 @@ /* This should only be incuded from files which provide crypto functions. */ +/** AES block size in bytes. */ +#define AES_CRT128_BLOCK_SIZE 16 + size_t is_ssh_rsa_key(char *data, size_t size); uint32_t read_ssh_u32(const void *vp); int uudecode(const char *src, unsigned char *target, size_t targsize); diff --git a/gcrypt.c b/gcrypt.c index 2736a6c7..2a1a90d3 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -912,11 +912,25 @@ struct stream_cipher { gcry_cipher_hd_t handle; }; -struct stream_cipher *sc_new(const unsigned char *data, int len) +struct stream_cipher *sc_new(const unsigned char *data, int len, + bool use_aes) { gcry_error_t gret; - struct stream_cipher *sc = para_malloc(sizeof(*sc)); + + if (use_aes) { + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_CTR, 0); + assert(gret == 0); + gret = gcry_cipher_setkey(sc->handle, data, + AES_CRT128_BLOCK_SIZE); + assert(gret == 0); + gret = gcry_cipher_setctr(sc->handle, + data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); + assert(gret == 0); + return sc; + } gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); if (gret) { diff --git a/web/manual.m4 b/web/manual.m4 index 4edce036..e809c8b2 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -112,7 +112,7 @@ can be used by any scripting language to produce user interfaces with little programming effort. All connections between para_server and para_client are encrypted -with a symmetric RC4 session key. For each user of paraslash you must +with a symmetric session key. For each user of paraslash you must create a public/secret RSA key pair for authentication. If para_client is started without non-option arguments, an interactive @@ -505,9 +505,9 @@ User management para_server uses a challenge-response mechanism to authenticate requests from incoming connections, similar to ssh's public key authentication method. Authenticated connections are encrypted using -the RC4 stream cipher. +a stream cipher, either RC4 or AES in integer counter mode. -In this chapter we briefly describe RSA and RC4 and sketch the +In this chapter we briefly describe RSA, RC4 and AES, and sketch the REFERENCE(Client-server authentication, authentication handshake) between para_client and para_server. User management is discussed in the section on REFERENCE(The user_list file, the user_list file). @@ -517,8 +517,8 @@ in a REFERENCE(Connecting para_audiod, separate section). -RSA and RC4 -~~~~~~~~~~~ +RSA, RC4, AES +~~~~~~~~~~~~~ RSA is an asymmetric block cipher which is used in many applications, including ssh and gpg. An RSA key consists in fact of two keys, @@ -537,6 +537,15 @@ strong encryption by today's standards. Since the same key must never be used twice, a different, randomly-generated key is used for every new connection. +AES, the advanced encryption standard, is a well-known symmetric block +cipher, i.e. a transformation operating on fixed-length blocks which +is determined by a single key for both encryption and decryption. Any +block cipher can be turned into a stream cipher by generating +a pseudo-random key stream by encrypting successive values of a +counter. The AES_CTR128 stream cipher used in paraslash is obtained +in this way from the AES block cipher with a 128 bit block size. + + Client-server authentication ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -557,7 +566,7 @@ as follows: fixed-length buffer with random bytes, encrypts that buffer using the public key and sends the encrypted buffer to the client. The first part of the buffer is the challenge which - is used for authentication while the second part is the RC4 + is used for authentication while the second part is the session key. - para_client receives the encrypted buffer and decrypts it @@ -574,12 +583,12 @@ as follows: - Otherwise the user is considered authenticated and the client is allowed to proceed by sending a command to be executed. From - this point on the communication is encrypted using the RC4 - stream cipher with the session key known to both peers. + this point on the communication is encrypted using the stream + cipher with the session key known to both peers. paraslash relies on the quality of the pseudo-random bytes provided by the crypto library (openssl or libgcrypt), on the security of -the implementation of the RSA and RC4 crypto routines and on the +the implementation of the RSA, RC4 and AES crypto routines and on the infeasibility to invert the SHA1 function. Neither para_server or para_client create RSA keys on their own. This