From 3725d22140c23a376dd79b69d130be8e2b91005f Mon Sep 17 00:00:00 2001 From: Sam Hartman Date: Mon, 19 Sep 2011 00:34:57 +0000 Subject: [PATCH] implementation of new function and test program for pkinit agility. 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 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25191 dc483132-0cff-0310-8789-dd5450dbe970 --- src/plugins/preauth/pkinit/Makefile.in | 11 + src/plugins/preauth/pkinit/pkinit_crypto.h | 19 ++ .../preauth/pkinit/pkinit_crypto_openssl.c | 208 +++++++++++++++++- .../preauth/pkinit/pkinit_kdf_constants.c | 57 +++++ src/plugins/preauth/pkinit/pkinit_kdf_test.c | 183 +++++++++++++++ 5 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 src/plugins/preauth/pkinit/pkinit_kdf_constants.c create mode 100644 src/plugins/preauth/pkinit/pkinit_kdf_test.c diff --git a/src/plugins/preauth/pkinit/Makefile.in b/src/plugins/preauth/pkinit/Makefile.in index 73bf6c1d0..1f81d1c02 100644 --- a/src/plugins/preauth/pkinit/Makefile.in +++ b/src/plugins/preauth/pkinit/Makefile.in @@ -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@ diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h index 2c584b6bf..ad8e81558 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto.h +++ b/src/plugins/preauth/pkinit/pkinit_crypto.h @@ -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 */ diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c index 6f7023f70..977daf346 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -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 index 000000000..2ff576b14 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_kdf_constants.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 index 000000000..ed8987336 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_kdf_test.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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); +} -- 2.26.2