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