Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .github/workflows/c99-compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
run: |
make c99-check CC=${{ matrix.cc }} WOLFSSL_INC=$HOME/wolfssl-install/include

- name: Countersignature configuration gate
run: |
make countersign-config-test CC=${{ matrix.cc }} WOLFSSL_INC=$HOME/wolfssl-install/include

- name: Experimental-feature acknowledgement gate
run: |
make experimental-check CC=${{ matrix.cc }} WOLFSSL_INC=$HOME/wolfssl-install/include
34 changes: 33 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config
SCEN_SENSOR = examples/scenarios/sensor_attestation
SCEN_BROADCAST = examples/scenarios/group_broadcast_mac

.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check zeroize-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check experimental-check clean FORCE
.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-test zero-alloc-check zeroize-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check experimental-check clean FORCE

# --- Core library ---
all: $(LIB_A)
Expand Down Expand Up @@ -293,6 +293,38 @@ rsapss-policy-test:
done
@echo "PASS: RSA-PSS operation guards compile cleanly"

# Countersignatures decode their target and encode a Countersign_structure in
# both directions. Verify the independent operation guards and CBOR dependency.
COUNTERSIGN_CONFIG_FLAGS = $(C99_FLAGS) -Werror=unused-function \
-Werror=implicit-function-declaration -DWOLFCOSE_LEAN \
-DWOLFCOSE_ENABLE_COUNTERSIGN -DWOLFCOSE_NO_SIGN1 \
-DWOLFCOSE_NO_ENCRYPT0 -DWOLFCOSE_NO_MAC0 \
-DWOLFCOSE_NO_KEY_ENCODE -DWOLFCOSE_NO_KEY_DECODE

countersign-config-test:
@set -e; \
log_file=$$(mktemp "$${TMPDIR:-/tmp}/wolfcose-countersign.XXXXXX"); \
trap 'rm -f "$$log_file"' 0 1 2 3 15; \
$(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_SIGN \
-fsyntax-only src/wolfcose.c; \
$(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_VERIFY \
-fsyntax-only src/wolfcose.c; \
if $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_SIGN \
-DWOLFCOSE_NO_CBOR_ENCODE -fsyntax-only src/wolfcose.c \
>"$$log_file" 2>&1; then \
echo "FAIL: countersign verify compiled without CBOR encode"; \
exit 1; \
fi; \
grep -q "WOLFCOSE_NO_CBOR_ENCODE conflicts" "$$log_file"; \
if $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_VERIFY \
-DWOLFCOSE_NO_CBOR_DECODE -fsyntax-only src/wolfcose.c \
>"$$log_file" 2>&1; then \
echo "FAIL: countersign creation compiled without CBOR decode"; \
exit 1; \
fi; \
grep -q "WOLFCOSE_NO_CBOR_DECODE conflicts" "$$log_file"
@echo "PASS: countersignature operation guards compile cleanly"

zero-alloc-check:
sh scripts/check_zero_alloc.sh

Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# wolfCOSE

wolfCOSE is a lightweight C library implementing [CBOR (RFC 8949)](https://www.rfc-editor.org/rfc/rfc8949), [COSE (RFC 9052/9053)](https://www.rfc-editor.org/rfc/rfc9052), and post-quantum [ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964) using [wolfSSL](https://www.wolfssl.com/) as the crypto backend.
wolfCOSE is a lightweight C library implementing
[CBOR (RFC 8949)](https://www.rfc-editor.org/rfc/rfc8949),
[COSE (RFC 9052/9053)](https://www.rfc-editor.org/rfc/rfc9052),
[COSE countersignatures (RFC 9338)](https://www.rfc-editor.org/rfc/rfc9338),
and post-quantum
[ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964) using
[wolfSSL](https://www.wolfssl.com/) as the crypto backend.

## Main Features

- **Complete RFC 9052 message set**: all six COSE message types, including multi-signer
`COSE_Sign` and multi-recipient `COSE_Encrypt` / `COSE_Mac`
- **[RFC 9338 countersignature support](https://www.rfc-editor.org/rfc/rfc9338)**:
standards-conformant full and abbreviated V2 countersignatures for all six
tagged COSE message types, with multiple countersigners and in-place
operation
- **Post-quantum signing**: ML-DSA (FIPS 204) at all three security levels, with RFC 9964 `COSE_Key` (AKP key type, seed-based private keys)
- **40 algorithms** across signing, encryption, MAC, and key distribution
- **Zero dynamic allocation**: heap-allocation-free and non-recursive. Every operation runs on caller-provided buffers
Expand Down Expand Up @@ -43,6 +53,11 @@ wolfCOSE has implemented all RFC 9052 messages both single-actor and multi-actor
| `COSE_Mac` | Sec. 6.1 | `wc_CoseMac_Create` / `wc_CoseMac_Verify` | **Multi-recipient** MAC (shared MAC key, distributed to recipients) |
| `COSE_Key` / `COSE_KeySet` | Sec. 7 | `wc_CoseKey_Encode` / `wc_CoseKey_Decode` | Key serialization for all key types |

RFC 9338 countersignatures can be attached to any tagged message in this
table. Use `wc_Cose_AddCounterSignature()` or
`wc_Cose_AddCounterSignature0()` to add one, then verify it independently with
the corresponding `wc_Cose_VerifyCounterSignature*()` API.

## Prerequisites (wolfSSL)

wolfCOSE requires [wolfSSL](https://www.wolfssl.com/) as its crypto backend.
Expand Down Expand Up @@ -224,7 +239,7 @@ make coverage-force-failure # Include crypto failure path testing
Full documentation is available in the [Wiki](https://github.com/wolfSSL/wolfCOSE/wiki):

- [Getting Started](https://github.com/wolfSSL/wolfCOSE/wiki/Getting-Started): Build instructions and first steps
- [Message Types](https://github.com/wolfSSL/wolfCOSE/wiki/Message-Types): All six RFC 9052 messages (Sign1, Sign, Encrypt0, Encrypt, Mac0, Mac) with code samples
- [Message Types](https://github.com/wolfSSL/wolfCOSE/wiki/Message-Types): All six RFC 9052 messages and RFC 9338 countersignatures with code samples
- [Algorithms](https://github.com/wolfSSL/wolfCOSE/wiki/Algorithms): Complete list of 40 supported algorithms with COSE IDs
- [API Reference](https://github.com/wolfSSL/wolfCOSE/wiki/API-Reference): Function signatures, data structures, error codes
- [Macros](https://github.com/wolfSSL/wolfCOSE/wiki/Macros): Compile-time configuration, size tuning, and ECDSA nonce policy
Expand Down
123 changes: 118 additions & 5 deletions docs/API-Reference.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# API Reference

Complete API documentation for wolfCOSE (RFC 9052/9053 COSE implementation).
Complete API documentation for wolfCOSE (RFC 9052/9053 COSE and RFC 9338
countersignature implementation).

## Table of Contents

- [Data Structures](#data-structures)
- [COSE_Key API](#cose_key-api)
- [COSE_Sign1 API](#cose_sign1-api)
- [COSE Countersignature API](#cose-countersignature-api)
- [COSE_Encrypt0 API](#cose_encrypt0-api)
- [COSE_Mac0 API](#cose_mac0-api)
- [COSE_Sign API (Multi-Signer)](#cose_sign-api-multi-signer)
Expand Down Expand Up @@ -89,6 +91,28 @@ Signer information for COSE_Sign multi-signer messages.

---

### WOLFCOSE_COUNTERSIGNATURE

```c
typedef struct WOLFCOSE_COUNTERSIGNATURE {
int32_t algId;
WOLFCOSE_KEY* key;
const uint8_t* kid;
size_t kidLen;
} WOLFCOSE_COUNTERSIGNATURE;

typedef struct WOLFCOSE_COUNTERSIGNATURE0 {
int32_t algId;
WOLFCOSE_KEY* key;
} WOLFCOSE_COUNTERSIGNATURE0;
```

The full form carries protected algorithm metadata and an optional key ID.
The abbreviated form carries only signature bytes, so its algorithm and key
selection must be supplied by trusted application context.

---

### WOLFCOSE_RECIPIENT

```c
Expand Down Expand Up @@ -662,6 +686,93 @@ Verify a COSE_Sign1 message and extract payload.

---

## COSE Countersignature API

These RFC 9338 APIs operate on tagged `COSE_Sign1`, `COSE_Sign`,
`COSE_Encrypt0`, `COSE_Encrypt`, `COSE_Mac0`, and `COSE_Mac` messages. A
countersignature authenticates the target message's protected header, payload,
and existing cryptographic output where applicable.

### wc_Cose_AddCounterSignature

```c
int wc_Cose_AddCounterSignature(
const WOLFCOSE_COUNTERSIGNATURE* counterSigner,
const uint8_t* in, size_t inSz,
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
);
```

Add a full V2 countersignature in unprotected header parameter 11. Existing
full countersignatures are retained and the new value is appended. `out` may
equal `in` for exact in-place growth; other overlapping buffers are rejected.
`scratch` must be disjoint from all input and output buffers.

### wc_Cose_AddCounterSignature0

```c
int wc_Cose_AddCounterSignature0(
const WOLFCOSE_COUNTERSIGNATURE0* counterSigner,
const uint8_t* in, size_t inSz,
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
);
```

Add one abbreviated V2 countersignature in unprotected header parameter 12.
Only one abbreviated countersignature is allowed per target. `scratch` must be
disjoint from all input and output buffers.

### wc_Cose_VerifyCounterSignature

```c
int wc_Cose_VerifyCounterSignature(
const WOLFCOSE_KEY* key, size_t counterIndex,
const uint8_t* in, size_t inSz,
const uint8_t* detachedPayload, size_t detachedLen,
const uint8_t* extAad, size_t extAadLen,
uint8_t* scratch, size_t scratchSz,
WOLFCOSE_HDR* counterHdr
);
```

Verify a full countersignature selected by zero-based index and return its
parsed headers. V2 label 11 and legacy RFC 8152 label 7 are accepted. V2 is
preferred when both are present. If the countersignature algorithm is carried
only in the unprotected header bucket, `key->alg` must pin the same algorithm;
an unset or mismatched key policy is rejected. `scratch` must be disjoint from
the message, detached payload, external AAD, and `counterHdr`.

### wc_Cose_VerifyCounterSignature0

```c
int wc_Cose_VerifyCounterSignature0(
const WOLFCOSE_COUNTERSIGNATURE0* counterSigner,
const uint8_t* in, size_t inSz,
const uint8_t* detachedPayload, size_t detachedLen,
const uint8_t* extAad, size_t extAadLen,
uint8_t* scratch, size_t scratchSz
);
```

Verify an abbreviated countersignature using the algorithm and key supplied by
the application. V2 label 12 and legacy RFC 8152 label 9 are accepted.
`scratch` must be disjoint from the message, detached payload, and external
AAD.

All four APIs require the detached payload or ciphertext and external AAD,
when used by the target, to match the original operation. They return
`WOLFCOSE_SUCCESS` or a negative wolfCOSE error code.

---

## COSE_Encrypt0 API

### wc_CoseEncrypt0_Encrypt
Expand Down Expand Up @@ -1158,10 +1269,12 @@ else {
}
```

Note that `wc_CoseKey_Decode()` and the COSE header parsers accept integer
labels only, by design: silently skipping text labels would break their
duplicate-label enforcement. `wc_CBOR_DecodeLabel()` is for caller-written
parsers of protocol maps such as CTAP2.
COSE header parsers accept both integer and text labels and enforce duplicate
labels within and across the protected and unprotected buckets. Unknown,
non-critical text-labeled parameters are preserved in the encoded message and
ignored during processing. `wc_CoseKey_Decode()` accepts the integer labels
defined for COSE_Key. `wc_CBOR_DecodeLabel()` is also available for
caller-written protocol parsers such as CTAP2.

**Returns:** `WOLFCOSE_SUCCESS` or error code

Expand Down
6 changes: 4 additions & 2 deletions docs/Getting-Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,10 @@ Related strictness that surprises integrators for the same reason:
- EC2 coordinates must be exactly the curve size, with leading zeros preserved
(RFC 9053 Section 7.1.1) - a 31-byte P-256 `x` is rejected, not left-padded.
- A duplicate label in a header or `COSE_Key` map is rejected.
- `COSE_Key` and COSE header maps accept integer labels only. For your own
protocol maps that mix integer and text labels, use
- `COSE_Key` maps accept the registered integer labels. COSE header maps accept
both integer and text labels, retain unknown non-critical parameters in the
encoded message, and reject duplicates within or across header buckets. For
caller-written protocol maps, use
[`wc_CBOR_DecodeLabel()`](API-Reference.md#wc_cbor_decodelabel).

None of this is configurable: relaxing it would let a signature or MAC be
Expand Down
24 changes: 23 additions & 1 deletion docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ Defining `WOLFCOSE_LEAN` keeps only the core — `COSE_Sign1`/`Encrypt0`/`Mac0`
| `WOLFCOSE_LEAN` | Core-only base; all extensions become opt-in |
| `WOLFCOSE_ENABLE_<X>` | Opt in a single extension (see list below) |

Extension names for `WOLFCOSE_ENABLE_<X>`: `ES384`, `ES512`, `EDDSA`, `ED448`, `RSAPSS`, `MLDSA`, `HMAC384`, `HMAC512`, `AESCCM`, `CHACHA20`, `AESMAC`, `AESWRAP`, `ECDH_ES`, `SIGN` (multi-signer), `ENCRYPT` (multi-recipient), `MAC` (multi-recipient).
Extension names for `WOLFCOSE_ENABLE_<X>`: `ES384`, `ES512`, `EDDSA`,
`ED448`, `RSAPSS`, `MLDSA`, `HMAC384`, `HMAC512`, `AESCCM`, `CHACHA20`,
`AESMAC`, `AESWRAP`, `ECDH_ES`, `SIGN` (multi-signer), `ENCRYPT`
(multi-recipient), `MAC` (multi-recipient), and `COUNTERSIGN` (RFC 9338
countersignatures).

An extension is compiled in when it is explicitly enabled (`WOLFCOSE_ENABLE_<X>`), or — in a non-lean build — when wolfSSL provides the primitive and it is not opted out with `WOLFCOSE_NO_<X>`. Enabling an extension wolfSSL cannot provide is a compile error. The resolved state is exposed internally as read-only `WOLFCOSE_HAVE_<X>` gates (e.g. `WOLFCOSE_HAVE_MLDSA`); sources, tests, and examples compile against those, so you set `WOLFCOSE_ENABLE_*`/`WOLFCOSE_NO_*`, not `WOLFCOSE_HAVE_*`.

Expand Down Expand Up @@ -131,6 +135,24 @@ off by default.

## Message Type Gates

### COSE Countersignatures

| Define | Description | Default |
|--------|-------------|---------|
| `WOLFCOSE_COUNTERSIGN` | Enable RFC 9338 countersignatures | Enabled |
| `WOLFCOSE_ENABLE_COUNTERSIGN` | Opt in under `WOLFCOSE_LEAN` | - |
| `WOLFCOSE_NO_COUNTERSIGN` | Disable countersignatures entirely | - |
| `WOLFCOSE_COUNTERSIGN_SIGN` | Enable countersignature creation | Enabled |
| `WOLFCOSE_NO_COUNTERSIGN_SIGN` | Disable countersignature creation | - |
| `WOLFCOSE_COUNTERSIGN_VERIFY` | Enable countersignature verification | Enabled |
| `WOLFCOSE_NO_COUNTERSIGN_VERIFY` | Disable countersignature verification | - |

The default full build includes countersignatures. `WOLFCOSE_LEAN` excludes
them unless `WOLFCOSE_ENABLE_COUNTERSIGN` is defined. Creation and verification
both require CBOR encoding and decoding because each operation decodes the
target message and encodes its `Countersign_structure`. Creation also requires
a signing algorithm, and verification requires a verification algorithm.

### COSE_Sign1 (Single Signer)

| Define | Description | Default |
Expand Down
74 changes: 74 additions & 0 deletions docs/Message-Types.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,80 @@ ret = wc_CoseSign_Verify(&vendorPubKey, /*signerIndex=*/0,
&hdr, &payload, &payloadLen);
```

## RFC 9338 countersignatures

A countersignature lets a second party sign an existing COSE cryptographic
object. The original signer and countersigner remain independently
verifiable. For example, a vendor can sign firmware and a release authority
can countersign the resulting `COSE_Sign1` after policy checks, without
replacing the vendor signature or copying the firmware into a new envelope.

wolfCOSE can add countersignatures to tagged `COSE_Sign1`, `COSE_Sign`,
`COSE_Encrypt0`, `COSE_Encrypt`, `COSE_Mac0`, and `COSE_Mac` messages.

```c
WOLFCOSE_COUNTERSIGNATURE approval = {
.algId = WOLFCOSE_ALG_ES256,
.key = &releaseKey,
.kid = (const uint8_t*)"release-2026",
.kidLen = 12
};

ret = wc_Cose_AddCounterSignature(&approval,
signedMsg, signedMsgLen,
NULL, 0, /* detached payload */
aad, aadLen,
scratch, sizeof(scratch),
approved, sizeof(approved), &approvedLen,
&rng);

ret = wc_Cose_VerifyCounterSignature(&releasePubKey, 0,
approved, approvedLen,
NULL, 0, aad, aadLen,
scratch, sizeof(scratch),
&counterHdr);
```

Verify the original COSE operation separately. For a `COSE_Sign1`, call
`wc_CoseSign1_Verify()` as well as
`wc_Cose_VerifyCounterSignature()`. Appending another full countersignature
preserves existing values; select one with the zero-based `counterIndex`.

`wc_Cose_AddCounterSignature0()` emits the abbreviated form when the algorithm
and key selection are known through trusted application context. Exact
in-place growth is supported by passing the same buffer for `in` and `out`,
provided the buffer has enough capacity. Detached payloads or ciphertext and
external AAD must be supplied again when adding or verifying the
countersignature.

Creation emits the RFC 9338 V2 labels 11 and 12. Verification also accepts the
legacy RFC 8152 labels 7 and 9 for deployed interoperability.

The command-line tool can countersign an existing message and verify a
selected full countersignature:

```bash
wolfcose_tool countersign -k release-key.cbor -a ES256 \
-i signed.cose -o approved.cose
wolfcose_tool counterverify -k release-public.cbor \
-i approved.cose --index 0
```

### Relationship to SCITT

Countersignatures are useful for supply-chain endorsement, notary, approval,
and separation-of-duty workflows. They can add an independently verifiable
approval layer to a signed statement.

They do not by themselves implement a SCITT transparency service. The SCITT
architecture is standardized in [RFC 9943](https://www.rfc-editor.org/rfc/rfc9943),
and its receipts use the formats in
[RFC 9942](https://www.rfc-editor.org/rfc/rfc9942). A complete SCITT product
also needs statement registration and policy, receipt generation and
validation, and transparency-service operation. wolfCOSE countersignatures
provide a complementary COSE building block for deployments that need an
extra independent endorsement.

## COSE_Encrypt0 — single-recipient AEAD (RFC 9052 Sec. 5.2)

Direct symmetric AEAD. The caller already has the content encryption key (CEK).
Expand Down
Loading
Loading