Finish implementation of CBC+CTS decryption and truncated HMAC for AES.
authorKen Raeburn <raeburn@mit.edu>
Sun, 13 Apr 2003 13:03:22 +0000 (13:03 +0000)
committerKen Raeburn <raeburn@mit.edu>
Sun, 13 Apr 2003 13:03:22 +0000 (13:03 +0000)
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
src/lib/crypto/dk/ChangeLog
src/lib/crypto/dk/dk.h
src/lib/crypto/dk/dk_decrypt.c
src/lib/crypto/dk/dk_encrypt.c
src/lib/crypto/enc_provider/ChangeLog
src/lib/crypto/enc_provider/aes.c
src/lib/crypto/etypes.c
src/lib/crypto/pbkdf2.c

index 6f73ddf62710f7d767f926ac0d19335a1a0b224e..1db5612524a39e7e04b4993ed6b9718c9ea6308c 100644 (file)
@@ -1,3 +1,12 @@
+2003-04-13  Ken Raeburn  <raeburn@mit.edu>
+
+       * 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  <lxs@mit.edu>
 
     * prng.c: use Unix randomness sources on Mac OS X.
index 9ed3a8de9e85e5bcfcd8d7844fd51bd758205484..4edfaabee3f064a2a7569468befd9ecd8ad033df 100644 (file)
@@ -1,3 +1,18 @@
+2003-04-13  Ken Raeburn  <raeburn@mit.edu>
+
+       * 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  <raeburn@mit.edu>
 
        * stringtokey.c (krb5int_dk_string_to_key): Renamed from
index 01710161716467eaaf876104b48723d3817956ab..a224167ea1a49f0fc0b18dfd4146c68f10a7b403 100644 (file)
@@ -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,
index adc4d2348f1e30e1c285a358793c973674d4fb06..5f35fa6ace353f3e2aa0c948bf2e55be2f9b4d36 100644 (file)
 
 #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;
     }
index eb9fe5fa37ebb1b82120512693f29afae7a3264b..7ff7d878f4e2cf2b6272bfb132a8a0b6661cc796 100644 (file)
@@ -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)
index 08a614e967fa8f495e8147b7a6d7d8b812521d47..f954f7fa22ded5747bd5f1ad26e0e000e28a640b 100644 (file)
@@ -1,3 +1,14 @@
+2003-04-13  Ken Raeburn  <raeburn@mit.edu>
+
+       * 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  <raeburn@mit.edu>
 
        * aes.c (krb5int_aes_init_state): Implement.
index d3dc2a5a73dfafac53db12a023057d6c23d52c81..013a688eb1d606f53d34437738854eda75986943 100644 (file)
@@ -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
index 1cc570cd8f8132a1b0eef4eee29f74881e146bfb..da234070ca3007c5efe3fdd8b940b0ef73b42a9e 100644 (file)
@@ -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
index d8a3f8b5899cb8fe18e4b1fd9b936839d854a914..165e4cf6ab4b373f5f612d8c7e9c9964d4fbe92f 100644 (file)
@@ -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;
 }