diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 963d71a..40b2b46 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -130,10 +130,20 @@ jobs: make -j$(nproc) make install + - name: Select wolfCOSE compatibility flags + run: | + VERSION_HEX=$(awk '/^#define LIBWOLFSSL_VERSION_HEX/{print $3}' \ + ~/wolfssl-install/include/wolfssl/version.h) + if (( VERSION_HEX < 0x05009002 )); then + echo "WOLFCOSE_COMPAT_FLAGS=-DWOLFCOSE_RSA_PUBLIC_ONLY -DWOLFCOSE_NO_MLDSA -DWOLFCOSE_NO_LMS" >> "$GITHUB_ENV" + else + echo "WOLFCOSE_COMPAT_FLAGS=" >> "$GITHUB_ENV" + fi + - name: Build wolfCOSE run: | export WOLFSSL_DIR=$HOME/wolfssl-install - make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \ + make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion $WOLFCOSE_COMPAT_FLAGS -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - name: Verify legacy AES Key Wrap rejection @@ -168,14 +178,14 @@ jobs: run: | export WOLFSSL_DIR=$HOME/wolfssl-install export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib - make test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \ + make test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion $WOLFCOSE_COMPAT_FLAGS -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - name: Run tool round-trip test run: | export WOLFSSL_DIR=$HOME/wolfssl-install export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib - make tool-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \ + make tool-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion $WOLFCOSE_COMPAT_FLAGS -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" # ML-DSA / PQC-specific coverage: CLI keygen->sign->verify for all three diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index dfcdeed..23b67be 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -8,10 +8,18 @@ This guide covers prerequisites, building wolfCOSE, and basic usage examples. wolfCOSE requires wolfSSL 5.8.0 or later with the appropriate algorithms enabled. AES Key Wrap requires wolfSSL 5.9.0 or later because that release -uses a constant-time integrity comparison during unwrap. ML-DSA requires a -wolfSSL release newer than 5.9.1. HSS/LMS (RFC 8778) requires wolfSSL 5.9.2 or -later, the first release whose public-key importer derives the parameter set -from the key bytes. +uses a constant-time integrity comparison during unwrap. Private RSA +`COSE_Key` decoding requires wolfSSL 5.9.0 or later, and private RSA +serialization requires wolfSSL 5.9.2 or later. ML-DSA requires a wolfSSL +release newer than 5.9.1. HSS/LMS (RFC 8778) requires wolfSSL 5.9.2 or later, +the first release whose public-key importer derives the parameter set from the +key bytes. + +These dependency floors are enforced at compile time whenever wolfCOSE selects +the corresponding feature. With an older wolfSSL, disable unused ML-DSA or LMS +support with `WOLFCOSE_NO_MLDSA` or `WOLFCOSE_NO_LMS`. Define +`WOLFCOSE_RSA_PUBLIC_ONLY` to retain RSA-PSS and public `COSE_Key` support +without private RSA serialization. Here is a full-featured build using a release that meets those feature floors: @@ -61,7 +69,9 @@ You can enable only the algorithms you need: | ECDH-ES key agreement | `--enable-ecc --enable-hkdf` | | AES Key Wrap | `--enable-aeskeywrap` (wolfSSL 5.9.0+) | | RSA-PSS signing | `--enable-rsapss --enable-keygen` | -| ML-DSA (post-quantum) | `--enable-mldsa` | +| Private RSA `COSE_Key` decoding | `--enable-rsapss` (wolfSSL 5.9.0+) | +| Private RSA `COSE_Key` serialization | `--enable-rsapss --enable-keygen` (wolfSSL 5.9.2+) | +| ML-DSA (post-quantum) | `--enable-mldsa` (wolfSSL newer than 5.9.1) | | HSS/LMS (stateful hash-based) | `--enable-lms` (wolfSSL 5.9.2+) | | AES-MAC | `--enable-aescbc` | diff --git a/docs/Macros.md b/docs/Macros.md index 7803b6f..1fd8934 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -63,7 +63,7 @@ Per-algorithm opt-outs for the default (non-lean) build. Each also has a `WOLFCO | `WOLFCOSE_NO_EDDSA` | Ed25519 | `HAVE_ED25519` | | `WOLFCOSE_NO_ED448` | Ed448 | `HAVE_ED448` | | `WOLFCOSE_NO_RSAPSS` | RSA-PSS (PS256/384/512) | `WC_RSA_PSS` | -| `WOLFCOSE_NO_MLDSA` | ML-DSA (FIPS 204) | `WOLFSSL_HAVE_MLDSA` | +| `WOLFCOSE_NO_MLDSA` | ML-DSA (FIPS 204) | `WOLFSSL_HAVE_MLDSA` (wolfSSL newer than 5.9.1) | | `WOLFCOSE_NO_LMS` | HSS/LMS (RFC 8778) | `WOLFSSL_HAVE_LMS` (wolfSSL 5.9.2+) | | `WOLFCOSE_NO_AESGCM` | AES-GCM | `HAVE_AESGCM` | | `WOLFCOSE_NO_AESCCM` | AES-CCM | `HAVE_AESCCM` | @@ -269,6 +269,10 @@ Two limits worth knowing before designing around this: | `WOLFCOSE_KEY_DECODE` | Enable COSE_Key decoding | Enabled | | `WOLFCOSE_NO_KEY_DECODE` | Disable COSE_Key decoding | - | +Private RSA `COSE_Key` serialization requires wolfSSL 5.9.2 or later. Define +`WOLFCOSE_RSA_PUBLIC_ONLY` to retain RSA-PSS and public-key encoding on an older +wolfSSL release. + --- ## Size Configuration diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 9a07818..343230b 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -227,28 +227,40 @@ extern "C" { /* ML-DSA (44/65/87) — extension */ #if defined(WOLFCOSE_ENABLE_MLDSA) - #ifndef WOLFSSL_HAVE_MLDSA + #if !defined(WOLFSSL_HAVE_MLDSA) #error "WOLFCOSE_ENABLE_MLDSA requires wolfSSL WOLFSSL_HAVE_MLDSA" + #elif LIBWOLFSSL_VERSION_HEX < 0x05009002 + #error "wolfCOSE ML-DSA requires wolfSSL newer than 5.9.1" + #else + #define WOLFCOSE_HAVE_MLDSA + #endif +#elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_MLDSA) && \ + defined(WOLFSSL_HAVE_MLDSA) + #if LIBWOLFSSL_VERSION_HEX < 0x05009002 + #error "wolfCOSE ML-DSA requires wolfSSL newer than 5.9.1" + #else + #define WOLFCOSE_HAVE_MLDSA #endif - #define WOLFCOSE_HAVE_MLDSA -#elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_MLDSA) && defined(WOLFSSL_HAVE_MLDSA) - #define WOLFCOSE_HAVE_MLDSA #endif /* HSS/LMS (RFC 8778) — extension */ /* wc_LmsKey_ImportPubRaw() derives the parameter set from the key bytes only * from wolfSSL 5.9.2; earlier releases dereference unset parameters. */ #if defined(WOLFCOSE_ENABLE_LMS) - #ifndef WOLFSSL_HAVE_LMS + #if !defined(WOLFSSL_HAVE_LMS) #error "WOLFCOSE_ENABLE_LMS requires wolfSSL WOLFSSL_HAVE_LMS" - #endif - #if LIBWOLFSSL_VERSION_HEX < 0x05009002 + #elif LIBWOLFSSL_VERSION_HEX < 0x05009002 #error "WOLFCOSE_ENABLE_LMS requires wolfSSL 5.9.2 or later" + #else + #define WOLFCOSE_HAVE_LMS #endif - #define WOLFCOSE_HAVE_LMS #elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_LMS) && \ - defined(WOLFSSL_HAVE_LMS) && (LIBWOLFSSL_VERSION_HEX >= 0x05009002) - #define WOLFCOSE_HAVE_LMS + defined(WOLFSSL_HAVE_LMS) + #if LIBWOLFSSL_VERSION_HEX < 0x05009002 + #error "wolfCOSE HSS/LMS requires wolfSSL 5.9.2 or later" + #else + #define WOLFCOSE_HAVE_LMS + #endif #endif /* RSA-PSS (PS256/384/512) — extension */ @@ -278,11 +290,31 @@ extern "C" { defined(WOLFCOSE_HAVE_PS512) #define WOLFCOSE_HAVE_RSAPSS #endif -/* Private RSA round-trip needs wc_export_int + RsaKey.u; else public-only. */ +/* Private-capable RSA builds enable private serialization with key encoding. + * Do not silently downgrade that configuration on an older backend. */ #if defined(WOLFCOSE_HAVE_RSAPSS) && !defined(WOLFCOSE_RSA_PUBLIC_ONLY) && \ !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + !defined(WOLFCOSE_NO_KEY_ENCODE) && \ + (defined(HAVE_ECC) || defined(WOLFSSL_EXPORT_INT)) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA) || \ + !defined(RSA_LOW_MEM)) && \ + (LIBWOLFSSL_VERSION_HEX < 0x05009002) + #error "Private RSA serialization requires wolfSSL 5.9.2 or later" +#endif +/* Private RSA decoding requires wolfSSL's hardened raw decoder from 5.9.0. + * Older backends explicitly reject private components. */ +#if defined(WOLFCOSE_HAVE_RSAPSS) && !defined(WOLFCOSE_RSA_PUBLIC_ONLY) && \ + !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + (LIBWOLFSSL_VERSION_HEX >= 0x05009000) + #define WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE +#endif +/* Private RSA serialization additionally needs wc_export_int + RsaKey.u and + * wolfSSL 5.9.2's bounds-checked fixed-width export. */ +#if defined(WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE) && \ (defined(HAVE_ECC) || defined(WOLFSSL_EXPORT_INT)) && \ - (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA) || !defined(RSA_LOW_MEM)) + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA) || \ + !defined(RSA_LOW_MEM)) && \ + (LIBWOLFSSL_VERSION_HEX >= 0x05009002) #define WOLFCOSE_HAVE_RSA_PRIVATE_KEY #endif #if defined(WOLFCOSE_HAVE_ECDSA) || defined(WOLFCOSE_HAVE_EDDSA) || \ diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 53a30c6..09f60aa 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -999,12 +999,6 @@ WOLFCOSE_API int wc_CoseKey_EncodeSize(const WOLFCOSE_KEY* key, * bound, so it can be used to size a buffer or to reject an oversized key * before committing storage. * - * One configuration limit: in a build with neither HAVE_ECC nor - * WOLFSSL_EXPORT_INT, reading the RSA public exponent needs a scratch copy of - * the modulus, so an RSA key whose modulus exceeds WOLFCOSE_MAX_SCRATCH_SZ - * returns WOLFCOSE_E_CRYPTO here even though wc_CoseKey_Encode_ex() encodes - * it. Any build with ECC or WOLFSSL_EXPORT_INT enabled is unaffected. - * * \param key Key to size. * \param outLen Output: exact encoded size in bytes. * \param flags Bitmask of WOLFCOSE_KEY_* output options. diff --git a/src/wolfcose_alg.c b/src/wolfcose_alg.c index b286ef9..5dde5b8 100644 --- a/src/wolfcose_alg.c +++ b/src/wolfcose_alg.c @@ -546,21 +546,24 @@ int wolfCose_RsaPssCheckKey(const WOLFCOSE_KEY* key, } else if (modulusSz == (int)WOLFCOSE_RSA_PSS_MIN_SZ) { int modulusMaterialized = 0; - uint8_t modulus[WOLFCOSE_RSA_PSS_MIN_SZ] = {0}; + int modulusExportRet; + uint8_t modulus[WOLFCOSE_RSA_PSS_MIN_SZ]; word32 modulusLen32 = (word32)sizeof(modulus); #if !defined(HAVE_ECC) && !defined(WOLFSSL_EXPORT_INT) && \ !defined(WOLFSSL_RSA_VERIFY_ONLY) word32 exponentLen = (word32)sizeof(modulus); #endif + + (void)XMEMSET(modulus, 0, sizeof(modulus)); #if defined(HAVE_ECC) || defined(WOLFSSL_EXPORT_INT) - int modulusExportRet = wc_export_int(&rsaKey->n, modulus, + modulusExportRet = wc_export_int(&rsaKey->n, modulus, &modulusLen32, (word32)sizeof(modulus), WC_TYPE_UNSIGNED_BIN); #elif defined(WOLFSSL_RSA_VERIFY_ONLY) - int modulusExportRet = -1; + modulusExportRet = -1; #else /* The modulus output overwrites the unused exponent output. */ - int modulusExportRet = wc_RsaFlattenPublicKey(rsaKey, + modulusExportRet = wc_RsaFlattenPublicKey(rsaKey, modulus, &exponentLen, modulus, &modulusLen32); #endif if (modulusExportRet == 0) { diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index e5918cb..d5d572b 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -384,9 +384,8 @@ WOLFCOSE_LOCAL int wolfCose_ExtSign(const WOLFCOSE_KEY* key, int32_t alg, /* ----- Internal helpers shared across the split source files ----- */ #ifdef WOLFCOSE_HAVE_RSAPSS -/* Widest RSA public exponent wolfCOSE emits and the RFC 8230 RSA-PSS - * minimum modulus width. These are shared by COSE_Key encoding and message - * operations, including builds that disable COSE_Key encoding. */ +/* RSA component limits shared by COSE_Key encoding and message operations, + * including builds that disable COSE_Key encoding. */ #define WOLFCOSE_RSA_E_MAX_SZ 8u #define WOLFCOSE_RSA_PSS_MIN_SZ 256u #endif diff --git a/src/wolfcose_key.c b/src/wolfcose_key.c index 00c0eda..feb384c 100644 --- a/src/wolfcose_key.c +++ b/src/wolfcose_key.c @@ -472,7 +472,8 @@ static int wolfCose_RsaExponentSize(RsaKey* rsa, size_t* eLen) size_t lead = 0u; int ret = WOLFCOSE_SUCCESS; #if !defined(HAVE_ECC) && !defined(WOLFSSL_EXPORT_INT) - uint8_t nBuf[WOLFCOSE_MAX_SCRATCH_SZ]; + /* RSA_MAX_SIZE is in bits. */ + uint8_t nBuf[(RSA_MAX_SIZE + 7) / 8]; word32 nLen = (word32)sizeof(nBuf); #endif @@ -485,21 +486,12 @@ static int wolfCose_RsaExponentSize(RsaKey* rsa, size_t* eLen) } #else /* Without wc_export_int() the only public reader of e also wants the - * modulus; take it into scratch and drop it. n is public, but there is - * no reason to leave a copy on the stack. - * - * wc_RsaFlattenPublicKey() has no way to decline the modulus, so this - * caps the size query at a WOLFCOSE_MAX_SCRATCH_SZ modulus while the - * encoder, which flattens n straight into the caller's output buffer, - * handles any modulus that fits there. A key wider than scratch (e.g. - * RSA-8192 with the 512-byte WOLFCOSE_MIN_BUFFERS scratch) therefore - * sizes as WOLFCOSE_E_CRYPTO but still encodes. Documented on - * wc_CoseKey_EncodeSize_ex(); it costs nothing in a build with ECC or - * WOLFSSL_EXPORT_INT, which is every build that reaches the branch above. */ + * modulus. Size that temporary from wolfCrypt's configured RSA limit, + * independently of the COSE scratch setting, then erase it. */ if (wc_RsaFlattenPublicKey(rsa, eBuf, &len, nBuf, &nLen) != 0) { ret = WOLFCOSE_E_CRYPTO; } - wolfCose_ForceZero(nBuf, sizeof(nBuf)); + (void)wolfCose_ForceZero(nBuf, sizeof(nBuf)); #endif if (ret == WOLFCOSE_SUCCESS) { @@ -565,8 +557,11 @@ static int wolfCose_EncodeRsaMp(WOLFCOSE_CBOR_CTX* ctx, int64_t label, WC_TYPE_UNSIGNED_BIN) != 0) { ret = WOLFCOSE_E_CRYPTO; } + else if (len != keySz) { + ret = WOLFCOSE_E_CRYPTO; + } else { - ctx->idx += (size_t)len; + ctx->idx += (size_t)keySz; } } return ret; @@ -804,6 +799,7 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, if (ret == WOLFCOSE_SUCCESS) { ret = wc_CBOR_EncodeBstr(&ctx, eBuf, (size_t)eLen); } +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY /* -3: d (private exponent, optional) — direct export */ if ((ret == WOLFCOSE_SUCCESS) && (rsaPriv != 0)) { ret = wc_CBOR_EncodeInt(&ctx, @@ -887,7 +883,6 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, } } } -#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY /* -4 p, -5 q, -8 qInv: CRT factors so a decoded key can sign. */ if ((ret == WOLFCOSE_SUCCESS) && (rsaPriv != 0)) { int modSz = wc_RsaEncryptSize(key->key.rsa); @@ -946,7 +941,10 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, emitPriv = 1; } - if (key->alg == WOLFCOSE_ALG_UNSET) { + if (key->key.mldsa == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (key->alg == WOLFCOSE_ALG_UNSET) { /* RFC 9964: alg is REQUIRED for AKP keys (it carries the * ML-DSA level). Never emit a key without it. */ ret = WOLFCOSE_E_COSE_BAD_ALG; @@ -1113,22 +1111,32 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, #ifdef WOLFCOSE_HAVE_EDDSA if (key->crv == WOLFCOSE_CRV_ED25519) { - INJECT_FAILURE(WOLF_FAIL_ED25519_EXPORT_PUB, -1, - ret = wc_ed25519_export_public(key->key.ed25519, - pubBuf, &pubLen)); - if (ret != 0) { - ret = WOLFCOSE_E_CRYPTO; + if (key->key.ed25519 == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED25519_EXPORT_PUB, -1, + ret = wc_ed25519_export_public(key->key.ed25519, + pubBuf, &pubLen)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } } } else #endif #ifdef WOLFCOSE_HAVE_ED448 if (key->crv == WOLFCOSE_CRV_ED448) { - INJECT_FAILURE(WOLF_FAIL_ED448_EXPORT_PUB, -1, - ret = wc_ed448_export_public(key->key.ed448, - pubBuf, &pubLen)); - if (ret != 0) { - ret = WOLFCOSE_E_CRYPTO; + if (key->key.ed448 == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED448_EXPORT_PUB, -1, + ret = wc_ed448_export_public(key->key.ed448, + pubBuf, &pubLen)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } } } else @@ -1954,6 +1962,27 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, return ret; } +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE +/* RSA labels -5 through -8 are unassigned for other supported key types. + * Defer wrong-type rejection until kty is known so map order remains free. */ +static int wolfCose_DecodeRsaPrivateComponent(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t** data, size_t* dataLen, uint8_t* invalidType) +{ + int ret; + + if ((ctx->idx < ctx->bufSz) && + (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_BSTR)) { + ret = wc_CBOR_DecodeBstr(ctx, data, dataLen); + } + else { + *invalidType = 1u; + ret = wc_CBOR_Skip(ctx); + } + + return ret; +} +#endif + int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) { int ret; @@ -1972,7 +2001,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) size_t dLen = 0; const uint8_t* nData = NULL; /* RSA: n (modulus) */ size_t nLen = 0; -#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE const uint8_t* qData = NULL; /* RSA: q (second prime) */ size_t qLen = 0; const uint8_t* dpData = NULL; /* RSA: dP = d mod (p-1) */ @@ -1981,6 +2010,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) size_t dqLen = 0; const uint8_t* qiData = NULL; /* RSA: qInv (CRT coefficient) */ size_t qiLen = 0; + uint8_t rsaPrivateTypeInvalid = 0u; #endif WOLFCOSE_HDR_STATE keyLabelState; @@ -2118,22 +2148,26 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) (label == WOLFCOSE_KEY_LABEL_D)) { ret = wc_CBOR_DecodeBstr(&ctx, &dData, &dLen); } -#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_RSA_Q)) { - ret = wc_CBOR_DecodeBstr(&ctx, &qData, &qLen); + ret = wolfCose_DecodeRsaPrivateComponent(&ctx, &qData, + &qLen, &rsaPrivateTypeInvalid); } else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_RSA_DP)) { - ret = wc_CBOR_DecodeBstr(&ctx, &dpData, &dpLen); + ret = wolfCose_DecodeRsaPrivateComponent(&ctx, &dpData, + &dpLen, &rsaPrivateTypeInvalid); } else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_RSA_DQ)) { - ret = wc_CBOR_DecodeBstr(&ctx, &dqData, &dqLen); + ret = wolfCose_DecodeRsaPrivateComponent(&ctx, &dqData, + &dqLen, &rsaPrivateTypeInvalid); } else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_RSA_QINV)) { - ret = wc_CBOR_DecodeBstr(&ctx, &qiData, &qiLen); + ret = wolfCose_DecodeRsaPrivateComponent(&ctx, &qiData, + &qiLen, &rsaPrivateTypeInvalid); } #endif else { @@ -2148,6 +2182,13 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) if ((ret == WOLFCOSE_SUCCESS) && (key->kty == 0)) { ret = WOLFCOSE_E_COSE_BAD_HDR; } +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_RSA) && + (rsaPrivateTypeInvalid != 0u)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#endif /* RFC 8949 Section 5.3.1: reject trailing data before importing any * key material so a failed decode leaves no key populated. */ @@ -2155,6 +2196,19 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = WOLFCOSE_E_CBOR_MALFORMED; } +#ifdef WOLFCOSE_HAVE_RSAPSS + /* RFC 8230 requires nonempty public modulus and exponent fields, + * including when no wolfCrypt RSA object is attached for import. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_RSA) && + ((key->attachedType == WOLFCOSE_ATT_NONE) || + (key->attachedType == WOLFCOSE_ATT_RSA)) && + ((nData == NULL) || (nLen == 0u) || + (xData == NULL) || (xLen == 0u))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#endif + #if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) /* wolfCrypt key import APIs take word32 lengths. Reject every parsed * component before any conversion can truncate it. */ @@ -2165,7 +2219,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) (wolfCose_LenFitsWord32(dLen) == 0))) { ret = WOLFCOSE_E_COSE_BAD_HDR; } -#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE if ((ret == WOLFCOSE_SUCCESS) && ((wolfCose_LenFitsWord32(qLen) == 0) || (wolfCose_LenFitsWord32(dpLen) == 0) || @@ -2174,6 +2228,48 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = WOLFCOSE_E_COSE_BAD_HDR; } #endif +#endif + +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE + /* RSA private values have widths derived from the modulus. Validate + * them before importing into a backend that may not reject an + * oversized fixed-width export safely. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_RSA) && (yData != NULL)) { + size_t rsaHalfLen = nLen - (nLen / 2u); + + if ((yLen > nLen) || (dLen > rsaHalfLen) || + (qLen > rsaHalfLen) || (dpLen > rsaHalfLen) || + (dqLen > rsaHalfLen) || (qiLen > rsaHalfLen)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + } +#endif + +#ifdef WOLFCOSE_HAVE_RSAPSS + /* Do not silently treat a private or incomplete RSA key as public. + * RFC 8230 requires every non-multiprime private-key field. */ +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_RSA) && + ((key->attachedType == WOLFCOSE_ATT_NONE) || + (key->attachedType == WOLFCOSE_ATT_RSA)) && + ((yData != NULL) || (dData != NULL) || (qData != NULL) || + (dpData != NULL) || (dqData != NULL) || (qiData != NULL))) { + if ((yData == NULL) || (dData == NULL) || (qData == NULL) || + (dpData == NULL) || (dqData == NULL) || (qiData == NULL)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + } +#else + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_RSA) && + ((key->attachedType == WOLFCOSE_ATT_NONE) || + (key->attachedType == WOLFCOSE_ATT_RSA)) && + ((yData != NULL) || (dData != NULL))) { + ret = WOLFCOSE_E_UNSUPPORTED; + } +#endif #endif /* An EC2 key must contain either a complete public point or a private @@ -2371,7 +2467,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) if ((nData == NULL) || (xData == NULL)) { ret = WOLFCOSE_E_COSE_BAD_HDR; } -#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE else if ((yData != NULL) && (dData != NULL) && (qData != NULL) && (qiData != NULL)) { /* dP/dQ (when present) are forwarded as-is; an imported @@ -2392,7 +2488,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) key->hasPrivate = 1u; } } -#endif /* WOLFCOSE_HAVE_RSA_PRIVATE_KEY */ +#endif /* WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE */ else { INJECT_FAILURE(WOLF_FAIL_RSA_PUBLIC_DECODE, -1, ret = wc_RsaPublicKeyDecodeRaw(nData, (word32)nLen, diff --git a/tests/test_cose.c b/tests/test_cose.c index 2a4743d..50c94f7 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -20394,6 +20394,23 @@ static void test_cose_key_decode_symmetric_missing_k(void) "CoseKey_Decode rejects empty symmetric k"); } +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE +static void test_cose_key_decode_rsa_private_component_type(void) +{ + WOLFCOSE_KEY key; + int ret; + /* {1: 3, -5: 0}: RSA q must be a byte string. */ + uint8_t nonBstrQ[] = {0xA2u, 0x01u, 0x03u, 0x24u, 0x00u}; + + TEST_LOG(" [CoseKey_Decode RSA private component type]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, nonBstrQ, sizeof(nonBstrQ)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "CoseKey_Decode rejects non-bstr RSA q"); +} +#endif + #if defined(WOLFCOSE_HAVE_ES256) static void test_cose_key_decode_ec2_short_coord(void) { @@ -25869,6 +25886,9 @@ int test_cose(void) test_cose_key_decode_trailing_bytes(); test_cose_key_decode_no_material_on_failure(); test_cose_key_decode_symmetric_missing_k(); +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY_DECODE + test_cose_key_decode_rsa_private_component_type(); +#endif #if defined(WOLFCOSE_HAVE_ES256) test_cose_key_decode_ec2_short_coord(); #endif