diff options
author | Simo Sorce <simo@redhat.com> | 2019-09-17 16:35:23 -0400 |
---|---|---|
committer | Tomas Mraz <tmraz@fedoraproject.org> | 2019-10-23 08:32:48 +0200 |
commit | 33f54da3dda8ef95c2a8d8580fde312c6fe4d3fb (patch) | |
tree | 587fa80c2a189d5bea1901eeb0f74fbe71f75dfe /providers/implementations | |
parent | eb2ff0408ac6e934e05db7ed4006855c018584f1 (diff) |
Add KRB5KDF from RFC 3961
Signed-off-by: Simo Sorce <simo@redhat.com>
Reviewed-by: Tomas Mraz <tmraz@fedoraproject.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/9949)
Diffstat (limited to 'providers/implementations')
-rw-r--r-- | providers/implementations/include/prov/implementations.h | 1 | ||||
-rw-r--r-- | providers/implementations/kdfs/build.info | 3 | ||||
-rw-r--r-- | providers/implementations/kdfs/krb5kdf.c | 432 |
3 files changed, 436 insertions, 0 deletions
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 <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include <openssl/core_names.h> +#include <openssl/des.h> +#include <openssl/evp.h> +#include <openssl/kdf.h> + +#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; +} + |