From 3afe75ddbc7bf4b1ae6df23a531c0f57ce266f12 Mon Sep 17 00:00:00 2001 From: Ken Raeburn Date: Sun, 13 Apr 2003 13:03:22 +0000 Subject: [PATCH] Finish implementation of CBC+CTS decryption and truncated HMAC for AES. Fix memory management bugs. ticket: 1418 status: open git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@15351 dc483132-0cff-0310-8789-dd5450dbe970 --- src/lib/crypto/ChangeLog | 9 ++ src/lib/crypto/dk/ChangeLog | 15 +++ src/lib/crypto/dk/dk.h | 19 +++ src/lib/crypto/dk/dk_decrypt.c | 51 +++++++- src/lib/crypto/dk/dk_encrypt.c | 172 +++++++++++++++++++++++++- src/lib/crypto/enc_provider/ChangeLog | 11 ++ src/lib/crypto/enc_provider/aes.c | 78 ++++++++---- src/lib/crypto/etypes.c | 4 +- src/lib/crypto/pbkdf2.c | 25 +++- 9 files changed, 348 insertions(+), 36 deletions(-) diff --git a/src/lib/crypto/ChangeLog b/src/lib/crypto/ChangeLog index 6f73ddf62..1db561252 100644 --- a/src/lib/crypto/ChangeLog +++ b/src/lib/crypto/ChangeLog @@ -1,3 +1,12 @@ +2003-04-13 Ken Raeburn + + * pbkdf2.c (krb5int_pbkdf2): Provide a temporary buffer for the + output from F, if the remaining space in the output buffer isn't + big enough. Free the temporary buffers before returning. + + * etypes.c (krb5_enctypes_list): Use krb5int_aes_encrypt_length, + and krb5int_aes_dk_encrypt, and krb5int_aes_dk_decrypt for AES. + 2003-03-06 Alexandra Ellwood * prng.c: use Unix randomness sources on Mac OS X. diff --git a/src/lib/crypto/dk/ChangeLog b/src/lib/crypto/dk/ChangeLog index 9ed3a8de9..4edfaabee 100644 --- a/src/lib/crypto/dk/ChangeLog +++ b/src/lib/crypto/dk/ChangeLog @@ -1,3 +1,18 @@ +2003-04-13 Ken Raeburn + + * dk_decrypt.c (krb5_dk_decrypt_maybe_trunc_hmac): Renamed from + krb5_dk_decrypt, made static, added extra HMACSIZE argument to + indicate size of HMAC. Cast byte values to char to silence + compiler warning. + (krb5_dk_decrypt): Call it. + (krb5int_aes_dk_decrypt): New function. + * dk_encrypt.c (krb5_dk_encrypt): Cast byte values to char to + silence compiler warning. + (krb5int_aes_encrypt_length, trunc_hmac, krb5int_aes_dk_encrypt): + New functions. + * dk.h (krb5int_aes_encrypt_length, krb5int_aes_dk_encrypt, + krb5int_aes_dk_decrypt): Declare. + 2003-03-04 Ken Raeburn * stringtokey.c (krb5int_dk_string_to_key): Renamed from diff --git a/src/lib/crypto/dk/dk.h b/src/lib/crypto/dk/dk.h index 017101617..a224167ea 100644 --- a/src/lib/crypto/dk/dk.h +++ b/src/lib/crypto/dk/dk.h @@ -38,6 +38,18 @@ krb5_error_code krb5_dk_encrypt const krb5_data *ivec, const krb5_data *input, krb5_data *output); +void krb5int_aes_encrypt_length +(const struct krb5_enc_provider *enc, + const struct krb5_hash_provider *hash, + size_t input, size_t *length); + +krb5_error_code krb5int_aes_dk_encrypt +(const struct krb5_enc_provider *enc, + const struct krb5_hash_provider *hash, + const krb5_keyblock *key, krb5_keyusage usage, + const krb5_data *ivec, + const krb5_data *input, krb5_data *output); + krb5_error_code krb5_dk_decrypt (const struct krb5_enc_provider *enc, const struct krb5_hash_provider *hash, @@ -45,6 +57,13 @@ krb5_error_code krb5_dk_decrypt const krb5_data *ivec, const krb5_data *input, krb5_data *arg_output); +krb5_error_code krb5int_aes_dk_decrypt +(const struct krb5_enc_provider *enc, + const struct krb5_hash_provider *hash, + const krb5_keyblock *key, krb5_keyusage usage, + const krb5_data *ivec, const krb5_data *input, + krb5_data *arg_output); + krb5_error_code krb5int_dk_string_to_key (const struct krb5_enc_provider *enc, const krb5_data *string, const krb5_data *salt, diff --git a/src/lib/crypto/dk/dk_decrypt.c b/src/lib/crypto/dk/dk_decrypt.c index adc4d2348..5f35fa6ac 100644 --- a/src/lib/crypto/dk/dk_decrypt.c +++ b/src/lib/crypto/dk/dk_decrypt.c @@ -29,6 +29,16 @@ #define K5CLENGTH 5 /* 32 bit net byte order integer + one byte seed */ +static krb5_error_code +krb5_dk_decrypt_maybe_trunc_hmac(const struct krb5_enc_provider *enc, + const struct krb5_hash_provider *hash, + const krb5_keyblock *key, + krb5_keyusage usage, + const krb5_data *ivec, + const krb5_data *input, + krb5_data *output, + size_t hmacsize); + krb5_error_code krb5_dk_decrypt(enc, hash, key, usage, ivec, input, output) const struct krb5_enc_provider *enc; @@ -38,6 +48,36 @@ krb5_dk_decrypt(enc, hash, key, usage, ivec, input, output) const krb5_data *ivec; const krb5_data *input; krb5_data *output; +{ + return krb5_dk_decrypt_maybe_trunc_hmac(enc, hash, key, usage, + ivec, input, output, 0); +} + +krb5_error_code +krb5int_aes_dk_decrypt(enc, hash, key, usage, ivec, input, output) + const struct krb5_enc_provider *enc; + const struct krb5_hash_provider *hash; + const krb5_keyblock *key; + krb5_keyusage usage; + const krb5_data *ivec; + const krb5_data *input; + krb5_data *output; +{ + return krb5_dk_decrypt_maybe_trunc_hmac(enc, hash, key, usage, + ivec, input, output, 96 / 8); +} + +static krb5_error_code +krb5_dk_decrypt_maybe_trunc_hmac(enc, hash, key, usage, ivec, input, output, + hmacsize) + const struct krb5_enc_provider *enc; + const struct krb5_hash_provider *hash; + const krb5_keyblock *key; + krb5_keyusage usage; + const krb5_data *ivec; + const krb5_data *input; + krb5_data *output; + size_t hmacsize; { krb5_error_code ret; size_t hashsize, blocksize, keybytes, keylength, enclen, plainlen; @@ -52,7 +92,12 @@ krb5_dk_decrypt(enc, hash, key, usage, ivec, input, output) (*(enc->block_size))(&blocksize); (*(enc->keysize))(&keybytes, &keylength); - enclen = input->length - hashsize; + if (hmacsize == 0) + hmacsize = hashsize; + else if (hmacsize > hashsize) + return KRB5KRB_AP_ERR_BAD_INTEGRITY; + + enclen = input->length - hmacsize; if ((kedata = (unsigned char *) malloc(keylength)) == NULL) return(ENOMEM); @@ -87,7 +132,7 @@ krb5_dk_decrypt(enc, hash, key, usage, ivec, input, output) d1.data[2] = (usage>>8)&0xff; d1.data[3] = usage&0xff; - d1.data[4] = 0xAA; + d1.data[4] = (char) 0xAA; if ((ret = krb5_derive_key(enc, key, &ke, &d1)) != 0) goto cleanup; @@ -121,7 +166,7 @@ krb5_dk_decrypt(enc, hash, key, usage, ivec, input, output) if ((ret = krb5_hmac(hash, &ki, 1, &d2, &d1)) != 0) goto cleanup; - if (memcmp(cksum, input->data+enclen, hashsize) != 0) { + if (memcmp(cksum, input->data+enclen, hmacsize) != 0) { ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; goto cleanup; } diff --git a/src/lib/crypto/dk/dk_encrypt.c b/src/lib/crypto/dk/dk_encrypt.c index eb9fe5fa3..7ff7d878f 100644 --- a/src/lib/crypto/dk/dk_encrypt.c +++ b/src/lib/crypto/dk/dk_encrypt.c @@ -108,7 +108,7 @@ krb5_dk_encrypt(enc, hash, key, usage, ivec, input, output) d1.data[2] = (usage>>8)&0xff; d1.data[3] = usage&0xff; - d1.data[4] = 0xAA; + d1.data[4] = (char) 0xAA; if ((ret = krb5_derive_key(enc, key, &ke, &d1))) goto cleanup; @@ -177,6 +177,176 @@ cleanup: return(ret); } +/* Not necessarily "AES", per se, but "a CBC+CTS mode block cipher + with a 96-bit truncated HMAC". */ +void +krb5int_aes_encrypt_length(enc, hash, inputlen, length) + const struct krb5_enc_provider *enc; + const struct krb5_hash_provider *hash; + size_t inputlen; + size_t *length; +{ + size_t blocksize, hashsize; + + (*(enc->block_size))(&blocksize); + hashsize = 96 / 8; + + /* No roundup, since CTS requires no padding once we've hit the + block size. */ + *length = blocksize+inputlen + hashsize; +} + +static krb5_error_code +trunc_hmac (const struct krb5_hash_provider *hash, + const krb5_keyblock *ki, int num, + const krb5_data *input, const krb5_data *output) +{ + size_t hashsize; + krb5_data tmp; + krb5_error_code ret; + + (hash->hash_size)(&hashsize); + if (hashsize < output->length) + return KRB5_CRYPTO_INTERNAL; + tmp.length = hashsize; + tmp.data = malloc(hashsize); + if (tmp.data == NULL) + return errno; + ret = krb5_hmac(hash, ki, num, input, &tmp); + if (ret == 0) + memcpy(output->data, tmp.data, output->length); + memset(tmp.data, 0, hashsize); + free(tmp.data); + return ret; +} + +krb5_error_code +krb5int_aes_dk_encrypt(enc, hash, key, usage, ivec, input, output) + const struct krb5_enc_provider *enc; + const struct krb5_hash_provider *hash; + const krb5_keyblock *key; + krb5_keyusage usage; + const krb5_data *ivec; + const krb5_data *input; + krb5_data *output; +{ + size_t blocksize, keybytes, keylength, plainlen, enclen; + krb5_error_code ret; + unsigned char constantdata[K5CLENGTH]; + krb5_data d1, d2; + unsigned char *plaintext, *kedata, *kidata, *cn; + krb5_keyblock ke, ki; + + /* allocate and set up plaintext and to-be-derived keys */ + + (*(enc->block_size))(&blocksize); + (*(enc->keysize))(&keybytes, &keylength); + plainlen = blocksize+input->length; + + krb5int_aes_encrypt_length(enc, hash, input->length, &enclen); + + /* key->length, ivec will be tested in enc->encrypt */ + + if (output->length < enclen) + return(KRB5_BAD_MSIZE); + + if ((kedata = (unsigned char *) malloc(keylength)) == NULL) + return(ENOMEM); + if ((kidata = (unsigned char *) malloc(keylength)) == NULL) { + free(kedata); + return(ENOMEM); + } + if ((plaintext = (unsigned char *) malloc(plainlen)) == NULL) { + free(kidata); + free(kedata); + return(ENOMEM); + } + + ke.contents = kedata; + ke.length = keylength; + ki.contents = kidata; + ki.length = keylength; + + /* derive the keys */ + + d1.data = constantdata; + d1.length = K5CLENGTH; + + d1.data[0] = (usage>>24)&0xff; + d1.data[1] = (usage>>16)&0xff; + d1.data[2] = (usage>>8)&0xff; + d1.data[3] = usage&0xff; + + d1.data[4] = (char) 0xAA; + + if ((ret = krb5_derive_key(enc, key, &ke, &d1))) + goto cleanup; + + d1.data[4] = 0x55; + + if ((ret = krb5_derive_key(enc, key, &ki, &d1))) + goto cleanup; + + /* put together the plaintext */ + + d1.length = blocksize; + d1.data = plaintext; + + if ((ret = krb5_c_random_make_octets(/* XXX */ 0, &d1))) + goto cleanup; + + memcpy(plaintext+blocksize, input->data, input->length); + + /* Ciphertext stealing; there should be no more. */ + if (plainlen != blocksize + input->length) + abort(); + + /* encrypt the plaintext */ + + d1.length = plainlen; + d1.data = plaintext; + + d2.length = plainlen; + d2.data = output->data; + + if ((ret = ((*(enc->encrypt))(&ke, ivec, &d1, &d2)))) + goto cleanup; + + if (ivec != NULL && ivec->length == blocksize) + cn = d2.data + d2.length - blocksize; + else + cn = NULL; + + /* hash the plaintext */ + + d2.length = enclen - plainlen; + d2.data = output->data+plainlen; + if (d2.length != 96 / 8) + abort(); + + if ((ret = trunc_hmac(hash, &ki, 1, &d1, &d2))) { + memset(d2.data, 0, d2.length); + goto cleanup; + } + + /* update ivec */ + if (cn != NULL) + memcpy(ivec->data, cn, blocksize); + + /* ret is set correctly by the prior call */ + +cleanup: + memset(kedata, 0, keylength); + memset(kidata, 0, keylength); + memset(plaintext, 0, plainlen); + + free(plaintext); + free(kidata); + free(kedata); + + return(ret); +} + #ifdef ATHENA_DES3_KLUDGE void krb5_marc_dk_encrypt_length(enc, hash, inputlen, length) diff --git a/src/lib/crypto/enc_provider/ChangeLog b/src/lib/crypto/enc_provider/ChangeLog index 08a614e96..f954f7fa2 100644 --- a/src/lib/crypto/enc_provider/ChangeLog +++ b/src/lib/crypto/enc_provider/ChangeLog @@ -1,3 +1,14 @@ +2003-04-13 Ken Raeburn + + * aes.c (enc): Replaced function with a macro. + (dec): New macro. + (krb5int_aes_encrypt): Use enc and dec. Delete unused variable + OFFSET. + (krb5int_aes_decrypt): Renamed from k5_aes_dencrypt, implemented + decryption, made non-static. + (krb5int_enc_aes128, krb5int_enc_aes256): Use new name for + krb5int_aes_decrypt. + 2003-03-04 Ken Raeburn * aes.c (krb5int_aes_init_state): Implement. diff --git a/src/lib/crypto/enc_provider/aes.c b/src/lib/crypto/enc_provider/aes.c index d3dc2a5a7..013a688eb 100644 --- a/src/lib/crypto/enc_provider/aes.c +++ b/src/lib/crypto/enc_provider/aes.c @@ -52,23 +52,8 @@ static void printd (const char *descr, krb5_data *d) { } printf("\n"); } -static void enc(char *out, const char *in, aes_ctx *ctx) -{ - if (aes_enc_blk(in, out, ctx) != aes_good) - abort(); -#if 0 - { - krb5_data e_in, e_out; - e_in.data = in; - e_out.data = out; - e_in.length = e_out.length = BLOCK_SIZE; - printf("encrypting [[\n"); - printd("input block", &e_in); - printd("output block", &e_out); - printf("]]\n"); - } -#endif -} +#define enc(OUT, IN, CTX) (aes_enc_blk((IN),(OUT),(CTX)) == aes_good ? (void) 0 : abort()) +#define dec(OUT, IN, CTX) (aes_dec_blk((IN),(OUT),(CTX)) == aes_good ? (void) 0 : abort()) static void xorblock(char *out, const char *in) { @@ -83,7 +68,6 @@ krb5int_aes_encrypt(const krb5_keyblock *key, const krb5_data *ivec, { aes_ctx ctx; unsigned char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE], tmp3[BLOCK_SIZE]; - int offset; int nblocks = 0, blockno; /* CHECK_SIZES; */ @@ -100,8 +84,7 @@ krb5int_aes_encrypt(const krb5_keyblock *key, const krb5_data *ivec, if (nblocks == 1) { /* XXX Used for DK function. */ - if (aes_enc_blk(input->data, output->data, &ctx) != aes_good) - abort(); + enc(output->data, input->data, &ctx); } else { int nleft; @@ -112,7 +95,6 @@ krb5int_aes_encrypt(const krb5_keyblock *key, const krb5_data *ivec, /* Set up for next block. */ memcpy(tmp, tmp2, BLOCK_SIZE); - offset += BLOCK_SIZE; } /* Do final CTS step for last two blocks (the second of which may or may not be incomplete). */ @@ -132,18 +114,60 @@ krb5int_aes_encrypt(const krb5_keyblock *key, const krb5_data *ivec, return 0; } -static krb5_error_code -k5_aes_decrypt(const krb5_keyblock *key, const krb5_data *ivec, - const krb5_data *input, krb5_data *output) +krb5_error_code +krb5int_aes_decrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) { aes_ctx ctx; + unsigned char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE], tmp3[BLOCK_SIZE]; + int nblocks = 0, blockno; CHECK_SIZES; if (aes_dec_key(key->contents, key->length, &ctx) != aes_good) abort(); - abort(); + if (ivec) + memcpy(tmp, ivec->data, BLOCK_SIZE); + else + memset(tmp, 0, BLOCK_SIZE); + + nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (nblocks == 1) { + if (input->length < BLOCK_SIZE) + abort(); + dec(output->data, input->data, &ctx); + } else { + int nleft; + + for (blockno = 0; blockno < nblocks - 2; blockno++) { + dec(tmp2, input->data + blockno * BLOCK_SIZE, &ctx); + xorblock(tmp2, tmp); + memcpy(output->data + blockno * BLOCK_SIZE, tmp2, BLOCK_SIZE); + memcpy(tmp, input->data + blockno * BLOCK_SIZE, BLOCK_SIZE); + } + /* Do last two blocks, the second of which (next-to-last block + of plaintext) may be incomplete. */ + dec(tmp2, input->data + (nblocks - 2) * BLOCK_SIZE, &ctx); + /* Set tmp3 to last ciphertext block, padded. */ + memset(tmp3, 0, sizeof(tmp3)); + memcpy(tmp3, input->data + (nblocks - 1) * BLOCK_SIZE, + input->length - (nblocks - 1) * BLOCK_SIZE); + /* Set tmp2 to last (possibly partial) plaintext block, and + save it. */ + xorblock(tmp2, tmp3); + memcpy(output->data + (nblocks - 1) * BLOCK_SIZE, tmp2, + input->length - (nblocks - 1) * BLOCK_SIZE); + /* Maybe keep the trailing part, and copy in the last + ciphertext block. */ + memcpy(tmp2, tmp3, input->length - (nblocks - 1) * BLOCK_SIZE); + /* Decrypt, to get next to last plaintext block xor previous + ciphertext. */ + dec(tmp3, tmp2, &ctx); + xorblock(tmp3, tmp); + memcpy(output->data + (nblocks - 2) * BLOCK_SIZE, tmp3, BLOCK_SIZE); + } return 0; } @@ -178,7 +202,7 @@ const struct krb5_enc_provider krb5int_enc_aes128 = { aes_block_size, aes128_keysize, krb5int_aes_encrypt, - k5_aes_decrypt, + krb5int_aes_decrypt, k5_aes_make_key, krb5int_aes_init_state, krb5int_default_free_state @@ -188,7 +212,7 @@ const struct krb5_enc_provider krb5int_enc_aes256 = { aes_block_size, aes256_keysize, krb5int_aes_encrypt, - k5_aes_decrypt, + krb5int_aes_decrypt, k5_aes_make_key, krb5int_aes_init_state, krb5int_default_free_state diff --git a/src/lib/crypto/etypes.c b/src/lib/crypto/etypes.c index 1cc570cd8..da234070c 100644 --- a/src/lib/crypto/etypes.c +++ b/src/lib/crypto/etypes.c @@ -125,12 +125,12 @@ const struct krb5_keytypes krb5_enctypes_list[] = { { ENCTYPE_AES128_CTS_HMAC_SHA1_96, "aes128-cts-hmac-sha1-96", "AES-128 CTS mode with 96-bit SHA-1 HMAC", &krb5int_enc_aes128, &krb5int_hash_sha1, - krb5_dk_encrypt_length, krb5_dk_encrypt, krb5_dk_decrypt, + krb5int_aes_encrypt_length, krb5int_aes_dk_encrypt, krb5int_aes_dk_decrypt, krb5int_aes_string_to_key }, { ENCTYPE_AES256_CTS_HMAC_SHA1_96, "aes256-cts-hmac-sha1-96", "AES-256 CTS mode with 96-bit SHA-1 HMAC", &krb5int_enc_aes256, &krb5int_hash_sha1, - krb5_dk_encrypt_length, krb5_dk_encrypt, krb5_dk_decrypt, + krb5int_aes_encrypt_length, krb5int_aes_dk_encrypt, krb5int_aes_dk_decrypt, krb5int_aes_string_to_key }, #ifdef ATHENA_DES3_KLUDGE diff --git a/src/lib/crypto/pbkdf2.c b/src/lib/crypto/pbkdf2.c index d8a3f8b58..165e4cf6a 100644 --- a/src/lib/crypto/pbkdf2.c +++ b/src/lib/crypto/pbkdf2.c @@ -158,6 +158,7 @@ krb5int_pbkdf2 (krb5_error_code (*prf)(krb5_keyblock *, krb5_data *, { int l, r, i; char *utmp1, *utmp2; + char utmp3[20]; /* XXX length shouldn't be hardcoded! */ if (output->length == 0 || hlen == 0) abort(); @@ -169,7 +170,13 @@ krb5int_pbkdf2 (krb5_error_code (*prf)(krb5_keyblock *, krb5_data *, r = output->length - (l - 1) * hlen; utmp1 = /*output + dklen; */ malloc(hlen); + if (utmp1 == NULL) + return errno; utmp2 = /*utmp1 + hlen; */ malloc(salt->length + 4 + hlen); + if (utmp2 == NULL) { + free(utmp1); + return errno; + } /* Step 3. */ for (i = 1; i <= l; i++) { @@ -177,11 +184,21 @@ krb5int_pbkdf2 (krb5_error_code (*prf)(krb5_keyblock *, krb5_data *, int j; #endif krb5_error_code err; + char *out; - err = F(output->data + (i-1) * hlen, utmp1, utmp2, prf, hlen, - pass, salt, count, i); - if (err) + if (i == l) + out = utmp3; + else + out = output->data + (i-1) * hlen; + err = F(out, utmp1, utmp2, prf, hlen, pass, salt, count, i); + if (err) { + free(utmp1); + free(utmp2); return err; + } + if (i == l) + memcpy(output->data + (i-1) * hlen, utmp3, + output->length - (i-1) * hlen); #if 0 printf("after F(%d), @%p:\n", i, output->data); @@ -190,6 +207,8 @@ krb5int_pbkdf2 (krb5_error_code (*prf)(krb5_keyblock *, krb5_data *, printf ("\n"); #endif } + free(utmp1); + free(utmp2); return 0; } -- 2.26.2