Derive DES/fcrypt session key from other key types
authorChaskiel Grundman <cg2v@andrew.cmu.edu>
Mon, 18 Mar 2013 01:58:47 +0000 (21:58 -0400)
committerSimon Wilkinson <sxw@your-file-system.com>
Sat, 13 Jul 2013 10:29:20 +0000 (11:29 +0100)
If a kerberos 5 ticket has a session key with a non-DES enctype,
use the NIST SP800-108 KDF in counter mode with HMAC_MD5 as the PRF to
construct a DES key to be used by rxkad.

To satisfy the requirements of the KDF, DES3 keys are first compressed into a
168 bit form by reversing the RFC3961 random-to-key algorithm

Windows has three additional places to get tokens, who knew?

Change-Id: I4dc8e83a641f9892b31c109fb9025251de3dcb27

src/WINNT/afsd/afskfw.c
src/WINNT/aklog/aklog.c
src/WINNT/netidmgr_plugin/afsfuncs.c
src/aklog/aklog.c
src/libafsrpc/afsrpc.def
src/libafsrpc/libafsrpc.la.sym
src/rxkad/liboafs_rxkad.la.sym
src/rxkad/rxkad_prototypes.h
src/rxkad/ticket5.c

index c37804a..636b58b 100644 (file)
@@ -2631,8 +2631,6 @@ KFW_AFS_klog(
 
     increds.client = client_principal;
     increds.times.endtime = 0;
-    /* Ask for DES since that is what V4 understands */
-    increds.session.keytype = ENCTYPE_DES_CBC_CRC;
 
     /* ALWAYS first try service/cell@CLIENT_REALM */
     if (code = krb5_build_principal(context, &increds.server,
@@ -2740,9 +2738,9 @@ KFW_AFS_klog(
     atoken.kvno = RXKAD_TKT_TYPE_KERBEROS_V5;
     atoken.startTime = k5creds->times.starttime;
     atoken.endTime = k5creds->times.endtime;
-    memcpy(&atoken.sessionKey,
-            k5creds->session.keyvalue.data,
-            k5creds->session.keyvalue.length);
+    if (tkt_DeriveDesKey(k5creds->session.keytype, k5creds->session.keyvalue.data,
+                        k5creds->session.keyvalue.length, &atoken.sessionKey))
+       goto cleanup;
     atoken.ticketLen = k5creds->ticket.length;
     memcpy(atoken.ticket, k5creds->ticket.data, atoken.ticketLen);
 
index 9044899..55a7ccc 100644 (file)
@@ -512,7 +512,8 @@ static int get_v5cred(krb5_context context,
     increds.client = client_principal;
     increds.times.endtime = 0;
        /* Ask for DES since that is what V4 understands */
-    increds.session.keytype = ENCTYPE_DES_CBC_CRC;
+    if (c != NULL)
+       increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
 
     r = krb5_get_credentials(context, 0, _krb425_ccache, &increds, creds);
     if (r) {
@@ -1001,7 +1002,12 @@ static int auth_to_cell(krb5_context context, char *cell, char *realm)
         atoken.kvno = RXKAD_TKT_TYPE_KERBEROS_V5;
         atoken.startTime = v5cred->times.starttime;
         atoken.endTime = v5cred->times.endtime;
-        memcpy(&atoken.sessionKey, v5cred->session.keyvalue.data, v5cred->session.keyvalue.length);
+       if (tkt_DeriveDesKey(v5cred->session.keytype,
+                            v5cred->session.keyvalue.data,
+                            v5cred->session.keyvalue.length, &atoken.sessionKey)) {
+           status = AKLOG_MISC;
+           goto done;
+       }
         atoken.ticketLen = v5cred->ticket.length;
         memcpy(atoken.ticket, v5cred->ticket.data, atoken.ticketLen);
     } else {
index 5dc0991..db9b725 100644 (file)
@@ -876,7 +876,8 @@ afs_klog(khm_handle identity,
         increds.client = client_principal;
         increds.times.endtime = 0;
         /* Ask for DES since that is what V4 understands */
-        increds.session.keytype = ENCTYPE_DES_CBC_CRC;
+        if (method == AFS_TOKEN_KRB524)
+           increds.session.keytype = ENCTYPE_DES_CBC_CRC;
 
 #ifdef KRB5_TC_NOTICKET
         flags = KRB5_TC_OPENCLOSE;
@@ -1060,9 +1061,11 @@ afs_klog(khm_handle identity,
         atoken.kvno = RXKAD_TKT_TYPE_KERBEROS_V5;
         atoken.startTime = k5creds->times.starttime;
         atoken.endTime = k5creds->times.endtime;
-        memcpy(&atoken.sessionKey,
-               k5creds->session.keyvalue.data,
-               k5creds->session.keyvalue.length);
+       if (tkt_DeriveDesKey(k5creds->session.keytype,
+                            k5creds->session.keyvalue.data,
+                            k5creds->session.keyvalue.length,
+                            &atoken.sessionKey))
+           goto cleanup;
         atoken.ticketLen = k5creds->ticket.length;
         memcpy(atoken.ticket, k5creds->ticket.data, atoken.ticketLen);
 
index 9e5b811..b5c0108 100644 (file)
@@ -669,6 +669,8 @@ rxkad_build_native_token(krb5_context context, krb5_creds *v5cred,
     char k4inst[INST_SZ];
     char k4realm[REALM_SZ];
 #endif
+    void *inkey = get_cred_keydata(v5cred);
+    size_t inkey_sz = get_cred_keylen(v5cred);
 
     afs_dprintf("Using Kerberos V5 ticket natively\n");
 
@@ -712,8 +714,10 @@ rxkad_build_native_token(krb5_context context, krb5_creds *v5cred,
     token.kvno = RXKAD_TKT_TYPE_KERBEROS_V5;
     token.startTime = v5cred->times.starttime;;
     token.endTime = v5cred->times.endtime;
-    memcpy(&token.sessionKey, get_cred_keydata(v5cred),
-          get_cred_keylen(v5cred));
+    if (tkt_DeriveDesKey(get_creds_enctype(v5cred), inkey, inkey_sz,
+                        &token.sessionKey) != 0) {
+       return RXKADBADKEY;
+    }
     token.ticketLen = v5cred->ticket.length;
     memcpy(token.ticket, v5cred->ticket.data, token.ticketLen);
 
@@ -2124,8 +2128,9 @@ get_credv5(krb5_context context, char *name, char *inst, char *realm,
 
     increds.client = client_principal;
     increds.times.endtime = 0;
-    /* Ask for DES since that is what V4 understands */
-    get_creds_enctype((&increds)) = ENCTYPE_DES_CBC_CRC;
+    if (do524)
+       /* Ask for DES since that is what V4 understands */
+       get_creds_enctype((&increds)) = ENCTYPE_DES_CBC_CRC;
 
     if (keytab) {
        int allowed_enctypes[] = {
index 5772ad0..d7beb72 100755 (executable)
@@ -342,6 +342,7 @@ EXPORTS
         afs_set_com_err_hook                    @347
        rxkad_NewKrb5ServerSecurityObject       @348
        tkt_MakeTicket5                         @349
+       tkt_DeriveDesKey                        @350
 
 ; for performance testing
         rx_TSFPQGlobSize                        @2001 DATA
index d1b9c46..7636116 100644 (file)
@@ -164,6 +164,7 @@ rxs_Release
 time_to_life
 tkt_CheckTimes
 tkt_DecodeTicket
+tkt_DeriveDesKey
 tkt_MakeTicket
 tkt_MakeTicket5
 xdr_afsUUID
index 4933f9b..cfcc585 100644 (file)
@@ -6,5 +6,6 @@ rxkad_NewKrb5ServerSecurityObject
 rxkad_NewServerSecurityObject
 time_to_life
 tkt_CheckTimes
+tkt_DeriveDesKey
 tkt_MakeTicket
 tkt_MakeTicket5
index 525b9d9..a13c9fe 100644 (file)
@@ -167,5 +167,11 @@ extern int tkt_MakeTicket5(char *ticket, int *ticketLen, int enctype, int *kvno,
                           char *name, char *inst, char *cell, afs_uint32 start,
                           afs_uint32 end, struct ktc_encryptionKey *sessionKey,
                           char *sname, char *sinst);
+/*
+ * Compute a des key from a key of a semi-arbitrary kerberos 5 enctype.
+ * Modifies keydata if enctype is 3des.
+ */
+extern int tkt_DeriveDesKey(int enctype, void *keydata, size_t keylen, struct ktc_encryptionKey
+                           *output);
 
 #endif
index 1dad81a..f875cd0 100644 (file)
 #include <rx/xdr.h>
 #include <rx/rx.h>
 
+#define HC_DEPRECATED_CRYPTO
 #include <hcrypto/md4.h>
 #include <hcrypto/md5.h>
 #include <hcrypto/des.h>
+#include <hcrypto/hmac.h>
 
 #include "lifetimes.h"
 #include "rxkad.h"
@@ -173,9 +175,9 @@ static const struct krb_convert sconv_list[] = {
 static int
   krb5_des_decrypt(struct ktc_encryptionKey *, int, void *, size_t, void *,
                   size_t *);
-
-
-
+static int rxkad_derive_des_key(const void *, size_t,
+                               struct ktc_encryptionKey *);
+static int compress_parity_bits(void *, size_t *);
 
 int
 tkt_DecodeTicket5(char *ticket, afs_int32 ticket_len,
@@ -374,21 +376,9 @@ tkt_DecodeTicket5(char *ticket, afs_int32 ticket_len,
     }
 
     /* Verify that decr_part.key is of right type */
-    switch (decr_part.key.keytype) {
-    case ETYPE_DES_CBC_CRC:
-    case ETYPE_DES_CBC_MD4:
-    case ETYPE_DES_CBC_MD5:
-       break;
-    default:
-       goto bad_ticket;
-    }
-
-    if (decr_part.key.keyvalue.length != 8)
+    if (tkt_DeriveDesKey(decr_part.key.keytype, decr_part.key.keyvalue.data,
+                        decr_part.key.keyvalue.length, session_key) != 0)
        goto bad_ticket;
-
-    /* Extract session key */
-    memcpy(session_key, decr_part.key.keyvalue.data, 8);
-
     /* Check lifetimes and host addresses, flags etc */
     {
        time_t now = time(0);   /* Use fast time package instead??? */
@@ -537,7 +527,6 @@ krb5_des_decrypt(struct ktc_encryptionKey *key, int etype, void *in,
     return ret;
 }
 
-
 int
 tkt_MakeTicket5(char *ticket, int *ticketLen, int enctype, int *kvno,
                void *key, size_t keylen,
@@ -632,3 +621,137 @@ cleanup:
        return RXKADINCONSISTENCY;
     return code;
 }
+
+/*
+ * Use NIST SP800-108 with HMAC(MD5) in counter mode as the PRF to derive a
+ * des key from another type of key.
+ *
+ * L is 64, as we take 64 random bits and turn them into a 56-bit des key.
+ * The output of hmac_md5 is 128 bits; we take the first 64 only, so n
+ * properly should be 1.  However, we apply a slight variation due to the
+ * possibility of producing a weak des key.  If the output key is weak, do NOT
+ * simply correct it, instead, the counter is advanced and the next output
+ * used.  As such, we code so as to have n be the full 255 permitted by our
+ * encoding of the counter i in an 8-bit field.  L itself is encoded as a
+ * 32-bit field, big-endian.  We use the constant string "rxkad" as a label
+ * for this key derivation, the standard NUL byte separator, and omit a
+ * key-derivation context.  The input key is unique to the krb5 service ticket,
+ * which is unlikely to be used in an other location.  If it is used in such
+ * a fashion, both locations will derive the same des key from the PRF, but
+ * this is no different from if a krb5 des key had been used in the same way,
+ * as traditional krb5 rxkad uses the ticket session key directly as the token
+ * key.
+ */
+static int
+rxkad_derive_des_key(const void *in, size_t insize,
+                    struct ktc_encryptionKey *out)
+{
+    unsigned char i;
+    char Lbuf[4];              /* bits of output, as 32 bit word, MSB first */
+    char tmp[64];              /* only needs to be 16 for md5, but lets be sure it fits */
+    unsigned int mdsize;
+    DES_cblock ktmp;
+    HMAC_CTX mctx;
+
+    Lbuf[0] = 0;
+    Lbuf[1] = 0;
+    Lbuf[2] = 0;
+    Lbuf[3] = 64;
+
+    /* stop when 8 bit counter wraps to 0 */
+    for (i = 1; i; i++) {
+       HMAC_CTX_init(&mctx);
+       HMAC_Init_ex(&mctx, in, insize, EVP_md5(), NULL);
+       HMAC_Update(&mctx, &i, 1);
+       HMAC_Update(&mctx, "rxkad", strlen("rxkad") + 1);   /* includes label and separator */
+       HMAC_Update(&mctx, Lbuf, 4);
+       mdsize = sizeof(tmp);
+       HMAC_Final(&mctx, tmp, &mdsize);
+       memcpy(ktmp, tmp, 8);
+       DES_set_odd_parity(&ktmp);
+       if (!DES_is_weak_key(&ktmp)) {
+           memcpy(out->data, ktmp, 8);
+           return 0;
+       }
+    }
+    return -1;
+}
+
+/*
+ * This is the inverse of the random-to-key for 3des specified in
+ * rfc3961, converting blocks of 8 bytes to blocks of 7 bytes by distributing
+ * the bits of each 8th byte as the lsb of the previous 7 bytes.
+ */
+static int
+compress_parity_bits(void *buffer, size_t *bufsiz)
+{
+    unsigned char *cb, tmp;
+    int i, j, nk;
+
+    if (*bufsiz % 8 != 0)
+       return 1;
+    cb = (unsigned char *)buffer;
+    nk = *bufsiz / 8;
+    for (i = 0; i < nk; i++) {
+       tmp = cb[8 * i + 7] >> 1;
+       for (j = 0; j < 7; j++) {
+           cb[8 * i + j] &= 0xfe;
+           cb[8 * i + j] |= tmp & 0x1;
+           tmp >>= 1;
+       }
+    }
+    for (i = 1; i < nk; i++)
+       memmove(cb + 7 * i, cb + 8 * i, 7);
+    *bufsiz = 7 * nk;
+    return 0;
+}
+
+/*
+ * Enctype-specific knowledge about how to derive a des key from a given
+ * key.  If given a des key, use it directly; otherwise, perform any
+ * parity fixup that may be needed and pass through to the hmad-md5 bits.
+ */
+int
+tkt_DeriveDesKey(int enctype, void *keydata, size_t keylen,
+                struct ktc_encryptionKey *output)
+{
+    switch (enctype) {
+    case ETYPE_DES_CBC_CRC:
+    case ETYPE_DES_CBC_MD4:
+    case ETYPE_DES_CBC_MD5:
+       if (keylen != 8)
+           return 1;
+
+       /* Extract session key */
+       memcpy(output, keydata, 8);
+       break;
+    case ETYPE_NULL:
+    case 4:
+    case 6:
+    case 8:
+    case 9:
+    case 10:
+    case 11:
+    case 12:
+    case 13:
+    case 14:
+    case 15:
+       return 1;
+       /*In order to become a "Cryptographic Key" as specified in
+        * SP800-108, it must be indistinguishable from a random bitstring. */
+    case ETYPE_DES3_CBC_MD5:
+    case ETYPE_OLD_DES3_CBC_SHA1:
+    case ETYPE_DES3_CBC_SHA1:
+       if (compress_parity_bits(keydata, &keylen))
+           return 1;
+       /* FALLTHROUGH */
+    default:
+       if (enctype < 0)
+           return 1;
+       if (keylen < 7)
+           return 1;
+       if (rxkad_derive_des_key(keydata, keylen, output) != 0)
+           return 1;
+    }
+    return 0;
+}