Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,15 @@ versions may need to update templates or error-handling:

- `C_DeriveKey` now enforces `CKA_DERIVE` on the base key. Keys that do not
set `CKA_DERIVE=CK_TRUE` at creation are rejected with
`CKR_KEY_TYPE_INCONSISTENT`. RSA, EC, and ML-DSA private keys default
`CKR_KEY_FUNCTION_NOT_PERMITTED`. RSA, EC, and ML-DSA private keys default
`CKA_DERIVE` to `CK_FALSE`, so applications doing ECDH or other key
derivation must now set `CKA_DERIVE=CK_TRUE` explicitly in the key
template. The check is skipped on `--enable-nss` builds because NSS
generates ephemeral ECDHE keys without setting `CKA_DERIVE` and relies on
the historic permissive behavior; non-NSS builds get the spec-compliant
enforcement.
template. The second key of `CKM_CONCATENATE_BASE_AND_KEY` and the
`CKF_HKDF_SALT_KEY` salt key of `CKM_HKDF_DERIVE` and `CKM_HKDF_DATA` need
`CKA_DERIVE=CK_TRUE` too. The check is skipped on `--enable-nss` builds
because NSS generates ephemeral ECDHE keys without setting `CKA_DERIVE`
and relies on the historic permissive behavior; non-NSS builds get the
spec-compliant enforcement.
- `C_DeriveKey` returns `CKR_TEMPLATE_INCONSISTENT` and creates no key when
the template sets `CKA_SENSITIVE=CK_FALSE` under a sensitive base key, or
`CKA_EXTRACTABLE=CK_TRUE` under a non-extractable one. The second key of
Expand All @@ -188,8 +190,12 @@ versions may need to update templates or error-handling:
leaves these attributes out gets a sensitive key from a sensitive base key
and a non-extractable key from a non-extractable one; for
`CKM_TLS12_KEY_AND_MAC_DERIVE` this is new, as its keys used the defaults
before. NSS builds and builds with `WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT`
are unchanged.
before. An HKDF salt key counts as a base key too, as the derived value
depends on it: its `CKA_SENSITIVE`, `CKA_EXTRACTABLE`,
`CKA_ALWAYS_SENSITIVE`, `CKA_NEVER_EXTRACTABLE` and
`CKA_WRAP_WITH_TRUSTED` now carry over to the derived key as those of the
second concatenation key do. NSS builds and builds with
`WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT` are unchanged.
- `C_DeriveKey` returns `CKR_ATTRIBUTE_VALUE_INVALID` and creates no key when
a key derived from a sensitive or non-extractable key would be shorter than
16 bytes. A `CKM_TLS12_KEY_AND_MAC_DERIVE` MAC or key size that short
Expand Down
55 changes: 44 additions & 11 deletions src/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -11086,6 +11086,7 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession,
WP11_Object* obj = NULL;
CK_ATTRIBUTE* valueAttr = NULL;
#ifdef WP11_DERIVE_SECRET_KEY
/* The CKM_CONCATENATE_BASE_AND_KEY second key or the HKDF salt key. */
WP11_Object* otherKey = NULL;
byte* derivedKey = NULL;
word32 keyLen;
Expand Down Expand Up @@ -11274,15 +11275,32 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession,
!CK_ULONG_FITS_WORD32(kdfParams->ulInfoLen))
return CKR_MECHANISM_PARAM_INVALID;
if (kdfParams->bExtract &&
kdfParams->ulSaltType == CKF_HKDF_SALT_KEY &&
WP11_Object_Find(session, kdfParams->hSaltKey,
&saltObj) == 0 &&
WP11_Object_GetClass(saltObj) == CKO_SECRET_KEY) {
rv = CheckKeyDecoded(saltObj);
if (rv != CKR_OK)
return rv;
kdfParams->ulSaltType == CKF_HKDF_SALT_KEY) {
/* The salt key is the HMAC key of HKDF-Extract. */
if (WP11_Object_Find(session, kdfParams->hSaltKey,
&saltObj) != 0 ||
WP11_Object_GetClass(saltObj) != CKO_SECRET_KEY ||
!WP11_KeyType_IsSecret(WP11_Object_GetType(saltObj)))
return CKR_MECHANISM_PARAM_INVALID;
#ifndef WP11_NSS_PERMISSIVE_KEY_DEFAULTS
ret = CheckOpSupported(saltObj, CKA_DERIVE);
#else
ret = (int)CheckKeyDecoded(saltObj);
#endif
if (ret != CKR_OK)
return ret;
if (TLS12_ONLY(saltObj))
return CKR_KEY_FUNCTION_NOT_PERMITTED;
#ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT
/* The derived key depends on the salt key's value, so it
* takes the salt key's protection as it does that of the
* second key of CKM_CONCATENATE_BASE_AND_KEY. */
otherKey = saltObj;
Comment thread
LinuxJedi marked this conversation as resolved.
Resolved
bLen = sizeof(CK_BBOOL);
if (WP11_Object_GetAttr(saltObj, CKA_WRAP_WITH_TRUSTED,
&otherState, &bLen) == 0 && otherState == CK_TRUE)
wrapTrusted = CK_TRUE;
#endif
}

FindAttributeType(pTemplate, ulAttributeCount, CKA_VALUE_LEN,
Expand Down Expand Up @@ -11759,10 +11777,25 @@ static CK_RV wp11_C_DeriveKey(CK_SESSION_HANDLE hSession,
if (reqValueLen > keyLen)
rv = CKR_ATTRIBUTE_VALUE_INVALID;
#ifndef WOLFPKCS11_NO_DERIVE_LENGTH_GUARD
if (rv == CKR_OK && !concat && WP11_Object_GetClass(obj) != CKO_DATA &&
DerivedKeyTooShort(baseSensitive, baseExtractable,
(reqValueLen > 0) ? reqValueLen : keyLen))
rv = CKR_ATTRIBUTE_VALUE_INVALID;
if (rv == CKR_OK && !concat) {
CK_BBOOL guardSensitive = baseSensitive;
CK_BBOOL guardExtractable = baseExtractable;
int guard = 1;

/* A data object base is readable, so only an HKDF salt key's
* protection limits the length. */
if (WP11_Object_GetClass(obj) == CKO_DATA) {
if (otherKey != NULL) {
GetBaseProtection(otherKey, &guardSensitive,
&guardExtractable);
}
else
guard = 0;
}
if (guard && DerivedKeyTooShort(guardSensitive, guardExtractable,
(reqValueLen > 0) ? reqValueLen : keyLen))
rv = CKR_ATTRIBUTE_VALUE_INVALID;
}
#endif
#ifndef WOLFPKCS11_LEGACY_DERIVE_NO_INHERIT
if (rv == CKR_OK) {
Expand Down
103 changes: 103 additions & 0 deletions tests/derive_length_guard_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,107 @@ static CK_RV hkdf_data(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base,
}
#endif

/* HKDF extract and expand into a sensitive key with the salt in a key, or
* with no salt when salt is CK_INVALID_HANDLE. */
static CK_RV hkdf_salt_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE base,
CK_OBJECT_HANDLE salt, CK_ULONG len,
CK_OBJECT_HANDLE* key)
{
CK_HKDF_PARAMS params;
CK_MECHANISM mech = { CKM_HKDF_DERIVE, &params, sizeof(params) };
CK_ATTRIBUTE tmpl[] = {
{ CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass) },
{ CKA_KEY_TYPE, &genericKeyType, sizeof(genericKeyType) },
{ CKA_VALUE_LEN, &len, sizeof(len) },
{ CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) },
};

XMEMSET(&params, 0, sizeof(params));
params.bExtract = CK_TRUE;
params.bExpand = CK_TRUE;
params.prfHashMechanism = CKM_SHA256;
params.ulSaltType = (salt != CK_INVALID_HANDLE) ? CKF_HKDF_SALT_KEY :
CKF_HKDF_SALT_NULL;
params.hSaltKey = salt;
*key = CK_INVALID_HANDLE;
return funcList->C_DeriveKey(session, &mech, base, tmpl,
sizeof(tmpl) / sizeof(*tmpl), key);
}

/* The derived key depends on the salt key's value as much as on the base
* key's, so a protected salt key limits the length even when the base key, or
* data object, is readable. */
static void test_hkdf_salt_key(CK_SESSION_HANDLE session)
{
CK_RV rv;
CK_OBJECT_HANDLE base = CK_INVALID_HANDLE;
CK_OBJECT_HANDLE data = CK_INVALID_HANDLE;
CK_OBJECT_HANDLE salt = CK_INVALID_HANDLE;
CK_OBJECT_HANDLE readableSalt = CK_INVALID_HANDLE;
CK_OBJECT_HANDLE key = CK_INVALID_HANDLE;
CK_OBJECT_CLASS dataClass = CKO_DATA;
byte ikm[32];
byte saltValue[16];
CK_ATTRIBUTE dataTmpl[] = {
{ CKA_CLASS, &dataClass, sizeof(dataClass) },
{ CKA_DERIVE, &ckTrue, sizeof(ckTrue) },
{ CKA_VALUE, ikm, sizeof(ikm) },
};

printf("\n--- HKDF salt key lengths ---\n");
XMEMSET(ikm, 0x0b, sizeof(ikm));
XMEMSET(saltValue, 0x3c, sizeof(saltValue));
rv = create_secret(session, CKK_GENERIC_SECRET, ikm, sizeof(ikm), CK_FALSE,
&base);
check_rv(rv, CKR_OK, "create readable HKDF base key");
if (rv == CKR_OK) {
rv = funcList->C_CreateObject(session, dataTmpl,
sizeof(dataTmpl) / sizeof(*dataTmpl),
&data);
check_rv(rv, CKR_OK, "create HKDF base data object");
}
if (rv == CKR_OK) {
rv = create_secret(session, CKK_GENERIC_SECRET, saltValue,
sizeof(saltValue), CK_TRUE, &salt);
check_rv(rv, CKR_OK, "create protected HKDF salt key");
}
if (rv == CKR_OK) {
rv = create_secret(session, CKK_GENERIC_SECRET, saltValue,
sizeof(saltValue), CK_FALSE, &readableSalt);
check_rv(rv, CKR_OK, "create readable HKDF salt key");
}
if (rv != CKR_OK)
goto cleanup;

rv = hkdf_salt_key(session, base, salt, 8, &key);
check_derive(session, rv, GUARD_RV(CKR_ATTRIBUTE_VALUE_INVALID), &key,
"8 byte HKDF key from a protected salt key");
rv = hkdf_salt_key(session, base, salt, 16, &key);
check_derive(session, rv, CKR_OK, &key,
"16 byte HKDF key from a protected salt key");
rv = hkdf_salt_key(session, base, readableSalt, 8, &key);
check_derive(session, rv, CKR_OK, &key,
"8 byte HKDF key from a readable salt key");
rv = hkdf_salt_key(session, data, salt, 8, &key);
check_derive(session, rv, GUARD_RV(CKR_ATTRIBUTE_VALUE_INVALID), &key,
"8 byte HKDF key from a data object and protected salt key");
rv = hkdf_salt_key(session, data, salt, 16, &key);
check_derive(session, rv, CKR_OK, &key,
"16 byte HKDF key from a data object and protected salt key");
rv = hkdf_salt_key(session, data, readableSalt, 8, &key);
check_derive(session, rv, CKR_OK, &key,
"8 byte HKDF key from a data object and readable salt key");
rv = hkdf_salt_key(session, data, CK_INVALID_HANDLE, 8, &key);
check_derive(session, rv, CKR_OK, &key,
"8 byte HKDF key from a data object and no salt key");

cleanup:
destroy_obj(session, &readableSalt);
destroy_obj(session, &salt);
destroy_obj(session, &data);
destroy_obj(session, &base);
}

static void test_hkdf(CK_SESSION_HANDLE session)
{
CK_RV rv;
Expand Down Expand Up @@ -414,6 +515,8 @@ static void test_hkdf(CK_SESSION_HANDLE session)
check_derive(session, rv, CKR_OK, &key, "16 byte protected HKDF key");
destroy_obj(session, &base);

test_hkdf_salt_key(session);

#ifdef WOLFPKCS11_NSS
rv = funcList->C_CreateObject(session, dataTmpl,
sizeof(dataTmpl) / sizeof(*dataTmpl), &base);
Expand Down
Loading
Loading