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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/API-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions include/wolfcose/wolfcose.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -707,6 +710,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)
Expand Down
92 changes: 63 additions & 29 deletions src/wolfcose.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3576,6 +3602,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];
Expand Down Expand Up @@ -3603,10 +3644,18 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg,
isDetached = 0u;
}

if ((key == NULL) || (sigPayload == NULL) || (scratch == NULL) ||
(out == NULL) || (outLen == NULL) || (rng == NULL)) {
if ((key == NULL) || (sigPayload == NULL) || (out == NULL) ||
(scratch == NULL) || (outLen == NULL) || (rng == NULL)) {
ret = WOLFCOSE_E_INVALID_ARG;
}
/* Reject bits this build does not define rather than ignoring them, so a
* caller compiled against a newer header fails loudly. */
else if ((flags & ~(uint32_t)WOLFCOSE_SIGN1_UNTAGGED) != 0u) {
ret = WOLFCOSE_E_INVALID_ARG;
}
else {
/* No action required */
}
#ifdef WOLFCOSE_CHECK_WORD32_LEN
if ((ret == WOLFCOSE_SUCCESS) &&
((wolfCose_LenFitsWord32(payloadLen) == 0) ||
Expand Down Expand Up @@ -3720,19 +3769,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;
}
}
Expand Down Expand Up @@ -3879,11 +3919,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);
}

Expand Down Expand Up @@ -3921,8 +3963,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 */
Expand Down Expand Up @@ -4141,19 +4184,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;
}
}
Expand Down
92 changes: 92 additions & 0 deletions tests/test_cose.c
Original file line number Diff line number Diff line change
Expand Up @@ -17394,12 +17394,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();
Expand Down
Loading