From 6c726468300b4a462ff986e78989337a3252f2f7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 28 Jul 2026 14:58:49 -0700 Subject: [PATCH 1/2] Add wc_CoseSign1_Sign_ex with an untagged output option --- docs/API-Reference.md | 35 ++++++++++++++ include/wolfcose/wolfcose.h | 22 +++++++++ src/wolfcose.c | 86 +++++++++++++++++++++++----------- tests/test_cose.c | 92 +++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 27 deletions(-) diff --git a/docs/API-Reference.md b/docs/API-Reference.md index a88d8c8..cb37f71 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -374,6 +374,41 @@ Create a COSE_Sign1 message (single signer). --- +### wc_CoseSign1_Sign_ex + +```c +int wc_CoseSign1_Sign_ex( + WOLFCOSE_KEY* key, + int32_t alg, + const uint8_t* kid, size_t kidLen, + const uint8_t* payload, size_t payloadLen, + const uint8_t* detachedPayload, size_t detachedPayloadLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen, + WC_RNG* rng, uint32_t flags +); +``` + +As `wc_CoseSign1_Sign()`, plus output options. `wc_CoseSign1_Sign()` is this +function called with `flags = 0`. + +**Flags:** +| Flag | Effect | +|------|--------| +| `WOLFCOSE_SIGN1_UNTAGGED` | Omit the CBOR tag 18 prefix, so output starts with the 4-element array (`0x84`). Verification accepts either form. | + +The tag is not covered by the signature. `Sig_structure` is +`["Signature1", protected, external_aad, payload]` (RFC 9052 Section 4.4), so the +tagged and untagged forms of the same message carry an identical signature and +either can be converted to the other without detection. That is a property of +COSE, not of this flag, but a caller emitting untagged output should establish +the message type out of band. + +**Returns:** `WOLFCOSE_SUCCESS` or error code + +--- + ### wc_CoseSign1_Verify ```c diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 5688c06..57492de 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -122,6 +122,9 @@ extern "C" { /* ----- COSE constants (RFC 9052) ----- */ +/* Output options for wc_CoseSign1_Sign_ex() */ +#define WOLFCOSE_SIGN1_UNTAGGED 0x0001u /* Omit the tag 18 prefix */ + /* Tags (RFC 9052) */ #define WOLFCOSE_TAG_SIGN1 18u #define WOLFCOSE_TAG_ENCRYPT0 16u @@ -759,6 +762,25 @@ WOLFCOSE_API int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, uint8_t* scratch, size_t scratchSz, uint8_t* out, size_t outSz, size_t* outLen, WC_RNG* rng); + +/** + * \brief Sign a payload producing a COSE_Sign1 message, with output options. + * + * Identical to wc_CoseSign1_Sign() with the addition of \p flags, which is a + * bitmask of WOLFCOSE_SIGN1_* values. Passing 0 is equivalent to calling + * wc_CoseSign1_Sign(). + * + * \param flags Bitmask of WOLFCOSE_SIGN1_* output options. + * \return WOLFCOSE_SUCCESS or negative error code. + */ +WOLFCOSE_API int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, + const uint8_t* kid, size_t kidLen, + const uint8_t* payload, size_t payloadLen, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen, + WC_RNG* rng, uint32_t flags); #endif /* WOLFCOSE_SIGN1_SIGN */ #if defined(WOLFCOSE_SIGN1_VERIFY) diff --git a/src/wolfcose.c b/src/wolfcose.c index 97b7e94..7d9ddc8 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -210,6 +210,32 @@ int wolfCose_AlgToHashType(int32_t alg, enum wc_HashType* hashType) return ret; } +/* Guarded to match its only call sites, which are all in Sign1, so a build + * without them does not carry an unused function. */ +#if (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY)) && \ + defined(WOLFCOSE_HAVE_ECDSA) +/* Each ECDSA alg is bound to one curve. Shared so sign and verify cannot + * disagree about which pairings are legal. */ +static int wolfCose_EccAlgCrv(int32_t alg, int32_t* crv) +{ + int ret = WOLFCOSE_SUCCESS; + + if (alg == WOLFCOSE_ALG_ES256) { + *crv = WOLFCOSE_CRV_P256; + } + else if (alg == WOLFCOSE_ALG_ES384) { + *crv = WOLFCOSE_CRV_P384; + } + else if (alg == WOLFCOSE_ALG_ES512) { + *crv = WOLFCOSE_CRV_P521; + } + else { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + return ret; +} +#endif + WOLFCOSE_LOCAL int wolfCose_SigSize(int32_t alg, size_t* sigSz) { int ret = WOLFCOSE_SUCCESS; @@ -3965,6 +3991,21 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, uint8_t* scratch, size_t scratchSz, uint8_t* out, size_t outSz, size_t* outLen, WC_RNG* rng) +{ + return wc_CoseSign1_Sign_ex(key, alg, kid, kidLen, payload, payloadLen, + detachedPayload, detachedLen, extAad, + extAadLen, scratch, scratchSz, out, outSz, + outLen, rng, 0); +} + +int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, + const uint8_t* kid, size_t kidLen, + const uint8_t* payload, size_t payloadLen, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen, + WC_RNG* rng, uint32_t flags) { int ret = WOLFCOSE_SUCCESS; uint8_t protectedBuf[WOLFCOSE_PROTECTED_HDR_MAX]; @@ -4008,6 +4049,12 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, ret = WOLFCOSE_E_INVALID_ARG; } #endif + /* Reject bits this build does not define rather than ignoring them, so a + * caller compiled against a newer header fails loudly. */ + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & ~(uint32_t)WOLFCOSE_SIGN1_UNTAGGED) != 0u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } #ifdef WOLFCOSE_CHECK_WORD32_LEN if ((ret == WOLFCOSE_SUCCESS) && ((wolfCose_LenFitsWord32(payloadLen) == 0) || @@ -4150,19 +4197,10 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, ret = WOLFCOSE_E_COSE_KEY_TYPE; } - /* Each ECDSA alg is bound to one curve. */ if (ret == WOLFCOSE_SUCCESS) { - int32_t expectedCrv; - if (alg == WOLFCOSE_ALG_ES256) { - expectedCrv = WOLFCOSE_CRV_P256; - } - else if (alg == WOLFCOSE_ALG_ES384) { - expectedCrv = WOLFCOSE_CRV_P384; - } - else { - expectedCrv = WOLFCOSE_CRV_P521; - } - if (key->crv != expectedCrv) { + int32_t expectedCrv = 0; + ret = wolfCose_EccAlgCrv(alg, &expectedCrv); + if ((ret == WOLFCOSE_SUCCESS) && (key->crv != expectedCrv)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } } @@ -4309,11 +4347,13 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, * Tag(18) [protected_bstr, unprotected_map, payload_bstr, signature_bstr] */ outCtx.buf = out; + outCtx.cbuf = NULL; outCtx.bufSz = outSz; outCtx.idx = 0; /* Encode COSE_Sign1 output */ - if (ret == WOLFCOSE_SUCCESS) { + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & WOLFCOSE_SIGN1_UNTAGGED) == 0u)) { ret = wc_CBOR_EncodeTag(&outCtx, WOLFCOSE_TAG_SIGN1); } @@ -4351,8 +4391,9 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, ret = wc_CBOR_EncodeBstr(&outCtx, sigPtr, sigSz); } - if ((ret == WOLFCOSE_SUCCESS) && (outLen != NULL)) { - *outLen = outCtx.idx; + if (outLen != NULL) { + /* Report 0 rather than leaving a stale length behind on failure. */ + *outLen = (ret == WOLFCOSE_SUCCESS) ? outCtx.idx : 0u; } /* Cleanup: always executed */ @@ -4577,19 +4618,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if (key->kty != WOLFCOSE_KTY_EC2) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } - /* Each ECDSA alg is bound to one curve. */ if (ret == WOLFCOSE_SUCCESS) { - int32_t expectedCrv; - if (alg == WOLFCOSE_ALG_ES256) { - expectedCrv = WOLFCOSE_CRV_P256; - } - else if (alg == WOLFCOSE_ALG_ES384) { - expectedCrv = WOLFCOSE_CRV_P384; - } - else { - expectedCrv = WOLFCOSE_CRV_P521; - } - if (key->crv != expectedCrv) { + int32_t expectedCrv = 0; + ret = wolfCose_EccAlgCrv(alg, &expectedCrv); + if ((ret == WOLFCOSE_SUCCESS) && (key->crv != expectedCrv)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } } diff --git a/tests/test_cose.c b/tests/test_cose.c index 0c6939e..ed065fb 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -18441,12 +18441,104 @@ static void test_sign_verify_bad_array_count(void) #endif /* ----- Entry point ----- */ +#if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) +static void test_cose_sign1_untagged(void) +{ + static const uint8_t payload[] = "untagged payload"; + WC_RNG rng; + ecc_key eccKey; + WOLFCOSE_KEY signKey; + uint8_t scratch[512]; + uint8_t tagged[512]; + uint8_t untagged[512]; + size_t taggedLen = 0; + size_t untaggedLen = 0; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0; + WOLFCOSE_HDR hdr; + int ret; + + TEST_LOG(" [Sign1 untagged output]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "untagged rng init"); + if (ret != 0) { + return; + } + ret = wc_ecc_init(&eccKey); + TEST_ASSERT(ret == 0, "untagged ecc init"); + if (ret != 0) { + /* eccKey was never initialized, so it must not be freed below. */ + wc_FreeRng(&rng); + return; + } + ret = wc_ecc_make_key(&rng, 32, &eccKey); + TEST_ASSERT(ret == 0, "untagged ecc keygen"); + if (ret != 0) { + wc_ecc_free(&eccKey); + wc_FreeRng(&rng); + return; + } + ret = wc_CoseKey_Init(&signKey); + TEST_ASSERT(ret == 0, "untagged key init"); + if (ret != 0) { + wc_ecc_free(&eccKey); + wc_FreeRng(&rng); + return; + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&signKey, WOLFCOSE_CRV_P256, &eccKey); + TEST_ASSERT(ret == 0, "untagged key set ecc"); + } + + if (ret == 0) { + ret = wc_CoseSign1_Sign_ex(&signKey, WOLFCOSE_ALG_ES256, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), + tagged, sizeof(tagged), &taggedLen, &rng, 0); + TEST_ASSERT(ret == 0, "flags=0 signs"); + TEST_ASSERT(taggedLen > 0u && tagged[0] == 0xD2u, + "flags=0 emits tag 18 (0xD2)"); + } + + if (ret == 0) { + ret = wc_CoseSign1_Sign_ex(&signKey, WOLFCOSE_ALG_ES256, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), + untagged, sizeof(untagged), &untaggedLen, &rng, + WOLFCOSE_SIGN1_UNTAGGED); + TEST_ASSERT(ret == 0, "untagged signs"); + TEST_ASSERT(untaggedLen > 0u && untagged[0] == 0x84u, + "untagged starts with 4-array (0x84)"); + TEST_ASSERT(untaggedLen == (taggedLen - 1u), + "untagged is exactly one byte shorter"); + } + + /* The verify path treats the tag as optional, so both forms round-trip. */ + if (ret == 0) { + ret = wc_CoseSign1_Verify(&signKey, untagged, untaggedLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == 0, "untagged verifies"); + TEST_ASSERT(decPayloadLen == (sizeof(payload) - 1), + "untagged payload length round-trips"); + } + + wc_CoseKey_Free(&signKey); + wc_ecc_free(&eccKey); + wc_FreeRng(&rng); +} +#endif /* WOLFCOSE_HAVE_ES256 */ + int test_cose(void) { g_failures = 0; /* Internal helper tests */ test_wolfcose_force_zero(); +#if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) + test_cose_sign1_untagged(); +#endif /* Key tests */ test_cose_key_init(); From c96270786043244f8a001595c5d59e79772eec72 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 3 Aug 2026 12:03:37 -0700 Subject: [PATCH 2/2] Add exact COSE Sign1 size query --- ChangeLog.md | 3 + docs/API-Reference.md | 43 +++-- include/wolfcose/wolfcose.h | 24 ++- src/wolfcose.c | 255 ++++++++++++++++++++++------- tests/test_cose.c | 311 +++++++++++++++++++++++++----------- 5 files changed, 475 insertions(+), 161 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3cbf757..8523a6c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,9 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures ## New Feature Additions +* `wc_CoseSign1_Sign_ex()` can emit untagged COSE_Sign1 messages, and + `wc_CoseSign1_SignSize_ex()` reports their exact encoded size without + signing or invoking an external signer. * CBOR engine implementing RFC 8949 encode/decode with no external dependency, enforcing deterministic/preferred-encoding rules and rejecting non-preferred or trailing input on decode. diff --git a/docs/API-Reference.md b/docs/API-Reference.md index cb37f71..d606787 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -390,20 +390,41 @@ int wc_CoseSign1_Sign_ex( ); ``` -As `wc_CoseSign1_Sign()`, plus output options. `wc_CoseSign1_Sign()` is this -function called with `flags = 0`. +As `wc_CoseSign1_Sign()`, plus output options. Passing `flags = 0` is +equivalent to calling `wc_CoseSign1_Sign()`. -**Flags:** | Flag | Effect | |------|--------| -| `WOLFCOSE_SIGN1_UNTAGGED` | Omit the CBOR tag 18 prefix, so output starts with the 4-element array (`0x84`). Verification accepts either form. | - -The tag is not covered by the signature. `Sig_structure` is -`["Signature1", protected, external_aad, payload]` (RFC 9052 Section 4.4), so the -tagged and untagged forms of the same message carry an identical signature and -either can be converted to the other without detection. That is a property of -COSE, not of this flag, but a caller emitting untagged output should establish -the message type out of band. +| `WOLFCOSE_SIGN1_UNTAGGED` | Omit the CBOR tag 18 prefix, so output starts with the four-element array (`0x84`). | + +The tag is not covered by the signature. A caller emitting untagged output +must establish the message type out of band. + +**Returns:** `WOLFCOSE_SUCCESS` or error code + +--- + +### wc_CoseSign1_SignSize_ex + +```c +int wc_CoseSign1_SignSize_ex( + const WOLFCOSE_KEY* key, + int32_t alg, + size_t kidLen, + size_t payloadLen, + size_t detachedLen, + uint32_t flags, + size_t* outLen +); +``` + +Computes the exact encoded size that `wc_CoseSign1_Sign_ex()` would produce +without signing. It does not use an RNG, private key operation, or external +signer callback. The data itself is not required because only `kidLen`, +`payloadLen`, and `detachedLen` affect the framing. + +`key` may be `NULL` when the algorithm fixes the signature length. It is +required for RSA-PSS and for EdDSA when both Ed25519 and Ed448 are enabled. **Returns:** `WOLFCOSE_SUCCESS` or error code diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 57492de..a2249e8 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -122,7 +122,8 @@ extern "C" { /* ----- COSE constants (RFC 9052) ----- */ -/* Output options for wc_CoseSign1_Sign_ex() */ +/* Output options for wc_CoseSign1_Sign_ex() and + * wc_CoseSign1_SignSize_ex(). */ #define WOLFCOSE_SIGN1_UNTAGGED 0x0001u /* Omit the tag 18 prefix */ /* Tags (RFC 9052) */ @@ -781,6 +782,27 @@ WOLFCOSE_API int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, uint8_t* scratch, size_t scratchSz, uint8_t* out, size_t outSz, size_t* outLen, WC_RNG* rng, uint32_t flags); + +/** + * \brief Compute the exact encoded size wc_CoseSign1_Sign_ex() would produce. + * + * This function does not sign, use an RNG, or invoke an external signer. + * Only the lengths of the key identifier and payload affect framing. + * + * \param key Key whose type determines the signature length. May be + * NULL when \p alg determines the exact length. Required + * for RSA-PSS and when both Ed25519 and Ed448 are enabled. + * \param alg Algorithm identifier (WOLFCOSE_ALG_ES256, etc). + * \param kidLen Key ID length (0 if none). + * \param payloadLen Attached payload length (0 if detached). + * \param detachedLen Detached payload length (0 if attached). + * \param flags WOLFCOSE_SIGN1_* output options. + * \param outLen Output: exact encoded size in bytes. + * \return WOLFCOSE_SUCCESS or negative error code. + */ +WOLFCOSE_API int wc_CoseSign1_SignSize_ex(const WOLFCOSE_KEY* key, + int32_t alg, size_t kidLen, size_t payloadLen, size_t detachedLen, + uint32_t flags, size_t* outLen); #endif /* WOLFCOSE_SIGN1_SIGN */ #if defined(WOLFCOSE_SIGN1_VERIFY) diff --git a/src/wolfcose.c b/src/wolfcose.c index 7d9ddc8..67bbd11 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -210,32 +210,6 @@ int wolfCose_AlgToHashType(int32_t alg, enum wc_HashType* hashType) return ret; } -/* Guarded to match its only call sites, which are all in Sign1, so a build - * without them does not carry an unused function. */ -#if (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY)) && \ - defined(WOLFCOSE_HAVE_ECDSA) -/* Each ECDSA alg is bound to one curve. Shared so sign and verify cannot - * disagree about which pairings are legal. */ -static int wolfCose_EccAlgCrv(int32_t alg, int32_t* crv) -{ - int ret = WOLFCOSE_SUCCESS; - - if (alg == WOLFCOSE_ALG_ES256) { - *crv = WOLFCOSE_CRV_P256; - } - else if (alg == WOLFCOSE_ALG_ES384) { - *crv = WOLFCOSE_CRV_P384; - } - else if (alg == WOLFCOSE_ALG_ES512) { - *crv = WOLFCOSE_CRV_P521; - } - else { - ret = WOLFCOSE_E_COSE_BAD_ALG; - } - return ret; -} -#endif - WOLFCOSE_LOCAL int wolfCose_SigSize(int32_t alg, size_t* sigSz) { int ret = WOLFCOSE_SUCCESS; @@ -3699,16 +3673,12 @@ static int wolfCose_MlDsaCheckKey(const WOLFCOSE_KEY* key, int32_t alg) } #endif /* WOLFCOSE_HAVE_MLDSA */ -/* Delegated signing is reachable from COSE_Sign1 and COSE_Sign alike, - * so these live outside the WOLFCOSE_SIGN1 region below. */ -#if defined(WOLFCOSE_EXT_SIGN) -/* Reject algorithms this build lacks; report whether alg pre-hashes. */ +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_EXT_SIGN) /* Exact signature length for this key and algorithm. wolfCose_SigSize() alone - * cannot serve the delegated path: it reports EdDSA's worst case rather than - * the key's curve, and has no RSA case at all, so a guard built on it silently - * skips both. Fails closed when the length cannot be determined. */ -static int wolfCose_ExtSignSigLen(const WOLFCOSE_KEY* key, int32_t alg, - size_t* expSigLen) + * reports EdDSA's worst case rather than the key's curve, and has no RSA case. + * Fails closed when the exact length cannot be determined. */ +static int wolfCose_SignSigLen(const WOLFCOSE_KEY* key, int32_t alg, + size_t* expSigLen) { int ret; @@ -3717,7 +3687,18 @@ static int wolfCose_ExtSignSigLen(const WOLFCOSE_KEY* key, int32_t alg, switch (alg) { #if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448) case WOLFCOSE_ALG_EDDSA: - if (key->kty != WOLFCOSE_KTY_OKP) { + if (key == NULL) { +#if defined(WOLFCOSE_HAVE_EDDSA) && defined(WOLFCOSE_HAVE_ED448) + ret = WOLFCOSE_E_INVALID_ARG; +#elif defined(WOLFCOSE_HAVE_EDDSA) + *expSigLen = 64; + ret = WOLFCOSE_SUCCESS; +#else + *expSigLen = 114; + ret = WOLFCOSE_SUCCESS; +#endif + } + else if (key->kty != WOLFCOSE_KTY_OKP) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } #ifdef WOLFCOSE_HAVE_EDDSA @@ -3748,10 +3729,15 @@ static int wolfCose_ExtSignSigLen(const WOLFCOSE_KEY* key, int32_t alg, case WOLFCOSE_ALG_PS512: #endif { - if ((key->kty != WOLFCOSE_KTY_RSA) || + if ((key == NULL) || (key->kty != WOLFCOSE_KTY_RSA) || (key->attachedType != WOLFCOSE_ATT_RSA) || (key->key.rsa == NULL)) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; + if (key == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } } else { int modSz = wc_RsaEncryptSize(key->key.rsa); @@ -3790,7 +3776,7 @@ static int wolfCose_ExtSignSigLen(const WOLFCOSE_KEY* key, int32_t alg, /* expectedCrv stays 0 for non-ECDSA, which this arm does not * bind. A declared kty or crv is honoured for ES* the way the * local path does; 0 means the caller declared none. */ - if (expectedCrv != 0) { + if ((key != NULL) && (expectedCrv != 0)) { if ((key->kty != 0) && (key->kty != WOLFCOSE_KTY_EC2)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } @@ -3807,7 +3793,12 @@ static int wolfCose_ExtSignSigLen(const WOLFCOSE_KEY* key, int32_t alg, } return ret; } +#endif +/* Delegated signing is reachable from COSE_Sign1 and COSE_Sign alike, + * so these live outside the WOLFCOSE_SIGN1 region below. */ +#if defined(WOLFCOSE_EXT_SIGN) +/* Reject algorithms this build lacks; report whether alg pre-hashes. */ static int wolfCose_ExtSignAlg(int32_t alg, int* preHashes) { int ret = WOLFCOSE_SUCCESS; @@ -3914,7 +3905,7 @@ int wolfCose_ExtSign(const WOLFCOSE_KEY* key, int32_t alg, * accepts has a determinable length, so an error here is fatal rather * than a reason to skip the check. */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_ExtSignSigLen(key, alg, &expSigLen); + ret = wolfCose_SignSigLen(key, alg, &expSigLen); if ((ret == WOLFCOSE_SUCCESS) && (sigSz < expSigLen)) { ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } @@ -3983,6 +3974,147 @@ static int wolfCose_BuildSigStructure(const uint8_t* protectedHdr, #if defined(WOLFCOSE_SIGN1_SIGN) +static int wolfCose_SizeAdd(size_t* total, size_t add) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((total == NULL) || (add > ((size_t)-1 - *total))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *total += add; + } + return ret; +} + +static size_t wolfCose_CborHeadSize(uint64_t val) +{ + size_t len; + + if (val <= 23u) { + len = 1u; + } + else if (val <= 0xFFu) { + len = 2u; + } + else if (val <= 0xFFFFu) { + len = 3u; + } + else if (val <= 0xFFFFFFFFu) { + len = 5u; + } + else { + len = 9u; + } + return len; +} + +static int wolfCose_CborStringSize(size_t len, size_t* encodedLen) +{ + size_t total = wolfCose_CborHeadSize((uint64_t)len); + int ret; + + ret = wolfCose_SizeAdd(&total, len); + if (ret == WOLFCOSE_SUCCESS) { + *encodedLen = total; + } + return ret; +} + +int wc_CoseSign1_SignSize_ex(const WOLFCOSE_KEY* key, int32_t alg, + size_t kidLen, size_t payloadLen, size_t detachedLen, + uint32_t flags, size_t* outLen) +{ + uint8_t protectedBuf[WOLFCOSE_PROTECTED_HDR_MAX]; + size_t protectedLen = 0u; + size_t sigLen = 0u; + size_t itemLen = 0u; + size_t total = 0u; + int ret = WOLFCOSE_SUCCESS; + + if (outLen == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *outLen = 0u; + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & ~(uint32_t)WOLFCOSE_SIGN1_UNTAGGED) != 0u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if ((ret == WOLFCOSE_SUCCESS) && (payloadLen != 0u) && + (detachedLen != 0u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#ifdef WOLFCOSE_CHECK_WORD32_LEN + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_LenFitsWord32(payloadLen) == 0) || + (wolfCose_LenFitsWord32(detachedLen) == 0))) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && (key != NULL) && + (key->alg != WOLFCOSE_ALG_UNSET) && (key->alg != alg)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SignSigLen(key, alg, &sigLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EncodeProtectedHdr(alg, protectedBuf, + sizeof(protectedBuf), + &protectedLen); + } + + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & WOLFCOSE_SIGN1_UNTAGGED) == 0u)) { + ret = wolfCose_SizeAdd(&total, 1u); /* tag 18 */ + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, 1u); /* array(4) */ + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CborStringSize(protectedLen, &itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, 1u); /* map(0) or map(1) */ + } + if ((ret == WOLFCOSE_SUCCESS) && (kidLen != 0u)) { + ret = wolfCose_SizeAdd(&total, 1u); /* kid label 4 */ + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CborStringSize(kidLen, &itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, itemLen); + } + } + if (ret == WOLFCOSE_SUCCESS) { + if (detachedLen != 0u) { + ret = wolfCose_SizeAdd(&total, 1u); /* null payload */ + } + else { + ret = wolfCose_CborStringSize(payloadLen, &itemLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, itemLen); + } + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CborStringSize(sigLen, &itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = total; + } + + return ret; +} + int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, const uint8_t* kid, size_t kidLen, const uint8_t* payload, size_t payloadLen, @@ -3995,7 +4127,7 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, return wc_CoseSign1_Sign_ex(key, alg, kid, kidLen, payload, payloadLen, detachedPayload, detachedLen, extAad, extAadLen, scratch, scratchSz, out, outSz, - outLen, rng, 0); + outLen, rng, 0u); } int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, @@ -4049,8 +4181,6 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, ret = WOLFCOSE_E_INVALID_ARG; } #endif - /* Reject bits this build does not define rather than ignoring them, so a - * caller compiled against a newer header fails loudly. */ if ((ret == WOLFCOSE_SUCCESS) && ((flags & ~(uint32_t)WOLFCOSE_SIGN1_UNTAGGED) != 0u)) { ret = WOLFCOSE_E_INVALID_ARG; @@ -4197,10 +4327,19 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, ret = WOLFCOSE_E_COSE_KEY_TYPE; } + /* Each ECDSA alg is bound to one curve. */ if (ret == WOLFCOSE_SUCCESS) { - int32_t expectedCrv = 0; - ret = wolfCose_EccAlgCrv(alg, &expectedCrv); - if ((ret == WOLFCOSE_SUCCESS) && (key->crv != expectedCrv)) { + int32_t expectedCrv; + if (alg == WOLFCOSE_ALG_ES256) { + expectedCrv = WOLFCOSE_CRV_P256; + } + else if (alg == WOLFCOSE_ALG_ES384) { + expectedCrv = WOLFCOSE_CRV_P384; + } + else { + expectedCrv = WOLFCOSE_CRV_P521; + } + if (key->crv != expectedCrv) { ret = WOLFCOSE_E_COSE_BAD_ALG; } } @@ -4343,9 +4482,7 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, /* No action required */ } - /* Encode COSE_Sign1 output: - * Tag(18) [protected_bstr, unprotected_map, payload_bstr, signature_bstr] - */ + /* Encode an optional tag 18 followed by the four Sign1 fields. */ outCtx.buf = out; outCtx.cbuf = NULL; outCtx.bufSz = outSz; @@ -4391,9 +4528,8 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, ret = wc_CBOR_EncodeBstr(&outCtx, sigPtr, sigSz); } - if (outLen != NULL) { - /* Report 0 rather than leaving a stale length behind on failure. */ - *outLen = (ret == WOLFCOSE_SUCCESS) ? outCtx.idx : 0u; + if ((ret == WOLFCOSE_SUCCESS) && (outLen != NULL)) { + *outLen = outCtx.idx; } /* Cleanup: always executed */ @@ -4618,10 +4754,19 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if (key->kty != WOLFCOSE_KTY_EC2) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } + /* Each ECDSA alg is bound to one curve. */ if (ret == WOLFCOSE_SUCCESS) { - int32_t expectedCrv = 0; - ret = wolfCose_EccAlgCrv(alg, &expectedCrv); - if ((ret == WOLFCOSE_SUCCESS) && (key->crv != expectedCrv)) { + int32_t expectedCrv; + if (alg == WOLFCOSE_ALG_ES256) { + expectedCrv = WOLFCOSE_CRV_P256; + } + else if (alg == WOLFCOSE_ALG_ES384) { + expectedCrv = WOLFCOSE_CRV_P384; + } + else { + expectedCrv = WOLFCOSE_CRV_P521; + } + if (key->crv != expectedCrv) { ret = WOLFCOSE_E_COSE_BAD_ALG; } } diff --git a/tests/test_cose.c b/tests/test_cose.c index ed065fb..d41521e 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -399,6 +399,7 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; uint8_t out[512]; size_t outLen = 0; + size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; @@ -429,6 +430,12 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, TEST_ASSERT(ret == 0, "sign key set ecc"); } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(&signKey, alg, + sizeof(kid) - 1u, sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 ecc size"); + } + /* Sign */ if (ret == 0) { ret = wc_CoseSign1_Sign(&signKey, alg, @@ -439,6 +446,7 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, scratch, sizeof(scratch), out, sizeof(out), &outLen, &rng); TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ecc sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 ecc exact size"); } } @@ -1588,6 +1596,7 @@ static void test_cose_sign1_eddsa(void) uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; uint8_t out[512]; size_t outLen = 0; + size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; @@ -1611,15 +1620,31 @@ static void test_cose_sign1_eddsa(void) (void)wc_CoseKey_Init(&signKey); (void)wc_CoseKey_SetEd25519(&signKey, &edKey); +#if defined(WOLFCOSE_HAVE_EDDSA) && defined(WOLFCOSE_HAVE_ED448) + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_EDDSA, + 0u, sizeof(payload) - 1u, 0u, 0u, &sizedLen) == + WOLFCOSE_E_INVALID_ARG, "sign1 eddsa ambiguous size needs key"); +#else + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_EDDSA, + 0u, sizeof(payload) - 1u, 0u, 0u, &sizedLen) == 0, + "sign1 eddsa size without key"); +#endif + ret = wc_CoseSign1_SignSize_ex(&signKey, WOLFCOSE_ALG_EDDSA, + 0u, sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 eddsa size"); + /* Sign */ - ret = wc_CoseSign1_Sign(&signKey, WOLFCOSE_ALG_EDDSA, - NULL, 0, - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen, &rng); - TEST_ASSERT(ret == 0 && outLen > 0, "sign1 eddsa sign"); + if (ret == 0) { + ret = wc_CoseSign1_Sign(&signKey, WOLFCOSE_ALG_EDDSA, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == 0 && outLen > 0, "sign1 eddsa sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 eddsa exact size"); + } } if (ret == 0) { @@ -1675,6 +1700,7 @@ static void test_cose_sign1_ed448(void) uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; uint8_t out[512]; size_t outLen = 0; + size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; @@ -1698,15 +1724,22 @@ static void test_cose_sign1_ed448(void) (void)wc_CoseKey_Init(&signKey); (void)wc_CoseKey_SetEd448(&signKey, &edKey); + ret = wc_CoseSign1_SignSize_ex(&signKey, WOLFCOSE_ALG_EDDSA, + 0u, sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 ed448 size"); + /* Sign */ - ret = wc_CoseSign1_Sign(&signKey, WOLFCOSE_ALG_EDDSA, - NULL, 0, - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen, &rng); - TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ed448 sign"); + if (ret == 0) { + ret = wc_CoseSign1_Sign(&signKey, WOLFCOSE_ALG_EDDSA, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ed448 sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 ed448 exact size"); + } } if (ret == 0) { @@ -2452,6 +2485,7 @@ static void test_cose_sign1_pss(const char* label, int32_t alg) uint8_t scratch[1024]; uint8_t out[1024]; size_t outLen = 0; + size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; @@ -2482,15 +2516,25 @@ static void test_cose_sign1_pss(const char* label, int32_t alg) (void)wc_CoseKey_Init(&signKey); (void)wc_CoseKey_SetRsa(&signKey, &rsaKey); + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, alg, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen) == + WOLFCOSE_E_INVALID_ARG, "sign1 pss size needs key"); + ret = wc_CoseSign1_SignSize_ex(&signKey, alg, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 pss size"); + /* Sign */ - ret = wc_CoseSign1_Sign(&signKey, alg, - NULL, 0, - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen, &rng); - TEST_ASSERT(ret == 0 && outLen > 0, "sign1 pss sign"); + if (ret == 0) { + ret = wc_CoseSign1_Sign(&signKey, alg, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == 0 && outLen > 0, "sign1 pss sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 pss exact size"); + } } if (ret == 0) { @@ -2551,6 +2595,7 @@ static void test_cose_sign1_ml_dsa(const char* label, int32_t alg, byte level) uint8_t scratch[8192]; uint8_t out[8192]; size_t outLen = 0; + size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; @@ -2586,15 +2631,22 @@ static void test_cose_sign1_ml_dsa(const char* label, int32_t alg, byte level) (void)wc_CoseKey_Init(&signKey); (void)wc_CoseKey_SetMlDsa(&signKey, alg, &dlKey); + ret = wc_CoseSign1_SignSize_ex(NULL, alg, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 ml-dsa size"); + /* Sign */ - ret = wc_CoseSign1_Sign(&signKey, alg, - NULL, 0, - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen, &rng); - TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ml-dsa sign"); + if (ret == 0) { + ret = wc_CoseSign1_Sign(&signKey, alg, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ml-dsa sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 ml-dsa exact size"); + } } if (ret == 0) { @@ -18442,93 +18494,164 @@ static void test_sign_verify_bad_array_count(void) /* ----- Entry point ----- */ #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) -static void test_cose_sign1_untagged(void) +static void test_cose_sign1_size_and_untagged(void) { - static const uint8_t payload[] = "untagged payload"; - WC_RNG rng; - ecc_key eccKey; - WOLFCOSE_KEY signKey; + static const uint8_t kid[] = "size-key"; + static const size_t boundaryLen[] = { + 23u, 24u, 255u, 256u, 65535u, 65536u + }; + static const size_t payloadExpected[] = { + 97u, 99u, 330u, 332u, 65611u, 65614u + }; + static const size_t kidExpected[] = { + 99u, 101u, 332u, 334u, 65613u, 65616u + }; + uint8_t payload[32]; + uint8_t detached[48]; uint8_t scratch[512]; uint8_t tagged[512]; uint8_t untagged[512]; - size_t taggedLen = 0; - size_t untaggedLen = 0; - const uint8_t* decPayload = NULL; - size_t decPayloadLen = 0; + WOLFCOSE_KEY key; WOLFCOSE_HDR hdr; + ecc_key eccKey; + WC_RNG rng; + const uint8_t* decoded = NULL; + size_t decodedLen = 0u; + size_t taggedLen = 0u; + size_t untaggedLen = 0u; + size_t sizedLen = 0u; + size_t i; + int rngInited = 0; + int eccInited = 0; + int keyInited = 0; + int sizeRet; int ret; - TEST_LOG(" [Sign1 untagged output]\n"); + TEST_LOG(" [Sign1 size and untagged output]\n"); + memset(payload, 0xA5, sizeof(payload)); + memset(detached, 0x5A, sizeof(detached)); ret = wc_InitRng(&rng); - TEST_ASSERT(ret == 0, "untagged rng init"); - if (ret != 0) { - return; - } - ret = wc_ecc_init(&eccKey); - TEST_ASSERT(ret == 0, "untagged ecc init"); - if (ret != 0) { - /* eccKey was never initialized, so it must not be freed below. */ - wc_FreeRng(&rng); - return; + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&eccKey); } - ret = wc_ecc_make_key(&rng, 32, &eccKey); - TEST_ASSERT(ret == 0, "untagged ecc keygen"); - if (ret != 0) { - wc_ecc_free(&eccKey); - wc_FreeRng(&rng); - return; + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_make_key(&rng, 32, &eccKey); } - ret = wc_CoseKey_Init(&signKey); - TEST_ASSERT(ret == 0, "untagged key init"); - if (ret != 0) { - wc_ecc_free(&eccKey); - wc_FreeRng(&rng); - return; + if (ret == 0) { + ret = wc_CoseKey_Init(&key); } if (ret == 0) { - ret = wc_CoseKey_SetEcc(&signKey, WOLFCOSE_CRV_P256, &eccKey); - TEST_ASSERT(ret == 0, "untagged key set ecc"); + keyInited = 1; + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); } + TEST_ASSERT(ret == 0, "size test key setup"); - if (ret == 0) { - ret = wc_CoseSign1_Sign_ex(&signKey, WOLFCOSE_ALG_ES256, NULL, 0, - payload, sizeof(payload) - 1, NULL, 0, NULL, 0, - scratch, sizeof(scratch), - tagged, sizeof(tagged), &taggedLen, &rng, 0); - TEST_ASSERT(ret == 0, "flags=0 signs"); - TEST_ASSERT(taggedLen > 0u && tagged[0] == 0xD2u, - "flags=0 emits tag 18 (0xD2)"); + for (i = 0u; i < (sizeof(boundaryLen) / sizeof(boundaryLen[0])); i++) { + sizeRet = wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, 0u, + boundaryLen[i], 0u, 0u, &sizedLen); + TEST_ASSERT(sizeRet == 0 && sizedLen == payloadExpected[i], + "payload size boundary"); + sizeRet = wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + boundaryLen[i], 0u, 0u, 0u, &sizedLen); + TEST_ASSERT(sizeRet == 0 && sizedLen == kidExpected[i], + "kid size boundary"); } if (ret == 0) { - ret = wc_CoseSign1_Sign_ex(&signKey, WOLFCOSE_ALG_ES256, NULL, 0, - payload, sizeof(payload) - 1, NULL, 0, NULL, 0, - scratch, sizeof(scratch), + ret = wc_CoseSign1_Sign_ex(&key, WOLFCOSE_ALG_ES256, + kid, sizeof(kid) - 1u, payload, sizeof(payload), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tagged, sizeof(tagged), &taggedLen, &rng, 0u); + TEST_ASSERT(ret == 0, "tagged sign"); + } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + sizeof(kid) - 1u, sizeof(payload), 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0 && sizedLen == taggedLen, + "tagged size equals signed size"); + } + if (ret == 0) { + ret = wc_CoseSign1_Sign_ex(&key, WOLFCOSE_ALG_ES256, + kid, sizeof(kid) - 1u, payload, sizeof(payload), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), untagged, sizeof(untagged), &untaggedLen, &rng, WOLFCOSE_SIGN1_UNTAGGED); - TEST_ASSERT(ret == 0, "untagged signs"); - TEST_ASSERT(untaggedLen > 0u && untagged[0] == 0x84u, - "untagged starts with 4-array (0x84)"); - TEST_ASSERT(untaggedLen == (taggedLen - 1u), - "untagged is exactly one byte shorter"); + TEST_ASSERT(ret == 0 && untagged[0] == 0x84u, + "untagged starts with array(4)"); + } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + sizeof(kid) - 1u, sizeof(payload), 0u, + WOLFCOSE_SIGN1_UNTAGGED, &sizedLen); + TEST_ASSERT(ret == 0 && sizedLen == untaggedLen, + "untagged size equals signed size"); + TEST_ASSERT(untaggedLen + 1u == taggedLen, + "untagged omits exactly tag 18"); + } + if (ret == 0) { + ret = wc_CoseSign1_Verify(&key, untagged, untaggedLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &decoded, &decodedLen); + TEST_ASSERT(ret == 0 && decodedLen == sizeof(payload) && + memcmp(decoded, payload, sizeof(payload)) == 0, + "untagged output verifies"); + } + if (ret == 0) { + ret = wc_CoseSign1_Sign_ex(&key, WOLFCOSE_ALG_ES256, + NULL, 0u, NULL, 0u, detached, sizeof(detached), + NULL, 0u, scratch, sizeof(scratch), + tagged, sizeof(tagged), &taggedLen, &rng, 0u); + TEST_ASSERT(ret == 0, "detached sign"); } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + 0u, 0u, sizeof(detached), 0u, &sizedLen); + TEST_ASSERT(ret == 0 && sizedLen == taggedLen, + "detached size equals signed size"); + } + + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + 0u, 1u, 1u, 0u, &sizedLen) == WOLFCOSE_E_INVALID_ARG, + "attached and detached lengths rejected"); + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, WOLFCOSE_ALG_ES256, + 0u, 1u, 0u, 0x80000000u, &sizedLen) == WOLFCOSE_E_INVALID_ARG, + "unknown size flags rejected"); + TEST_ASSERT(wc_CoseSign1_SignSize_ex(NULL, 12345, + 0u, 1u, 0u, 0u, &sizedLen) == WOLFCOSE_E_COSE_BAD_ALG, + "unknown size algorithm rejected"); - /* The verify path treats the tag as optional, so both forms round-trip. */ +#if defined(WOLFCOSE_EXT_SIGN) if (ret == 0) { - ret = wc_CoseSign1_Verify(&signKey, untagged, untaggedLen, - NULL, 0, NULL, 0, scratch, sizeof(scratch), - &hdr, &decPayload, &decPayloadLen); - TEST_ASSERT(ret == 0, "untagged verifies"); - TEST_ASSERT(decPayloadLen == (sizeof(payload) - 1), - "untagged payload length round-trips"); + WOLFCOSE_KEY delegatedKey; + test_ext_ctx extCtx; + + memset(&extCtx, 0, sizeof(extCtx)); + (void)wc_CoseKey_Init(&delegatedKey); + delegatedKey.kty = WOLFCOSE_KTY_EC2; + delegatedKey.crv = WOLFCOSE_CRV_P256; + (void)wc_CoseKey_SetExtSigner(&delegatedKey, test_ext_sign_cb, &extCtx); + ret = wc_CoseSign1_SignSize_ex(&delegatedKey, + WOLFCOSE_ALG_ES256, 0u, sizeof(payload), 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0 && extCtx.called == 0, + "size query does not invoke delegated signer"); + wc_CoseKey_Free(&delegatedKey); } +#endif - wc_CoseKey_Free(&signKey); - wc_ecc_free(&eccKey); - wc_FreeRng(&rng); + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&eccKey); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } } -#endif /* WOLFCOSE_HAVE_ES256 */ +#endif int test_cose(void) { @@ -18537,7 +18660,7 @@ int test_cose(void) /* Internal helper tests */ test_wolfcose_force_zero(); #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) - test_cose_sign1_untagged(); + test_cose_sign1_size_and_untagged(); #endif /* Key tests */