From 33f54da3dda8ef95c2a8d8580fde312c6fe4d3fb Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 17 Sep 2019 16:35:23 -0400 Subject: [PATCH] Add KRB5KDF from RFC 3961 Signed-off-by: Simo Sorce Reviewed-by: Tomas Mraz Reviewed-by: Richard Levitte (Merged from https://github.com/openssl/openssl/pull/9949) --- crypto/err/openssl.txt | 3 + doc/man3/EVP_KDF.pod | 6 +- doc/man7/EVP_KDF-KRB5KDF.pod | 119 +++++ include/openssl/core_names.h | 3 + .../common/include/prov/providercommonerr.h | 3 + providers/common/provider_err.c | 4 + providers/defltprov.c | 1 + .../include/prov/implementations.h | 1 + providers/implementations/kdfs/build.info | 3 + providers/implementations/kdfs/krb5kdf.c | 432 ++++++++++++++++++ test/evp_kdf_test.c | 38 ++ test/evp_test.c | 9 + test/recipes/30-test_evp_data/evpkdf.txt | 120 +++++ 13 files changed, 740 insertions(+), 2 deletions(-) create mode 100644 doc/man7/EVP_KDF-KRB5KDF.pod create mode 100644 providers/implementations/kdfs/krb5kdf.c diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index 46d2eaa2eb..9f405018c4 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -2681,6 +2681,7 @@ PROV_R_FAILED_TO_GET_PARAMETER:103:failed to get parameter PROV_R_FAILED_TO_SET_PARAMETER:104:failed to set parameter PROV_R_INAVLID_UKM_LENGTH:146:inavlid ukm length PROV_R_INVALID_AAD:108:invalid aad +PROV_R_INVALID_CONSTANT_LENGTH:157:invalid constant length PROV_R_INVALID_CUSTOM_LENGTH:111:invalid custom length PROV_R_INVALID_DATA:115:invalid data PROV_R_INVALID_DIGEST:122:invalid digest @@ -2698,6 +2699,8 @@ PROV_R_INVALID_SEED_LENGTH:154:invalid seed length PROV_R_INVALID_TAG:110:invalid tag PROV_R_INVALID_TAGLEN:118:invalid taglen PROV_R_MISSING_CEK_ALG:144:missing cek alg +PROV_R_MISSING_CIPHER:155:missing cipher +PROV_R_MISSING_CONSTANT:156:missing constant PROV_R_MISSING_KEY:128:missing key PROV_R_MISSING_MAC:150:missing mac PROV_R_MISSING_MESSAGE_DIGEST:129:missing message digest diff --git a/doc/man3/EVP_KDF.pod b/doc/man3/EVP_KDF.pod index 339129a943..9bb360d8b8 100644 --- a/doc/man3/EVP_KDF.pod +++ b/doc/man3/EVP_KDF.pod @@ -183,8 +183,10 @@ The default value, if any, is implementation dependent. =item "digest" (B) -For KDF implementations that use an underlying computation MAC or -digest, these parameters set what the algorithm should be. +=item "cipher" (B) + +For KDF implementations that use an underlying computation MAC, digest or +cipher, these parameters set what the algorithm should be. The value is always the name of the intended algorithm, or the properties. diff --git a/doc/man7/EVP_KDF-KRB5KDF.pod b/doc/man7/EVP_KDF-KRB5KDF.pod new file mode 100644 index 0000000000..08f50d1c05 --- /dev/null +++ b/doc/man7/EVP_KDF-KRB5KDF.pod @@ -0,0 +1,119 @@ +=pod + +=head1 NAME + +EVP_KDF-KRB5KDF - The RFC3961 Krb5 KDF EVP_KDF implementation + +=head1 DESCRIPTION + +Support for computing the B KDF through the B API. + +The EVP_KDF-KRB5KDF algorithm implements the key derivation function defined +in RFC 3961, section 5.1 and is used by Krb5 to derive session keys. +Three inputs are required to perform key derivation: a cipher, (for example +AES-128-CBC), the initial key, and a constant. + +=head2 Identity + +"KRB5KDF" is the name for this implementation; +it can be used with the EVP_KDF_fetch() function. + +=head2 Supported parameters + +The supported parameters are: + +=over 4 + +=item "properies" (B) + +=item "cipher" (B) + +=item "key" (B) + +These parameters work as described in L. + +=item "constant" (B) + +This parameter sets the constant value for the KDF. +If a value is already set, the contents are replaced. + +=back + +=head1 NOTES + +A context for KRB5KDF can be obtained by calling: + + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KRB5KDF", NULL); + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + +The output length of the KRB5KDF derivation is specified via the I +parameter to the L function, and MUST match the key +length for the chosen cipher or an error is returned. Moreover the +constant's length must not exceed the block size of the cipher. +Since the KRB5KDF output length depends on the chosen cipher, calling +L to obtain the requisite length returns the correct length +only after the cipher is set. Prior to that B is returned. +The caller must allocate a buffer of the correct length for the chosen +cipher, and pass that buffer to the L function along +with that length. + +=head1 EXAMPLES + +This example derives a key using the AES-128-CBC cipher: + + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; + unsigned char key[16] = "01234..."; + unsigned char constant[] = "I'm a constant"; + unsigned char out[16]; + size_t outlen = sizeof(out); + OSSL_PARAM params[4], *p = params; + + kdf = EVP_KDF_fetch(NULL, "KRB5KDF", NULL); + kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF_free(kdf); + + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_CIPHER, + SN_aes_128_cbc, + strlen(SN_aes_128_cbc)); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, + key, (size_t)16); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_CONSTANT, + constant, strlen(constant)); + *p = OSSL_PARAM_construct_end(); + if (EVP_KDF_set_params(kctx, params) <= 0) + /* Error */ + + if (EVP_KDF_derive(kctx, out, &outlen) <= 0) + /* Error */ + + +=head1 CONFORMING TO + +RFC 3961 + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L + +=head1 HISTORY + +This functionality was added to OpenSSL 3.0. + +=head1 COPYRIGHT + +Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the OpenSSL license (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut + diff --git a/include/openssl/core_names.h b/include/openssl/core_names.h index e7e5ea060e..4cbcf0cc71 100644 --- a/include/openssl/core_names.h +++ b/include/openssl/core_names.h @@ -133,6 +133,8 @@ extern "C" { #define OSSL_KDF_PARAM_SSHKDF_SESSION_ID "session_id" /* octet string */ #define OSSL_KDF_PARAM_SSHKDF_TYPE "type" /* int */ #define OSSL_KDF_PARAM_SIZE "size" /* size_t */ +#define OSSL_KDF_PARAM_CIPHER OSSL_ALG_PARAM_CIPHER /* utf8 string */ +#define OSSL_KDF_PARAM_CONSTANT "constant" /* octet string */ /* Known KDF names */ #define OSSL_KDF_NAME_HKDF "HKDF" @@ -144,6 +146,7 @@ extern "C" { #define OSSL_KDF_NAME_X942KDF "X942KDF" #define OSSL_KDF_NAME_X963KDF "X963KDF" #define OSSL_KDF_NAME_KBKDF "KBKDF" +#define OSSL_KDF_NAME_KRB5KDF "KRB5KDF" /* PKEY parameters */ /* Diffie-Hellman/DSA Parameters */ diff --git a/providers/common/include/prov/providercommonerr.h b/providers/common/include/prov/providercommonerr.h index d35789e832..c726a1ec29 100644 --- a/providers/common/include/prov/providercommonerr.h +++ b/providers/common/include/prov/providercommonerr.h @@ -60,6 +60,7 @@ int ERR_load_PROV_strings(void); # define PROV_R_FAILED_TO_SET_PARAMETER 104 # define PROV_R_INAVLID_UKM_LENGTH 146 # define PROV_R_INVALID_AAD 108 +# define PROV_R_INVALID_CONSTANT_LENGTH 157 # define PROV_R_INVALID_CUSTOM_LENGTH 111 # define PROV_R_INVALID_DATA 115 # define PROV_R_INVALID_DIGEST 122 @@ -77,6 +78,8 @@ int ERR_load_PROV_strings(void); # define PROV_R_INVALID_TAG 110 # define PROV_R_INVALID_TAGLEN 118 # define PROV_R_MISSING_CEK_ALG 144 +# define PROV_R_MISSING_CIPHER 155 +# define PROV_R_MISSING_CONSTANT 156 # define PROV_R_MISSING_KEY 128 # define PROV_R_MISSING_MAC 150 # define PROV_R_MISSING_MESSAGE_DIGEST 129 diff --git a/providers/common/provider_err.c b/providers/common/provider_err.c index 20060b0b35..589f37be70 100644 --- a/providers/common/provider_err.c +++ b/providers/common/provider_err.c @@ -32,6 +32,8 @@ static const ERR_STRING_DATA PROV_str_reasons[] = { {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INAVLID_UKM_LENGTH), "inavlid ukm length"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_AAD), "invalid aad"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CONSTANT_LENGTH), + "invalid constant length"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_CUSTOM_LENGTH), "invalid custom length"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_DATA), "invalid data"}, @@ -54,6 +56,8 @@ static const ERR_STRING_DATA PROV_str_reasons[] = { {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_TAG), "invalid tag"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_TAGLEN), "invalid taglen"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_CEK_ALG), "missing cek alg"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_CIPHER), "missing cipher"}, + {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_CONSTANT), "missing constant"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_KEY), "missing key"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_MAC), "missing mac"}, {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_MESSAGE_DIGEST), diff --git a/providers/defltprov.c b/providers/defltprov.c index 8104227fff..cf0a1755da 100644 --- a/providers/defltprov.c +++ b/providers/defltprov.c @@ -339,6 +339,7 @@ static const OSSL_ALGORITHM deflt_kdfs[] = { #ifndef OPENSSL_NO_SCRYPT { "SCRYPT:id-scrypt", "default=yes", kdf_scrypt_functions }, #endif + { OSSL_KDF_NAME_KRB5KDF, "default=yes", kdf_krb5kdf_functions }, { NULL, NULL, NULL } }; diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h index 16f2129115..ded3ef97f7 100644 --- a/providers/implementations/include/prov/implementations.h +++ b/providers/implementations/include/prov/implementations.h @@ -236,6 +236,7 @@ extern const OSSL_DISPATCH kdf_kbkdf_functions[]; #ifndef OPENSSL_NO_CMS extern const OSSL_DISPATCH kdf_x942_kdf_functions[]; #endif +extern const OSSL_DISPATCH kdf_krb5kdf_functions[]; /* Key management */ diff --git a/providers/implementations/kdfs/build.info b/providers/implementations/kdfs/build.info index dee8f532fa..2c6b2ce1c8 100644 --- a/providers/implementations/kdfs/build.info +++ b/providers/implementations/kdfs/build.info @@ -4,6 +4,7 @@ $TLS1_PRF_GOAL=../../libimplementations.a $HKDF_GOAL=../../libimplementations.a $KBKDF_GOAL=../../libimplementations.a +$KRB5KDF_GOAL=../../libimplementations.a $PBKDF2_GOAL=../../libimplementations.a $SSKDF_GOAL=../../libimplementations.a $SCRYPT_GOAL=../../libimplementations.a @@ -16,6 +17,8 @@ SOURCE[$HKDF_GOAL]=hkdf.c SOURCE[$KBKDF_GOAL]=kbkdf.c +SOURCE[$KRB5KDF_GOAL]=krb5kdf.c + SOURCE[$PBKDF2_GOAL]=pbkdf2.c # Extra code to satisfy the FIPS and non-FIPS separation. # When the PBKDF2 moves to legacy, this can be removed. diff --git a/providers/implementations/kdfs/krb5kdf.c b/providers/implementations/kdfs/krb5kdf.c new file mode 100644 index 0000000000..83dfa9c2dc --- /dev/null +++ b/providers/implementations/kdfs/krb5kdf.c @@ -0,0 +1,432 @@ +/* + * Copyright 2018-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "internal/cryptlib.h" +#include "crypto/evp.h" +#include "internal/numbers.h" +#include "prov/implementations.h" +#include "prov/provider_ctx.h" +#include "prov/provider_util.h" +#include "prov/providercommonerr.h" + +/* KRB5 KDF defined in RFC 3961, Section 5.1 */ + +static OSSL_OP_kdf_newctx_fn krb5kdf_new; +static OSSL_OP_kdf_freectx_fn krb5kdf_free; +static OSSL_OP_kdf_reset_fn krb5kdf_reset; +static OSSL_OP_kdf_derive_fn krb5kdf_derive; +static OSSL_OP_kdf_settable_ctx_params_fn krb5kdf_settable_ctx_params; +static OSSL_OP_kdf_set_ctx_params_fn krb5kdf_set_ctx_params; +static OSSL_OP_kdf_gettable_ctx_params_fn krb5kdf_gettable_ctx_params; +static OSSL_OP_kdf_get_ctx_params_fn krb5kdf_get_ctx_params; + +static int KRB5KDF(const EVP_CIPHER *cipher, ENGINE *engine, + const unsigned char *key, size_t key_len, + const unsigned char *constant, size_t constant_len, + unsigned char *okey, size_t okey_len); + +typedef struct { + void *provctx; + PROV_CIPHER cipher; + unsigned char *key; + size_t key_len; + unsigned char *constant; + size_t constant_len; +} KRB5KDF_CTX; + +static void *krb5kdf_new(void *provctx) +{ + KRB5KDF_CTX *ctx; + + if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) == NULL) + ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); + ctx->provctx = provctx; + return ctx; +} + +static void krb5kdf_free(void *vctx) +{ + KRB5KDF_CTX *ctx = (KRB5KDF_CTX *)vctx; + + krb5kdf_reset(ctx); + OPENSSL_free(ctx); +} + +static void krb5kdf_reset(void *vctx) +{ + KRB5KDF_CTX *ctx = (KRB5KDF_CTX *)vctx; + + ossl_prov_cipher_reset(&ctx->cipher); + OPENSSL_clear_free(ctx->key, ctx->key_len); + OPENSSL_clear_free(ctx->constant, ctx->constant_len); + memset(ctx, 0, sizeof(*ctx)); +} + +static int krb5kdf_set_membuf(unsigned char **dst, size_t *dst_len, + const OSSL_PARAM *p) +{ + OPENSSL_clear_free(*dst, *dst_len); + *dst = NULL; + return OSSL_PARAM_get_octet_string(p, (void **)dst, 0, dst_len); +} + +static int krb5kdf_derive(void *vctx, unsigned char *key, + size_t keylen) +{ + KRB5KDF_CTX *ctx = (KRB5KDF_CTX *)vctx; + const EVP_CIPHER *cipher = ossl_prov_cipher_cipher(&ctx->cipher); + ENGINE *engine = ossl_prov_cipher_engine(&ctx->cipher); + + if (cipher == NULL) { + ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_CIPHER); + return 0; + } + if (ctx->key == NULL) { + ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY); + return 0; + } + if (ctx->constant == NULL) { + ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_CONSTANT); + return 0; + } + return KRB5KDF(cipher, engine, ctx->key, ctx->key_len, + ctx->constant, ctx->constant_len, + key, keylen); +} + +static int krb5kdf_set_ctx_params(void *vctx, const OSSL_PARAM params[]) +{ + const OSSL_PARAM *p; + KRB5KDF_CTX *ctx = vctx; + OPENSSL_CTX *provctx = PROV_LIBRARY_CONTEXT_OF(ctx->provctx); + + if (!ossl_prov_cipher_load_from_params(&ctx->cipher, params, provctx)) + return 0; + + if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_KEY)) != NULL) + if (!krb5kdf_set_membuf(&ctx->key, &ctx->key_len, p)) + return 0; + + if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_CONSTANT)) + != NULL) + if (!krb5kdf_set_membuf(&ctx->constant, &ctx->constant_len, p)) + return 0; + + return 1; +} + +static const OSSL_PARAM *krb5kdf_settable_ctx_params(void) +{ + static const OSSL_PARAM known_settable_ctx_params[] = { + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_PROPERTIES, NULL, 0), + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_CIPHER, NULL, 0), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, NULL, 0), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_CONSTANT, NULL, 0), + OSSL_PARAM_END + }; + return known_settable_ctx_params; +} + +static int krb5kdf_get_ctx_params(void *vctx, OSSL_PARAM params[]) +{ + KRB5KDF_CTX *ctx = (KRB5KDF_CTX *)vctx; + const EVP_CIPHER *cipher; + size_t len; + OSSL_PARAM *p; + + cipher = ossl_prov_cipher_cipher(&ctx->cipher); + if (cipher) + len = EVP_CIPHER_key_length(cipher); + else + len = EVP_MAX_KEY_LENGTH; + + if ((p = OSSL_PARAM_locate(params, OSSL_KDF_PARAM_SIZE)) != NULL) + return OSSL_PARAM_set_size_t(p, len); + return -2; +} + +static const OSSL_PARAM *krb5kdf_gettable_ctx_params(void) +{ + static const OSSL_PARAM known_gettable_ctx_params[] = { + OSSL_PARAM_size_t(OSSL_KDF_PARAM_SIZE, NULL), + OSSL_PARAM_END + }; + return known_gettable_ctx_params; +} + +const OSSL_DISPATCH kdf_krb5kdf_functions[] = { + { OSSL_FUNC_KDF_NEWCTX, (void(*)(void))krb5kdf_new }, + { OSSL_FUNC_KDF_FREECTX, (void(*)(void))krb5kdf_free }, + { OSSL_FUNC_KDF_RESET, (void(*)(void))krb5kdf_reset }, + { OSSL_FUNC_KDF_DERIVE, (void(*)(void))krb5kdf_derive }, + { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS, + (void(*)(void))krb5kdf_settable_ctx_params }, + { OSSL_FUNC_KDF_SET_CTX_PARAMS, + (void(*)(void))krb5kdf_set_ctx_params }, + { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS, + (void(*)(void))krb5kdf_gettable_ctx_params }, + { OSSL_FUNC_KDF_GET_CTX_PARAMS, + (void(*)(void))krb5kdf_get_ctx_params }, + { 0, NULL } +}; + +#ifndef OPENSSL_NO_DES +/* + * DES3 is a special case, it requires a random-to-key function and its + * input truncated to 21 bytes of the 24 produced by the cipher. + * See RFC3961 6.3.1 + */ +static int fixup_des3_key(unsigned char *key) +{ + unsigned char *cblock; + int i, j; + + for (i = 2; i >= 0; i--) { + cblock = &key[i * 8]; + memmove(cblock, &key[i * 7], 7); + cblock[7] = 0; + for (j = 0; j < 7; j++) + cblock[7] |= (cblock[j] & 1) << (j + 1); + DES_set_odd_parity((DES_cblock *)cblock); + } + + /* fail if keys are such that triple des degrades to single des */ + if (CRYPTO_memcmp(&key[0], &key[8], 8) == 0 || + CRYPTO_memcmp(&key[8], &key[16], 8) == 0) { + return 0; + } + + return 1; +} +#endif + +/* + * N-fold(K) where blocksize is N, and constant_len is K + * Note: Here |= denotes concatenation + * + * L = lcm(N,K) + * R = L/K + * + * for r: 1 -> R + * s |= constant rot 13*(r-1)) + * + * block = 0 + * for k: 1 -> K + * block += s[N(k-1)..(N-1)k] (one's complement addition) + * + * Optimizing for space we compute: + * for each l in L-1 -> 0: + * s[l] = (constant rot 13*(l/K))[l%k] + * block[l % N] += s[l] (with carry) + * finally add carry if any + */ +static void n_fold(unsigned char *block, unsigned int blocksize, + const unsigned char *constant, size_t constant_len) +{ + unsigned int tmp, gcd, remainder, lcm, carry; + int b, l; + + if (constant_len == blocksize) { + memcpy(block, constant, constant_len); + return; + } + + /* Least Common Multiple of lengths: LCM(a,b)*/ + gcd = blocksize; + remainder = constant_len; + /* Calculate Great Common Divisor first GCD(a,b) */ + while (remainder != 0) { + tmp = gcd % remainder; + gcd = remainder; + remainder = tmp; + } + /* resulting a is the GCD, LCM(a,b) = |a*b|/GCD(a,b) */ + lcm = blocksize * constant_len / gcd; + + /* now spread out the bits */ + memset(block, 0, blocksize); + + /* last to first to be able to bring carry forward */ + carry = 0; + for (l = lcm - 1; l >= 0; l--) { + unsigned int rotbits, rshift, rbyte; + + /* destination byte in block is l % N */ + b = l % blocksize; + /* Our virtual s buffer is R = L/K long (K = constant_len) */ + /* So we rotate backwards from R-1 to 0 (none) rotations */ + rotbits = 13 * (l / constant_len); + /* find the byte on s where rotbits falls onto */ + rbyte = l - (rotbits / 8); + /* calculate how much shift on that byte */ + rshift = rotbits & 0x07; + /* rbyte % constant_len gives us the unrotated byte in the + * constant buffer, get also the previous byte then + * appropriately shift them to get the rotated byte we need */ + tmp = (constant[(rbyte-1) % constant_len] << (8 - rshift) + | constant[rbyte % constant_len] >> rshift) + & 0xff; + /* add with carry to any value placed by previous passes */ + tmp += carry + block[b]; + block[b] = tmp & 0xff; + /* save any carry that may be left */ + carry = tmp >> 8; + } + + /* if any carry is left at the end, add it through the number */ + for (b = blocksize - 1; b >= 0 && carry != 0; b--) { + carry += block[b]; + block[b] = carry & 0xff; + carry >>= 8; + } +} + +static int cipher_init(EVP_CIPHER_CTX *ctx, + const EVP_CIPHER *cipher, ENGINE *engine, + const unsigned char *key, size_t key_len) +{ + int klen, ret; + + ret = EVP_EncryptInit_ex(ctx, cipher, engine, key, NULL); + if (!ret) + goto out; + /* set the key len for the odd variable key len cipher */ + klen = EVP_CIPHER_CTX_key_length(ctx); + if (key_len != (size_t)klen) { + ret = EVP_CIPHER_CTX_set_key_length(ctx, key_len); + if (!ret) + goto out; + } + /* we never want padding, either the length requested is a multiple of + * the cipher block size or we are passed a cipher that can cope with + * partial blocks via techniques like cipher text stealing */ + ret = EVP_CIPHER_CTX_set_padding(ctx, 0); + if (!ret) + goto out; + +out: + return ret; +} + +static int KRB5KDF(const EVP_CIPHER *cipher, ENGINE *engine, + const unsigned char *key, size_t key_len, + const unsigned char *constant, size_t constant_len, + unsigned char *okey, size_t okey_len) +{ + EVP_CIPHER_CTX *ctx = NULL; + unsigned char block[EVP_MAX_BLOCK_LENGTH * 2]; + unsigned char *plainblock, *cipherblock; + size_t blocksize; + size_t cipherlen; + size_t osize; + int des3_no_fixup = 0; + int ret; + + if (key_len != okey_len) { + /* special case for 3des, where the caller may be requesting + * the random raw key, instead of the fixed up key */ + if (EVP_CIPHER_nid(cipher) == NID_des_ede3_cbc && + key_len == 24 && okey_len == 21) { + des3_no_fixup = 1; + } else { + ERR_raise(ERR_LIB_PROV, PROV_R_WRONG_OUTPUT_BUFFER_SIZE); + return 0; + } + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + ret = cipher_init(ctx, cipher, engine, key, key_len); + if (!ret) + goto out; + + /* Initialize input block */ + blocksize = EVP_CIPHER_CTX_block_size(ctx); + + if (constant_len > blocksize) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CONSTANT_LENGTH); + ret = 0; + goto out; + } + + n_fold(block, blocksize, constant, constant_len); + plainblock = block; + cipherblock = block + EVP_MAX_BLOCK_LENGTH; + + for (osize = 0; osize < okey_len; osize += cipherlen) { + int olen; + + ret = EVP_EncryptUpdate(ctx, cipherblock, &olen, + plainblock, blocksize); + if (!ret) + goto out; + cipherlen = olen; + ret = EVP_EncryptFinal_ex(ctx, cipherblock, &olen); + if (!ret) + goto out; + if (olen != 0) { + ERR_raise(ERR_LIB_PROV, PROV_R_WRONG_FINAL_BLOCK_LENGTH); + ret = 0; + goto out; + } + + /* write cipherblock out */ + if (cipherlen > okey_len - osize) + cipherlen = okey_len - osize; + memcpy(okey + osize, cipherblock, cipherlen); + + if (okey_len > osize + cipherlen) { + /* we need to reinitialize cipher context per spec */ + ret = EVP_CIPHER_CTX_reset(ctx); + if (!ret) + goto out; + ret = cipher_init(ctx, cipher, engine, key, key_len); + if (!ret) + goto out; + + /* also swap block offsets so last ciphertext becomes new + * plaintext */ + plainblock = cipherblock; + if (cipherblock == block) { + cipherblock += EVP_MAX_BLOCK_LENGTH; + } else { + cipherblock = block; + } + } + } + +#ifndef OPENSSL_NO_DES + if (EVP_CIPHER_nid(cipher) == NID_des_ede3_cbc && !des3_no_fixup) { + ret = fixup_des3_key(okey); + if (!ret) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY); + goto out; + } + } +#endif + + ret = 1; + +out: + EVP_CIPHER_CTX_free(ctx); + OPENSSL_cleanse(block, EVP_MAX_BLOCK_LENGTH * 2); + return ret; +} + diff --git a/test/evp_kdf_test.c b/test/evp_kdf_test.c index 21a0c270fe..ddf935b818 100644 --- a/test/evp_kdf_test.c +++ b/test/evp_kdf_test.c @@ -732,6 +732,43 @@ static int test_kdf_x942_asn1(void) } #endif /* OPENSSL_NO_CMS */ +static int test_kdf_krb5kdf(void) +{ + int ret; + EVP_KDF_CTX *kctx; + OSSL_PARAM params[4], *p = params; + unsigned char out[16]; + static unsigned char key[] = { + 0x42, 0x26, 0x3C, 0x6E, 0x89, 0xF4, 0xFC, 0x28, + 0xB8, 0xDF, 0x68, 0xEE, 0x09, 0x79, 0x9F, 0x15 + }; + static unsigned char constant[] = { + 0x00, 0x00, 0x00, 0x02, 0x99 + }; + static const unsigned char expected[sizeof(out)] = { + 0x34, 0x28, 0x0A, 0x38, 0x2B, 0xC9, 0x27, 0x69, + 0xB2, 0xDA, 0x2F, 0x9E, 0xF0, 0x66, 0x85, 0x4B + }; + + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_CIPHER, + (char *)"AES-128-CBC", + sizeof("AES-128-CBC")); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, key, + sizeof(key)); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_CONSTANT, + constant, sizeof(constant)); + *p = OSSL_PARAM_construct_end(); + + ret = + TEST_ptr(kctx = get_kdfbyname(OSSL_KDF_NAME_KRB5KDF)) + && TEST_true(EVP_KDF_CTX_set_params(kctx, params)) + && TEST_int_gt(EVP_KDF_derive(kctx, out, sizeof(out)), 0) + && TEST_mem_eq(out, sizeof(out), expected, sizeof(expected)); + + EVP_KDF_CTX_free(kctx); + return ret; +} + int setup_tests(void) { ADD_TEST(test_kdf_kbkdf_6803_128); @@ -753,5 +790,6 @@ int setup_tests(void) #ifndef OPENSSL_NO_CMS ADD_TEST(test_kdf_x942_asn1); #endif + ADD_TEST(test_kdf_krb5kdf); return 1; } diff --git a/test/evp_test.c b/test/evp_test.c index b68ad3b9c4..1039c2131a 100644 --- a/test/evp_test.c +++ b/test/evp_test.c @@ -2116,6 +2116,15 @@ static int kdf_test_ctrl(EVP_TEST *t, EVP_KDF_CTX *kctx, if (nid != NID_undef && EVP_get_digestbynid(nid) == NULL) t->skip = 1; } + if (p != NULL && strcmp(name, "cipher") == 0) { + /* If p has an OID and lookup fails assume disabled algorithm */ + int nid = OBJ_sn2nid(p); + + if (nid == NID_undef) + nid = OBJ_ln2nid(p); + if (nid != NID_undef && EVP_get_cipherbynid(nid) == NULL) + t->skip = 1; + } OPENSSL_free(name); return 1; } diff --git a/test/recipes/30-test_evp_data/evpkdf.txt b/test/recipes/30-test_evp_data/evpkdf.txt index 3905f17ebc..73d3b3ca9e 100644 --- a/test/recipes/30-test_evp_data/evpkdf.txt +++ b/test/recipes/30-test_evp_data/evpkdf.txt @@ -6523,3 +6523,123 @@ Ctrl.hexsecret = hexsecret:000102030405060708090a0b0c0d0e0f10111213 Ctrl.cekalg = cekalg:id-smime-alg-CMSRC2wrap Ctrl.hexukm = hexukm:0123456789abcdeffedcba98765432010123456789abcdeffedcba98765432010123456789abcdeffedcba98765432010123456789abcdeffedcba9876543201 Output = 48950c46e0530075403cce72889604e0 + +Title = KRB5KDF tests (from RFC 3961 test vectors and krb5 sources) + +#RFC3961 +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92 +Ctrl.hexconstant = hexconstant:0000000155 +Output = 925179d04591a79b5d3192c4a7e9c289b049c71f6ee604cd + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:5e13d31c70ef765746578531cb51c15bf11ca82c97cee9f2 +Ctrl.hexconstant = hexconstant:00000001aa +Output = 9e58e5a146d9942a101c469845d67a20e3c4259ed913f207 + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:98e6fd8a04a4b6859b75a176540b9752bad3ecd610a252bc +Ctrl.hexconstant = hexconstant:0000000155 +Output = 13fef80d763e94ec6d13fd2ca1d085070249dad39808eabf + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:622aec25a2fe2cad7094680b7c64940280084c1a7cec92b5 +Ctrl.hexconstant = hexconstant:00000001aa +Output = f8dfbf04b097e6d9dc0702686bcb3489d91fd9a4516b703e + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:d3f8298ccb166438dcb9b93ee5a7629286a491f838f802fb +Ctrl.hexconstant = hexconstant:6b65726265726f73 +Output = 2370da575d2a3da864cebfdc5204d56df779a7df43d9da43 + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:c1081649ada74362e6a1459d01dfd30d67c2234c940704da +Ctrl.hexconstant = hexconstant:0000000155 +Output = 348057ec98fdc48016161c2a4c7a943e92ae492c989175f7 + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:5d154af238f46713155719d55e2f1f790dd661f279a7917c +Ctrl.hexconstant = hexconstant:00000001aa +Output = a8808ac267dada3dcbe9a7c84626fbc761c294b01315e5c1 + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:798562e049852f57dc8c343ba17f2ca1d97394efc8adc443 +Ctrl.hexconstant = hexconstant:0000000155 +Output = c813f88a3be3b334f75425ce9175fbe3c8493b89c8703b49 + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:26dce334b545292f2feab9a8701a89a4b99eb9942cecd016 +Ctrl.hexconstant = hexconstant:00000001aa +Output = f48ffd6e83f83e7354e694fd252cf83bfe58f7d5ba37ec5d + +#Krb5 sources +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:850BB51358548CD05E86768C313E3BFEF7511937DCF72C3E +Ctrl.hexconstant = hexconstant:0000000299 +Output = F78C496D16E6C2DAE0E0B6C24057A84C0426AEEF26FD6DCE + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:850BB51358548CD05E86768C313E3BFEF7511937DCF72C3E +Ctrl.hexconstant = hexconstant:00000002AA +Output = 5B5723D0B634CB684C3EBA5264E9A70D52E683231AD3C4CE + +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:850BB51358548CD05E86768C313E3BFEF7511937DCF72C3E +Ctrl.hexconstant = hexconstant:0000000255 +Output = A77C94980E9B7345A81525C423A737CE67F4CD91B6B3DA45 + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-128-CBC +Ctrl.hexkey = hexkey:42263C6E89F4FC28B8DF68EE09799F15 +Ctrl.hexconstant = hexconstant:0000000299 +Output = 34280A382BC92769B2DA2F9EF066854B + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-128-CBC +Ctrl.hexkey = hexkey:42263C6E89F4FC28B8DF68EE09799F15 +Ctrl.hexconstant = hexconstant:00000002AA +Output = 5B14FC4E250E14DDF9DCCF1AF6674F53 + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-128-CBC +Ctrl.hexkey = hexkey:42263C6E89F4FC28B8DF68EE09799F15 +Ctrl.hexconstant = hexconstant:0000000255 +Output = 4ED31063621684F09AE8D89991AF3E8F + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-256-CBC +Ctrl.hexkey = hexkey:FE697B52BC0D3CE14432BA036A92E65BBB52280990A2FA27883998D72AF30161 +Ctrl.hexconstant = hexconstant:0000000299 +Output = BFAB388BDCB238E9F9C98D6A878304F04D30C82556375AC507A7A852790F4674 + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-256-CBC +Ctrl.hexkey = hexkey:FE697B52BC0D3CE14432BA036A92E65BBB52280990A2FA27883998D72AF30161 +Ctrl.hexconstant = hexconstant:00000002AA +Output = C7CFD9CD75FE793A586A542D87E0D1396F1134A104BB1A9190B8C90ADA3DDF37 + +KDF = KRB5KDF +Ctrl.cipher = cipher:AES-256-CBC +Ctrl.hexkey = hexkey:FE697B52BC0D3CE14432BA036A92E65BBB52280990A2FA27883998D72AF30161 +Ctrl.hexconstant = hexconstant:0000000255 +Output = 97151B4C76945063E2EB0529DC067D97D7BBA90776D8126D91F34F3101AEA8BA + +#Same as the first but with no "fixup" +KDF = KRB5KDF +Ctrl.cipher = cipher:DES-EDE3-CBC +Ctrl.hexkey = hexkey:dce06b1f64c857a11c3db57c51899b2cc1791008ce973b92 +Ctrl.hexconstant = hexconstant:0000000155 +Output = 935079d14490a75c3093c4a6e8c3b049c71e6ee705 + -- 2.25.1