implementation of new function and test program for pkinit agility.
authorSam Hartman <hartmans@mit.edu>
Mon, 19 Sep 2011 00:34:57 +0000 (00:34 +0000)
committerSam Hartman <hartmans@mit.edu>
Mon, 19 Sep 2011 00:34:57 +0000 (00:34 +0000)
Implementation of pkinit_alg_agility_kdf() function to implement the
key derivation function defined in draft-ietf-krb-wg-pkinit-alg-agility-04,
and implementation of pkinit_kdf_test program to test the new KDF
against the test vector in the draft.

Signed-off-by: Margaret Wasserman <mrw@painless-security.com>
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25191 dc483132-0cff-0310-8789-dd5450dbe970

src/plugins/preauth/pkinit/Makefile.in
src/plugins/preauth/pkinit/pkinit_crypto.h
src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
src/plugins/preauth/pkinit/pkinit_kdf_constants.c [new file with mode: 0644]
src/plugins/preauth/pkinit/pkinit_kdf_test.c [new file with mode: 0644]

index 73bf6c1d091499376f0134deee88122bd6870a03..1f81d1c029b0fc80334b4ecc62a8577975312e4f 100644 (file)
@@ -8,6 +8,7 @@ MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
 DEFS=@DEFS@
 
 LOCALINCLUDES = -I../../../include/krb5 -I.
+RUN_SETUP = @KRB5_RUN_ENV@
 
 LIBBASE=pkinit
 LIBMAJOR=0
@@ -29,6 +30,7 @@ STLIBOBJS= \
        pkinit_srv.o \
        pkinit_lib.o \
        pkinit_clnt.o \
+        pkinit_kdf_constants.o \
        pkinit_profile.o \
        pkinit_identity.o \
        pkinit_matching.o \
@@ -38,6 +40,8 @@ SRCS= \
        $(srcdir)/pkinit_accessor.c \
        $(srcdir)/pkinit_srv.c \
        $(srcdir)/pkinit_lib.c \
+       $(srcdir)/pkinit_kdf_test.c \
+       $(srcdir)/pkinit_kdf_constants.c \
        $(srcdir)/pkinit_clnt.c \
        $(srcdir)/pkinit_profile.c \
        $(srcdir)/pkinit_identity.c \
@@ -50,6 +54,13 @@ clean-unix:: clean-libs clean-libobjs
 
 clean::
        $(RM) lib$(LIBBASE)$(SO_EXT)
+       $(RM) pkinit_test_kdf
+
+check-unix:: pkinit_kdf_test
+            $(RUN_SETUP) $(VALGRIND) ./pkinit_kdf_test
+
+pkinit_kdf_test: pkinit_kdf_test.o $(STLIBOBJS) $(SHLIB_EXPDEPS)
+                $(CC_LINK) -o $@ pkinit_kdf_test.o $(STLIBOBJS) $(SHLIB_EXPLIBS)
 
 @libnover_frag@
 @libobj_frag@
index 2c584b6bf4aa3ca252ea2f85ed28bff6e1fd273c..ad8e81558673e34838c7c4daa4b024dff69a2e93 100644 (file)
@@ -631,4 +631,23 @@ krb5_error_code pkinit_identity_set_prompter
        krb5_prompter_fct prompter,                     /* IN */
        void *prompter_data);                           /* IN */
 
+krb5_error_code
+pkinit_alg_agility_kdf(krb5_context context,
+                       krb5_octet_data *secret,
+                       krb5_algorithm_identifier *alg_id,
+                       krb5_principal party_u_info,
+                       krb5_principal party_v_info,
+                       krb5_enctype enctype,
+                       krb5_octet_data *as_req,
+                       krb5_octet_data *pk_as_rep,
+                       const krb5_ticket  *ticket,
+                       krb5_keyblock *key_block);
+
+extern const krb5_octet krb5_pkinit_sha1_oid[];
+extern const size_t krb5_pkinit_sha1_oid_len;
+extern const krb5_octet krb5_pkinit_sha256_oid[];
+extern const size_t krb5_pkinit_sha256_oid_len;
+extern const krb5_octet krb5_pkinit_sha512_oid[];
+extern const size_t  krb5_pkinit_sha512_oid_len;
+
 #endif /* _PKINIT_CRYPTO_H */
index 6f7023f700bdfddd5ee6e12b93c8f70dc2e523c6..977daf3465029c2a93b8c4292fdd9dc3db28832d 100644 (file)
@@ -2101,7 +2101,7 @@ pkinit_octetstring2key(krb5_context context,
                        krb5_enctype etype,
                        unsigned char *key,
                        unsigned int dh_key_len,
-                       krb5_keyblock * key_block)
+                       krb5_keyblock *key_block)
 {
     krb5_error_code retval;
     unsigned char *buf = NULL;
@@ -2140,7 +2140,7 @@ pkinit_octetstring2key(krb5_context context,
 
     retval = krb5_c_keylengths(context, etype, &keybytes, &keylength);
     if (retval)
-        goto cleanup;
+       goto cleanup;
 
     key_block->length = keylength;
     key_block->contents = malloc(keylength);
@@ -2156,7 +2156,7 @@ pkinit_octetstring2key(krb5_context context,
 
 cleanup:
     free(buf);
-    // If this is an error return, free the allocated keyblock, if any
+    /* If this is an error return, free the allocated keyblock, if any */
     if (retval) {
         krb5_free_keyblock_contents(context, key_block);
     }
@@ -2164,6 +2164,208 @@ cleanup:
     return retval;
 }
 
+
+/**
+ * Given an algorithm_identifier, this function returns the hash length
+ * and EVP function associated with that algorithm.
+ */
+static krb5_error_code
+pkinit_alg_values(krb5_context context,
+                  krb5_algorithm_identifier *alg_id,
+                  size_t *hash_bytes,
+                  const EVP_MD *(**func)(void))
+{
+    *hash_bytes = 0;
+    *func = NULL;
+    if ((alg_id->algorithm.length == krb5_pkinit_sha1_oid_len) &&
+        (0 == memcmp(alg_id->algorithm.data, &krb5_pkinit_sha1_oid,
+                     krb5_pkinit_sha1_oid_len))) {
+        *hash_bytes = 20;
+        *func = &EVP_sha1;
+        return 0;
+    }
+    else if ((alg_id->algorithm.length == krb5_pkinit_sha256_oid_len) &&
+        (0 == memcmp(alg_id->algorithm.data, krb5_pkinit_sha256_oid,
+                     krb5_pkinit_sha256_oid_len))) {
+        *hash_bytes = 32;
+        *func = &EVP_sha256;
+        return 0;
+    }
+    else if ((alg_id->algorithm.length == krb5_pkinit_sha512_oid_len) &&
+        (0 == memcmp(alg_id->algorithm.data, krb5_pkinit_sha512_oid,
+                     krb5_pkinit_sha512_oid_len))) {
+        *hash_bytes = 32;
+        *func = &EVP_sha512;
+        return 0;
+    }
+    else {
+        krb5_set_error_message(context, KRB5_ERR_BAD_S2K_PARAMS,
+                               "Bad algorithm ID passed to PK-INIT KDF.");
+        return KRB5_ERR_BAD_S2K_PARAMS;
+    }
+} /* pkinit_alg_values() */
+
+
+/* pkinit_alg_agility_kdf() --
+ * This function generates a key using the KDF described in
+ * draft_ietf_krb_wg_pkinit_alg_agility-04.txt.  The algorithm is
+ * described as follows:
+ *
+ *     1.  reps = keydatalen (K) / hash length (H)
+ *
+ *     2.  Initialize a 32-bit, big-endian bit string counter as 1.
+ *
+ *     3.  For i = 1 to reps by 1, do the following:
+ *
+ *         -  Compute Hashi = H(counter || Z || OtherInfo).
+ *
+ *         -  Increment counter (modulo 2^32)
+ *
+ *     4.  Set key = Hash1 || Hash2 || ... so that length of key is K bytes.
+ */
+krb5_error_code
+pkinit_alg_agility_kdf(krb5_context context,
+                       krb5_octet_data *secret,
+                       krb5_algorithm_identifier *alg_id,
+                       krb5_principal party_u_info,
+                       krb5_principal party_v_info,
+                       krb5_enctype enctype,
+                       krb5_octet_data *as_req,
+                       krb5_octet_data *pk_as_rep,
+                       const krb5_ticket *ticket,
+                       krb5_keyblock *key_block)
+{
+    krb5_error_code retval = 0;
+
+    unsigned int reps = 0;
+    uint32_t counter = 1;       /* Does this type work on Windows? */
+    size_t offset = 0;
+    size_t hash_len = 0;
+    unsigned char *rand_buf = NULL;
+    krb5_data random_data;
+    krb5_sp80056a_other_info other_info_fields;
+    krb5_pkinit_supp_pub_info supp_pub_info_fields;
+    krb5_data *other_info = NULL;
+    krb5_data *supp_pub_info = NULL;
+    const EVP_MD *(*EVP_func)(void);
+
+    /* initialize random_data here to make clean-up safe */
+    random_data.length = 0;
+    random_data.data = NULL;
+
+    /* allocate and initialize the key block */
+    key_block->magic = 0;
+    key_block->enctype = enctype;
+    if (0 != (retval = krb5_c_keylengths(context, enctype, (size_t *)NULL,
+                                         (size_t *)&(key_block->length))))
+        goto cleanup;
+    if (NULL == (key_block->contents = malloc(key_block->length))) {
+        retval = ENOMEM;
+        goto cleanup;
+    }
+    memset (key_block->contents, 0, key_block->length);
+
+    if (0 != (retval = pkinit_alg_values(context, alg_id, &hash_len, &EVP_func)))
+        goto cleanup;
+
+    /* 1.  reps = keydatalen (K) / hash length (H) */
+    reps = key_block->length/hash_len;
+
+    /* ... and round up, if necessary */
+    if (key_block->length > (reps * hash_len))
+        reps++;
+
+    /* Allocate enough space in the random data buffer to hash directly into
+     * it, even if the last hash will make it bigger than the key length. */
+    if (NULL == (rand_buf = malloc(reps * hash_len))) {
+        retval = ENOMEM;
+        goto cleanup;
+    }
+    memset(rand_buf, 0, reps * hash_len);
+
+    random_data.length = reps * hash_len;
+    random_data.data = (char *)rand_buf;
+
+    /* Encode the ASN.1 octet string for "SuppPubInfo" */
+    supp_pub_info_fields.enctype = enctype;
+    supp_pub_info_fields.as_req = *as_req;
+    supp_pub_info_fields.pk_as_rep = *pk_as_rep;
+    supp_pub_info_fields.ticket = (krb5_ticket *) ticket;
+    if (0 != ((retval = encode_krb5_pkinit_supp_pub_info(&supp_pub_info_fields,
+                                                         &supp_pub_info))))
+        goto cleanup;
+
+    /* Now encode the ASN.1 octet string for "OtherInfo" */
+    other_info_fields.algorithm_identifier = *alg_id;
+    other_info_fields.party_u_info = party_u_info;
+    other_info_fields.party_v_info = party_v_info;
+    other_info_fields.supp_pub_info = *supp_pub_info;
+    if (0 != (retval = encode_krb5_sp80056a_other_info(&other_info_fields, &other_info)))
+        goto cleanup;
+
+    /* 2.  Initialize a 32-bit, big-endian bit string counter as 1.
+     * 3.  For i = 1 to reps by 1, do the following:
+     *     -   Compute Hashi = H(counter || Z || OtherInfo).
+     *     -   Increment counter (modulo 2^32)
+     */
+    for (counter = 1, offset = 0; ((counter <= reps) && (offset <= key_block->length)); counter++) {
+        EVP_MD_CTX c;
+        uint s = 0;
+        uint32_t be_counter = htonl(counter);
+
+        EVP_MD_CTX_init(&c);
+
+        /* -   Compute Hashi = H(counter || Z || OtherInfo). */
+        if (0 == EVP_DigestInit(&c, EVP_func())) {
+            krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
+                                   "Call to OpenSSL EVP_DigestInit() returned an error.");
+            retval = KRB5_CRYPTO_INTERNAL;
+            goto cleanup;
+        }
+
+        if ((0 == EVP_DigestUpdate(&c, &be_counter, 4)) ||
+            (0 == EVP_DigestUpdate(&c, secret->data, secret->length)) ||
+            (0 == EVP_DigestUpdate(&c, other_info->data, other_info->length))) {
+            krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
+                               "Call to OpenSSL EVP_DigestUpdate() returned an error.");
+            retval = KRB5_CRYPTO_INTERNAL;
+            goto cleanup;
+        }
+
+        /* 4.  Set key = Hash1 || Hash2 || ... so that length of key is K bytes. */
+        if (0 == EVP_DigestFinal(&c, (rand_buf + offset), &s)) {
+        krb5_set_error_message(context, KRB5_CRYPTO_INTERNAL,
+                                   "Call to OpenSSL EVP_DigestUpdate() returned an error.");
+            retval = KRB5_CRYPTO_INTERNAL;
+            goto cleanup;
+        }
+        offset += s;
+
+        assert(s == hash_len); /* add a message to this assert? */
+
+    EVP_MD_CTX_cleanup(&c);
+    }
+
+    /* Reduce length of random data to key_len to avoid errors. */
+    random_data.length = key_block->length;
+    retval = krb5_c_random_to_key(context, enctype, &random_data,
+                                  key_block);
+
+cleanup:
+    /* If this has been an error, free the allocated key_block, if any */
+    if (retval) {
+        krb5_free_keyblock_contents(context, key_block);
+    }
+
+    /* free other allocated resources, either way */
+    if (rand_buf)
+        free(rand_buf);
+    krb5_free_data(context, other_info);
+    krb5_free_data(context, supp_pub_info);
+
+    return retval;
+} /*pkinit_alg_agility_kdf() */
+
 /* Call DH_compute_key() and ensure that we left-pad short results instead of
  * leaving junk bytes at the end of the buffer. */
 static void
diff --git a/src/plugins/preauth/pkinit/pkinit_kdf_constants.c b/src/plugins/preauth/pkinit/pkinit_kdf_constants.c
new file mode 100644 (file)
index 0000000..2ff576b
--- /dev/null
@@ -0,0 +1,57 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* prototype/prototype.c */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * pkinit_kdf_test.c -- Structures and constants for implementation of
+ * pkinit algorithm agility.  Includes definitions of algorithm identifiers
+ * for SHA-1, SHA-256 and SHA-512.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "k5-platform.h"
+#include "krb5.h"
+#include "k5-int-pkinit.h"
+
+#include "pkinit.h"
+#include "pkinit_crypto.h"
+
+/* statically declare OID constants for all three algorithms */
+const krb5_octet krb5_pkinit_sha1_oid[10] =
+                    {0x2B,0x06,0x01,0x05,0x02,0x03,0x06,0x01};
+const size_t krb5_pkinit_sha1_oid_len = 8;
+const krb5_octet krb5_pkinit_sha256_oid[10] =
+                    {0x2B,0x06,0x01,0x05,0x02,0x03,0x06,0x02};
+const size_t krb5_pkinit_sha256_oid_len = 8;
+const krb5_octet krb5_pkinit_sha512_oid [10] =
+                    {0x2B,0x06,0x01,0x05,0x02,0x03,0x06,0x03};
+const size_t krb5_pkinit_sha512_oid_len = 8;
diff --git a/src/plugins/preauth/pkinit/pkinit_kdf_test.c b/src/plugins/preauth/pkinit/pkinit_kdf_test.c
new file mode 100644 (file)
index 0000000..ed89873
--- /dev/null
@@ -0,0 +1,183 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* prototype/prototype.c */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * pkinit_kdf_test.c -- Test to verify the correctness of the function
+ * pkinit_alg_agility_kdf() in pkinit_crypto_openssl, which implements
+ * the Key Derivation Function from the PKInit Algorithm Agility
+ * document, currently draft-ietf-krb-wg-pkinit-alg-agility-04.txt.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "k5-platform.h"
+
+#include "pkinit_crypto_openssl.h"
+
+/**
+ * Initialize a krb5_data from @a s, a constant string. Note @a s is evaluated
+ * multiple times; this is acceptable for constants.
+ */
+#define DATA_FROM_STRING(s) \
+    {0, sizeof(s)-1, (char *) s}
+
+
+/* values from the test vector in the pkinit-alg-agility draft */
+int secret_len = 256;
+char twenty_as[10];
+char eighteen_bs[9] ;
+char party_u_name [] = "lha@SU.SE";
+char party_v_name [] = "krbtgt/SU.SE@SU.SE";
+int enctype_value = 18;
+krb5_octet key_hex [] =
+  {0xC7, 0x62, 0x89, 0xEC, 0x4B, 0x28, 0xA6, 0x91,
+   0xFF, 0xCE, 0x80, 0xBB, 0xB7, 0xEC, 0x82, 0x41,
+   0x52, 0x3F, 0x99, 0xB1, 0x90, 0xCF, 0x2D, 0x34,
+   0x8F, 0x54, 0xA8, 0x65, 0x81, 0x2C, 0x32, 0x73};
+const krb5_data lha_data = DATA_FROM_STRING("lha");
+const krb5_principal_data ticket_server = {
+    0, /*magic*/
+    DATA_FROM_STRING("SU.SE"),
+    (krb5_data *) &lha_data,
+    1, 1};
+const krb5_ticket test_ticket = {
+    KV5M_TICKET,
+    (krb5_principal) &ticket_server,
+    {0, /*magic*/
+     18,
+     0,
+     DATA_FROM_STRING("hejhej") },
+    NULL};
+
+
+
+int
+main (int argc,
+      char  **argv)
+{
+    /* arguments for calls to pkinit_alg_agility_kdf() */
+    krb5_context context = 0;
+    krb5_octet_data secret;
+    krb5_algorithm_identifier alg_id;
+    krb5_enctype enctype = enctype_value;
+    krb5_octet_data as_req;
+    krb5_octet_data pk_as_rep;
+    krb5_keyblock key_block;
+
+    /* other local variables */
+    int retval = 0;
+    int max_keylen = 2048;
+    krb5_principal u_principal = NULL;
+    krb5_principal v_principal = NULL;
+    krb5_keyblock *key_block_ptr = &key_block;
+
+    /* initialize variables that get malloc'ed, so cleanup is safe */
+    krb5_init_context (&context);
+    memset (&alg_id, 0, sizeof(alg_id));
+    memset (&as_req, 0, sizeof(as_req));
+    memset (&pk_as_rep, 0, sizeof(pk_as_rep));
+    memset (&key_block, 0, sizeof(key_block));
+
+    /* set up algorithm id */
+    alg_id.algorithm.data = (unsigned char *) &krb5_pkinit_sha1_oid;
+    alg_id.algorithm.length = krb5_pkinit_sha1_oid_len;
+
+    /* set up a 256-byte, ALL-ZEROS secret */
+    if (NULL == (secret.data = malloc(secret_len))) {
+        printf("ERROR in pkinit_kdf_test: Memory allocation failed.");
+        retval = ENOMEM;
+        goto cleanup;
+    }
+    secret.length = secret_len;
+    memset(secret.data, 0, secret_len);
+
+    /* set-up the partyUInfo and partyVInfo principals */
+    if ((0 != (retval = krb5_parse_name(context, party_u_name,
+                                       &u_principal)))
+       (0 != (retval = krb5_parse_name(context, party_v_name,
+                                       &v_principal)))) {
+      printf("ERROR in pkinit_kdf_test: Error parsing names, retval = %d",
+            retval);
+      goto cleanup;
+    }
+
+    /* set-up the as_req and and pk_as_rep data */
+    memset(twenty_as, 0xaa, sizeof(twenty_as));
+           memset(eighteen_bs, 0xbb, sizeof(eighteen_bs));
+    as_req.length = sizeof(twenty_as);
+    as_req.data = (unsigned char *)&twenty_as;
+
+    pk_as_rep.length = sizeof(eighteen_bs);
+    pk_as_rep.data = (unsigned char *)&eighteen_bs;
+
+    /* set-up the key_block */
+    if (0 != (retval = krb5_init_keyblock(context, enctype, max_keylen,
+                                          &key_block_ptr))) {
+         printf("ERROR in pkinit_kdf_test: can't init keybloc, retval = %d",
+                retval);
+         goto cleanup;
+
+       }
+
+    /* call krb5_pkinit_alg_agility_kdf() with test vector values*/
+    if (0 != (retval = pkinit_alg_agility_kdf(context, &secret, &alg_id,
+                                             u_principal, v_principal,
+                                             enctype, &as_req, &pk_as_rep,
+                                             &test_ticket, &key_block))) {
+        printf("ERROR in pkinit_kdf_test: kdf call failed, retval = %d",
+              retval);
+       goto cleanup;
+    }
+
+    /* compare key to expected key value */
+
+    if ((key_block.length == sizeof(key_hex)) &&
+            (0 == memcmp(key_block.contents, key_hex, key_block.length))) {
+            printf("SUCCESS: Correct key value generated!");
+            retval = 0;
+        }
+        else {
+            printf("FAILURE: Incorrect key value generated!");
+            retval = 1;
+        }
+
+    cleanup:
+       /* release all allocated resources, whether good or bad return */
+       if (secret.data)
+         free(secret.data);
+       if (u_principal)
+         free(u_principal);
+       if (v_principal)
+         free(v_principal);
+               krb5_free_keyblock_contents(context, &key_block);
+       exit(retval);
+}