Skip to content

Commit

Permalink
[KDF] Add feedback-mode and CMAC support to KBKDF
Browse files Browse the repository at this point in the history
Implement SP800-108 section 5.2 with CMAC support.  As a side effect,
enable 5.1 with CMAC and 5.2 with HMAC.  Add test vectors from RFC 6803.

Add OSSL_KDF_PARAM_CIPHER and PROV_R_INVALID_SEED_LENGTH.

Signed-off-by: Robbie Harwood <rharwood@redhat.com>

Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
(Merged from #10143)
  • Loading branch information
frozencemetery authored and slontis committed Oct 17, 2019
1 parent 028687c commit f6dead1
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 33 deletions.
3 changes: 2 additions & 1 deletion crypto/cmac/cmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ int CMAC_Final(CMAC_CTX *ctx, unsigned char *out, size_t *poutlen)
return 0;
if ((bl = EVP_CIPHER_CTX_block_size(ctx->cctx)) < 0)
return 0;
*poutlen = (size_t)bl;
if (poutlen != NULL)
*poutlen = (size_t)bl;
if (!out)
return 1;
lb = ctx->nlast_block;
Expand Down
1 change: 1 addition & 0 deletions crypto/err/openssl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2694,6 +2694,7 @@ PROV_R_INVALID_MAC:151:invalid mac
PROV_R_INVALID_MODE:125:invalid mode
PROV_R_INVALID_MODE_INT:126:invalid mode int
PROV_R_INVALID_SALT_LENGTH:112:invalid salt length
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
Expand Down
67 changes: 56 additions & 11 deletions doc/man7/EVP_KDF-KB.pod
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,36 @@ The supported parameters are:

=over 4

=item B<OSSL_KDF_PARAM_PROPERTIES> ("properties") <UTF8 string>
=item "properties" (B<OSSL_KDF_PARAM_PROPERTIES>) <UTF8 string>

=item B<OSSL_KDF_PARAM_DIGEST> ("digest") <UTF8 string>
=item "mode" (B<OSSL_KDF_PARAM_MODE>) <UTF8 string>

=item B<OSSL_KDF_PARAM_DIGEST> ("mac") <UTF8 string>
=item "mac" (B<OSSL_KDF_PARAM_MAC>) <UTF8 string>

=item B<OSSL_KDF_PARAM_KEY> ("key") <octet string>
=item "digest" (B<OSSL_KDF_PARAM_DIGEST>) <UTF8 string>

=item B<OSSL_KDF_PARAM_SALT> ("salt") <octet string>
=item "cipher" (B<OSSL_KDF_PARAM_DIGEST>) <UTF8 string>

=item B<OSSL_KDF_PARAM_INFO> ("info") <octet string>
=item "key" (B<OSSL_KDF_PARAM_KEY>) <octet string>

=item "salt" (B<OSSL_KDF_PARAM_SALT>) <octet string>

=item "info (B<OSSL_KDF_PARAM_INFO>) <octet string>

=item "seed" (B<OSSL_KDF_PARAM_SEED>) <octet string>

=back

The parameters key, salt, and info correspond to KI, Label, and Context
(respectively) in SP800-108. As in that document, salt and info are optional
and may be omitted. Currently, only HMAC is supported for mac.
The mode parameter determines which flavor of KBKDF to use - currently the
choices are "counter" and "feedback". Counter is the default, and will be
used if unspecified. The seed parameter is unused in counter mode.

The parameters key, salt, info, and seed correspond to KI, Label, Context, and
IV (respectively) in SP800-108. As in that document, salt, info, and seed are
optional and may be omitted.

Depending on whether mac is CMAC or HMAC, either digest or cipher is required
(respectively) and the other is unused.

=head1 NOTES

Expand All @@ -49,7 +62,7 @@ A context for KBKDF can be obtained by calling:
The output length of an KBKDF is specified via the C<keylen>
parameter to the L<EVP_KDF_derive(3)> function.

Note that currently OpenSSL only implements Counter mode with HMAC. Other
Note that currently OpenSSL only implements counter and feedback modes. Other
variants may be supported in the future.

=head1 EXAMPLES
Expand Down Expand Up @@ -84,9 +97,41 @@ Label "label", and Context "context".

EVP_KDF_CTX_free(kctx);

This example derives 10 bytes using FEEDBACK-CMAC-AES256, with KI "secret",
Label "label", and IV "sixteen bytes iv".

EVP_KDF *kdf;
EVP_KDF_CTX *kctx;
unsigned char out[10];
OSSL_PARAM params[8], *p = params;
unsigned char *iv = "sixteen bytes iv";

kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL);
kctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);

*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_CIPHER, "AES256", 0);
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MAC, "CMAC", 0);
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MODE, "FEEDBACK", 0);
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
"secret", strlen("secret"));
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
"context", strlen("context"));
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
"label", strlen("label"));
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED,
iv, strlen(iv));
*p = OSSL_PARAM_construct_end();
if (EVP_KDF_CTX_set_params(kctx, params) <= 0)
error("EVP_KDF_CTX_set_params");
else if (EVP_KDF_derive(kctx, out, sizeof(out)) <= 0)
error("EVP_KDF_derive");

EVP_KDF_CTX_free(kctx);

=head1 CONFORMING TO

NIST SP800-108, IETF RFC 8009.
NIST SP800-108, IETF RFC 6803, IETF RFC 8009.

=head1 SEE ALSO

Expand Down
1 change: 1 addition & 0 deletions include/openssl/core_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ extern "C" {
#define OSSL_KDF_PARAM_SALT "salt" /* octet string */
#define OSSL_KDF_PARAM_PASSWORD "pass" /* octet string */
#define OSSL_KDF_PARAM_DIGEST OSSL_ALG_PARAM_DIGEST /* utf8 string */
#define OSSL_KDF_PARAM_CIPHER OSSL_ALG_PARAM_CIPHER /* utf8 string */
#define OSSL_KDF_PARAM_MAC OSSL_ALG_PARAM_MAC /* utf8 string */
#define OSSL_KDF_PARAM_MAC_SIZE "maclen" /* size_t */
#define OSSL_KDF_PARAM_PROPERTIES OSSL_ALG_PARAM_PROPERTIES /* utf8 string */
Expand Down
1 change: 1 addition & 0 deletions providers/common/include/prov/providercommonerr.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ int ERR_load_PROV_strings(void);
# define PROV_R_INVALID_MODE 125
# define PROV_R_INVALID_MODE_INT 126
# define PROV_R_INVALID_SALT_LENGTH 112
# define PROV_R_INVALID_SEED_LENGTH 154
# define PROV_R_INVALID_TAG 110
# define PROV_R_INVALID_TAGLEN 118
# define PROV_R_MISSING_CEK_ALG 144
Expand Down
2 changes: 2 additions & 0 deletions providers/common/provider_err.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_MODE_INT), "invalid mode int"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_SALT_LENGTH),
"invalid salt length"},
{ERR_PACK(ERR_LIB_PROV, 0, PROV_R_INVALID_SEED_LENGTH),
"invalid seed length"},
{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"},
Expand Down
2 changes: 1 addition & 1 deletion providers/common/provider_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ int ossl_prov_macctx_load_from_params(EVP_MAC_CTX **macctx,
*mp++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
(char *)mdname, 0);
if (ciphername != NULL)
*mp++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
*mp++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER,
(char *)ciphername, 0);
if (properties != NULL)
*mp++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_PROPERTIES,
Expand Down
83 changes: 63 additions & 20 deletions providers/implementations/kdfs/kbkdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

/*
* This implements https://csrc.nist.gov/publications/detail/sp/800-108/final
* section 5.1 ("counter mode") in HMAC only. That document does not name the
* KDFs it defines; the name is derived from
* section 5.1 ("counter mode") and section 5.2 ("feedback mode") in both HMAC
* and CMAC. That document does not name the KDFs it defines; the name is
* derived from
* https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Key-Derivation
*
* Note that sections 5.2 ("feedback mode") and 5.3 ("double-pipeline mode")
* are not implemented, though it would be possible to do so in the future.
* CMAC mode is also not implemented; some plumbing would be required.
* Note that section 5.3 ("double-pipeline mode") is not implemented, though
* it would be possible to do so in the future.
*
* These versions all assume the counter is used. It would be relatively
* straightforward to expose a configuration handle should the need arise.
Expand Down Expand Up @@ -46,9 +46,15 @@

#define MIN(a, b) ((a) < (b)) ? (a) : (b)

typedef enum {
COUNTER = 0,
FEEDBACK
} kbkdf_mode;

/* Our context structure. */
typedef struct {
void *provctx;
kbkdf_mode mode;
EVP_MAC_CTX *ctx_init;

/* Names are lowercased versions of those found in SP800-108. */
Expand All @@ -58,6 +64,8 @@ typedef struct {
size_t label_len;
unsigned char *context;
size_t context_len;
unsigned char *iv;
size_t iv_len;
} KBKDF;

/* Definitions needed for typechecking. */
Expand Down Expand Up @@ -117,29 +125,38 @@ static void kbkdf_reset(void *vctx)
OPENSSL_clear_free(ctx->context, ctx->context_len);
OPENSSL_clear_free(ctx->label, ctx->label_len);
OPENSSL_clear_free(ctx->ki, ctx->ki_len);
OPENSSL_clear_free(ctx->iv, ctx->iv_len);
memset(ctx, 0, sizeof(*ctx));
}

/* SP800-108 section 5.1. */
static int kbkdf_derive_counter(EVP_MAC_CTX *ctx_init,
unsigned char *label, size_t label_len,
unsigned char *context, size_t context_len,
unsigned char *k_i, size_t h, uint32_t l,
unsigned char *ko, size_t ko_len)
/* SP800-108 section 5.1 or section 5.2 depending on mode. */
static int derive(EVP_MAC_CTX *ctx_init, kbkdf_mode mode, unsigned char *iv,
size_t iv_len, unsigned char *label, size_t label_len,
unsigned char *context, size_t context_len,
unsigned char *k_i, size_t h, uint32_t l, unsigned char *ko,
size_t ko_len)
{
int ret = 0;
EVP_MAC_CTX *ctx = NULL;
size_t written = 0, to_write;
size_t written = 0, to_write, k_i_len = iv_len;
const unsigned char zero = 0;
uint32_t counter, i;

/* Setup K(0) for feedback mode. */
if (iv_len > 0)
memcpy(k_i, iv, iv_len);

for (counter = 1; written < ko_len; counter++) {
i = be32(counter);

ctx = EVP_MAC_CTX_dup(ctx_init);
if (ctx == NULL)
goto done;

/* Perform feedback, if appropriate. */
if (mode == FEEDBACK && !EVP_MAC_update(ctx, k_i, k_i_len))
goto done;

if (!EVP_MAC_update(ctx, (unsigned char *)&i, 4)
|| !EVP_MAC_update(ctx, label, label_len)
|| !EVP_MAC_update(ctx, &zero, 1)
Expand All @@ -152,6 +169,7 @@ static int kbkdf_derive_counter(EVP_MAC_CTX *ctx_init,
memcpy(ko + written, k_i, MIN(to_write, h));
written += h;

k_i_len = h;
EVP_MAC_CTX_free(ctx);
ctx = NULL;
}
Expand All @@ -170,29 +188,34 @@ static int kbkdf_derive(void *vctx, unsigned char *key, size_t keylen)
uint32_t l = be32(keylen * 8);
size_t h = 0;

/* Label and Context are permitted to be empty. Check everything else. */
/* label, context, and iv are permitted to be empty. Check everything
* else. */
if (ctx->ctx_init == NULL) {
if (ctx->ki_len == 0 || ctx->ki == NULL) {
ERR_raise(ERR_LIB_PROV, PROV_R_NO_KEY_SET);
return 0;
}
/* Could either be missing MAC or missing message digest -
* arbitrarily, I pick this one. */
/* Could either be missing MAC or missing message digest or missing
* cipher - arbitrarily, I pick this one. */
ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_MAC);
return 0;
}

h = EVP_MAC_size(ctx->ctx_init);
if (h == 0)
goto done;
if (ctx->iv_len != 0 && ctx->iv_len != h) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
goto done;
}

k_i = OPENSSL_zalloc(h);
if (k_i == NULL)
goto done;

ret = kbkdf_derive_counter(
ctx->ctx_init, ctx->label, ctx->label_len, ctx->context,
ctx->context_len, k_i, h, l, key, keylen);
ret = derive(ctx->ctx_init, ctx->mode, ctx->iv, ctx->iv_len, ctx->label,
ctx->label_len, ctx->context, ctx->context_len, k_i, h, l,
key, keylen);
done:
if (ret != 1)
OPENSSL_cleanse(key, keylen);
Expand Down Expand Up @@ -222,12 +245,25 @@ static int kbkdf_set_ctx_params(void *vctx, const OSSL_PARAM params[])
NULL, NULL, libctx))
return 0;
else if (ctx->ctx_init != NULL
&& !EVP_MAC_is_a(EVP_MAC_CTX_mac(ctx->ctx_init),
OSSL_MAC_NAME_HMAC)) {
&& !EVP_MAC_is_a(EVP_MAC_CTX_mac(ctx->ctx_init),
OSSL_MAC_NAME_HMAC)
&& !EVP_MAC_is_a(EVP_MAC_CTX_mac(ctx->ctx_init),
OSSL_MAC_NAME_CMAC)) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MAC);
return 0;
}

p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_MODE);
if (p != NULL && strncasecmp("counter", p->data, p->data_size) == 0) {
ctx->mode = COUNTER;
} else if (p != NULL
&& strncasecmp("feedback", p->data, p->data_size) == 0) {
ctx->mode = FEEDBACK;
} else if (p != NULL) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_MODE);
return 0;
}

p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_KEY);
if (p != NULL && !kbkdf_set_buffer(&ctx->ki, &ctx->ki_len, p))
return 0;
Expand All @@ -240,6 +276,10 @@ static int kbkdf_set_ctx_params(void *vctx, const OSSL_PARAM params[])
if (p != NULL && !kbkdf_set_buffer(&ctx->context, &ctx->context_len, p))
return 0;

p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_SEED);
if (p != NULL && !kbkdf_set_buffer(&ctx->iv, &ctx->iv_len, p))
return 0;

/* Set up digest context, if we can. */
if (ctx->ctx_init != NULL && ctx->ki_len != 0) {
mparams[0] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY,
Expand All @@ -260,8 +300,11 @@ static const OSSL_PARAM *kbkdf_settable_ctx_params(void)
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_INFO, NULL, 0),
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, NULL, 0),
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, NULL, 0),
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SEED, NULL, 0),
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, NULL, 0),
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_CIPHER, NULL, 0),
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_MAC, NULL, 0),
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_MODE, NULL, 0),

OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_PROPERTIES, NULL, 0),
OSSL_PARAM_END,
Expand Down

0 comments on commit f6dead1

Please sign in to comment.