Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b547096
F-5213/F-5251 - Reject trailing data after top-level COSE objects
aidangarske Jun 1, 2026
6ca0a88
F-5214/F-5282 - Reject oversized header and key integers before int32…
aidangarske Jun 1, 2026
6c3245f
F-5216/F-5283 - Harden CBOR encoder bounds and reject oversized conta…
aidangarske Jun 1, 2026
a4f5376
F-5304/F-5361 - Clear stale state and reject trailing data before COS…
aidangarske Jun 1, 2026
b87d16e
F-5215/F-5220/F-5366 - Require fixed-length EC2 coordinates per RFC 9053
aidangarske Jun 1, 2026
c76b5e0
F-5218/F-5288/F-5365 - Validate recipient and signer unprotected headers
aidangarske Jun 1, 2026
078bfb5
F-5221 - Reject multi-recipient ECDH-ES direct messages on decrypt
aidangarske Jun 1, 2026
e643270
F-5285 - Use zero-length protected header for direct COSE_Encrypt rec…
aidangarske Jun 1, 2026
66244ef
F-5367/F-5377 - Classify recipient key-management alg and enforce alg…
aidangarske Jun 1, 2026
6a54815
F-5284 - Decode kid in the protected header bucket
aidangarske Jun 1, 2026
a5247d2
F-5301 - Require ML-DSA key level to match the algorithm level
aidangarske Jun 1, 2026
2043c33
F-5289 - Enforce minimum HMAC key length for COSE MAC
aidangarske Jun 2, 2026
c36b58e
F-5231/F-5303 - Validate MAC payload arguments for Mac0 and Mac
aidangarske Jun 2, 2026
a249269
F-5291 - Clear header outputs on verify and decrypt failure
aidangarske Jun 2, 2026
96a81a8
F-5295 - Give wolfCose_BuildEncStructure internal linkage
aidangarske Jun 2, 2026
874d688
F-5237/F-5368/F-5372/F-5244 - Fix trivial MISRA required-rule violations
aidangarske Jun 2, 2026
582ba58
F-5235/F-5236/F-5239/F-5242/F-5243/F-5292/F-5293/F-5294/F-5370 - Docu…
aidangarske Jun 2, 2026
a27d1eb
F-5238/F-5240/F-5369 - Fix DEMO_ASSERT parens, const cast, shared tes…
aidangarske Jun 2, 2026
3126d0a
F-5299 - Document detached COSE_Encrypt unsupported and fix misleadin…
aidangarske Jun 2, 2026
bec1ad8
F-5228/F-5229/F-5230/F-5290 - Document AEAD nonce, short-tag, and alg…
aidangarske Jun 2, 2026
a987a6b
F-5250/F-5302 - Reject lengths that would truncate to word32
aidangarske Jun 2, 2026
be48cb2
F-5217/F-5286/F-5287 - Document recipient encoding analysis (empty-bs…
aidangarske Jun 2, 2026
7fdfb3d
F-5297 - Add CBOR INT64_MAX/INT64_MIN boundary decode tests
aidangarske Jun 2, 2026
df40fd7
F-5376 - Add empty inline-payload Mac0 roundtrip test
aidangarske Jun 2, 2026
f812f49
F-5232/F-5233/F-5234 - Assert AEAD/MAC structure context bytes
aidangarske Jun 2, 2026
e99e715
F-5296 - Add ECDH-ES recipient-protected-to-CEK binding test
aidangarske Jun 2, 2026
456589b
F-5248/F-5249 - Add per-recipient roundtrip tests for direct multi-re…
aidangarske Jun 2, 2026
f9760ee
F-5300/F-5375 - Cover all eight AES-CCM parameter sets (Encrypt0 and …
aidangarske Jun 2, 2026
9ada36d
F-5373 - Assert RSA private-key encode scrubs scratch tail past outLen
aidangarske Jun 2, 2026
e9c47ee
F-5374 - Reject non-preferred CBOR encodings per RFC 8949 determinist…
aidangarske Jun 2, 2026
715f2c5
F-5291 - Also clear header on wc_CoseEncrypt_Decrypt failure (complet…
aidangarske Jun 2, 2026
1b1278f
skoll: document decode-strictness, HMAC-min, and MAC payload API cont…
aidangarske Jun 2, 2026
1624678
Deduplicate header-clear and ML-DSA level checks into helpers
aidangarske Jun 2, 2026
898606a
Resolve MISRA findings: drop word32 guards, fix 10.4 cast and 20.9 SI…
aidangarske Jun 2, 2026
1b9111f
Fix MISRA 10.8: bound EC2 coordSz with sizeof instead of casting MAX_…
aidangarske Jun 2, 2026
f3f1a5a
Drop dead variable initializers flagged by cppcheck unreadVariable
aidangarske Jun 2, 2026
91a2b95
Avoid MISRA 10.8 by dropping redundant casts of INT32_MIN/MAX in rang…
aidangarske Jun 2, 2026
0f50b8a
Merge main into cose-hardening: adopt ML-DSA API rename, drop duplica…
aidangarske Jun 3, 2026
12dc456
Fix MISRA in examples/tests (const literals, unsigned sizes, braces, …
aidangarske Jun 3, 2026
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
28 changes: 28 additions & 0 deletions docs/MISRA-Compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,34 @@ The following clang-tidy checks are suppressed in the MISRA 2023 workflow. GCC s

**Justification:** Advisory warning about adjacent function parameters of the same type (e.g., `size_t payloadLen, size_t detachedLen`). Fixing requires reordering or wrapping parameters, which would break the public API. The parameter ordering follows RFC 9052 structure conventions.

## Examples and Test Code

`examples/` and `tests/` are runnable demonstration and test programs, not part of the shippable library, and are held to the same style rules where it keeps them useful as reference implementations. They are clean of the rules the library observes (no `goto`, fixed-length-coordinate checks, const-qualified literal payloads, braced statement bodies, unsigned size arithmetic, explicit precedence). The remaining deviations are inherent to runnable demos:

### Rule 21.6 — Standard I/O (and 17.7 on its return value)

**Location:** `examples/`, `tests/`.

**Justification:** Demos and test harnesses print human-readable status and PASS/FAIL to the console with `printf`/`fprintf`; the ignored return value (Rule 17.7) is part of the same console-output use. A real integration replaces this one call with a platform output routine. Library code under `src/` uses no standard I/O.

### Rule 15.5 — Multiple return / single point of exit

**Location:** `examples/`, `tests/`.

**Justification:** Demonstration code uses early returns for linear, readable top-to-bottom flow. The library itself observes single-exit with cascading `if (ret == 0)` and a single `return`.

### Rule 2.5 — Unused macro definitions

**Location:** `examples/`, `tests/`.

**Justification:** Same false positive as the library: CI passes explicit `-D` feature flags so cppcheck checks one code path, which makes the guarded-away feature `#define`s look unused.

### Rules 8.6 / 5.9 / 8.9 — External/internal identifier definitions

**Location:** `examples/`.

**Justification:** Each demo is a standalone program with its own `main` and `demo_*` helpers. cppcheck reports these when scanning all demo translation units in a single pass; each program links independently, so there is no real multiple-definition.

## Fully Compliant Rules (Notable)

| Rule | Status | Notes |
Expand Down
68 changes: 68 additions & 0 deletions docs/cose-encoding-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# wolfCOSE Encoding Notes (analyzed findings)

Records the rationale for two recipient-encoding findings that were analyzed
against RFC 9052 and intentionally left as-is or deferred.

## Recipient ciphertext for direct / ECDH-ES / direct key-wrap: empty bstr (not nil)

For recipients that carry no encrypted key (direct key `-6`, ECDH-ES direct,
and direct-mode entries), wolfCOSE encodes the `COSE_recipient` ciphertext
field as a zero-length byte string (`h''`).

This is correct and intentional. RFC 9052 Appendix C.3.1 (the "Direct ECDH"
example) and the cose-wg `Examples` repository both encode the recipient
ciphertext as empty `bstr` (`h''`), not the CBOR `nil` simple value. Changing
to `nil` would diverge from the RFC's own test vectors and break interop, so it
is deliberately not done.

## AES Key Wrap recipient: `alg` placement

wolfCOSE currently encodes the key-wrap algorithm in the recipient *protected*
header. RFC 9052 Section 3 permits a header parameter to appear in either the
protected or unprotected bucket, so this is conformant. For AES Key Wrap the
recipient headers are not cryptographically bound in either bucket, so there is
no security difference.

The canonical RFC/cose-wg examples place the key-wrap `alg` in the *unprotected*
bucket with a zero-length protected header. Aligning with that canonical form
is a worthwhile interoperability enhancement, but it requires reordering the
multi-recipient decrypt path (the algorithm is currently classified from the
protected header before the unprotected map is decoded). Because the current
encoding is already RFC-legal, that change is deferred to a dedicated, reviewed
update rather than bundled here. A robust implementation should accept `alg`
from either bucket on decode (Postel's law) when it is made.

## Decode strictness (API contract)

The hardening pass tightened several decode-side behaviors. These apply to all
public decode/verify/decrypt entry points (and, where noted, the generic
`wc_CBOR_Decode*` primitives), so integrators upgrading from older wolfCOSE
should be aware:

- **Preferred (shortest-form) CBOR is required on decode.** Per RFC 8949
Section 4.2.1 (deterministic encoding, which COSE mandates for
security-relevant structures), `wolfCose_CBOR_DecodeHead` rejects overlong
integer/length/tag arguments for every major type except simple/float.
Because it is the lowest-level primitive, this also applies to the generic
`wc_CBOR_Decode*` APIs. wolfCOSE's own encoder always emits shortest form, so
round-trips and RFC test vectors are unaffected; only non-preferred input
from lenient third-party encoders is now rejected with
`WOLFCOSE_E_CBOR_MALFORMED`.
- **`inSz` must equal the exact encoded object length.** Every verify/decrypt
API (and `wc_CoseKey_Decode`) now rejects trailing bytes after the COSE
object (RFC 8949 Section 5.3.1) with `WOLFCOSE_E_CBOR_MALFORMED`. Callers must
pass the precise message length, not a fixed receive-buffer capacity.
- **EC2 / ephemeral coordinates must be exactly the curve size.** Per RFC 9053
Section 7.1.1, `x`/`y`/`d` (and ephemeral `x`/`y`) must equal the curve
coordinate size with leading zeros preserved; leading-zero-stripped (short)
coordinates are rejected with `WOLFCOSE_E_COSE_BAD_HDR`, including on
metadata-only EC2 key decode.
- **HMAC keys must meet the per-algorithm minimum.** HMAC-256/384/512 require a
key of at least 32/48/64 bytes (RFC 9053 Section 3.1); shorter keys are
rejected with `WOLFCOSE_E_COSE_KEY_TYPE`. Define
`WOLFCOSE_ALLOW_SHORT_HMAC_KEY` to restore the legacy accept-any-length
behavior.
- **MAC create requires an explicit payload.** `wc_CoseMac0_Create` /
`wc_CoseMac_Create` reject an omitted payload (`payload == NULL` and
`detachedPayload == NULL`) with `WOLFCOSE_E_INVALID_ARG`. To authenticate an
empty payload, pass a non-NULL zero-length buffer.
40 changes: 22 additions & 18 deletions examples/comprehensive/encrypt_all.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ static int test_encrypt0(int32_t alg, int keySz, int detached, int useAad)
};
uint8_t out[512];
uint8_t scratch[512];
uint8_t payload[] = "test payload for encryption";
uint8_t aad[] = "external additional authenticated data";
const uint8_t payload[] = "test payload for encryption";
const uint8_t aad[] = "external additional authenticated data";
uint8_t detachedCt[512];
size_t detachedCtLen = 0;
uint8_t plaintext[256];
Expand All @@ -104,7 +104,7 @@ static int test_encrypt0(int32_t alg, int keySz, int detached, int useAad)
if (detached != 0) {
ret = wc_CoseEncrypt0_Encrypt(&cosKey, alg,
iv, sizeof(iv),
payload, sizeof(payload) - 1,
payload, sizeof(payload) - 1u,
detachedCt, sizeof(detachedCt), &detachedCtLen,
(useAad != 0) ? aad : NULL,
(useAad != 0) ? (sizeof(aad) - 1u) : 0u,
Expand All @@ -114,7 +114,7 @@ static int test_encrypt0(int32_t alg, int keySz, int detached, int useAad)
else {
ret = wc_CoseEncrypt0_Encrypt(&cosKey, alg,
iv, sizeof(iv),
payload, sizeof(payload) - 1,
payload, sizeof(payload) - 1u,
NULL, 0, NULL,
(useAad != 0) ? aad : NULL,
(useAad != 0) ? (sizeof(aad) - 1u) : 0u,
Expand Down Expand Up @@ -152,7 +152,7 @@ static int test_encrypt0(int32_t alg, int keySz, int detached, int useAad)
}
}
if (ret == 0) {
if (plaintextLen != sizeof(payload) - 1) {
if (plaintextLen != sizeof(payload) - 1u) {
ret = -2;
}
}
Expand Down Expand Up @@ -181,8 +181,8 @@ static int test_encrypt_multi_direct(int32_t contentAlg, int keySz,
};
uint8_t out[1024];
uint8_t scratch[512];
uint8_t payload[] = "multi-recipient encrypted payload";
uint8_t aad[] = "multi-recipient aad";
const uint8_t payload[] = "multi-recipient encrypted payload";
const uint8_t aad[] = "multi-recipient aad";
uint8_t plaintext[256];
static const uint8_t recipKid[] = { 0x72u, 0x63u, 0x70u, 0x58u };
size_t plaintextLen = 0;
Expand Down Expand Up @@ -262,9 +262,13 @@ static int test_encrypt_multi_direct(int32_t contentAlg, int keySz,
static int test_encrypt_wrong_key(void)
{
int ret = 0;
WOLFCOSE_KEY cek, wrongKey;
WOLFCOSE_KEY cek;
WOLFCOSE_KEY wrongKey;
WOLFCOSE_RECIPIENT recipients[2];
WOLFCOSE_RECIPIENT wrongRecipient;
const uint8_t recip1Kid[] = "recip1";
const uint8_t recip2Kid[] = "recip2";
const uint8_t wrongKid[] = "wrong";
uint8_t keyData1[16] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
Expand All @@ -279,7 +283,7 @@ static int test_encrypt_wrong_key(void)
};
uint8_t out[512];
uint8_t scratch[512];
uint8_t payload[] = "wrong key test";
const uint8_t payload[] = "wrong key test";
uint8_t plaintext[256];
size_t plaintextLen = 0;
size_t outLen = 0;
Expand Down Expand Up @@ -309,27 +313,27 @@ static int test_encrypt_wrong_key(void)
if (ret == 0) {
recipients[0].algId = WOLFCOSE_ALG_DIRECT;
recipients[0].key = &cek;
recipients[0].kid = (const uint8_t*)"recip1";
recipients[0].kidLen = 6;
recipients[0].kid = recip1Kid;
recipients[0].kidLen = sizeof(recip1Kid) - 1u;

recipients[1].algId = WOLFCOSE_ALG_DIRECT;
recipients[1].key = &cek;
recipients[1].kid = (const uint8_t*)"recip2";
recipients[1].kidLen = 6;
recipients[1].kid = recip2Kid;
recipients[1].kidLen = sizeof(recip2Kid) - 1u;

/* Wrong recipient with different key */
wrongRecipient.algId = WOLFCOSE_ALG_DIRECT;
wrongRecipient.key = &wrongKey;
wrongRecipient.kid = (const uint8_t*)"wrong";
wrongRecipient.kidLen = 5;
wrongRecipient.kid = wrongKid;
wrongRecipient.kidLen = sizeof(wrongKid) - 1u;
}

/* Encrypt */
if (ret == 0) {
ret = wc_CoseEncrypt_Encrypt(recipients, 2,
WOLFCOSE_ALG_A128GCM,
iv, sizeof(iv),
payload, sizeof(payload) - 1,
payload, sizeof(payload) - 1u,
NULL, 0,
NULL, 0,
scratch, sizeof(scratch),
Expand Down Expand Up @@ -534,7 +538,7 @@ static int test_encrypt0_interop(void)
PRINT_TEST("interop_encrypt0_a128gcm_roundtrip");
ret = wc_CoseEncrypt0_Encrypt(&cosKey, WOLFCOSE_ALG_A128GCM,
iv, sizeof(iv),
payload, sizeof(payload) - 1,
payload, sizeof(payload) - 1u,
NULL, 0, NULL,
NULL, 0,
scratch, sizeof(scratch),
Expand All @@ -557,7 +561,7 @@ static int test_encrypt0_interop(void)
}
}
if (ret == 0) {
if (plaintextLen != sizeof(payload) - 1) {
if (plaintextLen != sizeof(payload) - 1u) {
ret = -2;
}
}
Expand Down
Loading
Loading