From 6b46c69e475e0c57eb31168d8ac0c67be4b5465a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 1 Sep 2026 10:56:20 +0200 Subject: [PATCH] cryptocb: add WC_ALGO_TYPE_KEYSTORE for hardware key store operations A hardware key store holds keys with a lifetime of their own, and none of the existing callbacks can manage them. WC_ALGO_TYPE_SETKEY and WC_ALGO_TYPE_EXPORT_KEY are bound to a wolfCrypt key object and carry material for that object's own use: neither can name a stored key, ask for one to be created exportable, or make one outlive the object that used it. Wrapped keys add a second reason, since a wrapped blob never becomes plaintext on this side of the boundary at all. Add a dedicated algorithm type covering the operations that manage a stored key, whether or not they cross that boundary: WC_KEYSTORE_IMPORT_PLAIN place plaintext key material into a slot WC_KEYSTORE_EXPORT_PLAIN read a stored key back as plaintext WC_KEYSTORE_IMPORT_WRAPPED unwrap a blob directly into a slot WC_KEYSTORE_EXPORT_WRAPPED wrap a stored key back out WC_KEYSTORE_DERIVE derive slot to slot without touching RAM WC_KEYSTORE_DELETE destroy a stored key WC_KEYSTORE_GET_INFO query what a slot holds The plaintext pair is what most key stores outside the secure-element class offer, and it is the shape wc_Pkcs11StoreKey() already implements privately: that function takes a Pkcs11Token* rather than a devId, so an application using it is bound to PKCS#11 even though the operation is generic. Devices that hold keys which may never appear in plaintext decline the pair and offer only the wrapped form. Delete is deliberately separate from WC_ALGO_TYPE_FREE: freeing a wolfCrypt key object must never destroy the hardware key it refers to. A key reference is not a new naming scheme. It is the identifier WOLF_PRIVATE_KEY_ID already uses, so the bytes that name a key here are the bytes wc_ecc_init_id() or wc_AesInit_Id() take to bind an object to that key, and the bytes read back from key->id afterwards. wolfCrypt copies them through without inspection. Two details come from building the NXP EdgeLock port against this surface, which is what a design like this needs before it is fixed in place. Every operation that creates a key takes keyType, because a key store has to know what a key is for before it can set its permissions, and the material does not always say. Raw bytes carry no metadata, and neither does a bare RFC 3394 wrap, which is a pure data transformation. A vendor container that carries its own property word stays authoritative over what it holds and keyType is then a cross-check, which catches the right blob going into the wrong kind of slot; a device must refuse a mismatch rather than silently prefer one source. WC_KEYSTORE_KEY_NONE leaves the choice to the device. The export operations take no keyType, since the key already exists and GET_INFO reports it. attrs travels with every creating operation for the same reason, but as a source only. A format carrying no attributes of its own leaves attrs as the device's only word on what the new key may do. A container that carries them wins and attrs is ignored, and it cannot usefully be cross-checked there: a container's attributes are inside the wrap and are not known until the key exists, whereas keyType is checkable up front against the reference being imported into. wc_KeyStore_GetInfo() is how a caller confirms what an import produced. keyType also fixes how plaintext material is encoded, so no further argument is needed to say. A symmetric type takes the raw key bytes; an asymmetric type takes DER, a private key as PKCS#8 PrivateKeyInfo and a public key as SubjectPublicKeyInfo. Stating it matters: WC_KEYSTORE_KEY_ECC_SIGN and a byte string do not otherwise say whether the bytes are a scalar, SEC1 or PKCS#8, and two devices could each pick differently and both be defensible. Keys that act on other keys need naming. Without WC_KEYSTORE_KEY_WRAP and WC_KEYSTORE_KEY_DERIVE a device asked about a wrapping key can only answer NONE, which is indistinguishable from an empty slot and defeats the point of GET_INFO. Measured on hardware, a 256-bit wrapping key reported type 0 with 256 bits, which tells a caller nothing about what it may do with it. Both are listed ahead of the algorithm types, since they name what a key acts on rather than which algorithm it serves. The algorithm types cover what the two other backends this was checked against actually store. wc_Pkcs11StoreKey() handles RSA, ECC and ML-KEM today, and wolfHSM's WH_KEY_ALGO_ENUM names RSA, ECC, Curve25519, Ed25519, ML-DSA, ML-KEM, LMS and XMSS, so a shorter vocabulary would have left the facility unusable to both for anything but symmetric keys. An algorithm serving two purposes a key store grants separately is split, since one key doing both is the key-separation problem and a caller has to be able to ask for the narrower key. RSA and ECC are the only two: PKCS#11 carries CKA_SIGN and CKA_DECRYPT independently for RSA, and CKA_SIGN against CKA_DERIVE for ECC. wolfCrypt's own PKCS#11 layer honours that for ECC, selecting from the ecc_key flags, but sets both CKA_DECRYPT and CKA_SIGN unconditionally for RSA because no equivalent RsaKey flag exists; expressing the distinction here is what would let that be tightened later. The remaining types serve one purpose each and are not split. Signature use is listed before agreement or transport use throughout. Argument order follows one rule: two interchangeable control words are never left adjacent, because a transposition between them compiles cleanly and surfaces much later as an unrelated-looking failure. An earlier arrangement had attrs and kdfType separated only by a pointer and a length, and swapping them silently created a key without the exportable attribute, whose export then failed two operations away from the mistake. Beyond that each control word sits beside what it describes. wc_KeyStore_ImportPlain is the one place the two aims collide and the first wins: attrs sits at the tail of every creating operation rather than beside keyType, because WC_KEYSTORE_ATTR_EXPORTABLE and WC_KEYSTORE_KEY_WRAP are both 1 and a swap would be silent. All three creating operations therefore read keyRef, keyRefSz, keyType, then what they draw the key from, then attrs. kdfType refers to enum wc_KdfType, and WC_KDF_TYPE_NONE asks for the device's own derivation, which is all many key stores offer. Attributes are fixed when a key is created; hardware generally burns them in, so there is no operation here to change them afterwards. They round-trip: a device that can represent one must report it back through GET_INFO, so a caller can ask whether an export is permitted rather than attempting one and interpreting the error. Attributes the device cannot represent read as absent, and out parameters it does not fill are cleared rather than left holding the caller's stack. Gated behind WOLF_CRYPTO_CB_KEYSTORE, with tests in wolfcrypt/test and tests/api that assert each operation reaches the device carrying the arguments the caller passed, not merely that the call returned zero. --- .skoll-known.md | 14 + CMakeLists.txt | 1 + cmake/functions.cmake | 3 + configure.ac | 9 +- doc/dox_comments/header_files/wc_keystore.h | 346 +++++++++++ tests/api.c | 3 + tests/api/include.am | 2 + tests/api/test_keystore.c | 563 ++++++++++++++++++ tests/api/test_keystore.h | 51 ++ wolfcrypt/src/cryptocb.c | 278 +++++++++ wolfcrypt/src/include.am | 1 + wolfcrypt/src/wc_keystore.c | 92 +++ wolfcrypt/test/test.c | 320 ++++++++++ wolfssl/wolfcrypt/cryptocb.h | 145 +++++ wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/types.h | 4 +- wolfssl/wolfcrypt/wc_keystore.h | 103 ++++ .../wolfssl_tls_sock/prj-no-malloc.conf | 7 +- 18 files changed, 1934 insertions(+), 9 deletions(-) create mode 100644 .skoll-known.md create mode 100644 doc/dox_comments/header_files/wc_keystore.h create mode 100644 tests/api/test_keystore.c create mode 100644 tests/api/test_keystore.h create mode 100644 wolfcrypt/src/wc_keystore.c create mode 100644 wolfssl/wolfcrypt/wc_keystore.h diff --git a/.skoll-known.md b/.skoll-known.md new file mode 100644 index 00000000000..b3e4e3747c1 --- /dev/null +++ b/.skoll-known.md @@ -0,0 +1,14 @@ +# Known issues + +Findings already investigated and closed. A scan that raises one of these again is re-reporting settled work; check the code before acting on it. + +## EdgeLock (els_pkc) port + +- **ECDSA sign/verify stack overflow for digests over 80 bytes.** Not present. Both guards are `inlen > 255 || inlen > sizeof(hash)` (and `hashLen` likewise); the buffer bound is the second half of the condition, and reading only the first half suggests an overflow that cannot happen. `ElsPkcEccSign` and `ElsPkcEccVerify` in `wolfcrypt/src/port/nxp/els_pkc_port.c`, covered by the "oversized digest does not overflow the port buffer" check in the Zephyr sample. +- **`ElsRandom` violating the ELS DRBG word-multiple contract.** Handled deliberately: the whole-word part is generated into the caller's buffer and the remainder comes from a word-sized scratch, because declining would fail the call rather than fall back. Sizes 1 through 33 are exercised in the sample. +- **`ElsSha256Copy` leaking the destination's `msg` and `W` buffers.** Closed: `wc_Sha256Free(dst)` runs before the struct copy, for exactly this reason. +- **`ElsEccKeyGen` not validating the slot with `ElsCheckSlot()`.** By design. On a key generation the reference is a request rather than a lookup: the slot is empty and the key does not exist until the call returns, so a check requiring an active key would reject every valid keygen. +- **`wc_ElsPkc_HashOffloadCount` overstating the offload.** Fixed 2026-09-01; it now increments only after `ElsWait()` succeeds, so it counts engine runs rather than accepted updates. +- **No HMAC arm in the dispatch table.** Deliberate. The ELS HMAC command is one-shot while wolfCrypt drives HMAC incrementally, so an arm would have to buffer whole messages. HMAC is accelerated anyway because the inner hash inherits the devId. +- **RSA encrypt/decrypt (OAEP) declined.** Blocked upstream: every `MCUXCLRSA_ENCRYPT_*` and `_DECRYPT_*` workarea macro in NXP's `mcuxClRsa_MemoryConsumption.h` for rw61x is an unsubstituted `$(...)` template placeholder and will not compile. +- **AES-192, Ed448, deterministic ECDSA, SHA-1/SHA-3, AES-CCM/XTS/CFB/OFB declined.** The hardware has no such capability; each decline sits next to a comment saying so. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8263cd43ab9..2b48daa5a1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4546,6 +4546,7 @@ if(WOLFSSL_EXAMPLES) tests/api/test_hpke.c tests/api/test_kdf.c tests/api/test_she.c + tests/api/test_keystore.c tests/api/test_async.c tests/api/test_des3.c tests/api/test_chacha.c diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 2ad868035b0..6c5fa97658f 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -1296,6 +1296,9 @@ function(generate_lib_src_list LIB_SOURCES) # Corresponds to wolfcrypt/src/include.am if(BUILD_CRYPTOCB) list(APPEND LIB_SOURCES wolfcrypt/src/cryptocb.c) + # entirely #ifdef WOLF_CRYPTO_CB_KEYSTORE, so it costs nothing when the + # key store facility is not enabled + list(APPEND LIB_SOURCES wolfcrypt/src/wc_keystore.c) endif() if(BUILD_SHE) diff --git a/configure.ac b/configure.ac index b2ff85b3c7d..61db961ed80 100644 --- a/configure.ac +++ b/configure.ac @@ -11744,7 +11744,7 @@ fi # Crypto Callbacks Utils (Copy/Free/etc) AC_ARG_ENABLE([cryptocbutils], - [AS_HELP_STRING([--enable-cryptocbutils@<:@=copy,free,setkey,export,...@:>@], + [AS_HELP_STRING([--enable-cryptocbutils@<:@=copy,free,setkey,export,keystore,...@:>@], [Enable crypto callback utilities (default: all)])], [ ENABLED_CRYPTOCB_UTILS=$enableval ], [ ENABLED_CRYPTOCB_UTILS=no ] @@ -11757,7 +11757,7 @@ if test "$ENABLED_CRYPTOCB_UTILS" != "no"; then if test "$ENABLED_CRYPTOCB_UTILS" = "yes"; then # Enable all utilities - AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_COPY -DWOLF_CRYPTO_CB_FREE -DWOLF_CRYPTO_CB_SETKEY -DWOLF_CRYPTO_CB_EXPORT_KEY" + AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_COPY -DWOLF_CRYPTO_CB_FREE -DWOLF_CRYPTO_CB_SETKEY -DWOLF_CRYPTO_CB_EXPORT_KEY -DWOLF_CRYPTO_CB_KEYSTORE" else # Parse comma-separated list OIFS="$IFS" @@ -11776,8 +11776,11 @@ if test "$ENABLED_CRYPTOCB_UTILS" != "no"; then export) AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_EXPORT_KEY" ;; + keystore) + AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_KEYSTORE" + ;; *) - AC_MSG_ERROR([Unknown cryptocbutils option: $util. Valid options: copy, free, setkey, export]) + AC_MSG_ERROR([Unknown cryptocbutils option: $util. Valid options: copy, free, setkey, export, keystore]) ;; esac done diff --git a/doc/dox_comments/header_files/wc_keystore.h b/doc/dox_comments/header_files/wc_keystore.h new file mode 100644 index 00000000000..5d280e4159a --- /dev/null +++ b/doc/dox_comments/header_files/wc_keystore.h @@ -0,0 +1,346 @@ +/*! + \ingroup CryptoCb + \brief Place plaintext key material into a device's key store. This is the + counterpart to wc_KeyStore_ImportWrapped for devices that accept a key in + the clear; a device holding keys that may never appear in plaintext + declines it and offers only the wrapped form. + + keyType says what the material is for, which is what lets the device set + the stored key's properties: a raw byte string cannot tell an AES key from + an HMAC key of the same length. It also fixes the encoding, so no further + argument is needed to say: a symmetric type takes the raw key bytes, and an + asymmetric type takes DER, a private key as PKCS#8 PrivateKeyInfo and a + public key as SubjectPublicKeyInfo. keySz is the length of that encoding + in bytes either way. + + The caller owns the plaintext copy and should zeroize it once the import + succeeds, for example with ForceZero(). + + Key references are opaque byte strings, interpreted only by the device. + They are the same identifier WOLF_PRIVATE_KEY_ID uses, so the bytes that + name a key here are the bytes wc_ecc_init_id() or wc_AesInit_Id() take to + bind a wolfCrypt object to that key. + + attrs sits at the end of the argument list rather than beside keyRef, + because WC_KEYSTORE_ATTR_EXPORTABLE and WC_KEYSTORE_KEY_WRAP are both 1 and + a swap between two adjacent control words would compile cleanly. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef or key is NULL, or either size is zero + + \param devId crypto callback device ID + \param keyRef opaque reference naming where the key should land + \param keyRefSz length of keyRef in bytes + \param keyType what the key is for, from enum wc_KeyStoreKeyType + \param key plaintext key material + \param keySz length of key in bytes + \param attrs attributes for the key being created, WC_KEYSTORE_ATTR_* + \param ctx read-only caller context passed through to the device + + _Example_ + \code + byte keyRef[8]; + byte aesKey[32]; + + ret = wc_KeyStore_ImportPlain(devId, keyRef, sizeof(keyRef), + WC_KEYSTORE_KEY_AES, aesKey, sizeof(aesKey), + WC_KEYSTORE_ATTR_PERSISTENT, NULL); + ForceZero(aesKey, sizeof(aesKey)); + if (ret != 0) { + // handle error + } + \endcode + + \sa wc_KeyStore_ExportPlain + \sa wc_KeyStore_ImportWrapped + \sa wc_KeyStore_Delete +*/ +int wc_KeyStore_ImportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + word32 keyType, const byte* key, word32 keySz, + word32 attrs, const void* ctx); + +/*! + \ingroup CryptoCb + \brief Read a stored key back as plaintext. Typically requires the key to + have been created with WC_KEYSTORE_ATTR_EXPORTABLE, and many devices refuse + this operation entirely; wc_KeyStore_ExportWrapped is the form that keeps + the material inside the boundary. + + The encoding matches wc_KeyStore_ImportPlain(): raw bytes for a symmetric + key, DER for an asymmetric one. Call wc_KeyStore_GetInfo() to learn which + the stored key is. + + keySz is in/out: the capacity of key on entry, the number of bytes written + on return. Passing key as NULL is a size query, which returns the size the + device would produce through keySz. keySz is cleared first in that form, + so any value passed in is discarded rather than preserved. + + Call wc_KeyStore_GetInfo() first to learn whether the key is exportable, + rather than attempting the export and interpreting the failure. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef or keySz is NULL, keyRefSz is zero, or key + is non-NULL with a zero capacity + + \param devId crypto callback device ID + \param keyRef opaque reference naming the key to export + \param keyRefSz length of keyRef in bytes + \param key buffer receiving the plaintext key, or NULL to query the size + \param keySz in: capacity of key in bytes, out: bytes written + \param ctx read-only caller context passed through to the device + + _Example_ + \code + byte out[32]; + word32 outSz = sizeof(out); + + ret = wc_KeyStore_ExportPlain(devId, keyRef, sizeof(keyRef), + out, &outSz, NULL); + if (ret != 0) { + // handle error + } + \endcode + + \sa wc_KeyStore_ImportPlain + \sa wc_KeyStore_ExportWrapped + \sa wc_KeyStore_GetInfo +*/ +int wc_KeyStore_ExportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + byte* key, word32* keySz, const void* ctx); + +/*! + \ingroup CryptoCb + \brief Unwrap a key blob directly into a device's key store. The key + material is never plaintext on this side of the boundary: the device + unwraps it internally and the result exists only at keyRef. + + keyType says what the wrapped key is for. A bare RFC 3394 wrap + (WC_KEYWRAP_FORMAT_AESKW) is a pure data transformation carrying no + metadata, so for that format keyType is the device's only source. A vendor + container that carries its own property word stays authoritative, and + keyType is then a cross-check the device must refuse a mismatch on, since + a mismatch means the right blob is going into the wrong kind of slot. + + Key references are opaque byte strings, interpreted only by the device. + They are the same identifier WOLF_PRIVATE_KEY_ID uses, so the bytes that + name a key here are the bytes wc_ecc_init_id() or wc_AesInit_Id() take to + bind a wolfCrypt object to that key. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef or blob is NULL, if either size is zero, or + if wrapKeyRef is NULL with a non-zero wrapKeyRefSz + + \param devId crypto callback device ID + \param keyRef opaque reference naming where the key should land + \param keyRefSz length of keyRef in bytes + \param keyType what the wrapped key is for, from enum wc_KeyStoreKeyType, + or WC_KEYSTORE_KEY_NONE to leave it to the container + \param wrapKeyRef opaque reference naming the wrapping key, or NULL when + the device uses an implicit one + \param wrapKeyRefSz length of wrapKeyRef in bytes, zero when wrapKeyRef + is NULL + \param format container format, from enum wc_KeyWrapFormat + \param blob the wrapped key blob + \param blobSz length of blob in bytes + \param attrs WC_KEYSTORE_ATTR_* for the key being created. Used only when + the format carries no attributes of its own; a container that carries them + wins and this is ignored. Call wc_KeyStore_GetInfo() afterwards to confirm + what the import actually produced + \param ctx read-only caller context passed through to the device + + _Example_ + \code + byte keyRef[8]; + byte kekRef[8]; + + ret = wc_KeyStore_ImportWrapped(devId, keyRef, sizeof(keyRef), + WC_KEYSTORE_KEY_AES, + kekRef, sizeof(kekRef), + WC_KEYWRAP_FORMAT_AESKW, + blob, blobSz, + WC_KEYSTORE_ATTR_EXPORTABLE, NULL); + if (ret != 0) { + // handle error + } + \endcode + + \sa wc_KeyStore_ExportWrapped + \sa wc_KeyStore_Delete +*/ +int wc_KeyStore_ImportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, const byte* blob, word32 blobSz, + word32 attrs, const void* ctx); + +/*! + \ingroup CryptoCb + \brief Wrap a stored key under another stored key and emit the blob. + Typically requires the key to have been created with + WC_KEYSTORE_ATTR_EXPORTABLE, which is irrevocable on most hardware. + + blobSz is in/out: the capacity of blob on entry, the number of bytes + written on return. Passing blob as NULL is a size query, which returns the + size the device would produce through blobSz. blobSz is cleared first in + that form, so any value passed in is discarded rather than preserved. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef or blobSz is NULL, if keyRefSz is zero, if + blob is non-NULL with a zero capacity, or if wrapKeyRef is NULL with a + non-zero wrapKeyRefSz + + \param devId crypto callback device ID + \param keyRef opaque reference naming the key to wrap out + \param keyRefSz length of keyRef in bytes + \param wrapKeyRef opaque reference naming the wrapping key, or NULL when + the device uses an implicit one + \param wrapKeyRefSz length of wrapKeyRef in bytes, zero when wrapKeyRef + is NULL + \param format container format, from enum wc_KeyWrapFormat + \param blob buffer receiving the wrapped key, or NULL to query the size + \param blobSz in/out capacity then length, in bytes + \param ctx read-only caller context passed through to the device + + _Example_ + \code + word32 blobSz = 0; + + // ask how large the container will be + ret = wc_KeyStore_ExportWrapped(devId, keyRef, sizeof(keyRef), + kekRef, sizeof(kekRef), + WC_KEYWRAP_FORMAT_AESKW, + NULL, &blobSz, NULL); + \endcode + + \sa wc_KeyStore_ImportWrapped + \sa wc_KeyStore_GetInfo +*/ +int wc_KeyStore_ExportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, byte* blob, word32* blobSz, const void* ctx); + +/*! + \ingroup CryptoCb + \brief Derive a new stored key from an existing one without either key + touching RAM. + + Nothing in a derivation says what its result is for, so keyType does. A + device that encodes the key's purpose inside its own key references may + treat this as a cross-check, but it must not require that, since a + reference is opaque to wolfCrypt and need carry no such field. + + Argument order follows the rule used throughout this API: no two + interchangeable control words sit next to each other. keyType names the key + being created and follows its reference, kdfType precedes the derivation + data, and attrs moves to the tail rather than sit beside keyType, since + WC_KEYSTORE_ATTR_EXPORTABLE and WC_KEYSTORE_KEY_WRAP are both 1. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef or srcKeyRef is NULL, if keyRefSz or + srcKeyRefSz is zero, or if deriv is NULL with a non-zero derivSz. A zero + derivSz is legal: it is the form used when the KDF takes no extra input + + \param devId crypto callback device ID + \param keyRef opaque reference naming where the derived key should land + \param keyRefSz length of keyRef in bytes + \param keyType what the derived key is for, from enum wc_KeyStoreKeyType, + or WC_KEYSTORE_KEY_NONE to leave it to the device + \param srcKeyRef opaque reference naming the derivation key + \param srcKeyRefSz length of srcKeyRef in bytes + \param kdfType derivation function, from enum wc_KdfType. + WC_KDF_TYPE_NONE asks for the device's own derivation, which is all many + key stores offer + \param deriv derivation data + \param derivSz length of deriv in bytes, often fixed by the hardware + \param attrs WC_KEYSTORE_ATTR_* requested for the key being created + \param ctx read-only caller context passed through to the device + + _Example_ + \code + ret = wc_KeyStore_Derive(devId, keyRef, sizeof(keyRef), + WC_KEYSTORE_KEY_AES, + parentRef, sizeof(parentRef), + WC_KDF_TYPE_NONE, deriv, sizeof(deriv), + WC_KEYSTORE_ATTR_EXPORTABLE, NULL); + \endcode + + \sa wc_KeyStore_GetInfo +*/ +int wc_KeyStore_Derive(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* srcKeyRef, word32 srcKeyRefSz, + word32 kdfType, const byte* deriv, word32 derivSz, + word32 attrs, const void* ctx); + +/*! + \ingroup CryptoCb + \brief Destroy a stored key. Deliberately separate from + WC_ALGO_TYPE_FREE: freeing a wolfCrypt key object must never destroy the + hardware key it refers to. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef is NULL or keyRefSz is zero + + \param devId crypto callback device ID + \param keyRef opaque reference naming the key to destroy + \param keyRefSz length of keyRef in bytes + \param ctx read-only caller context passed through to the device + + _Example_ + \code + ret = wc_KeyStore_Delete(devId, keyRef, sizeof(keyRef), NULL); + \endcode + + \sa wc_KeyStore_ImportWrapped +*/ +int wc_KeyStore_Delete(int devId, const byte* keyRef, word32 keyRefSz, + const void* ctx); + +/*! + \ingroup CryptoCb + \brief Report what a slot holds: key type, size in bits, and attributes. + + Any WC_KEYSTORE_ATTR_* the device can represent is reported here, so a + caller can ask whether an export is permitted rather than attempting one + and interpreting the failure. Attributes the device cannot represent read + as absent, and out parameters the device does not fill are cleared rather + than left holding the caller's stack. + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no registered device handles the operation + \return BAD_FUNC_ARG if keyRef is NULL or keyRefSz is zero + + \param devId crypto callback device ID + \param keyRef opaque reference naming the key to query + \param keyRefSz length of keyRef in bytes + \param keyType out, a value from enum wc_KeyStoreKeyType + \param keyBits out, key size in bits + \param attrs out, WC_KEYSTORE_ATTR_* the key carries + \param ctx read-only caller context passed through to the device + + _Example_ + \code + word32 keyType = 0, keyBits = 0, attrs = 0; + + ret = wc_KeyStore_GetInfo(devId, keyRef, sizeof(keyRef), + &keyType, &keyBits, &attrs, NULL); + if (ret == 0 && (attrs & WC_KEYSTORE_ATTR_EXPORTABLE)) { + // the key may be wrapped out + } + \endcode + + \sa wc_KeyStore_ExportWrapped + \sa wc_KeyStore_Derive +*/ +int wc_KeyStore_GetInfo(int devId, const byte* keyRef, word32 keyRefSz, + word32* keyType, word32* keyBits, word32* attrs, const void* ctx); + diff --git a/tests/api.c b/tests/api.c index fc98fe8800d..367f4ca9f16 100644 --- a/tests/api.c +++ b/tests/api.c @@ -221,6 +221,7 @@ #include #include #include +#include #include #include #include @@ -40950,6 +40951,8 @@ TEST_CASE testCases[] = { #if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_SHE) TEST_SHE_CB_DECLS, #endif + /* Hardware key store */ + TEST_KEYSTORE_DECLS, /* Cipher */ /* Crypto callback async poll completion */ diff --git a/tests/api/include.am b/tests/api/include.am index 418f31c977a..efb49b40c32 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -26,6 +26,7 @@ tests_unit_test_SOURCES += tests/api/test_hpke.c tests_unit_test_SOURCES += tests/api/test_kdf.c # SHE tests_unit_test_SOURCES += tests/api/test_she.c +tests_unit_test_SOURCES += tests/api/test_keystore.c # Cipher tests_unit_test_SOURCES += tests/api/test_async.c tests_unit_test_SOURCES += tests/api/test_des3.c @@ -168,6 +169,7 @@ EXTRA_DIST += tests/api/test_eccsi.h EXTRA_DIST += tests/api/test_sakke.h EXTRA_DIST += tests/api/test_hpke.h EXTRA_DIST += tests/api/test_kdf.h +EXTRA_DIST += tests/api/test_keystore.h EXTRA_DIST += tests/api/test_she.h EXTRA_DIST += tests/api/test_async.h EXTRA_DIST += tests/api/test_des3.h diff --git a/tests/api/test_keystore.c b/tests/api/test_keystore.c new file mode 100644 index 00000000000..0bdf1d920d3 --- /dev/null +++ b/tests/api/test_keystore.c @@ -0,0 +1,563 @@ +/* test_keystore.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + + +#include +#include + +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + +#include +#include +#include + +#define KS_TEST_DEVID 7654 + +/* What the last dispatch carried, so a test can assert the callback saw the + * arguments the caller passed rather than only that the call returned 0. */ +typedef struct KsSeen { + int op; + int calls; + const void* ctx; + const byte* outBuf; /* the caller's output buffer, for exports */ + const byte* keyRef; + word32 keyRefSz; + const byte* otherRef; + word32 otherRefSz; + word32 format; + word32 kdfType; + word32 attrs; + word32 keyType; + word32 keySz; + word32 blobSz; + const byte* deriv; + word32 derivSz; +} KsSeen; + +static int KsCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + KsSeen* seen = (KsSeen*)ctx; + + (void)devIdArg; + + if (info == NULL || info->algo_type != WC_ALGO_TYPE_KEYSTORE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* Clear what this call does not set, so an assertion can never pass on a + * value the previous call left behind. */ + { + int calls = seen->calls; + XMEMSET(seen, 0, sizeof(*seen)); + seen->calls = calls; + } + + seen->calls++; + seen->op = info->keystore.type; + seen->ctx = info->keystore.ctx; + + switch (info->keystore.type) { + case WC_KEYSTORE_IMPORT_PLAIN: + seen->keyRef = info->keystore.op.importPlain.keyRef; + seen->keyRefSz = info->keystore.op.importPlain.keyRefSz; + seen->keyType = info->keystore.op.importPlain.keyType; + seen->otherRef = info->keystore.op.importPlain.key; + seen->keySz = info->keystore.op.importPlain.keySz; + seen->attrs = info->keystore.op.importPlain.attrs; + break; + case WC_KEYSTORE_EXPORT_PLAIN: + seen->keyRef = info->keystore.op.exportPlain.keyRef; + seen->keyRefSz = info->keystore.op.exportPlain.keyRefSz; + seen->outBuf = info->keystore.op.exportPlain.key; + /* answer the size query so the caller can check it propagates */ + if (info->keystore.op.exportPlain.key == NULL) { + *info->keystore.op.exportPlain.keySz = 32; + } + break; + case WC_KEYSTORE_IMPORT_WRAPPED: + seen->keyRef = info->keystore.op.importWrapped.keyRef; + seen->keyRefSz = info->keystore.op.importWrapped.keyRefSz; + seen->keyType = info->keystore.op.importWrapped.keyType; + seen->otherRef = info->keystore.op.importWrapped.wrapKeyRef; + seen->otherRefSz = info->keystore.op.importWrapped.wrapKeyRefSz; + seen->format = info->keystore.op.importWrapped.format; + seen->blobSz = info->keystore.op.importWrapped.blobSz; + seen->attrs = info->keystore.op.importWrapped.attrs; + break; + case WC_KEYSTORE_EXPORT_WRAPPED: + seen->keyRef = info->keystore.op.exportWrapped.keyRef; + seen->keyRefSz = info->keystore.op.exportWrapped.keyRefSz; + seen->otherRef = info->keystore.op.exportWrapped.wrapKeyRef; + seen->otherRefSz = info->keystore.op.exportWrapped.wrapKeyRefSz; + seen->format = info->keystore.op.exportWrapped.format; + seen->outBuf = info->keystore.op.exportWrapped.blob; + /* answer the size query so the caller can check it propagates */ + if (info->keystore.op.exportWrapped.blob == NULL) { + *info->keystore.op.exportWrapped.blobSz = 40; + } + break; + case WC_KEYSTORE_DERIVE: + seen->keyRef = info->keystore.op.derive.keyRef; + seen->keyRefSz = info->keystore.op.derive.keyRefSz; + seen->otherRef = info->keystore.op.derive.srcKeyRef; + seen->otherRefSz = info->keystore.op.derive.srcKeyRefSz; + seen->keyType = info->keystore.op.derive.keyType; + seen->attrs = info->keystore.op.derive.attrs; + seen->kdfType = info->keystore.op.derive.kdfType; + seen->deriv = info->keystore.op.derive.deriv; + seen->derivSz = info->keystore.op.derive.derivSz; + break; + case WC_KEYSTORE_DELETE: + seen->keyRef = info->keystore.op.deleteKey.keyRef; + seen->keyRefSz = info->keystore.op.deleteKey.keyRefSz; + break; + case WC_KEYSTORE_GET_INFO: + seen->keyRef = info->keystore.op.getInfo.keyRef; + seen->keyRefSz = info->keystore.op.getInfo.keyRefSz; + /* each output is optional; a device must not assume otherwise */ + if (info->keystore.op.getInfo.keyType != NULL) { + *info->keystore.op.getInfo.keyType = WC_KEYSTORE_KEY_WRAP; + } + if (info->keystore.op.getInfo.keyBits != NULL) { + *info->keystore.op.getInfo.keyBits = 256; + } + if (info->keystore.op.getInfo.attrs != NULL) { + *info->keystore.op.getInfo.attrs = WC_KEYSTORE_ATTR_UNWRAP_ONLY; + } + break; + default: + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return 0; +} + +static const byte ksPlain[] = { 0x30, 0x31, 0x32, 0x33, 0x34 }; +static const byte ksKeyRef[] = { 0x01, 0x02, 0x03, 0x04 }; +static const byte ksOtherRef[] = { 0x11, 0x12 }; +static const byte ksBlob[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; +static const byte ksDeriv[] = { 0x21, 0x22, 0x23 }; +/* Sentinel for the caller context every entry point forwards untouched. */ +static const byte ksCallerCtx[] = { 0x5a }; + +#endif /* WOLF_CRYPTO_CB && WOLF_CRYPTO_CB_KEYSTORE */ + +int test_wc_KeyStore_ImportPlain(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + /* WC_KEYSTORE_KEY_WRAP and WC_KEYSTORE_ATTR_EXPORTABLE are both 1, so a + * swap would compile and run. Assert each landed in its own field. */ + ExpectIntEQ(wc_KeyStore_ImportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + WC_KEYSTORE_KEY_HMAC, ksPlain, (word32)sizeof(ksPlain), + WC_KEYSTORE_ATTR_EXPORTABLE, ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_IMPORT_PLAIN); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectIntEQ(seen.keyType, WC_KEYSTORE_KEY_HMAC); + ExpectPtrEq(seen.otherRef, ksPlain); + ExpectIntEQ(seen.keySz, (word32)sizeof(ksPlain)); + ExpectIntEQ(seen.attrs, WC_KEYSTORE_ATTR_EXPORTABLE); + ExpectPtrEq(seen.ctx, ksCallerCtx); + + /* arguments are validated before the device is consulted */ + seen.calls = 0; + ExpectIntEQ(wc_KeyStore_ImportPlain(KS_TEST_DEVID, NULL, 0, + WC_KEYSTORE_KEY_AES, ksPlain, (word32)sizeof(ksPlain), 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_KeyStore_ImportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + WC_KEYSTORE_KEY_AES, NULL, 0, 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_ExportPlain(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + byte out[64]; + word32 outSz = (word32)sizeof(out); + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + ExpectIntEQ(wc_KeyStore_ExportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), out, &outSz, ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_EXPORT_PLAIN); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectPtrEq(seen.ctx, ksCallerCtx); + /* the device must be handed the caller's own buffer to write into */ + ExpectPtrEq(seen.outBuf, out); + + /* key == NULL is the size query and must still reach the device */ + outSz = 0; + ExpectIntEQ(wc_KeyStore_ExportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, &outSz, NULL), 0); + ExpectIntEQ(outSz, 32); + ExpectPtrEq(seen.outBuf, NULL); + + /* a buffer with no size is not a size query, it is a mistake */ + seen.calls = 0; + outSz = 0; + ExpectIntEQ(wc_KeyStore_ExportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), out, &outSz, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_KeyStore_ExportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), out, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_ImportWrapped(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_VENDOR, ksBlob, (word32)sizeof(ksBlob), + WC_KEYSTORE_ATTR_PERSISTENT, ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_IMPORT_WRAPPED); + ExpectIntEQ(seen.keyType, WC_KEYSTORE_KEY_AES); + ExpectIntEQ(seen.attrs, WC_KEYSTORE_ATTR_PERSISTENT); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectPtrEq(seen.otherRef, ksOtherRef); + ExpectIntEQ(seen.otherRefSz, (word32)sizeof(ksOtherRef)); + ExpectIntEQ(seen.format, WC_KEYWRAP_FORMAT_VENDOR); + ExpectIntEQ(seen.blobSz, (word32)sizeof(ksBlob)); + ExpectPtrEq(seen.ctx, ksCallerCtx); + + /* arguments are validated before the device is consulted */ + seen.calls = 0; + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, NULL, 0, + WC_KEYSTORE_KEY_AES, ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_VENDOR, ksBlob, (word32)sizeof(ksBlob), 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_VENDOR, NULL, 0, 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* A NULL wrapping key means "use the device's own", so a length beside + * it is a caller mistake rather than an implicit-key request. */ + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + NULL, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_VENDOR, ksBlob, (word32)sizeof(ksBlob), 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + /* The implicit wrapping key: NULL with no length reaches the device. */ + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, NULL, 0, + WC_KEYWRAP_FORMAT_VENDOR, ksBlob, (word32)sizeof(ksBlob), 0, NULL), + 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_IMPORT_WRAPPED); + ExpectPtrEq(seen.otherRef, NULL); + ExpectIntEQ(seen.otherRefSz, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_ExportWrapped(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + byte out[64]; + word32 outSz = (word32)sizeof(out); + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, out, &outSz, ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_EXPORT_WRAPPED); + /* Which reference is the key and which is the KEK: a transposition in the + * dispatcher is invisible without this. */ + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectPtrEq(seen.otherRef, ksOtherRef); + ExpectIntEQ(seen.otherRefSz, (word32)sizeof(ksOtherRef)); + ExpectIntEQ(seen.format, WC_KEYWRAP_FORMAT_AESKW); + ExpectPtrEq(seen.ctx, ksCallerCtx); + /* the device must be handed the caller's own buffer to write into */ + ExpectPtrEq(seen.outBuf, out); + + /* blob == NULL is the size query and must still reach the device */ + outSz = 0; + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, NULL, &outSz, NULL), 0); + ExpectIntEQ(outSz, 40); + ExpectPtrEq(seen.outBuf, NULL); + + /* a buffer with no size is not a size query, it is a mistake */ + seen.calls = 0; + outSz = 0; + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, out, &outSz, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, out, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + NULL, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, out, &outSz, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + /* The implicit wrapping key: NULL with no length reaches the device. */ + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, 0, + WC_KEYWRAP_FORMAT_AESKW, out, &outSz, NULL), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_EXPORT_WRAPPED); + ExpectPtrEq(seen.otherRef, NULL); + ExpectIntEQ(seen.otherRefSz, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_Derive(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + /* WC_KDF_TYPE_HKDF and WC_KEYSTORE_ATTR_EXPORTABLE are both 1, so keep + * kdfType and attrs numerically apart or a transposition reads as equal. */ + ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KDF_TYPE_HKDF, ksDeriv, (word32)sizeof(ksDeriv), + WC_KEYSTORE_ATTR_PERSISTENT, ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_DERIVE); + ExpectIntEQ(seen.keyType, WC_KEYSTORE_KEY_AES); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectPtrEq(seen.otherRef, ksOtherRef); + ExpectIntEQ(seen.otherRefSz, (word32)sizeof(ksOtherRef)); + ExpectIntEQ(seen.attrs, WC_KEYSTORE_ATTR_PERSISTENT); + ExpectIntEQ(seen.kdfType, WC_KDF_TYPE_HKDF); + ExpectPtrEq(seen.deriv, ksDeriv); + ExpectIntEQ(seen.derivSz, (word32)sizeof(ksDeriv)); + ExpectPtrEq(seen.ctx, ksCallerCtx); + + /* WC_KDF_TYPE_NONE asks for the device's own derivation */ + ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_NONE, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), + WC_KEYSTORE_ATTR_EXPORTABLE, NULL), 0); + ExpectIntEQ(seen.kdfType, WC_KDF_TYPE_NONE); + ExpectIntEQ(seen.attrs, WC_KEYSTORE_ATTR_EXPORTABLE); + + seen.calls = 0; + ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, NULL, 0, + WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* No derivation data means no length beside it. */ + ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KDF_TYPE_NONE, NULL, (word32)sizeof(ksDeriv), 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_Delete(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + ExpectIntEQ(wc_KeyStore_Delete(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_DELETE); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectPtrEq(seen.ctx, ksCallerCtx); + + seen.calls = 0; + ExpectIntEQ(wc_KeyStore_Delete(KS_TEST_DEVID, ksKeyRef, 0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_GetInfo(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + KsSeen seen; + word32 keyType = 0xDEADBEEF; + word32 keySz = 0xDEADBEEF; + word32 attrs = 0xDEADBEEF; + + XMEMSET(&seen, 0, sizeof(seen)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(KS_TEST_DEVID, KsCb, &seen), 0); + + /* A wrapping key must be nameable: NONE here is indistinguishable from + * an empty slot. */ + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), &keyType, &keySz, &attrs, + ksCallerCtx), 0); + ExpectIntEQ(seen.op, WC_KEYSTORE_GET_INFO); + ExpectPtrEq(seen.keyRef, ksKeyRef); + ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); + ExpectIntEQ(keyType, WC_KEYSTORE_KEY_WRAP); + ExpectIntEQ(keySz, 256); + ExpectIntEQ(attrs, WC_KEYSTORE_ATTR_UNWRAP_ONLY); + ExpectPtrEq(seen.ctx, ksCallerCtx); + + /* Each output is optional and asked for on its own. */ + keyType = 0xDEADBEEF; + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), &keyType, NULL, NULL, NULL), 0); + ExpectIntEQ(keyType, WC_KEYSTORE_KEY_WRAP); + keySz = 0xDEADBEEF; + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, &keySz, NULL, NULL), 0); + ExpectIntEQ(keySz, 256); + attrs = 0xDEADBEEF; + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, NULL, &attrs, NULL), 0); + ExpectIntEQ(attrs, WC_KEYSTORE_ATTR_UNWRAP_ONLY); + + /* Asking for nothing still reaches the device, and writes nowhere. */ + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, NULL, NULL, NULL), 0); + + /* arguments are validated before the device is consulted */ + seen.calls = 0; + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, NULL, 0, + &keyType, &keySz, &attrs, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, ksKeyRef, 0, + &keyType, &keySz, &attrs, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(seen.calls, 0); + + wc_CryptoCb_UnRegisterDevice(KS_TEST_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_KeyStore_NoDevice(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_KEYSTORE) + /* Poisoned, not zeroed: with nothing registered the dispatcher's own clear + * is the only thing that can touch these, so this is where it is + * observable at all. */ + word32 keyType = 0xDEADBEEF, keySz = 0xDEADBEEF, attrs = 0xDEADBEEF; + + /* Nothing registered: every entry point must decline. */ + ExpectIntEQ(wc_KeyStore_Delete(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_KeyStore_GetInfo(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), &keyType, &keySz, &attrs, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + /* Cleared before dispatch, so a declined query cannot leave the caller + * reading its own stack - attrs in particular gates an export. */ + ExpectIntEQ(keyType, 0); + ExpectIntEQ(keySz, 0); + ExpectIntEQ(attrs, 0); + ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), 0, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(wc_KeyStore_ImportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + WC_KEYSTORE_KEY_AES, ksPlain, (word32)sizeof(ksPlain), 0, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + keySz = 0xDEADBEEF; + ExpectIntEQ(wc_KeyStore_ExportPlain(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), NULL, &keySz, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + /* the size-query form clears it too */ + ExpectIntEQ(keySz, 0); + ExpectIntEQ(wc_KeyStore_ImportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, ksBlob, (word32)sizeof(ksBlob), 0, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + keySz = 0xDEADBEEF; + ExpectIntEQ(wc_KeyStore_ExportWrapped(KS_TEST_DEVID, + ksKeyRef, (word32)sizeof(ksKeyRef), + ksOtherRef, (word32)sizeof(ksOtherRef), + WC_KEYWRAP_FORMAT_AESKW, NULL, &keySz, NULL), + WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); + ExpectIntEQ(keySz, 0); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_keystore.h b/tests/api/test_keystore.h new file mode 100644 index 00000000000..c48d3de659b --- /dev/null +++ b/tests/api/test_keystore.h @@ -0,0 +1,51 @@ +/* test_keystore.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + + +#ifndef WOLFCRYPT_TEST_KEYSTORE_H +#define WOLFCRYPT_TEST_KEYSTORE_H + +#include + +int test_wc_KeyStore_ImportPlain(void); +int test_wc_KeyStore_ExportPlain(void); +int test_wc_KeyStore_ImportWrapped(void); +int test_wc_KeyStore_ExportWrapped(void); +int test_wc_KeyStore_Derive(void); +int test_wc_KeyStore_Delete(void); +int test_wc_KeyStore_GetInfo(void); +int test_wc_KeyStore_NoDevice(void); + +/* Defined unconditionally, like TEST_SRP_DECLS and TEST_ASYNC_DECLS: each test + * body compiles to a skip when the feature is off, so the entries are always + * listed and always reported. TEST_SHE_CB_DECLS takes the conditional route + * instead, which keeps them out of the listing entirely. */ +#define TEST_KEYSTORE_DECLS \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_ImportPlain), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_ExportPlain), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_ImportWrapped), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_ExportWrapped), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_Derive), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_Delete), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_GetInfo), \ + TEST_DECL_GROUP("keystore", test_wc_KeyStore_NoDevice) + +#endif /* WOLFCRYPT_TEST_KEYSTORE_H */ diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index dd2c40b2d7e..3811c85be20 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -135,6 +135,9 @@ static const char* GetAlgoTypeStr(int algo) #ifdef WOLF_CRYPTO_CB_EXPORT_KEY case WC_ALGO_TYPE_EXPORT_KEY: return "ExportKey"; #endif /* WOLF_CRYPTO_CB_EXPORT_KEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE + case WC_ALGO_TYPE_KEYSTORE: return "KeyStore"; +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ } return NULL; } @@ -371,6 +374,12 @@ void wc_CryptoCb_InfoString(wc_CryptoInfo* info) GetAlgoTypeStr(info->algo_type), info->export_key.type); } #endif /* WOLF_CRYPTO_CB_EXPORT_KEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE + else if (info->algo_type == WC_ALGO_TYPE_KEYSTORE) { + printf("Crypto CB: %s Type=%d\n", + GetAlgoTypeStr(info->algo_type), info->keystore.type); + } +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ #if (defined(HAVE_HKDF) && !defined(NO_HMAC)) || \ defined(HAVE_CMAC_KDF) else if (info->algo_type == WC_ALGO_TYPE_KDF) { @@ -3698,6 +3707,275 @@ int wc_CryptoCb_ExportKey(int devId, int type, const void* obj, void* out) } #endif /* WOLF_CRYPTO_CB_EXPORT_KEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE +/* Hardware key store operations. Key references are opaque to wolfCrypt: it + * copies the pointers through and never interprets them, exactly as it treats + * the id[] blob on a key object. */ +int wc_CryptoCb_KeyStoreImportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + word32 keyType, const byte* key, word32 keySz, + word32 attrs, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (keyRef == NULL || keyRefSz == 0 || key == NULL || keySz == 0) { + return BAD_FUNC_ARG; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_IMPORT_PLAIN; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.importPlain.keyRef = keyRef; + cryptoInfo.keystore.op.importPlain.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.importPlain.keyType = keyType; + cryptoInfo.keystore.op.importPlain.key = key; + cryptoInfo.keystore.op.importPlain.keySz = keySz; + cryptoInfo.keystore.op.importPlain.attrs = attrs; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreExportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + byte* key, word32* keySz, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + /* key == NULL is the required-size query. Tie buffer and capacity together + * so a non-NULL buffer cannot arrive with an uninitialised capacity. */ + if (keyRef == NULL || keyRefSz == 0 || keySz == NULL) { + return BAD_FUNC_ARG; + } + if (key != NULL && *keySz == 0) { + return BAD_FUNC_ARG; + } + + /* On the query form it is a pure output, so clear it as getInfo does; on + * the buffer form it carries the capacity in and must be left alone. */ + if (key == NULL) { + *keySz = 0; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_EXPORT_PLAIN; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.exportPlain.keyRef = keyRef; + cryptoInfo.keystore.op.exportPlain.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.exportPlain.key = key; + cryptoInfo.keystore.op.exportPlain.keySz = keySz; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreImportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, const byte* blob, word32 blobSz, + word32 attrs, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (keyRef == NULL || keyRefSz == 0 || blob == NULL || blobSz == 0) { + return BAD_FUNC_ARG; + } + if (wrapKeyRef == NULL && wrapKeyRefSz != 0) { + return BAD_FUNC_ARG; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_IMPORT_WRAPPED; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.importWrapped.keyRef = keyRef; + cryptoInfo.keystore.op.importWrapped.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.importWrapped.keyType = keyType; + cryptoInfo.keystore.op.importWrapped.wrapKeyRef = wrapKeyRef; + cryptoInfo.keystore.op.importWrapped.wrapKeyRefSz = wrapKeyRefSz; + cryptoInfo.keystore.op.importWrapped.blob = blob; + cryptoInfo.keystore.op.importWrapped.blobSz = blobSz; + cryptoInfo.keystore.op.importWrapped.format = format; + cryptoInfo.keystore.op.importWrapped.attrs = attrs; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreExportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, byte* blob, word32* blobSz, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + /* blob == NULL is the required-size query. Tie buffer and capacity together + * so a non-NULL buffer cannot arrive with an uninitialised capacity. */ + if (keyRef == NULL || keyRefSz == 0 || blobSz == NULL) { + return BAD_FUNC_ARG; + } + if (blob != NULL && *blobSz == 0) { + return BAD_FUNC_ARG; + } + if (wrapKeyRef == NULL && wrapKeyRefSz != 0) { + return BAD_FUNC_ARG; + } + + /* Cleared only on the query form; see wc_CryptoCb_KeyStoreExportPlain. */ + if (blob == NULL) { + *blobSz = 0; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_EXPORT_WRAPPED; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.exportWrapped.keyRef = keyRef; + cryptoInfo.keystore.op.exportWrapped.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.exportWrapped.wrapKeyRef = wrapKeyRef; + cryptoInfo.keystore.op.exportWrapped.wrapKeyRefSz = wrapKeyRefSz; + cryptoInfo.keystore.op.exportWrapped.blob = blob; + cryptoInfo.keystore.op.exportWrapped.blobSz = blobSz; + cryptoInfo.keystore.op.exportWrapped.format = format; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreDerive(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* srcKeyRef, word32 srcKeyRefSz, + word32 kdfType, const byte* deriv, word32 derivSz, + word32 attrs, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (keyRef == NULL || keyRefSz == 0 || + srcKeyRef == NULL || srcKeyRefSz == 0) { + return BAD_FUNC_ARG; + } + if (deriv == NULL && derivSz != 0) { + return BAD_FUNC_ARG; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_DERIVE; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.derive.keyRef = keyRef; + cryptoInfo.keystore.op.derive.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.derive.keyType = keyType; + cryptoInfo.keystore.op.derive.srcKeyRef = srcKeyRef; + cryptoInfo.keystore.op.derive.srcKeyRefSz = srcKeyRefSz; + cryptoInfo.keystore.op.derive.deriv = deriv; + cryptoInfo.keystore.op.derive.derivSz = derivSz; + cryptoInfo.keystore.op.derive.kdfType = kdfType; + cryptoInfo.keystore.op.derive.attrs = attrs; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreDelete(int devId, const byte* keyRef, word32 keyRefSz, + const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (keyRef == NULL || keyRefSz == 0) { + return BAD_FUNC_ARG; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_DELETE; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.deleteKey.keyRef = keyRef; + cryptoInfo.keystore.op.deleteKey.keyRefSz = keyRefSz; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_KeyStoreGetInfo(int devId, const byte* keyRef, word32 keyRefSz, + word32* keyType, word32* keyBits, word32* attrs, const void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (keyRef == NULL || keyRefSz == 0) { + return BAD_FUNC_ARG; + } + + /* Clear the caller's storage first: a device may answer only part of the + * query, and attrs & WC_KEYSTORE_ATTR_EXPORTABLE is a security decision. */ + if (keyType != NULL) { + *keyType = 0; + } + if (keyBits != NULL) { + *keyBits = 0; + } + if (attrs != NULL) { + *attrs = 0; + } + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_KEYSTORE); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_KEYSTORE; + cryptoInfo.keystore.type = WC_KEYSTORE_GET_INFO; + cryptoInfo.keystore.ctx = ctx; + cryptoInfo.keystore.op.getInfo.keyRef = keyRef; + cryptoInfo.keystore.op.getInfo.keyRefSz = keyRefSz; + cryptoInfo.keystore.op.getInfo.keyType = keyType; + cryptoInfo.keystore.op.getInfo.keyBits = keyBits; + cryptoInfo.keystore.op.getInfo.attrs = attrs; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ + #if defined(HAVE_CMAC_KDF) /* Crypto callback for NIST SP 800 56C two-step CMAC KDF. See software * implementation in wc_KDA_KDF_twostep_cmac for more comments. diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index b7e3a4c9480..366c141f2d9 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -206,6 +206,7 @@ $(ASYNC_FILES): if BUILD_CRYPTOCB src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/cryptocb.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/wc_keystore.c endif if BUILD_PKCS11 diff --git a/wolfcrypt/src/wc_keystore.c b/wolfcrypt/src/wc_keystore.c new file mode 100644 index 00000000000..93055d57b20 --- /dev/null +++ b/wolfcrypt/src/wc_keystore.c @@ -0,0 +1,92 @@ +/* wc_keystore.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include + +#if defined(WOLF_CRYPTO_CB_KEYSTORE) && defined(WOLF_CRYPTO_CB) + +#include +#include +#include + +/* Thin public wrappers over the crypto-callback dispatch, mirroring how + * wc_she.c sits over the WC_ALGO_TYPE_SHE dispatch. */ + +int wc_KeyStore_ImportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + word32 keyType, const byte* key, word32 keySz, + word32 attrs, const void* ctx) +{ + return wc_CryptoCb_KeyStoreImportPlain(devId, keyRef, keyRefSz, + keyType, key, keySz, attrs, ctx); +} + +int wc_KeyStore_ExportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + byte* key, word32* keySz, const void* ctx) +{ + return wc_CryptoCb_KeyStoreExportPlain(devId, keyRef, keyRefSz, + key, keySz, ctx); +} + +int wc_KeyStore_ImportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, const byte* blob, word32 blobSz, + word32 attrs, const void* ctx) +{ + return wc_CryptoCb_KeyStoreImportWrapped(devId, keyRef, keyRefSz, keyType, + wrapKeyRef, wrapKeyRefSz, format, blob, blobSz, attrs, ctx); +} + +int wc_KeyStore_ExportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, byte* blob, word32* blobSz, const void* ctx) +{ + return wc_CryptoCb_KeyStoreExportWrapped(devId, keyRef, keyRefSz, + wrapKeyRef, wrapKeyRefSz, format, blob, blobSz, ctx); +} + +int wc_KeyStore_Derive(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* srcKeyRef, word32 srcKeyRefSz, + word32 kdfType, const byte* deriv, word32 derivSz, + word32 attrs, const void* ctx) +{ + return wc_CryptoCb_KeyStoreDerive(devId, keyRef, keyRefSz, keyType, + srcKeyRef, srcKeyRefSz, kdfType, deriv, derivSz, attrs, ctx); +} + +int wc_KeyStore_Delete(int devId, const byte* keyRef, word32 keyRefSz, + const void* ctx) +{ + return wc_CryptoCb_KeyStoreDelete(devId, keyRef, keyRefSz, ctx); +} + +int wc_KeyStore_GetInfo(int devId, const byte* keyRef, word32 keyRefSz, + word32* keyType, word32* keyBits, word32* attrs, const void* ctx) +{ + return wc_CryptoCb_KeyStoreGetInfo(devId, keyRef, keyRefSz, + keyType, keyBits, attrs, ctx); +} + +#endif /* WOLF_CRYPTO_CB_KEYSTORE && WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 2cfc25eecad..cb90f8ab263 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -353,6 +353,9 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #include #include #include +#ifdef WOLF_CRYPTO_CB_KEYSTORE + #include +#endif #ifdef WOLFSSL_SHE #include #endif @@ -889,6 +892,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cmac_test(void); #ifdef WOLFSSL_SHE WOLFSSL_TEST_SUBROUTINE wc_test_ret_t she_test(void); #endif +#ifdef WOLF_CRYPTO_CB_KEYSTORE +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t keystore_cb_test(void); +#endif #ifdef HAVE_ASCON WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ascon_hash256_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ascon_aead128_test(void); @@ -3376,6 +3382,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("SHE test passed!\n"); #endif +#ifdef WOLF_CRYPTO_CB_KEYSTORE + if ( (ret = keystore_cb_test()) != 0) + TEST_FAIL("KeyStore test failed!\n", ret); + else + TEST_PASS("KeyStore test passed!\n"); +#endif + #if defined(WOLFSSL_SIPHASH) if ( (ret = siphash_test()) != 0) TEST_FAIL("SipHash test failed!\n", ret); @@ -69187,6 +69200,313 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t she_test(void) #endif /* WOLFSSL_SHE && !NO_AES */ + +#ifdef WOLF_CRYPTO_CB_KEYSTORE + +/* Mock device recording what the key store dispatch handed it: the contract + * worth testing is that each call reaches the device as the right operation + * with the pointers unmodified. */ +typedef struct keystoreCbCtx { + int calls; + int lastOp; + const void* lastCallerCtx; + const byte* lastKeyRef; + word32 lastKeyRefSz; + const byte* lastSrcKeyRef; + word32 lastOtherRefSz; /* wrapKeyRefSz or srcKeyRefSz */ + word32 lastBlobSz; + word32 lastFormat; + word32 lastAttrs; + word32 lastKdfType; + const byte* lastDeriv; + word32 lastDerivSz; + word32 lastKeyType; + word32 lastKeySz; + word32 getInfoSaw; /* which optional getInfo outputs were offered */ +} keystoreCbCtx; + +static int keystoreTestCb(int cbDevId, wc_CryptoInfo* info, void* ctx) +{ + keystoreCbCtx* c = (keystoreCbCtx*)ctx; + + (void)cbDevId; + + if (info->algo_type != WC_ALGO_TYPE_KEYSTORE) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + + /* Clear everything but the call count, so no assertion can pass on a value + * the previous call left behind - lastBlobSz and lastFormat are read by the + * IMPORT_WRAPPED checks and were the two this list used to miss. */ + { + int calls = c->calls; + XMEMSET(c, 0, sizeof(*c)); + c->calls = calls; + } + + c->calls++; + c->lastOp = info->keystore.type; + c->lastCallerCtx = info->keystore.ctx; + + switch (info->keystore.type) { + case WC_KEYSTORE_IMPORT_PLAIN: + c->lastKeyRef = info->keystore.op.importPlain.keyRef; + c->lastKeyType = info->keystore.op.importPlain.keyType; + c->lastKeySz = info->keystore.op.importPlain.keySz; + c->lastAttrs = info->keystore.op.importPlain.attrs; + break; + case WC_KEYSTORE_EXPORT_PLAIN: + c->lastKeyRef = info->keystore.op.exportPlain.keyRef; + /* answer the size query so the caller can check it propagates */ + if (info->keystore.op.exportPlain.key == NULL) + *info->keystore.op.exportPlain.keySz = 32; + break; + case WC_KEYSTORE_IMPORT_WRAPPED: + c->lastKeyRef = info->keystore.op.importWrapped.keyRef; + c->lastKeyRefSz = info->keystore.op.importWrapped.keyRefSz; + c->lastSrcKeyRef = info->keystore.op.importWrapped.wrapKeyRef; + c->lastOtherRefSz = info->keystore.op.importWrapped.wrapKeyRefSz; + c->lastKeyType = info->keystore.op.importWrapped.keyType; + c->lastBlobSz = info->keystore.op.importWrapped.blobSz; + c->lastFormat = info->keystore.op.importWrapped.format; + c->lastAttrs = info->keystore.op.importWrapped.attrs; + break; + case WC_KEYSTORE_EXPORT_WRAPPED: + c->lastKeyRef = info->keystore.op.exportWrapped.keyRef; + c->lastKeyRefSz = info->keystore.op.exportWrapped.keyRefSz; + c->lastSrcKeyRef = info->keystore.op.exportWrapped.wrapKeyRef; + c->lastOtherRefSz = info->keystore.op.exportWrapped.wrapKeyRefSz; + break; + case WC_KEYSTORE_DERIVE: + c->lastKeyRef = info->keystore.op.derive.keyRef; + c->lastKeyRefSz = info->keystore.op.derive.keyRefSz; + c->lastSrcKeyRef = info->keystore.op.derive.srcKeyRef; + c->lastOtherRefSz = info->keystore.op.derive.srcKeyRefSz; + c->lastKeyType = info->keystore.op.derive.keyType; + c->lastAttrs = info->keystore.op.derive.attrs; + c->lastKdfType = info->keystore.op.derive.kdfType; + c->lastDeriv = info->keystore.op.derive.deriv; + c->lastDerivSz = info->keystore.op.derive.derivSz; + break; + case WC_KEYSTORE_DELETE: + c->lastKeyRef = info->keystore.op.deleteKey.keyRef; + break; + case WC_KEYSTORE_GET_INFO: + c->lastKeyRef = info->keystore.op.getInfo.keyRef; + c->lastKeyRefSz = info->keystore.op.getInfo.keyRefSz; + /* answer only part of the query, to prove the dispatcher cleared + * the rest of the caller's storage rather than leaving it stale */ + if (info->keystore.op.getInfo.keyBits != NULL) + *info->keystore.op.getInfo.keyBits = 256; + if (info->keystore.op.getInfo.keyType != NULL) + c->getInfoSaw |= 1; + if (info->keystore.op.getInfo.attrs != NULL) + c->getInfoSaw |= 2; + break; + default: + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return 0; +} + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t keystore_cb_test(void) +{ + /* Deliberately three different lengths: with all of them the same size a + * transposed reference reaches the device unnoticed. */ + WOLFSSL_SMALL_STACK_STATIC const byte kref[4] = { 'K','R','E','F' }; + WOLFSSL_SMALL_STACK_STATIC const byte wref[6] = { 'W','R','E','F','_','2' }; + WOLFSSL_SMALL_STACK_STATIC const byte sref[3] = { 'S','R','F' }; + WOLFSSL_SMALL_STACK_STATIC const byte blob[8] = { 1,2,3,4,5,6,7,8 }; + WOLFSSL_SMALL_STACK_STATIC const byte dd[4] = { 9,9,9,9 }; + /* sentinel for the caller context every entry point forwards untouched */ + WOLFSSL_SMALL_STACK_STATIC const byte callerCtx[1] = { 0x5a }; + keystoreCbCtx ctx; + byte outBlob[32]; + word32 outSz = (word32)sizeof(outBlob); + word32 keyType, keySz, attrs; + int ksDevId = 0x4B53; /* 'KS' */ + int ret; + + XMEMSET(&ctx, 0, sizeof(ctx)); + + ret = wc_CryptoCb_RegisterDevice(ksDevId, keystoreTestCb, &ctx); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + /* WC_KEYSTORE_KEY_WRAP and WC_KEYSTORE_ATTR_EXPORTABLE are both 1, so + * check each control word landed in its own field. */ + ret = wc_KeyStore_ImportPlain(ksDevId, kref, (word32)sizeof(kref), + WC_KEYSTORE_KEY_HMAC, blob, (word32)sizeof(blob), + WC_KEYSTORE_ATTR_EXPORTABLE, callerCtx); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_IMPORT_PLAIN || + ctx.lastKeyRef != kref || ctx.lastKeySz != (word32)sizeof(blob) || + ctx.lastKeyType != WC_KEYSTORE_KEY_HMAC || + ctx.lastAttrs != WC_KEYSTORE_ATTR_EXPORTABLE || + ctx.lastCallerCtx != callerCtx) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* key == NULL is the required-size query and must still dispatch */ + outSz = 0; + ret = wc_KeyStore_ExportPlain(ksDevId, kref, (word32)sizeof(kref), + NULL, &outSz, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_EXPORT_PLAIN || outSz != 32) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + outSz = (word32)sizeof(outBlob); + + ret = wc_KeyStore_ImportWrapped(ksDevId, kref, (word32)sizeof(kref), + WC_KEYSTORE_KEY_AES, wref, (word32)sizeof(wref), + WC_KEYWRAP_FORMAT_VENDOR, blob, (word32)sizeof(blob), + WC_KEYSTORE_ATTR_PERSISTENT, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_IMPORT_WRAPPED || + ctx.lastKeyRef != kref || ctx.lastKeyRefSz != (word32)sizeof(kref) || + ctx.lastSrcKeyRef != wref || + ctx.lastOtherRefSz != (word32)sizeof(wref) || + ctx.lastBlobSz != (word32)sizeof(blob) || + ctx.lastKeyType != WC_KEYSTORE_KEY_AES || + ctx.lastAttrs != WC_KEYSTORE_ATTR_PERSISTENT || + ctx.lastFormat != WC_KEYWRAP_FORMAT_VENDOR) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + ret = wc_KeyStore_ExportWrapped(ksDevId, kref, (word32)sizeof(kref), + wref, (word32)sizeof(wref), WC_KEYWRAP_FORMAT_AESKW, + outBlob, &outSz, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_EXPORT_WRAPPED || + ctx.lastKeyRef != kref || ctx.lastKeyRefSz != (word32)sizeof(kref) || + ctx.lastSrcKeyRef != wref || + ctx.lastOtherRefSz != (word32)sizeof(wref)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* blob == NULL is the required-size query and must still dispatch */ + outSz = 0; + ret = wc_KeyStore_ExportWrapped(ksDevId, kref, (word32)sizeof(kref), + wref, (word32)sizeof(wref), WC_KEYWRAP_FORMAT_AESKW, + NULL, &outSz, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_EXPORT_WRAPPED) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + ret = wc_KeyStore_Derive(ksDevId, kref, (word32)sizeof(kref), + WC_KEYSTORE_KEY_CMAC, sref, (word32)sizeof(sref), + WC_KDF_TYPE_HKDF, dd, (word32)sizeof(dd), + WC_KEYSTORE_ATTR_PERSISTENT, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_DERIVE || + ctx.lastKeyRef != kref || ctx.lastKeyRefSz != (word32)sizeof(kref) || + ctx.lastSrcKeyRef != sref || + ctx.lastOtherRefSz != (word32)sizeof(sref) || + ctx.lastKeyType != WC_KEYSTORE_KEY_CMAC || + ctx.lastAttrs != WC_KEYSTORE_ATTR_PERSISTENT || + ctx.lastKdfType != WC_KDF_TYPE_HKDF || + ctx.lastDeriv != dd || ctx.lastDerivSz != (word32)sizeof(dd)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + ret = wc_KeyStore_Delete(ksDevId, kref, (word32)sizeof(kref), NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_DELETE) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* the device answers keySz only; keyType and attrs must come back cleared, + * never left holding whatever was on the caller's stack */ + keyType = 0xDEADBEEF; + keySz = 0xDEADBEEF; + attrs = 0xDEADBEEF; + ret = wc_KeyStore_GetInfo(ksDevId, kref, (word32)sizeof(kref), + &keyType, &keySz, &attrs, NULL); + if (ret != 0 || ctx.lastOp != WC_KEYSTORE_GET_INFO || + ctx.lastKeyRef != kref || ctx.lastKeyRefSz != (word32)sizeof(kref)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (keySz != 256 || keyType != 0 || attrs != 0) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* Every output is optional. The device is offered exactly the ones the + * caller asked for, so getInfoSaw reads back what was passed down. */ + ctx.getInfoSaw = 0; + ret = wc_KeyStore_GetInfo(ksDevId, kref, (word32)sizeof(kref), + &keyType, &keySz, &attrs, NULL); + if (ret != 0 || ctx.getInfoSaw != 3) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + ctx.getInfoSaw = 0; + keySz = 0xDEADBEEF; + ret = wc_KeyStore_GetInfo(ksDevId, kref, (word32)sizeof(kref), + NULL, &keySz, NULL, NULL); + if (ret != 0 || keySz != 256 || ctx.getInfoSaw != 0) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + ret = wc_KeyStore_GetInfo(ksDevId, kref, (word32)sizeof(kref), + NULL, NULL, NULL, NULL); + if (ret != 0) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + if (ctx.calls != 11) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* a key reference is not optional, and is checked before the device */ + ctx.calls = 0; + if (wc_KeyStore_GetInfo(ksDevId, NULL, 0, &keyType, &keySz, &attrs, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (wc_KeyStore_GetInfo(ksDevId, kref, 0, &keyType, &keySz, &attrs, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (ctx.calls != 0) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* argument validation must happen before the device is consulted */ + ctx.calls = 0; + if (wc_KeyStore_ImportWrapped(ksDevId, NULL, 0, WC_KEYSTORE_KEY_AES, + wref, (word32)sizeof(wref), 0, blob, (word32)sizeof(blob), 0, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (wc_KeyStore_Delete(ksDevId, kref, 0, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (wc_KeyStore_ImportPlain(ksDevId, kref, (word32)sizeof(kref), + WC_KEYSTORE_KEY_AES, NULL, 0, 0, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + /* a buffer with no capacity beside it is not a size query, it is a slip */ + outSz = 0; + if (wc_KeyStore_ExportPlain(ksDevId, kref, (word32)sizeof(kref), + outBlob, &outSz, NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + if (ctx.calls != 0) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + /* an unregistered ksDevId must report CRYPTOCB_UNAVAILABLE, not success */ + if (wc_KeyStore_Delete(ksDevId + 1, kref, (word32)sizeof(kref), NULL) + != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = WC_TEST_RET_ENC_NC; goto out; + } + + ret = 0; + +out: + wc_CryptoCb_UnRegisterDevice(ksDevId); + + return ret; +} + +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ + + + #if defined(WOLFSSL_SIPHASH) #if WOLFSSL_SIPHASH_CROUNDS == 2 && WOLFSSL_SIPHASH_DROUNDS == 4 diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index d34eff49ec7..775084f8d7f 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -162,6 +162,59 @@ enum wc_SetKeyType { }; #endif /* WOLF_CRYPTO_CB_SETKEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE +/* Lifetime operations on a key held in a hardware key store. SETKEY and + * EXPORT_KEY cannot express these: both are bound to a wolfCrypt key object + * and carry material for that object's own use. */ +enum wc_KeyStoreType { + WC_KEYSTORE_NONE = 0, + WC_KEYSTORE_IMPORT_PLAIN = 1, /* plaintext key -> slot */ + WC_KEYSTORE_EXPORT_PLAIN = 2, /* slot -> plaintext key */ + WC_KEYSTORE_IMPORT_WRAPPED = 3, /* wrapped blob -> slot */ + WC_KEYSTORE_EXPORT_WRAPPED = 4, /* slot -> wrapped blob */ + WC_KEYSTORE_DERIVE = 5, /* slot -> slot, never in RAM */ + WC_KEYSTORE_DELETE = 6, /* destroy a stored key */ + WC_KEYSTORE_GET_INFO = 7 /* what is in this slot? */ +}; + +/* What a stored key is for. Also fixes the plaintext encoding: raw bytes for a + * symmetric type, DER for an asymmetric one. RSA and ECC are split because a + * key store grants their two purposes separately. */ +enum wc_KeyStoreKeyType { + WC_KEYSTORE_KEY_NONE = 0, + WC_KEYSTORE_KEY_WRAP = 1, /* wraps and unwraps other keys */ + WC_KEYSTORE_KEY_DERIVE = 2, /* parent of derived keys */ + WC_KEYSTORE_KEY_AES = 3, + WC_KEYSTORE_KEY_HMAC = 4, + WC_KEYSTORE_KEY_CMAC = 5, + WC_KEYSTORE_KEY_RSA_SIGN = 6, /* signature */ + WC_KEYSTORE_KEY_RSA_ENC = 7, /* key transport */ + WC_KEYSTORE_KEY_ECC_SIGN = 8, /* ECDSA */ + WC_KEYSTORE_KEY_ECC_DH = 9, /* ECDH */ + WC_KEYSTORE_KEY_ED25519 = 10, + WC_KEYSTORE_KEY_CURVE25519 = 11, + WC_KEYSTORE_KEY_ED448 = 12, + WC_KEYSTORE_KEY_CURVE448 = 13, + WC_KEYSTORE_KEY_MLDSA = 14, + WC_KEYSTORE_KEY_MLKEM = 15 +}; + +/* Wrapped blob formats. A device that only speaks one format ignores this. */ +enum wc_KeyWrapFormat { + WC_KEYWRAP_FORMAT_NONE = 0, + WC_KEYWRAP_FORMAT_VENDOR = 1, /* device-defined container */ + WC_KEYWRAP_FORMAT_AESKW = 2 /* bare RFC 3394, no vendor wrapper */ +}; + +/* Attributes requested when a key is created, fixed for its lifetime; there is + * no operation to change them afterwards. A device that can express one must + * report it back through WC_KEYSTORE_GET_INFO. Attributes it cannot represent + * read as absent. */ +#define WC_KEYSTORE_ATTR_EXPORTABLE 0x0001 /* may later be wrapped out */ +#define WC_KEYSTORE_ATTR_UNWRAP_ONLY 0x0002 /* KWK may unwrap, never wrap */ +#define WC_KEYSTORE_ATTR_PERSISTENT 0x0004 /* survives reset, if supported */ +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ + /* Crypto Information Structure for callbacks */ typedef struct wc_CryptoInfo { int algo_type; /* enum wc_AlgoType */ @@ -803,6 +856,70 @@ typedef struct wc_CryptoInfo { void* out; /* Software key to fill (same type as obj) */ } export_key; #endif /* WOLF_CRYPTO_CB_EXPORT_KEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE + struct { /* uses wc_AlgoType=WC_ALGO_TYPE_KEYSTORE */ + int type; /* enum wc_KeyStoreType - discriminator */ + const void* ctx; /* read-only caller context */ + union { + struct { /* WC_KEYSTORE_IMPORT_PLAIN */ + const byte* keyRef; /* opaque: where it should land */ + word32 keyRefSz; + word32 keyType; /* enum wc_KeyStoreKeyType */ + const byte* key; /* plaintext key material */ + word32 keySz; /* in bytes */ + word32 attrs; /* WC_KEYSTORE_ATTR_* */ + } importPlain; + struct { /* WC_KEYSTORE_EXPORT_PLAIN */ + const byte* keyRef; /* opaque: the key to export */ + word32 keyRefSz; + byte* key; /* out: plaintext key material */ + word32* keySz; /* in: capacity, out: written */ + } exportPlain; + struct { /* WC_KEYSTORE_IMPORT_WRAPPED */ + const byte* keyRef; /* opaque: where it should land */ + word32 keyRefSz; + word32 keyType; /* enum wc_KeyStoreKeyType */ + const byte* wrapKeyRef; /* opaque: the wrapping key */ + word32 wrapKeyRefSz; + const byte* blob; /* the wrapped container */ + word32 blobSz; + word32 format; /* enum wc_KeyWrapFormat */ + word32 attrs; /* WC_KEYSTORE_ATTR_* */ + } importWrapped; + struct { /* WC_KEYSTORE_EXPORT_WRAPPED */ + const byte* keyRef; /* opaque: the key to export */ + word32 keyRefSz; + const byte* wrapKeyRef; + word32 wrapKeyRefSz; + byte* blob; /* out: the wrapped container */ + word32* blobSz; /* in: capacity, out: written */ + word32 format; + } exportWrapped; + struct { /* WC_KEYSTORE_DERIVE */ + const byte* keyRef; /* opaque: where it should land */ + word32 keyRefSz; + word32 keyType; /* enum wc_KeyStoreKeyType */ + word32 attrs; /* WC_KEYSTORE_ATTR_* */ + const byte* srcKeyRef; /* opaque: the derivation key */ + word32 srcKeyRefSz; + word32 kdfType; /* enum wc_KdfType */ + const byte* deriv; /* derivation data */ + word32 derivSz; /* size is algorithm-specific */ + } derive; + struct { /* WC_KEYSTORE_DELETE */ + const byte* keyRef; + word32 keyRefSz; + } deleteKey; + struct { /* WC_KEYSTORE_GET_INFO */ + const byte* keyRef; + word32 keyRefSz; + word32* keyType; /* out: enum wc_KeyStoreKeyType */ + word32* keyBits; /* out: key size in bits */ + word32* attrs; /* out: WC_KEYSTORE_ATTR_* */ + } getInfo; + } op; + } keystore; +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ #if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF) struct { int type; /* enum wc_KdfType */ @@ -1257,6 +1374,34 @@ WOLFSSL_LOCAL int wc_CryptoCb_SetKey(int devId, int type, void* obj, WOLFSSL_LOCAL int wc_CryptoCb_ExportKey(int devId, int type, const void* obj, void* out); #endif /* WOLF_CRYPTO_CB_EXPORT_KEY */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreImportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + word32 keyType, const byte* key, word32 keySz, + word32 attrs, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreExportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + byte* key, word32* keySz, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreImportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, const byte* blob, word32 blobSz, + word32 attrs, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreExportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, byte* blob, word32* blobSz, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreDerive(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* srcKeyRef, word32 srcKeyRefSz, + word32 kdfType, const byte* deriv, word32 derivSz, + word32 attrs, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreDelete(int devId, + const byte* keyRef, word32 keyRefSz, const void* ctx); +WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreGetInfo(int devId, + const byte* keyRef, word32 keyRefSz, + word32* keyType, word32* keyBits, word32* attrs, const void* ctx); +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ #endif /* WOLF_CRYPTO_CB */ diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index da504f617e5..c5533eb5dc9 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -12,6 +12,7 @@ nobase_include_HEADERS+= \ wolfssl/wolfcrypt/poly1305.h \ wolfssl/wolfcrypt/camellia.h \ wolfssl/wolfcrypt/cmac.h \ + wolfssl/wolfcrypt/wc_keystore.h \ wolfssl/wolfcrypt/wc_she.h \ wolfssl/wolfcrypt/coding.h \ wolfssl/wolfcrypt/compress.h \ diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 48f3364813f..720f3a4033a 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1527,7 +1527,9 @@ enum wc_AlgoType { /* async: re-enter a crypto callback device to poll a pending operation so * it can complete the work and fill the output buffer (QAT-style). */ WC_ALGO_TYPE_ASYNC_POLL = 15, - WC_ALGO_TYPE_MAX = WC_ALGO_TYPE_ASYNC_POLL + /* hardware key store lifecycle: import, export, derive, delete, query */ + WC_ALGO_TYPE_KEYSTORE = 16, + WC_ALGO_TYPE_MAX = WC_ALGO_TYPE_KEYSTORE }; /* KDF types */ diff --git a/wolfssl/wolfcrypt/wc_keystore.h b/wolfssl/wolfcrypt/wc_keystore.h new file mode 100644 index 00000000000..1e268778448 --- /dev/null +++ b/wolfssl/wolfcrypt/wc_keystore.h @@ -0,0 +1,103 @@ +/* wc_keystore.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! + \file wolfssl/wolfcrypt/wc_keystore.h +*/ + +#ifndef WOLF_CRYPT_KEYSTORE_H +#define WOLF_CRYPT_KEYSTORE_H + +#include + +#ifdef WOLF_CRYPTO_CB_KEYSTORE + +/* The backing wc_CryptoCb_KeyStore* functions live inside WOLF_CRYPTO_CB, so + * without it this header would advertise an API that does not link. */ +#ifndef WOLF_CRYPTO_CB + #error WOLF_CRYPTO_CB_KEYSTORE requires WOLF_CRYPTO_CB +#endif + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Lifecycle operations on a key held inside a hardware key store, which the + * algorithm callbacks cannot express: those are bound to a wolfCrypt key + * object. A key reference is the identifier WOLF_PRIVATE_KEY_ID already uses, + * copied through without inspection. Full docs in doc/dox_comments. */ + +/* Place plaintext key material into the device's key store. The caller owns + * the plaintext copy and should zeroize it once the import succeeds. */ +WOLFSSL_API int wc_KeyStore_ImportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + word32 keyType, const byte* key, word32 keySz, + word32 attrs, const void* ctx); + +/* Read a stored key back as plaintext. keySz is in/out: capacity on entry, + * bytes written on return; key == NULL queries the size. */ +WOLFSSL_API int wc_KeyStore_ExportPlain(int devId, + const byte* keyRef, word32 keyRefSz, + byte* key, word32* keySz, const void* ctx); + +/* Unwrap a key blob directly into the device's key store. Nothing is returned: + * on success the key exists at keyRef and its material was never in memory. */ +WOLFSSL_API int wc_KeyStore_ImportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, const byte* blob, word32 blobSz, + word32 attrs, const void* ctx); + +/* Wrap a stored key under wrapKeyRef and emit the blob. blobSz is in/out: + * capacity on entry, bytes written on return; blob == NULL queries the size. */ +WOLFSSL_API int wc_KeyStore_ExportWrapped(int devId, + const byte* keyRef, word32 keyRefSz, + const byte* wrapKeyRef, word32 wrapKeyRefSz, + word32 format, byte* blob, word32* blobSz, const void* ctx); + +/* Derive a new stored key from an existing one without either touching RAM. + * derivSz is algorithm-specific and often fixed by the hardware. */ +WOLFSSL_API int wc_KeyStore_Derive(int devId, + const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* srcKeyRef, word32 srcKeyRefSz, + word32 kdfType, const byte* deriv, word32 derivSz, + word32 attrs, const void* ctx); + +/* Destroy a stored key. Deliberately separate from WC_ALGO_TYPE_FREE: freeing + * a wolfCrypt key object must never destroy the hardware key it refers to. */ +WOLFSSL_API int wc_KeyStore_Delete(int devId, + const byte* keyRef, word32 keyRefSz, const void* ctx); + +/* Report what a slot holds: key type, size in bits and attributes. Out + * parameters the device does not fill are cleared. */ +WOLFSSL_API int wc_KeyStore_GetInfo(int devId, + const byte* keyRef, word32 keyRefSz, + word32* keyType, word32* keyBits, word32* attrs, const void* ctx); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ +#endif /* WOLF_CRYPT_KEYSTORE_H */ diff --git a/zephyr/samples/wolfssl_tls_sock/prj-no-malloc.conf b/zephyr/samples/wolfssl_tls_sock/prj-no-malloc.conf index d14a77e3b54..19a849e52ac 100644 --- a/zephyr/samples/wolfssl_tls_sock/prj-no-malloc.conf +++ b/zephyr/samples/wolfssl_tls_sock/prj-no-malloc.conf @@ -36,12 +36,9 @@ CONFIG_LOG=y CONFIG_LOG_MODE_IMMEDIATE=y # TLS configuration +# The settings file below is authoritative, so the feature Kconfigs +# do not apply - they now say so with depends on !WOLFSSL_HAS_SETTINGS_FILE. CONFIG_WOLFSSL_SETTINGS_FILE="user_settings-no-malloc.h" CONFIG_WOLFSSL=y CONFIG_WOLFSSL_BUILTIN=y -CONFIG_WOLFSSL_TLS_VERSION_1_2=y -CONFIG_WOLFSSL_KEY_EXCHANGE_ALL_ENABLED=y -CONFIG_WOLFSSL_CIPHER_ALL_ENABLED=y -CONFIG_WOLFSSL_MAC_ALL_ENABLED=y -CONFIG_WOLFSSL_HMAC_DRBG_ENABLED=y