diff --git a/.github/workflows/c99-compliance.yml b/.github/workflows/c99-compliance.yml index 9919d16..9afa262 100644 --- a/.github/workflows/c99-compliance.yml +++ b/.github/workflows/c99-compliance.yml @@ -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 diff --git a/Makefile b/Makefile index a3edbf7..6d2be2a 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ SRC = src/wolfcose_cbor.c \ src/wolfcose_recipient.c \ src/wolfcose_sign1.c \ src/wolfcose_sign.c \ + src/wolfcose_countersign.c \ src/wolfcose_encrypt0.c \ src/wolfcose_mac0.c \ src/wolfcose_encrypt.c \ @@ -120,7 +121,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 lms-demo lms-verify comprehensive scenarios interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms 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 lms-demo lms-verify comprehensive scenarios interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms experimental-check clean FORCE # --- Core library --- all: $(LIB_A) @@ -315,6 +316,52 @@ 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); \ + $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_VERIFY \ + -fsyntax-only $(SRC); \ + $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_SIGN \ + -DWOLFCOSE_NO_COUNTERSIGN_VERIFY -fsyntax-only $(SRC); \ + if $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_NO_COUNTERSIGN_SIGN \ + -DWOLFCOSE_NO_CBOR_ENCODE -fsyntax-only $(SRC) \ + >"$$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) \ + >"$$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"; \ + if $(CC) $(COUNTERSIGN_CONFIG_FLAGS) -DWOLFCOSE_ENABLE_RSAPSS \ + -DWOLFCOSE_MAX_SCRATCH_SZ=512u -fsyntax-only \ + src/wolfcose_countersign.c >"$$log_file" 2>&1; then \ + echo "FAIL: RSA-PSS countersigning compiled with no structure room"; \ + exit 1; \ + fi; \ + grep -q "countersigning needs WOLFCOSE_MAX_SCRATCH_SZ" "$$log_file"; \ + printf '%s\n' '#include "wolfcose/wolfcose.h"' \ + '#if WOLFCOSE_MAX_SCRATCH_SZ != 512u' \ + '#error "RSA-PSS without countersigning must keep the 512-byte scratch default"' \ + '#endif' | $(CC) $(C99_FLAGS) -DWOLFCOSE_LEAN -DWOLFCOSE_ENABLE_RSAPSS \ + -DWOLFCOSE_NO_COUNTERSIGN -x c -fsyntax-only - + @echo "PASS: countersignature operation guards compile cleanly" + zero-alloc-check: sh scripts/check_zero_alloc.sh diff --git a/README.md b/README.md index 0e292c7..4536203 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # 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) and [HSS/LMS for COSE (RFC 8778)](https://www.rfc-editor.org/rfc/rfc8778) 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) and [HSS/LMS for COSE (RFC 8778)](https://www.rfc-editor.org/rfc/rfc8778) 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) - **Stateful hash-based signing**: HSS/LMS (RFC 8778, NIST SP 800-208) with `COSE_Key` type 5 — the CNSA 2.0 algorithm for firmware and software signing, verify-only friendly for constrained devices - **41 algorithms** across signing, encryption, MAC, and key distribution @@ -44,6 +48,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. @@ -191,7 +200,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 (Sign1, Sign, Encrypt0, Encrypt, Mac0, Mac) and RFC 9338 countersignatures with code samples - [Algorithms](https://github.com/wolfSSL/wolfCOSE/wiki/Algorithms): Complete list of 41 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 diff --git a/docs/API-Reference.md b/docs/API-Reference.md index bbddb15..8327e4d 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -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) @@ -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 @@ -689,6 +713,100 @@ 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. + +Countersignatures support ECDSA, EdDSA, RSA-PSS, and ML-DSA. +`WOLFCOSE_ALG_HSS_LMS` is rejected with `WOLFCOSE_E_COSE_BAD_ALG` before any +signer runs, and a build whose only signature algorithm is HSS/LMS does not +enable the feature. + +### 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, when used by the +target, to match the original operation. Verification must also receive the +same countersignature external AAD that was supplied when the countersignature +was created; it is independent of any AAD the target operation used. They +return `WOLFCOSE_SUCCESS` or a negative wolfCOSE error code. + +--- + ## COSE_Encrypt0 API ### wc_CoseEncrypt0_Encrypt @@ -1163,8 +1281,9 @@ spellings for the same field (`3` vs `"alg"`, `1` vs `"type"`, `2` vs `"id"`). so a parser writes the dispatch once instead of duplicating a `wc_CBOR_PeekType()` branch at every map. -Major types 0 and 1 fill `val` with `isText == 0`; major type 3 fills -`text`/`textLen` with `isText == 1` and no copy. Anything else returns +Major types 0 and 1 fill `val` with `isText == 0`; major type 3 validates +UTF-8 and, when valid, fills `text`/`textLen` with `isText == 1` and no copy. +Invalid UTF-8 returns `WOLFCOSE_E_CBOR_MALFORMED`; any other major type returns `WOLFCOSE_E_CBOR_TYPE`. `wc_CBOR_LabelIsInt()` and `wc_CBOR_LabelIsText()` return 1 on match and 0 @@ -1185,10 +1304,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 diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index dfcdeed..1a3f02f 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -391,8 +391,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 diff --git a/docs/Macros.md b/docs/Macros.md index 7803b6f..bff4977 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -47,7 +47,7 @@ Defining `WOLFCOSE_LEAN` keeps only the core — `COSE_Sign1`/`Encrypt0`/`Mac0` | `WOLFCOSE_LEAN` | Core-only base; all extensions become opt-in | | `WOLFCOSE_ENABLE_` | Opt in a single extension (see list below) | -Extension names for `WOLFCOSE_ENABLE_`: `ES384`, `ES512`, `EDDSA`, `ED448`, `RSAPSS`, `MLDSA`, `LMS`, `HMAC384`, `HMAC512`, `AESCCM`, `CHACHA20`, `AESMAC`, `AESWRAP`, `ECDH_ES`, `SIGN` (multi-signer), `ENCRYPT` (multi-recipient), `MAC` (multi-recipient). +Extension names for `WOLFCOSE_ENABLE_`: `ES384`, `ES512`, `EDDSA`, `ED448`, `RSAPSS`, `MLDSA`, `LMS`, `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_`), or — in a non-lean build — when wolfSSL provides the primitive and it is not opted out with `WOLFCOSE_NO_`. Enabling an extension wolfSSL cannot provide is a compile error. The resolved state is exposed internally as read-only `WOLFCOSE_HAVE_` gates (e.g. `WOLFCOSE_HAVE_MLDSA`); sources, tests, and examples compile against those, so you set `WOLFCOSE_ENABLE_*`/`WOLFCOSE_NO_*`, not `WOLFCOSE_HAVE_*`. @@ -132,6 +132,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 | @@ -275,7 +293,7 @@ Two limits worth knowing before designing around this: | Define | Description | Default | |--------|-------------|---------| -| `WOLFCOSE_MAX_SCRATCH_SZ` | Scratch buffer size for Sig_structure/Enc_structure | 512 | +| `WOLFCOSE_MAX_SCRATCH_SZ` | Scratch buffer size for Sig_structure/Enc_structure | 512 (1024 with RSA-PSS countersigning) | | `WOLFCOSE_PROTECTED_HDR_MAX` | Max protected header size | 64 | | `WOLFCOSE_CBOR_MAX_DEPTH` | Max CBOR nesting depth | 8 | | `WOLFCOSE_MIN_BUFFERS` | Trim the working set to the minimum that fits the enabled algorithms | - | @@ -287,12 +305,14 @@ One define that trims the caller working set to the minimum that still fits the | Enabled signature algorithm | `WOLFCOSE_MAX_SIG_SZ` | `WOLFCOSE_MAX_SCRATCH_SZ` | |---|---|---| | ES256/384/512, EdDSA (Ed25519/Ed448) | 132 | 512 | -| RSA-PSS (PS256/384/512) | 512 | 512 | +| RSA-PSS (PS256/384/512) | 512 | 512 (1024 with countersigning) | | ML-DSA-44/65/87 | 4627 | 8192 | | HSS-LMS | 10240 | 11264 | Because the floor follows the algorithm, `WOLFCOSE_MIN_BUFFERS` stays valid with any algorithm — ML-DSA, HSS-LMS, and RSA-PSS simply use that algorithm's floor rather than the ECC floor. HSS-LMS uses the largest default floors because its signature size follows the key's parameter set rather than a fixed constant: the `WOLFCOSE_MAX_SIG_SZ` default of 10240 and `WOLFCOSE_MAX_SCRATCH_SZ` default of 11264 (scratch also holds the `Sig_structure`) cover every W4 and W8 predefined set, the largest being L4_H10_W4 at 10204 bytes. The low-Winternitz (W1/W2) multi-level sets reach 18012 bytes and need a `-D` override; a key whose signature exceeds these buffers is rejected via `wc_LmsKey_GetSigLen()` before any signing, so no one-time state is consumed. It stays zero-heap and shrinks buffers, not stack frames. An explicit `-D` override of any individual limit takes precedence. +**Countersignature sizing.** Countersigning builds the `Countersign_structure` and then keeps the signature after it in scratch, so `WOLFCOSE_MAX_SCRATCH_SZ` must be at least `WOLFCOSE_MAX_SIG_SZ` + 256. The RSA-PSS default therefore rises to 1024 (a 4096-bit signature is 512 bytes) only when countersignature creation is enabled; RSA-PSS builds without it keep 512. An override below the floor is a build error. + --- ## Tuning for Size diff --git a/docs/Message-Types.md b/docs/Message-Types.md index daa6b4a..c63c67e 100644 --- a/docs/Message-Types.md +++ b/docs/Message-Types.md @@ -70,6 +70,83 @@ 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. +Countersigners use ECDSA, EdDSA, RSA-PSS, or ML-DSA; HSS/LMS is rejected with +`WOLFCOSE_E_COSE_BAD_ALG`, and an HSS/LMS-only build does not enable the +feature. + +```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). diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 9a07818..b461ab5 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -429,6 +429,35 @@ extern "C" { #endif #endif +/* RFC 9338 countersignatures, extension */ +#if defined(WOLFCOSE_ENABLE_COUNTERSIGN) + #define WOLFCOSE_COUNTERSIGN_WANT +#elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_COUNTERSIGN) + #define WOLFCOSE_COUNTERSIGN_WANT +#endif +/* Countersignatures implement ECDSA, EdDSA, RSA-PSS, and ML-DSA only; the + * HSS/LMS one-time-state path is not provided, so LMS alone enables nothing. */ +#if defined(WOLFCOSE_HAVE_ECDSA) || defined(WOLFCOSE_HAVE_EDDSA) || \ + defined(WOLFCOSE_HAVE_ED448) || defined(WOLFCOSE_HAVE_RSAPSS) || \ + defined(WOLFCOSE_HAVE_MLDSA) + #define WOLFCOSE_HAVE_COUNTERSIGN_ALG +#endif +#if defined(WOLFCOSE_ENABLE_COUNTERSIGN) && \ + !defined(WOLFCOSE_HAVE_COUNTERSIGN_ALG) + #error "WOLFCOSE_ENABLE_COUNTERSIGN requires an ECDSA, EdDSA, RSA-PSS, or ML-DSA algorithm; HSS/LMS is not supported for countersignatures" +#endif +#if defined(WOLFCOSE_COUNTERSIGN_WANT) && defined(WOLFCOSE_HAVE_COUNTERSIGN_ALG) && \ + (!defined(WOLFCOSE_NO_COUNTERSIGN_SIGN) || \ + !defined(WOLFCOSE_NO_COUNTERSIGN_VERIFY)) + #define WOLFCOSE_COUNTERSIGN + #ifndef WOLFCOSE_NO_COUNTERSIGN_SIGN + #define WOLFCOSE_COUNTERSIGN_SIGN + #endif + #ifndef WOLFCOSE_NO_COUNTERSIGN_VERIFY + #define WOLFCOSE_COUNTERSIGN_VERIFY + #endif +#endif + /* Exact enforcement of RFC 8230's 2048-bit RSA-PSS minimum needs access to * the modulus at the byte boundary. Backend-enabled builds may also carry * software keys, so verify-only builds must export the modulus. */ @@ -436,14 +465,17 @@ extern "C" { defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(HAVE_ECC) && \ !defined(WOLFSSL_EXPORT_INT) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) #error "wolfCOSE RSA-PSS key validation requires WOLFSSL_EXPORT_INT" #endif /* Optional RFC 6979 deterministic ECDSA signing. */ #if defined(WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA) && \ defined(WOLFCOSE_HAVE_ECDSA) && \ - (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN)) && \ + (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN)) && \ !defined(WOLFCOSE_HAVE_DETERMINISTIC_ECDSA) #if !defined(WOLFSSL_ECDSA_DETERMINISTIC_K) && \ !defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) @@ -561,13 +593,17 @@ extern "C" { /* ----- CBOR layer ----- * Encode is required by any sign/encrypt/MAC-create op and by COSE_Key encode; - * decode by any verify/decrypt/MAC-verify op and COSE_Key decode. On by + * decode by any verify/decrypt/MAC-verify op and COSE_Key decode. + * Countersignature creation and verification require both layers because each + * operation decodes its target and encodes a Countersign_structure. On by * default; fail loud if explicitly disabled while still required. */ #if !defined(WOLFCOSE_NO_CBOR_ENCODE) #define WOLFCOSE_CBOR_ENCODE #elif defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_ENCRYPT0_ENCRYPT) || \ defined(WOLFCOSE_MAC0_CREATE) || defined(WOLFCOSE_SIGN_SIGN) || \ defined(WOLFCOSE_ENCRYPT_ENCRYPT) || defined(WOLFCOSE_MAC_CREATE) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) || \ defined(WOLFCOSE_KEY_ENCODE) #error "WOLFCOSE_NO_CBOR_ENCODE conflicts with an enabled encode operation" #endif @@ -576,6 +612,8 @@ extern "C" { #elif defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_ENCRYPT0_DECRYPT) || \ defined(WOLFCOSE_MAC0_VERIFY) || defined(WOLFCOSE_SIGN_VERIFY) || \ defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) || \ defined(WOLFCOSE_KEY_DECODE) #error "WOLFCOSE_NO_CBOR_DECODE conflicts with an enabled decode operation" #endif @@ -589,6 +627,10 @@ extern "C" { #define WOLFCOSE_MAX_SCRATCH_SZ 11264u #elif defined(WOLFCOSE_HAVE_MLDSA) #define WOLFCOSE_MAX_SCRATCH_SZ 8192u + #elif defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFCOSE_COUNTERSIGN_SIGN) + /* A countersignature keeps the 512-byte RSA-4096 signature after the + * Countersign_structure in the same buffer. */ + #define WOLFCOSE_MAX_SCRATCH_SZ 1024u #else #define WOLFCOSE_MAX_SCRATCH_SZ 512u #endif @@ -656,8 +698,15 @@ extern "C" { #error "wolfCOSE: HSS/LMS enabled but WOLFCOSE_MAX_SCRATCH_SZ too small" #endif +/* Countersigning builds the Countersign_structure and then places the signature + * after it in scratch, so the buffer must hold both. */ +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + (WOLFCOSE_MAX_SCRATCH_SZ < (WOLFCOSE_MAX_SIG_SZ + 256u)) + #error "wolfCOSE: countersigning needs WOLFCOSE_MAX_SCRATCH_SZ >= WOLFCOSE_MAX_SIG_SZ + 256" +#endif + #if defined(WOLFCOSE_EXT_SIGN) && !defined(WOLFCOSE_SIGN1_SIGN) && \ - !defined(WOLFCOSE_SIGN_SIGN) + !defined(WOLFCOSE_SIGN_SIGN) && !defined(WOLFCOSE_COUNTERSIGN_SIGN) #error "WOLFCOSE_ENABLE_EXT_SIGN needs a signing op, which needs at least one local signature algorithm; the LEAN_VERIFY profiles are incompatible" #endif diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 53a30c6..91af829 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -139,6 +139,7 @@ extern "C" { #define WOLFCOSE_TAG_SIGN1 18u #define WOLFCOSE_TAG_ENCRYPT0 16u #define WOLFCOSE_TAG_MAC0 17u +#define WOLFCOSE_TAG_COUNTERSIGNATURE 19u #define WOLFCOSE_TAG_SIGN 98u /* Multi-signer */ #define WOLFCOSE_TAG_ENCRYPT 96u /* Multi-recipient encryption */ #define WOLFCOSE_TAG_MAC 97u /* Multi-recipient MAC */ @@ -150,6 +151,10 @@ extern "C" { #define WOLFCOSE_HDR_KID 4 #define WOLFCOSE_HDR_IV 5 #define WOLFCOSE_HDR_PARTIAL_IV 6 +#define WOLFCOSE_HDR_COUNTERSIGNATURE_LEGACY 7 +#define WOLFCOSE_HDR_COUNTERSIGNATURE0_LEGACY 9 +#define WOLFCOSE_HDR_COUNTERSIGNATURE_V2 11 +#define WOLFCOSE_HDR_COUNTERSIGNATURE0_V2 12 #define WOLFCOSE_HDR_EPHEMERAL_KEY (-1) /* Ephemeral COSE_Key for ECDH */ /* @@ -443,6 +448,20 @@ typedef struct WOLFCOSE_SIGNATURE { size_t kidLen; /**< Key ID length */ } WOLFCOSE_SIGNATURE; +/** \brief Full RFC 9338 countersigner configuration. */ +typedef struct WOLFCOSE_COUNTERSIGNATURE { + int32_t algId; /**< Signature algorithm (ES256, EdDSA, etc.) */ + WOLFCOSE_KEY* key; /**< Caller-owned countersigning key */ + const uint8_t* kid; /**< Optional countersigner key identifier */ + size_t kidLen; /**< Key identifier length */ +} WOLFCOSE_COUNTERSIGNATURE; + +/** \brief Abbreviated RFC 9338 countersigner configuration. */ +typedef struct WOLFCOSE_COUNTERSIGNATURE0 { + int32_t algId; /**< Signature algorithm supplied out of band */ + WOLFCOSE_KEY* key; /**< Caller-owned countersigning key */ +} WOLFCOSE_COUNTERSIGNATURE0; + /* ----- * CBOR Encode API (RFC 8949) * @@ -726,7 +745,8 @@ typedef struct WOLFCOSE_CBOR_LABEL { * * Consumes exactly one item. Major types 0 and 1 populate label->val with * isText 0; major type 3 populates label->text / label->textLen with isText 1 - * and no copy. Anything else is WOLFCOSE_E_CBOR_TYPE with the item consumed. + * and no copy. Invalid UTF-8 returns WOLFCOSE_E_CBOR_MALFORMED. Anything else + * is WOLFCOSE_E_CBOR_TYPE with the item consumed. * * \param ctx Decoder context. * \param label Output: decoded label. @@ -1190,6 +1210,84 @@ WOLFCOSE_API int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, const uint8_t** payload, size_t* payloadLen); #endif /* WOLFCOSE_SIGN1_VERIFY */ +/* ----- COSE Countersignature API (RFC 9338) ----- */ + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) +/** + * \brief Add a full V2 countersignature to a tagged COSE message. + * + * Existing full countersignatures are retained and the new value is appended. + * \p out may equal \p in for exact in-place growth. Other overlap is rejected. + * \p scratch must not overlap the input, output, detached payload, external + * AAD, or key identifier buffers. + * A detached target payload must be supplied through + * \p detachedPayload. The counter signature is stored in unprotected header + * parameter 11 as specified by RFC 9338. + */ +WOLFCOSE_API 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); + +/** + * \brief Add an abbreviated V2 countersignature to a tagged COSE message. + * + * The signature is stored in unprotected header parameter 12. The algorithm + * and key identifier remain application context and are not carried in the + * message. Only one abbreviated countersignature may be attached to a target. + * \p scratch must not overlap the input, output, detached payload, or external + * AAD buffers. + */ +WOLFCOSE_API 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); +#endif /* WOLFCOSE_COUNTERSIGN_SIGN */ + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) +/** + * \brief Verify one full countersignature on a tagged COSE message. + * + * \p counterIndex selects a value from V2 header parameter 11 or legacy + * header parameter 7. Zero selects the sole value when the compact + * single-value representation is used. Parsed countersigner headers are + * returned through \p counterHdr. V2 is preferred when both labels exist. An + * algorithm in the unprotected bucket is accepted only when \p key has the + * same non-UNSET alg value, providing the external authentication required by + * RFC 9052. \p scratch must not overlap any input buffer or \p counterHdr. + */ +WOLFCOSE_API 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); + +/** + * \brief Verify an abbreviated countersignature on a tagged COSE message. + * + * The verification algorithm is supplied through \p counterSigner because a + * COSE_Countersignature0 carries only the signature bytes. V2 header + * parameter 12 and legacy header parameter 9 are accepted, with V2 preferred. + * \p scratch must not overlap any input buffer. + */ +WOLFCOSE_API 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); +#endif /* WOLFCOSE_COUNTERSIGN_VERIFY */ + /* ----- COSE_Encrypt0 API (RFC 9052 Section 5.3) ----- */ #if defined(WOLFCOSE_ENCRYPT0_ENCRYPT) diff --git a/scripts/cmdline-test.sh b/scripts/cmdline-test.sh index 0b10637..ac69889 100755 --- a/scripts/cmdline-test.sh +++ b/scripts/cmdline-test.sh @@ -68,6 +68,42 @@ for A in $SIGN_ALGS; do fi done +echo "== Countersignatures: sign -> countersign -> verify both layers ==" +PK="$WORK/primary.key"; CK="$WORK/counter.key" +BASE="$WORK/primary.cose"; COUNTER="$WORK/counter.cose" +COUNTER2="$WORK/counter2.cose"; AAD="$WORK/counter.aad" +printf 'release approval policy' > "$AAD" +if "$TOOL" keygen -a ES256 -o "$PK" >/dev/null 2>&1 && \ + "$TOOL" keygen -a ES256 -o "$CK" >/dev/null 2>&1 && \ + "$TOOL" sign -k "$PK" -a ES256 -i "$IN" -o "$BASE" \ + >/dev/null 2>&1; then + if "$TOOL" countersign -k "$CK" -a ES256 -i "$BASE" \ + -o "$COUNTER" --aad "$AAD" >/dev/null 2>&1 && \ + "$TOOL" counterverify -k "$CK" -i "$COUNTER" --aad "$AAD" \ + >/dev/null 2>&1 && \ + "$TOOL" verify -k "$PK" -i "$COUNTER" >/dev/null 2>&1; then + ok "ES256 countersign and verify both layers" + else + bad "ES256 countersign round-trip" + fi + if "$TOOL" countersign -k "$CK" -a ES256 -i "$COUNTER" \ + -o "$COUNTER2" --aad "$AAD" >/dev/null 2>&1 && \ + "$TOOL" counterverify -k "$CK" -i "$COUNTER2" --index 1 \ + --aad "$AAD" >/dev/null 2>&1; then + ok "ES256 second countersignature index" + else + bad "ES256 second countersignature index" + fi + if "$TOOL" counterverify -k "$CK" -i "$COUNTER" \ + >/dev/null 2>&1; then + bad "countersignature wrong AAD rejected" + else + ok "countersignature wrong AAD rejected" + fi +else + skip "countersignature (ES256)" +fi + # Public-only RSA builds can't sign a decoded key, so skip; the self-test # still covers RSA signing. echo "== RSA-PSS: keygen -> sign -> verify -> self-test ==" @@ -157,6 +193,11 @@ fi echo "== Usage errors must exit non-zero ==" if "$TOOL" >/dev/null 2>&1; then bad "no-args exits non-zero"; else ok "no-args exits non-zero"; fi if "$TOOL" boguscmd >/dev/null 2>&1; then bad "bad command exits non-zero"; else ok "bad command exits non-zero"; fi +if "$TOOL" verify -k x -i y --index 1 >/dev/null 2>&1; then bad "counter-only option on verify exits non-zero"; else ok "counter-only option on verify exits non-zero"; fi +if "$TOOL" sign -k x -a ES256 -i y -o z --aad w >/dev/null 2>&1; then bad "counter-only option on sign exits non-zero"; else ok "counter-only option on sign exits non-zero"; fi +head -c 65536 /dev/zero > "$WORK/oversize.bin" +if "$TOOL" verify -k "$WORK/oversize.bin" -i "$WORK/oversize.bin" 2>&1 | grep -q "File too large"; then ok "oversized input file is rejected, not truncated"; else bad "oversized input file is rejected, not truncated"; fi +if "$TOOL" countersign -k x -a ES256 -i y -o z --index 1 >/dev/null 2>&1; then bad "--index on countersign exits non-zero"; else ok "--index on countersign exits non-zero"; fi echo echo "== Command-line test summary: $PASS passed, $FAIL failed, $SKIP skipped ==" diff --git a/src/wolfcose_alg.c b/src/wolfcose_alg.c index b286ef9..2a1f1a0 100644 --- a/src/wolfcose_alg.c +++ b/src/wolfcose_alg.c @@ -511,7 +511,9 @@ int wolfCose_HmacCheckKeyLen(int32_t alg, size_t keyLen) /* ----- Internal: RSA-PSS hash-to-MGF mapping ----- */ #if defined(WOLFCOSE_HAVE_RSAPSS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) /* RFC 8230 Section 6.1 requires RSA-PSS keys of at least 2048 bits. */ int wolfCose_RsaPssCheckKey(const WOLFCOSE_KEY* key, size_t* modulusLen) @@ -604,7 +606,9 @@ int wolfCose_RsaPssCheckKey(const WOLFCOSE_KEY* key, #if defined(WOLFCOSE_HAVE_RSAPSS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) int wolfCose_HashToMgf(enum wc_HashType hashType, int* mgf) { int ret = WOLFCOSE_SUCCESS; diff --git a/src/wolfcose_cbor.c b/src/wolfcose_cbor.c index 898ab7e..b897dbb 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -699,6 +699,73 @@ int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, return ret; } +static int wolfCose_CBOR_IsUtf8Continuation(uint8_t value) +{ + return ((value >= 0x80u) && (value <= 0xBFu)) ? 1 : 0; +} + +static int wolfCose_CBOR_IsValidUtf8(const uint8_t* text, size_t textLen) +{ + int valid = 1; + size_t i = 0u; + + if ((text == NULL) && (textLen > 0u)) { + valid = 0; + } + + while ((valid != 0) && (i < textLen)) { + uint8_t first = text[i]; + size_t remaining = textLen - i; + + if (first <= 0x7Fu) { + i++; + } + else if ((first >= 0xC2u) && (first <= 0xDFu)) { + if ((remaining < 2u) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0)) { + valid = 0; + } + else { + i += 2u; + } + } + else if ((first >= 0xE0u) && (first <= 0xEFu)) { + if ((remaining < 3u) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 2u]) == 0)) { + valid = 0; + } + else if (((first == 0xE0u) && (text[i + 1u] < 0xA0u)) || + ((first == 0xEDu) && (text[i + 1u] > 0x9Fu))) { + valid = 0; + } + else { + i += 3u; + } + } + else if ((first >= 0xF0u) && (first <= 0xF4u)) { + if ((remaining < 4u) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 2u]) == 0) || + (wolfCose_CBOR_IsUtf8Continuation(text[i + 3u]) == 0)) { + valid = 0; + } + else if (((first == 0xF0u) && (text[i + 1u] < 0x90u)) || + ((first == 0xF4u) && (text[i + 1u] > 0x8Fu))) { + valid = 0; + } + else { + i += 4u; + } + } + else { + valid = 0; + } + } + + return valid; +} + int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) { int ret; @@ -733,9 +800,14 @@ int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) } } else if (item.majorType == WOLFCOSE_CBOR_TSTR) { - label->text = item.data; - label->textLen = item.dataLen; - label->isText = 1u; + if (wolfCose_CBOR_IsValidUtf8(item.data, item.dataLen) == 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + label->text = item.data; + label->textLen = item.dataLen; + label->isText = 1u; + } } else { /* RFC 9052: label = int / tstr, nothing else. */ diff --git a/src/wolfcose_countersign.c b/src/wolfcose_countersign.c new file mode 100644 index 0000000..43b1644 --- /dev/null +++ b/src/wolfcose_countersign.c @@ -0,0 +1,1784 @@ +/* wolfcose_countersign.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + * + * wolfCOSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCOSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/** + * COSE countersignatures. RFC 9338 Section 3 and Section 4. + * Full countersignatures and abbreviated (Countersignature0) over a COSE + * message body. All crypto via wolfCrypt wc_* APIs. Zero allocation. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "wolfcose_internal.h" +/* wolfcose.h (via internal.h) includes ecc.h, ed25519.h, ed448.h, + * wc_mldsa.h (ML-DSA), rsa.h, random.h. Only list headers not pulled in. */ +#include +#include +#include /* XMEMCPY */ +#if defined(WOLFCOSE_HAVE_AESGCM) || defined(WOLFCOSE_HAVE_AESCCM) || \ + defined(WOLFCOSE_HAVE_AESMAC) || defined(WOLFCOSE_KEY_WRAP) + #include +#endif +#ifdef WOLFCOSE_HAVE_HMAC + #include +#endif +#if defined(WOLFCOSE_HAVE_CHACHA20) + #include +#endif +#include +#include + +#if defined(WOLFCOSE_COUNTERSIGN) + +typedef struct WOLFCOSE_COUNTER_TARGET { + uint64_t tag; + const uint8_t* bodyProtected; + size_t bodyProtectedLen; + const uint8_t* payload; + size_t payloadLen; + const uint8_t* other; + size_t otherLen; + size_t otherCount; + size_t mapStart; + size_t mapContentStart; + size_t mapEnd; + size_t mapCount; + size_t fullValueStart; + size_t fullValueEnd; + size_t abbreviatedValueStart; + size_t abbreviatedValueEnd; + size_t legacyFullValueStart; + size_t legacyFullValueEnd; + size_t legacyAbbreviatedValueStart; + size_t legacyAbbreviatedValueEnd; + uint8_t hasFull; + uint8_t hasAbbreviated; + uint8_t hasLegacyFull; + uint8_t hasLegacyAbbreviated; +} WOLFCOSE_COUNTER_TARGET; + +typedef struct WOLFCOSE_COUNTER_LIST_INFO { + size_t count; + size_t contentOffset; + uint8_t isList; +} WOLFCOSE_COUNTER_LIST_INFO; + +typedef struct WOLFCOSE_COUNTER_ATTACH_PLAN { + WOLFCOSE_COUNTER_LIST_INFO listInfo; + size_t oldValueLen; + size_t newValueLen; + size_t counterLen; + size_t delta; + size_t finalLen; + size_t newMapCount; + size_t newMapHeadLen; + uint8_t replacesValue; +} WOLFCOSE_COUNTER_ATTACH_PLAN; + +static int wolfCose_ParseCounterMap(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_COUNTER_TARGET* target, WOLFCOSE_HDR_STATE* hdrState) +{ + int ret; + size_t i; + size_t count = 0u; + WOLFCOSE_HDR_STATE mapState; + + wolfCose_HdrStateInit(&mapState); + target->mapStart = ctx->idx; + ret = wc_CBOR_DecodeMapStart(ctx, &count); + if ((ret == WOLFCOSE_SUCCESS) && + (count > (size_t)WOLFCOSE_MAX_MAP_ITEMS)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + count = 0u; + } + target->mapCount = count; + target->mapContentStart = ctx->idx; + + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + WOLFCOSE_CBOR_LABEL label; + const uint8_t* encodedLabel = NULL; + const uint8_t* value = NULL; + size_t valueLen = 0u; + size_t valueStart; + uint8_t valueType = 0xFFu; + + if ((ctx->cbuf != NULL) && (ctx->idx < ctx->bufSz)) { + encodedLabel = &ctx->cbuf[ctx->idx]; + } + ret = wc_CBOR_DecodeLabel(ctx, &label); + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_CRIT) != 0)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + if ((wolfCose_HdrStateContainsLabel(&mapState, &label) != 0) || + (wolfCose_HdrStateContainsLabel(hdrState, &label) != 0)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + ret = wolfCose_HdrStateAddLabel(&mapState, &label, + encodedLabel); + } + } + valueStart = ctx->idx; + valueType = wc_CBOR_PeekType(ctx); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_SkipItem(ctx, &value, &valueLen); + } + /* Registered countersignature labels carry a fixed major type; a wrong + * type is malformed even when this call does not use that variant. */ + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_COUNTERSIGNATURE_V2) != 0)) { + if ((target->hasFull != 0u) || + (valueType != WOLFCOSE_CBOR_ARRAY)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + target->hasFull = 1u; + target->fullValueStart = valueStart; + target->fullValueEnd = ctx->idx; + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_COUNTERSIGNATURE0_V2) != 0)) { + if ((target->hasAbbreviated != 0u) || + (valueType != WOLFCOSE_CBOR_BSTR)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + target->hasAbbreviated = 1u; + target->abbreviatedValueStart = valueStart; + target->abbreviatedValueEnd = ctx->idx; + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_COUNTERSIGNATURE_LEGACY) != 0)) { + if ((target->hasLegacyFull != 0u) || + (valueType != WOLFCOSE_CBOR_ARRAY)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + target->hasLegacyFull = 1u; + target->legacyFullValueStart = valueStart; + target->legacyFullValueEnd = ctx->idx; + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_COUNTERSIGNATURE0_LEGACY) != 0)) { + if ((target->hasLegacyAbbreviated != 0u) || + (valueType != WOLFCOSE_CBOR_BSTR)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + target->hasLegacyAbbreviated = 1u; + target->legacyAbbreviatedValueStart = valueStart; + target->legacyAbbreviatedValueEnd = ctx->idx; + } + } + else { + /* No action required */ + } + (void)value; + (void)valueLen; + } + if (ret == WOLFCOSE_SUCCESS) { + target->mapEnd = ctx->idx; + } + return ret; +} + +static int wolfCose_ParseCounterTarget(const uint8_t* in, size_t inSz, + const uint8_t* detachedPayload, size_t detachedLen, + WOLFCOSE_COUNTER_TARGET* target) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR hdr; + WOLFCOSE_HDR_STATE hdrState; + size_t arrayCount = 0u; + size_t expectedCount = 0u; + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + if ((in == NULL) || (target == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(target, 0, sizeof(*target)); + (void)XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((ctx.idx >= ctx.bufSz) || + (wc_CBOR_PeekType(&ctx) != WOLFCOSE_CBOR_TAG))) { + ret = WOLFCOSE_E_COSE_BAD_TAG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeTag(&ctx, &target->tag); + } + if (ret == WOLFCOSE_SUCCESS) { + switch (target->tag) { + case WOLFCOSE_TAG_ENCRYPT0: + expectedCount = 3u; + break; + case WOLFCOSE_TAG_MAC0: + case WOLFCOSE_TAG_SIGN1: + case WOLFCOSE_TAG_SIGN: + case WOLFCOSE_TAG_ENCRYPT: + expectedCount = 4u; + break; + case WOLFCOSE_TAG_MAC: + expectedCount = 5u; + break; + default: + ret = WOLFCOSE_E_COSE_BAD_TAG; + break; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &arrayCount); + if ((ret == WOLFCOSE_SUCCESS) && (arrayCount != expectedCount)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, &target->bodyProtected, + &target->bodyProtectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeProtectedHdr(target->bodyProtected, + target->bodyProtectedLen, &hdr, &hdrState); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE_V2) != 0) || + (wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE0_V2) != 0) || + (wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE_LEGACY) != 0) || + (wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE0_LEGACY) != 0))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_ParseCounterMap(&ctx, target, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ctx.idx = target->mapStart; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != target->mapEnd)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + if ((ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + ctx.idx++; + if (detachedPayload == NULL) { + ret = WOLFCOSE_E_DETACHED_PAYLOAD; + } + else { + target->payload = detachedPayload; + target->payloadLen = detachedLen; + } + } + else { + ret = wc_CBOR_DecodeBstr(&ctx, &target->payload, + &target->payloadLen); + } + } + + if ((ret == WOLFCOSE_SUCCESS) && + ((target->tag == WOLFCOSE_TAG_SIGN1) || + (target->tag == WOLFCOSE_TAG_MAC0) || + (target->tag == WOLFCOSE_TAG_MAC))) { + ret = wc_CBOR_DecodeBstr(&ctx, &target->other, &target->otherLen); + if (ret == WOLFCOSE_SUCCESS) { + target->otherCount = 1u; + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((target->tag == WOLFCOSE_TAG_SIGN) || + (target->tag == WOLFCOSE_TAG_ENCRYPT) || + (target->tag == WOLFCOSE_TAG_MAC))) { + size_t childCount = 0u; + size_t i; + + /* RFC 9338 Section 3.3 counts bstr fields in the selected target, + * not bstr values nested inside an aggregate child array. COSE_Sign + * and COSE_Encrypt have no direct bstr after the payload or + * ciphertext. COSE_Mac's direct tag bstr is captured above. Appendix + * A.1.1 exercises this COSE_Sign form with other_fields absent. */ + ret = wc_CBOR_DecodeArrayStart(&ctx, &childCount); + if ((ret == WOLFCOSE_SUCCESS) && (childCount == 0u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < childCount); i++) { + if (target->tag == WOLFCOSE_TAG_SIGN) { + ret = wolfCose_DecodeSkippedSignature(&ctx); + } + else { + int32_t recipientAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedRecipient(&ctx, &recipientAlg); + } + } + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + return ret; +} + +static int wolfCose_ValidateFullCounter(const uint8_t* in, size_t inSz) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR hdr; + WOLFCOSE_HDR_STATE hdrState; + const uint8_t* protectedData = NULL; + const uint8_t* sig = NULL; + size_t protectedLen = 0u; + size_t sigLen = 0u; + size_t count = 0u; + + (void)XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + if ((ret == WOLFCOSE_SUCCESS) && (count != 3u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, + &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, &sig, &sigLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + (void)sig; + (void)sigLen; + return ret; +} + +static int wolfCose_CounterListInfo(const uint8_t* in, size_t inSz, + WOLFCOSE_COUNTER_LIST_INFO* info) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 0u; + size_t i; + + if ((in == NULL) || (info == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(info, 0, sizeof(*info)); + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((count == 0u) || (ctx.idx >= ctx.bufSz))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_BSTR)) { + ret = wolfCose_ValidateFullCounter(in, inSz); + if (ret == WOLFCOSE_SUCCESS) { + info->count = 1u; + info->contentOffset = 0u; + info->isList = 0u; + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_ARRAY)) { + if (count > (size_t)WOLFCOSE_MAX_MAP_ITEMS) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + info->contentOffset = ctx.idx; + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + const uint8_t* value = NULL; + size_t valueLen = 0u; + + ret = wc_CBOR_SkipItem(&ctx, &value, &valueLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_ValidateFullCounter(value, valueLen); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + info->count = count; + info->isList = 1u; + } + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + /* No action required */ + } + return ret; +} + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) +static int wolfCose_SelectFullCounter(const uint8_t* in, size_t inSz, + size_t counterIndex, const uint8_t** selected, size_t* selectedLen) +{ + int ret; + WOLFCOSE_COUNTER_LIST_INFO info; + + (void)XMEMSET(&info, 0, sizeof(info)); + if ((selected == NULL) || (selectedLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *selected = NULL; + *selectedLen = 0u; + ret = wolfCose_CounterListInfo(in, inSz, &info); + } + if ((ret == WOLFCOSE_SUCCESS) && (counterIndex >= info.count)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if ((ret == WOLFCOSE_SUCCESS) && (info.isList == 0u)) { + *selected = in; + *selectedLen = inSz; + } + else if (ret == WOLFCOSE_SUCCESS) { + WOLFCOSE_CBOR_CTX ctx; + size_t count = 0u; + size_t i; + + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i <= counterIndex); i++) { + ret = wc_CBOR_SkipItem(&ctx, selected, selectedLen); + } + (void)count; + } + else { + /* No action required */ + } + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_VERIFY */ + +static int wolfCose_BuildCounterStructure( + const WOLFCOSE_COUNTER_TARGET* target, + const uint8_t* signProtected, size_t signProtectedLen, + const uint8_t* extAad, size_t extAadLen, uint8_t abbreviated, + uint8_t legacy, uint8_t* scratch, size_t scratchSz, size_t* structLen) +{ + static const uint8_t contextCounter[] = "CounterSignature"; + static const uint8_t contextCounter0[] = "CounterSignature0"; + static const uint8_t contextCounterV2[] = "CounterSignatureV2"; + static const uint8_t contextCounter0V2[] = "CounterSignature0V2"; + int ret; + WOLFCOSE_CBOR_CTX ctx; + const uint8_t* context; + size_t contextLen; + size_t arrayCount; + + if ((target == NULL) || (scratch == NULL) || (structLen == NULL) || + ((extAad == NULL) && (extAadLen != 0u)) || + ((abbreviated == 0u) && (signProtected == NULL))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + if (legacy != 0u) { + if (abbreviated != 0u) { + context = contextCounter0; + contextLen = sizeof(contextCounter0) - 1u; + arrayCount = 4u; + } + else { + context = contextCounter; + contextLen = sizeof(contextCounter) - 1u; + arrayCount = 5u; + } + } + else if (abbreviated != 0u) { + if (target->otherCount != 0u) { + context = contextCounter0V2; + contextLen = sizeof(contextCounter0V2) - 1u; + arrayCount = 5u; + } + else { + context = contextCounter0; + contextLen = sizeof(contextCounter0) - 1u; + arrayCount = 4u; + } + } + else { + if (target->otherCount != 0u) { + context = contextCounterV2; + contextLen = sizeof(contextCounterV2) - 1u; + arrayCount = 6u; + } + else { + context = contextCounter; + contextLen = sizeof(contextCounter) - 1u; + arrayCount = 5u; + } + } + ret = wc_CBOR_EncoderInit(&ctx, scratch, scratchSz); + } + + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, arrayCount); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, context, contextLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, target->bodyProtected, + target->bodyProtectedLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (abbreviated == 0u)) { + ret = wc_CBOR_EncodeBstr(&ctx, signProtected, signProtectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, extAad, extAadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, target->payload, target->payloadLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (legacy == 0u) && + (target->otherCount != 0u)) { + ret = wc_CBOR_EncodeArrayStart(&ctx, target->otherCount); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, target->other, target->otherLen); + } + } + if (ret == WOLFCOSE_SUCCESS) { + *structLen = ctx.idx; + } + return ret; +} + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) +static int wolfCose_FullCounterSize(size_t protectedLen, size_t kidLen, + size_t sigLen, size_t* encodedLen) +{ + int ret; + size_t itemLen = 0u; + size_t total = 1u; + + ret = wolfCose_CborStringSize(protectedLen, &itemLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, itemLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&total, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (kidLen != 0u)) { + ret = wolfCose_SizeAdd(&total, 1u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CborStringSize(kidLen, &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) { + *encodedLen = total; + } + return ret; +} + +static int wolfCose_EncodeFullCounter(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t* protectedData, size_t protectedLen, + const uint8_t* kid, size_t kidLen, + const uint8_t* sig, size_t sigLen) +{ + int ret; + size_t mapCount = 0u; + + if (kidLen != 0u) { + mapCount = 1u; + } + ret = wc_CBOR_EncodeArrayStart(ctx, 3u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, protectedData, protectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(ctx, mapCount); + } + if ((ret == WOLFCOSE_SUCCESS) && (kidLen != 0u)) { + ret = wc_CBOR_EncodeInt(ctx, WOLFCOSE_HDR_KID); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kid, kidLen); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, sig, sigLen); + } + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_SIGN */ + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) +static int wolfCose_CounterSignTbs(WOLFCOSE_KEY* key, int32_t alg, + const uint8_t* tbs, size_t tbsLen, + uint8_t* sig, size_t sigSz, size_t* sigLen, WC_RNG* rng) +{ + int ret = WOLFCOSE_SUCCESS; + uint8_t hashBuf[WC_MAX_DIGEST_SIZE]; + + if ((key == NULL) || (tbs == NULL) || (sig == NULL) || + (sigLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *sigLen = 0u; + } +#if defined(WOLFCOSE_EXT_SIGN) + if ((ret == WOLFCOSE_SUCCESS) && (key->signCb == NULL) && (rng == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#else + if ((ret == WOLFCOSE_SUCCESS) && (rng == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#endif +#ifdef WOLFCOSE_CHECK_WORD32_LEN + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_LenFitsWord32(tbsLen) == 0) || + (wolfCose_LenFitsWord32(sigSz) == 0))) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_KeyCanSign(key) == 0)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + if ((ret == WOLFCOSE_SUCCESS) && + (key->alg != WOLFCOSE_ALG_UNSET) && (key->alg != alg)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + +#if defined(WOLFCOSE_EXT_SIGN) + if ((ret == WOLFCOSE_SUCCESS) && (key->signCb != NULL)) { + ret = wolfCose_ExtSign(key, alg, tbs, tbsLen, + sig, sigSz, sigLen); + } + else +#endif +#ifdef WOLFCOSE_HAVE_ECDSA + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_ES256) || (alg == WOLFCOSE_ALG_ES384) || + (alg == WOLFCOSE_ALG_ES512))) { + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int digestSz = 0; + int32_t expectedCrv; + size_t coordSz = 0u; + + 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->kty != WOLFCOSE_KTY_EC2) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else if (key->crv != expectedCrv) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + ret = wolfCose_EccKeyCheckCurve(key->crv, key->key.ecc); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AlgToHashType(alg, &hashType); + } + if (ret == WOLFCOSE_SUCCESS) { + digestSz = wc_HashGetDigestSize(hashType); + if (digestSz <= 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_HASH, -1, + ret = wc_Hash(hashType, tbs, (word32)tbsLen, + hashBuf, (word32)digestSz)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvKeySize(key->crv, &coordSz); + } + if (ret == WOLFCOSE_SUCCESS) { + *sigLen = sigSz; + ret = wolfCose_EccSignRaw(hashBuf, (size_t)digestSz, + sig, sigLen, coordSz, hashType, + rng, key->key.ecc); + } + } + else +#endif +#if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448) + if ((ret == WOLFCOSE_SUCCESS) && (alg == WOLFCOSE_ALG_EDDSA)) { + word32 outLen = (word32)sigSz; + + if (key->kty != WOLFCOSE_KTY_OKP) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } +#ifdef WOLFCOSE_HAVE_EDDSA + if ((ret == WOLFCOSE_SUCCESS) && + (key->crv == WOLFCOSE_CRV_ED25519)) { + if (key->key.ed25519 == NULL) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED25519_SIGN, -1, + ret = wc_ed25519_sign_msg(tbs, (word32)tbsLen, + sig, &outLen, + key->key.ed25519)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_ED448 + if ((ret == WOLFCOSE_SUCCESS) && + (key->crv == WOLFCOSE_CRV_ED448)) { + if (key->key.ed448 == NULL) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED448_SIGN, -1, + ret = wc_ed448_sign_msg(tbs, (word32)tbsLen, + sig, &outLen, + key->key.ed448, NULL, 0)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + } + else +#endif + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + /* No action required */ + } + if (ret == WOLFCOSE_SUCCESS) { + *sigLen = (size_t)outLen; + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_RSAPSS + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_PS256) || (alg == WOLFCOSE_ALG_PS384) || + (alg == WOLFCOSE_ALG_PS512))) { + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int digestSz = 0; + int mgf = 0; + + ret = wolfCose_RsaPssCheckKey(key, NULL); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AlgToHashType(alg, &hashType); + } + if (ret == WOLFCOSE_SUCCESS) { + digestSz = wc_HashGetDigestSize(hashType); + if (digestSz <= 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_HASH, -1, + ret = wc_Hash(hashType, tbs, (word32)tbsLen, + hashBuf, (word32)digestSz)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_HashToMgf(hashType, &mgf); + } + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_RSA_SSL_SIGN, -1, + ret = wc_RsaPSS_Sign_ex(hashBuf, (word32)digestSz, + sig, (word32)sigSz, + hashType, mgf, digestSz, + key->key.rsa, rng)); + if (ret <= 0) { + ret = WOLFCOSE_E_CRYPTO; + } + else { + *sigLen = (size_t)ret; + ret = WOLFCOSE_SUCCESS; + } + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_MLDSA + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_ML_DSA_44) || + (alg == WOLFCOSE_ALG_ML_DSA_65) || + (alg == WOLFCOSE_ALG_ML_DSA_87))) { + word32 outLen = (word32)sigSz; + + ret = wolfCose_MlDsaCheckKey(key, alg); + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_MLDSA_SIGN, -1, + ret = wc_MlDsaKey_SignCtx(key->key.mldsa, NULL, 0, + sig, &outLen, tbs, (word32)tbsLen, rng)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + else { + *sigLen = (size_t)outLen; + } + } + } + else +#endif + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + /* No action required */ + } + + (void)wolfCose_ForceZero(hashBuf, sizeof(hashBuf)); + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_SIGN */ + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) +static int wolfCose_CounterVerifyTbs(const WOLFCOSE_KEY* key, int32_t alg, + const uint8_t* tbs, size_t tbsLen, + const uint8_t* sig, size_t sigLen, + uint8_t* scratch, size_t scratchSz) +{ + int ret = WOLFCOSE_SUCCESS; + uint8_t hashBuf[WC_MAX_DIGEST_SIZE]; + + if ((key == NULL) || (tbs == NULL) || (sig == NULL) || + (scratch == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#ifdef WOLFCOSE_CHECK_WORD32_LEN + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_LenFitsWord32(tbsLen) == 0) || + (wolfCose_LenFitsWord32(sigLen) == 0) || + (wolfCose_LenFitsWord32(scratchSz) == 0))) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && + (key->alg != WOLFCOSE_ALG_UNSET) && (key->alg != alg)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + +#if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448) + if ((ret == WOLFCOSE_SUCCESS) && (alg == WOLFCOSE_ALG_EDDSA)) { + int verified = 0; + + if (key->kty != WOLFCOSE_KTY_OKP) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } +#ifdef WOLFCOSE_HAVE_EDDSA + if ((ret == WOLFCOSE_SUCCESS) && + (key->crv == WOLFCOSE_CRV_ED25519)) { + if ((key->attachedType != WOLFCOSE_ATT_ED25519) || + (key->key.ed25519 == NULL)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + ed25519_key* ed25519Key = key->key.ed25519; + + INJECT_FAILURE(WOLF_FAIL_ED25519_VERIFY, -1, + ret = wc_ed25519_verify_msg(sig, (word32)sigLen, + tbs, (word32)tbsLen, &verified, ed25519Key)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_ED448 + if ((ret == WOLFCOSE_SUCCESS) && + (key->crv == WOLFCOSE_CRV_ED448)) { + if ((key->attachedType != WOLFCOSE_ATT_ED448) || + (key->key.ed448 == NULL)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + ed448_key* ed448Key = key->key.ed448; + + INJECT_FAILURE(WOLF_FAIL_ED448_VERIFY, -1, + ret = wc_ed448_verify_msg(sig, (word32)sigLen, + tbs, (word32)tbsLen, &verified, ed448Key, + NULL, 0)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + } + else +#endif + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + /* No action required */ + } + if ((ret == WOLFCOSE_SUCCESS) && (verified != 1)) { + ret = WOLFCOSE_E_COSE_SIG_FAIL; + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_ECDSA + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_ES256) || (alg == WOLFCOSE_ALG_ES384) || + (alg == WOLFCOSE_ALG_ES512))) { + ecc_key* eccKey = NULL; + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int digestSz = 0; + int verified = 0; + int32_t expectedCrv; + size_t coordSz = 0u; + + 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->kty != WOLFCOSE_KTY_EC2) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else if (key->crv != expectedCrv) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + eccKey = key->key.ecc; + ret = wolfCose_EccKeyCheckCurve(key->crv, eccKey); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AlgToHashType(alg, &hashType); + } + if (ret == WOLFCOSE_SUCCESS) { + digestSz = wc_HashGetDigestSize(hashType); + if (digestSz <= 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_HASH, -1, + ret = wc_Hash(hashType, tbs, (word32)tbsLen, + hashBuf, (word32)digestSz)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvKeySize(key->crv, &coordSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccVerifyRaw(sig, sigLen, hashBuf, + (size_t)digestSz, coordSz, eccKey, &verified); + } + if ((ret == WOLFCOSE_SUCCESS) && (verified != 1)) { + ret = WOLFCOSE_E_COSE_SIG_FAIL; + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_RSAPSS + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_PS256) || (alg == WOLFCOSE_ALG_PS384) || + (alg == WOLFCOSE_ALG_PS512))) { + RsaKey* rsaKey = NULL; + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int digestSz = 0; + int mgf = 0; + + ret = wolfCose_RsaPssCheckKey(key, NULL); + if (ret == WOLFCOSE_SUCCESS) { + rsaKey = key->key.rsa; + ret = wolfCose_AlgToHashType(alg, &hashType); + } + if (ret == WOLFCOSE_SUCCESS) { + digestSz = wc_HashGetDigestSize(hashType); + if (digestSz <= 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_HASH, -1, + ret = wc_Hash(hashType, tbs, (word32)tbsLen, + hashBuf, (word32)digestSz)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_HashToMgf(hashType, &mgf); + } + if ((ret == WOLFCOSE_SUCCESS) && (sigLen > scratchSz)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMCPY(scratch, sig, sigLen); + INJECT_FAILURE(WOLF_FAIL_RSA_SSL_VERIFY, -1, + ret = wc_RsaPSS_VerifyCheck(scratch, (word32)sigLen, + scratch, (word32)scratchSz, hashBuf, + (word32)digestSz, hashType, mgf, rsaKey)); + if (ret < 0) { + ret = WOLFCOSE_E_COSE_SIG_FAIL; + } + else { + ret = WOLFCOSE_SUCCESS; + } + } + } + else +#endif +#ifdef WOLFCOSE_HAVE_MLDSA + if ((ret == WOLFCOSE_SUCCESS) && + ((alg == WOLFCOSE_ALG_ML_DSA_44) || + (alg == WOLFCOSE_ALG_ML_DSA_65) || + (alg == WOLFCOSE_ALG_ML_DSA_87))) { + int verified = 0; + + ret = wolfCose_MlDsaCheckKey(key, alg); + if (ret == WOLFCOSE_SUCCESS) { + wc_MlDsaKey* mldsaKey = key->key.mldsa; + + INJECT_FAILURE(WOLF_FAIL_MLDSA_VERIFY, -1, + ret = wc_MlDsaKey_VerifyCtx(mldsaKey, + sig, (word32)sigLen, NULL, 0, + tbs, (word32)tbsLen, &verified)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if ((ret == WOLFCOSE_SUCCESS) && (verified != 1)) { + ret = WOLFCOSE_E_COSE_SIG_FAIL; + } + } + else +#endif + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + /* No action required */ + } + + (void)wolfCose_ForceZero(hashBuf, sizeof(hashBuf)); + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_VERIFY */ + +static int wolfCose_RangesOverlap(const uint8_t* a, size_t aLen, + const uint8_t* b, size_t bLen) +{ + int overlaps = 0; + + if ((aLen != 0u) && (bLen != 0u)) { + size_t i; + + for (i = 0u; (i < aLen) && (overlaps == 0); i++) { + if (&a[i] == b) { + overlaps = 1; + } + } + for (i = 0u; (i < bLen) && (overlaps == 0); i++) { + if (&b[i] == a) { + overlaps = 1; + } + } + } + return overlaps; +} + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) +/* Every check and size that attaching depends on follows from the expected + * signature length alone, so this runs before any signer: a request that would + * fail here must not consume one-time-state (HSS/LMS or delegated) key material. */ +static int wolfCose_PlanAttachCounter(const WOLFCOSE_COUNTER_TARGET* target, + const uint8_t* in, size_t inSz, size_t protectedLen, size_t kidLen, + size_t sigLen, uint8_t abbreviated, size_t outSz, + WOLFCOSE_COUNTER_ATTACH_PLAN* plan) +{ + int ret = WOLFCOSE_SUCCESS; + size_t oldMapHeadLen; + + (void)XMEMSET(plan, 0, sizeof(*plan)); + oldMapHeadLen = target->mapContentStart - target->mapStart; + plan->newMapCount = target->mapCount; + plan->finalLen = inSz; + + /* Verification prefers the V2 label when both versions are present, so + * attaching beside a legacy (RFC 8152) countersignature would make that + * signature unreachable; refuse rather than silently hide it. */ + if (abbreviated != 0u) { + if ((target->hasAbbreviated != 0u) || + (target->hasLegacyAbbreviated != 0u)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + else { + ret = wolfCose_CborStringSize(sigLen, &plan->counterLen); + } + } + else { + if (target->hasLegacyFull != 0u) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + else { + ret = wolfCose_FullCounterSize(protectedLen, kidLen, sigLen, + &plan->counterLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (target->hasFull != 0u)) { + plan->oldValueLen = target->fullValueEnd - target->fullValueStart; + ret = wolfCose_CounterListInfo( + &in[target->fullValueStart], plan->oldValueLen, + &plan->listInfo); + if (ret == WOLFCOSE_SUCCESS) { + plan->replacesValue = 1u; + if (plan->listInfo.isList != 0u) { + size_t contentLen = + plan->oldValueLen - plan->listInfo.contentOffset; + + if (plan->listInfo.count >= + (size_t)WOLFCOSE_MAX_MAP_ITEMS) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + else { + plan->newValueLen = wolfCose_CborHeadSize( + (uint64_t)(plan->listInfo.count + 1u)); + ret = wolfCose_SizeAdd(&plan->newValueLen, + contentLen); + } + } + else { + plan->newValueLen = wolfCose_CborHeadSize(2u); + ret = wolfCose_SizeAdd(&plan->newValueLen, + plan->oldValueLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&plan->newValueLen, + plan->counterLen); + } + } + } + } + + if ((ret == WOLFCOSE_SUCCESS) && (plan->replacesValue == 0u)) { + if (target->mapCount >= (size_t)WOLFCOSE_MAX_MAP_ITEMS) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + else { + plan->newMapCount++; + plan->delta = 1u; + ret = wolfCose_SizeAdd(&plan->delta, plan->counterLen); + } + } + else if (ret == WOLFCOSE_SUCCESS) { + plan->delta = plan->newValueLen - plan->oldValueLen; + } + else { + /* No action required */ + } + + plan->newMapHeadLen = wolfCose_CborHeadSize((uint64_t)plan->newMapCount); + if ((ret == WOLFCOSE_SUCCESS) && (plan->replacesValue == 0u)) { + ret = wolfCose_SizeAdd(&plan->delta, + plan->newMapHeadLen - oldMapHeadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SizeAdd(&plan->finalLen, plan->delta); + } + if ((ret == WOLFCOSE_SUCCESS) && (plan->finalLen > outSz)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + return ret; +} + +static int wolfCose_AttachCounter(const WOLFCOSE_COUNTER_TARGET* target, + const WOLFCOSE_COUNTER_ATTACH_PLAN* plan, + const uint8_t* in, size_t inSz, + const uint8_t* protectedData, size_t protectedLen, + const uint8_t* kid, size_t kidLen, + const uint8_t* sig, size_t sigLen, uint8_t abbreviated, + uint8_t* out, size_t* outLen) +{ + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_CBOR_CTX ctx; + size_t suffixLen = inSz - target->mapEnd; + + if (out != in) { + (void)XMEMMOVE(out, in, inSz); + } + (void)XMEMMOVE(&out[target->mapEnd + plan->delta], + &out[target->mapEnd], suffixLen); + + if (plan->replacesValue != 0u) { + size_t afterValueLen = target->mapEnd - target->fullValueEnd; + size_t newValueEnd = target->fullValueStart + plan->newValueLen; + size_t headLen; + + (void)XMEMMOVE(&out[newValueEnd], &out[target->fullValueEnd], + afterValueLen); + if (plan->listInfo.isList != 0u) { + size_t contentLen = + plan->oldValueLen - plan->listInfo.contentOffset; + headLen = wolfCose_CborHeadSize( + (uint64_t)(plan->listInfo.count + 1u)); + (void)XMEMMOVE(&out[target->fullValueStart + headLen], + &out[target->fullValueStart + plan->listInfo.contentOffset], + contentLen); + ret = wc_CBOR_EncoderInit(&ctx, + &out[target->fullValueStart], plan->newValueLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, + plan->listInfo.count + 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ctx.idx = headLen + contentLen; + } + } + else { + headLen = wolfCose_CborHeadSize(2u); + (void)XMEMMOVE(&out[target->fullValueStart + headLen], + &out[target->fullValueStart], plan->oldValueLen); + ret = wc_CBOR_EncoderInit(&ctx, + &out[target->fullValueStart], plan->newValueLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ctx.idx = headLen + plan->oldValueLen; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EncodeFullCounter(&ctx, protectedData, + protectedLen, kid, kidLen, sig, sigLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != plan->newValueLen)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + else { + size_t contentLen = target->mapEnd - target->mapContentStart; + size_t newContentStart = target->mapStart + plan->newMapHeadLen; + size_t appendOffset = newContentStart + contentLen; + int64_t label; + + if (abbreviated != 0u) { + label = WOLFCOSE_HDR_COUNTERSIGNATURE0_V2; + } + else { + label = WOLFCOSE_HDR_COUNTERSIGNATURE_V2; + } + + (void)XMEMMOVE(&out[newContentStart], + &out[target->mapContentStart], contentLen); + ret = wc_CBOR_EncoderInit(&ctx, &out[target->mapStart], + plan->finalLen - target->mapStart); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, plan->newMapCount); + } + if (ret == WOLFCOSE_SUCCESS) { + ctx.idx = appendOffset - target->mapStart; + ret = wc_CBOR_EncodeInt(&ctx, label); + } + if ((ret == WOLFCOSE_SUCCESS) && (abbreviated != 0u)) { + ret = wc_CBOR_EncodeBstr(&ctx, sig, sigLen); + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EncodeFullCounter(&ctx, protectedData, + protectedLen, kid, kidLen, sig, sigLen); + } + else { + /* No action required */ + } + if ((ret == WOLFCOSE_SUCCESS) && + (ctx.idx != ((target->mapEnd + plan->delta) - target->mapStart))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + + if (ret == WOLFCOSE_SUCCESS) { + *outLen = plan->finalLen; + } + return ret; +} + +static int wolfCose_AddCounterCommon(WOLFCOSE_KEY* key, int32_t alg, + const uint8_t* kid, size_t kidLen, uint8_t abbreviated, + 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) +{ + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_COUNTER_TARGET target; + uint8_t protectedBuf[WOLFCOSE_PROTECTED_HDR_MAX]; + size_t protectedLen = 0u; + size_t sigLen = 0u; + size_t actualSigLen = 0u; + size_t tbsLen = 0u; + uint8_t* sig; + int scratchAliases = 0; + int lensOk = 1; + WOLFCOSE_COUNTER_ATTACH_PLAN plan; + + if ((key == NULL) || (in == NULL) || (scratch == NULL) || + (out == NULL) || (outLen == NULL) || + ((kid == NULL) && (kidLen != 0u)) || + ((kid != NULL) && (kidLen == 0u)) || + ((detachedPayload == NULL) && (detachedLen != 0u)) || + ((extAad == NULL) && (extAadLen != 0u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *outLen = 0u; + } + /* Countersignatures have no HSS/LMS one-time-state path; refuse the + * algorithm before any signer, delegated or local, can be reached. */ + if ((ret == WOLFCOSE_SUCCESS) && (alg == WOLFCOSE_ALG_HSS_LMS)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } +#ifdef WOLFCOSE_CHECK_WORD32_LEN + /* Refuse lengths wolfCrypt cannot take before any per-byte overlap walk + * or scratch zeroing could be driven by them. */ + if ((wolfCose_LenFitsWord32(inSz) == 0) || + (wolfCose_LenFitsWord32(detachedLen) == 0) || + (wolfCose_LenFitsWord32(extAadLen) == 0) || + (wolfCose_LenFitsWord32(scratchSz) == 0) || + (wolfCose_LenFitsWord32(outSz) == 0)) { + lensOk = 0; + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_INVALID_ARG; + } + } +#endif + if ((lensOk != 0) && (scratch != NULL) && + (((in != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, in, inSz) != 0)) || + ((out != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, out, outSz) != 0)) || + ((kid != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, kid, kidLen) != 0)) || + ((detachedPayload != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + detachedPayload, detachedLen) != 0)) || + ((extAad != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + extAad, extAadLen) != 0)))) { + scratchAliases = 1; + } + if ((ret == WOLFCOSE_SUCCESS) && (scratchAliases != 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if ((ret == WOLFCOSE_SUCCESS) && (out != in) && + (wolfCose_RangesOverlap(in, inSz, out, outSz) != 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + /* kid is read while the countersignature is encoded into out; an in-place + * attach rewrites out first, so a kid aliasing it would read stale bytes. */ + if ((ret == WOLFCOSE_SUCCESS) && (kid != NULL) && + (wolfCose_RangesOverlap(kid, kidLen, out, outSz) != 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_ParseCounterTarget(in, inSz, detachedPayload, + detachedLen, &target); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_SignSigLen(key, alg, &sigLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (sigLen > scratchSz)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + if ((ret == WOLFCOSE_SUCCESS) && (abbreviated == 0u)) { + ret = wolfCose_EncodeProtectedHdr(alg, protectedBuf, + sizeof(protectedBuf), &protectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_BuildCounterStructure(&target, + (abbreviated != 0u) ? NULL : protectedBuf, protectedLen, + extAad, extAadLen, abbreviated, + 0u, scratch, scratchSz - sigLen, &tbsLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_PlanAttachCounter(&target, in, inSz, protectedLen, + kidLen, sigLen, abbreviated, outSz, &plan); + } + if (ret == WOLFCOSE_SUCCESS) { + sig = &scratch[tbsLen]; + ret = wolfCose_CounterSignTbs(key, alg, scratch, tbsLen, + sig, sigLen, &actualSigLen, rng); + } + else { + sig = NULL; + } + if ((ret == WOLFCOSE_SUCCESS) && (actualSigLen != sigLen)) { + ret = WOLFCOSE_E_CRYPTO; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AttachCounter(&target, &plan, in, inSz, + protectedBuf, protectedLen, kid, kidLen, sig, sigLen, + abbreviated, out, outLen); + } + + (void)wolfCose_ForceZero(protectedBuf, sizeof(protectedBuf)); + if ((scratch != NULL) && (scratchAliases == 0) && (lensOk != 0)) { + (void)wolfCose_ForceZero(scratch, scratchSz); + } + if ((ret != WOLFCOSE_SUCCESS) && (outLen != NULL)) { + *outLen = 0u; + } + return ret; +} + +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) +{ + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if (counterSigner == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + WOLFCOSE_KEY* key = counterSigner->key; + + ret = wolfCose_AddCounterCommon(key, + counterSigner->algId, counterSigner->kid, + counterSigner->kidLen, 0u, in, inSz, + detachedPayload, detachedLen, extAad, extAadLen, + scratch, scratchSz, out, outSz, outLen, rng); + } + return ret; +} + +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) +{ + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if (counterSigner == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + WOLFCOSE_KEY* key = counterSigner->key; + + ret = wolfCose_AddCounterCommon(key, + counterSigner->algId, NULL, 0u, 1u, in, inSz, + detachedPayload, detachedLen, extAad, extAadLen, + scratch, scratchSz, out, outSz, outLen, rng); + } + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_SIGN */ + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) +static int wolfCose_DecodeFullCounter(const uint8_t* in, size_t inSz, + WOLFCOSE_HDR* hdr, const uint8_t** protectedData, + size_t* protectedLen, const uint8_t** sig, size_t* sigLen, + int* algProtected) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR_STATE hdrState; + size_t count = 0u; + + (void)XMEMSET(hdr, 0, sizeof(*hdr)); + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + if ((ret == WOLFCOSE_SUCCESS) && (count != 3u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, protectedData, protectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeProtectedHdr(*protectedData, *protectedLen, + hdr, &hdrState); + if (ret == WOLFCOSE_SUCCESS) { + *algProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, sig, sigLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + return ret; +} + +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) +{ + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_COUNTER_TARGET target; + const uint8_t* selected = NULL; + const uint8_t* protectedData = NULL; + const uint8_t* sig = NULL; + size_t selectedLen = 0u; + size_t protectedLen = 0u; + size_t sigLen = 0u; + size_t tbsLen = 0u; + int algProtected = 0; + uint8_t legacy = 0u; + int scratchAliases = 0; + int lensOk = 1; + +#ifdef WOLFCOSE_CHECK_WORD32_LEN + /* Refuse lengths wolfCrypt cannot take before any per-byte overlap walk + * or scratch zeroing could be driven by them. */ + if ((wolfCose_LenFitsWord32(inSz) == 0) || + (wolfCose_LenFitsWord32(detachedLen) == 0) || + (wolfCose_LenFitsWord32(extAadLen) == 0) || + (wolfCose_LenFitsWord32(scratchSz) == 0)) { + lensOk = 0; + } +#endif + if ((lensOk != 0) && (scratch != NULL) && + (((in != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, in, inSz) != 0)) || + ((detachedPayload != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + detachedPayload, detachedLen) != 0)) || + ((extAad != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + extAad, extAadLen) != 0)) || + ((counterHdr != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + (const uint8_t*)counterHdr, + sizeof(*counterHdr)) != 0)))) { + scratchAliases = 1; + } + if ((key == NULL) || (in == NULL) || (scratch == NULL) || + (counterHdr == NULL) || + ((detachedPayload == NULL) && (detachedLen != 0u)) || + ((extAad == NULL) && (extAadLen != 0u)) || + (scratchAliases != 0) || (lensOk == 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_ParseCounterTarget(in, inSz, detachedPayload, + detachedLen, &target); + } + if ((ret == WOLFCOSE_SUCCESS) && (target.hasFull == 0u) && + (target.hasLegacyFull == 0u)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + size_t valueStart = target.fullValueStart; + size_t valueEnd = target.fullValueEnd; + + if (target.hasFull == 0u) { + legacy = 1u; + valueStart = target.legacyFullValueStart; + valueEnd = target.legacyFullValueEnd; + } + ret = wolfCose_SelectFullCounter(&in[valueStart], + valueEnd - valueStart, counterIndex, &selected, &selectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeFullCounter(selected, selectedLen, + counterHdr, &protectedData, &protectedLen, + &sig, &sigLen, &algProtected); + } + if ((ret == WOLFCOSE_SUCCESS) && (algProtected == 0) && + ((key->alg == WOLFCOSE_ALG_UNSET) || + (key->alg != counterHdr->alg))) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_BuildCounterStructure(&target, protectedData, + protectedLen, extAad, extAadLen, 0u, + legacy, scratch, scratchSz, &tbsLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CounterVerifyTbs(key, counterHdr->alg, + scratch, tbsLen, sig, sigLen, scratch, scratchSz); + } + + wolfCose_HdrClearOnFail(ret, counterHdr); + if ((scratch != NULL) && (scratchAliases == 0) && (lensOk != 0)) { + (void)wolfCose_ForceZero(scratch, scratchSz); + } + return ret; +} + +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) +{ + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_COUNTER_TARGET target; + WOLFCOSE_CBOR_CTX ctx; + const uint8_t* sig = NULL; + size_t sigLen = 0u; + size_t tbsLen = 0u; + uint8_t legacy = 0u; + int scratchAliases = 0; + int lensOk = 1; + +#ifdef WOLFCOSE_CHECK_WORD32_LEN + /* Refuse lengths wolfCrypt cannot take before any per-byte overlap walk + * or scratch zeroing could be driven by them. */ + if ((wolfCose_LenFitsWord32(inSz) == 0) || + (wolfCose_LenFitsWord32(detachedLen) == 0) || + (wolfCose_LenFitsWord32(extAadLen) == 0) || + (wolfCose_LenFitsWord32(scratchSz) == 0)) { + lensOk = 0; + } +#endif + if ((lensOk != 0) && (scratch != NULL) && + (((in != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, in, inSz) != 0)) || + ((detachedPayload != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + detachedPayload, detachedLen) != 0)) || + ((extAad != NULL) && + (wolfCose_RangesOverlap(scratch, scratchSz, + extAad, extAadLen) != 0)))) { + scratchAliases = 1; + } + if ((counterSigner == NULL) || (counterSigner->key == NULL) || + (in == NULL) || (scratch == NULL) || + ((detachedPayload == NULL) && (detachedLen != 0u)) || + ((extAad == NULL) && (extAadLen != 0u)) || + (scratchAliases != 0) || (lensOk == 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_ParseCounterTarget(in, inSz, detachedPayload, + detachedLen, &target); + } + if ((ret == WOLFCOSE_SUCCESS) && (target.hasAbbreviated == 0u) && + (target.hasLegacyAbbreviated == 0u)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + size_t valueStart = target.abbreviatedValueStart; + size_t valueEnd = target.abbreviatedValueEnd; + + if (target.hasAbbreviated == 0u) { + legacy = 1u; + valueStart = target.legacyAbbreviatedValueStart; + valueEnd = target.legacyAbbreviatedValueEnd; + } + ret = wc_CBOR_DecoderInit(&ctx, + &in[valueStart], valueEnd - valueStart); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, &sig, &sigLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_BuildCounterStructure(&target, NULL, 0u, + extAad, extAadLen, 1u, legacy, + scratch, scratchSz, &tbsLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CounterVerifyTbs(counterSigner->key, + counterSigner->algId, scratch, tbsLen, + sig, sigLen, scratch, scratchSz); + } + + if ((scratch != NULL) && (scratchAliases == 0) && (lensOk != 0)) { + (void)wolfCose_ForceZero(scratch, scratchSz); + } + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN_VERIFY */ + +#endif /* WOLFCOSE_COUNTERSIGN */ diff --git a/src/wolfcose_ecc.c b/src/wolfcose_ecc.c index fb58b18..17e92d9 100644 --- a/src/wolfcose_ecc.c +++ b/src/wolfcose_ecc.c @@ -49,7 +49,8 @@ /* ----- Internal: ECC DER <-> raw r||s conversion ----- */ #ifdef WOLFCOSE_HAVE_ECDSA -#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, size_t coordSz, enum wc_HashType hashType, @@ -148,7 +149,7 @@ int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, } return ret; } -#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ +#endif /* signature creation */ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, const uint8_t* hash, size_t hashLen, diff --git a/src/wolfcose_encrypt.c b/src/wolfcose_encrypt.c index f2c02e7..924a763 100644 --- a/src/wolfcose_encrypt.c +++ b/src/wolfcose_encrypt.c @@ -879,7 +879,9 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, (wolfCose_IsEcdhEsDirectAlg(recipientHdr.alg) != 0)) { size_t mapCount = 0; size_t j; + WOLFCOSE_HDR_STATE unprotState; + wolfCose_HdrStateInit(&unprotState); ret = wc_CBOR_DecodeMapStart(&ctx, &mapCount); if ((ret == WOLFCOSE_SUCCESS) && (mapCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS)) { @@ -888,22 +890,32 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, } for (j = 0; (ret == WOLFCOSE_SUCCESS) && (j < mapCount); j++) { - int64_t label = 0; - int recipSkipped = 0; + WOLFCOSE_CBOR_LABEL label; + const uint8_t* encodedLabel = NULL; - ret = wolfCose_SkipIfTstrLabel(&ctx, &recipSkipped); - if ((ret == WOLFCOSE_SUCCESS) && (recipSkipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); + if ((ctx.cbuf != NULL) && (ctx.idx < ctx.bufSz)) { + encodedLabel = &ctx.cbuf[ctx.idx]; } + ret = wc_CBOR_DecodeLabel(&ctx, &label); /* Reject duplicate labels within the unprotected map and labels * also present in the recipient protected bucket. */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(&recipientHdrState, label); + if ((wolfCose_HdrStateContainsLabel(&unprotState, + &label) != 0) || + (wolfCose_HdrStateContainsLabel(&recipientHdrState, + &label) != 0)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + ret = wolfCose_HdrStateAddLabel(&unprotState, &label, + encodedLabel); + } } if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_EPHEMERAL_KEY)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_EPHEMERAL_KEY) != 0)) { if (haveEphemKey != 0) { ret = WOLFCOSE_E_CBOR_MALFORMED; } diff --git a/src/wolfcose_hdr.c b/src/wolfcose_hdr.c index 93d3227..63f4f21 100644 --- a/src/wolfcose_hdr.c +++ b/src/wolfcose_hdr.c @@ -86,7 +86,8 @@ void wolfCose_HdrStateInit(WOLFCOSE_HDR_STATE* state) { if (state != NULL) { state->labelBits = 0u; - state->extraCount = 0u; + state->extraIntegerCount = 0u; + state->extraTextCount = 0u; } } @@ -103,8 +104,8 @@ int wolfCose_HdrStateContains(const WOLFCOSE_HDR_STATE* state, } else { size_t i; - for (i = 0u; i < state->extraCount; i++) { - if (state->extraLabels[i] == label) { + for (i = 0u; i < state->extraIntegerCount; i++) { + if (state->extraIntegerLabels[i] == label) { found = 1; break; } @@ -128,12 +129,13 @@ static int wolfCose_HdrStateAdd(WOLFCOSE_HDR_STATE* state, int64_t label) if (bit != 0u) { state->labelBits |= bit; } - else if (state->extraCount >= (size_t)WOLFCOSE_MAX_MAP_ITEMS) { + else if (state->extraIntegerCount >= + (size_t)WOLFCOSE_MAX_HEADER_LABELS) { ret = WOLFCOSE_E_CBOR_MALFORMED; } else { - state->extraLabels[state->extraCount] = label; - state->extraCount++; + state->extraIntegerLabels[state->extraIntegerCount] = label; + state->extraIntegerCount++; } } @@ -155,28 +157,129 @@ int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, return ret; } -static int wolfCose_HdrStateMerge(WOLFCOSE_HDR_STATE* dst, - const WOLFCOSE_HDR_STATE* src) +static int wolfCose_TextLabelEquals(const uint8_t* encoded, + const WOLFCOSE_CBOR_LABEL* label) +{ + uint8_t ai; + uint64_t textLen = UINT64_MAX; + size_t headLen = 0u; + size_t i; + int equal = 0; + + ai = (uint8_t)(encoded[0] & 0x1Fu); + if (ai < 24u) { + textLen = ai; + headLen = 1u; + } + else if (ai <= 27u) { + /* additional-info 24/25/26/27 carry 1/2/4/8 big-endian length bytes; + * the stored head already passed shortest-form decoding. */ + if (ai == 24u) { + headLen = 2u; + } + else if (ai == 25u) { + headLen = 3u; + } + else if (ai == 26u) { + headLen = 5u; + } + else { + headLen = 9u; + } + textLen = 0u; + for (i = 1u; i < headLen; i++) { + textLen <<= 8u; + textLen |= (uint64_t)encoded[i]; + } + } + else { + /* The stored label was already decoded. */ + } + + /* The core library takes no variable-time compares even on public map + * labels (.github/semgrep-rules.yml); wc_CBOR_LabelIsText scans the whole + * label. */ + if ((textLen == (uint64_t)label->textLen) && + (wc_CBOR_LabelIsText(label, &encoded[headLen], + label->textLen) != 0)) { + equal = 1; + } + + return equal; +} + +int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label) +{ + int found = 0; + + if ((state != NULL) && (label != NULL)) { + if (label->isText == 0u) { + found = wolfCose_HdrStateContains(state, label->val); + } + else { + size_t i; + + for (i = 0u; i < state->extraTextCount; i++) { + if (wolfCose_TextLabelEquals( + state->extraTextLabels[i], label) != 0) { + found = 1; + break; + } + } + } + } + + return found; +} + +int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label, const uint8_t* encodedLabel) { int ret = WOLFCOSE_SUCCESS; - if ((dst == NULL) || (src == NULL)) { + if ((state == NULL) || (label == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (label->isText == 0u) { + ret = wolfCose_HdrStateAdd(state, label->val); + } + else if (encodedLabel == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } + else if (state->extraTextCount >= + (size_t)WOLFCOSE_MAX_HEADER_LABELS) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } else { - size_t i; - dst->labelBits |= src->labelBits; - for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->extraCount); i++) { - ret = wolfCose_HdrStateAdd(dst, src->extraLabels[i]); - } + state->extraTextLabels[state->extraTextCount] = encodedLabel; + state->extraTextCount++; + } + + return ret; +} + +static int wolfCose_HdrStateCheckAndAddLabel(WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label, const uint8_t* encodedLabel) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((label != NULL) && (label->isText == 0u)) { + ret = wolfCose_HdrStateCheckAndAdd(state, label->val); + } + else if (wolfCose_HdrStateContainsLabel(state, label) != 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + ret = wolfCose_HdrStateAddLabel(state, label, encodedLabel); } return ret; } -/* If the next decoder item is a tstr label, reject it. The implementation - * only supports integer labels, and silently skipping text labels breaks - * duplicate-label enforcement across header and key maps. */ +/* COSE_Key maps use integer labels. Header maps use the label-aware helpers + * above because RFC 9052 permits both integer and text-string labels. */ +#if defined(WOLFCOSE_KEY_DECODE) || defined(WOLFCOSE_ECDH_ES_DIRECT) int wolfCose_SkipIfTstrLabel(const WOLFCOSE_CBOR_CTX* ctx, int* skipped) { int ret; @@ -193,6 +296,7 @@ int wolfCose_SkipIfTstrLabel(const WOLFCOSE_CBOR_CTX* ctx, int* skipped) } return ret; } +#endif int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, size_t* outLen) @@ -231,11 +335,10 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, WOLFCOSE_CBOR_CTX ctx; size_t mapCount = 0; size_t i; - int64_t label; + WOLFCOSE_CBOR_LABEL label; int64_t intVal; uint64_t contentTypeVal; uint32_t critLabels = 0u; - int skipped; if ((hdr == NULL) || (hdrState == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; @@ -259,17 +362,19 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - /* Reject tstr labels: only integer labels are supported. */ - ret = wolfCose_SkipIfTstrLabel(&ctx, &skipped); - if ((ret == WOLFCOSE_SUCCESS) && (skipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); - } + const uint8_t* encodedLabel = NULL; + if ((ctx.cbuf != NULL) && (ctx.idx < ctx.bufSz)) { + encodedLabel = &ctx.cbuf[ctx.idx]; + } + ret = wc_CBOR_DecodeLabel(&ctx, &label); if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(hdrState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(hdrState, &label, + encodedLabel); } - if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_HDR_ALG)) { + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { if ((ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TSTR)) { ret = wc_CBOR_Skip(&ctx); @@ -286,7 +391,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CRIT)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_CRIT) != 0)) { size_t critCount = 0; size_t k; int64_t critLabel; @@ -323,7 +429,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CONTENT_TYPE)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_CONTENT_TYPE) != 0)) { if ((ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TSTR)) { ret = wc_CBOR_Skip(&ctx); @@ -340,7 +447,7 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_KID)) { + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_KID) != 0)) { /* RFC 9052 Section 3.1: kid may appear in the protected * bucket; populate it the same way the unprotected decoder * does instead of skipping it as unknown. */ @@ -353,7 +460,7 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_IV)) { + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_IV) != 0)) { const uint8_t* ivData; size_t ivBstrLen; ret = wc_CBOR_DecodeBstr(&ctx, &ivData, &ivBstrLen); @@ -363,7 +470,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_PARTIAL_IV)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_PARTIAL_IV) != 0)) { const uint8_t* pivData; size_t pivBstrLen; ret = wc_CBOR_DecodeBstr(&ctx, &pivData, &pivBstrLen); @@ -386,6 +494,20 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, ret = WOLFCOSE_E_COSE_BAD_HDR; } + /* RFC 9338 Section 3 requires all countersignature parameters to be + * carried in an unprotected header bucket. */ + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_HdrStateContains(hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE_V2) != 0) || + (wolfCose_HdrStateContains(hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE0_V2) != 0) || + (wolfCose_HdrStateContains(hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE_LEGACY) != 0) || + (wolfCose_HdrStateContains(hdrState, + WOLFCOSE_HDR_COUNTERSIGNATURE0_LEGACY) != 0))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + /* IV and Partial IV are mutually exclusive. */ if ((ret == WOLFCOSE_SUCCESS) && (hdr->iv != NULL) && (hdr->partialIv != NULL)) { @@ -404,10 +526,9 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, { int ret; size_t mapCount = 0; - int64_t label; + WOLFCOSE_CBOR_LABEL label; const uint8_t* bstrData; size_t bstrLen; - int skipped; if ((ctx == NULL) || (hdr == NULL) || (hdrState == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; @@ -425,27 +546,31 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - /* Reject tstr labels: only integer labels are supported. */ - ret = wolfCose_SkipIfTstrLabel(ctx, &skipped); - if ((ret == WOLFCOSE_SUCCESS) && (skipped == 0)) { - ret = wc_CBOR_DecodeInt(ctx, &label); - } + const uint8_t* encodedLabel = NULL; + if ((ctx->cbuf != NULL) && (ctx->idx < ctx->bufSz)) { + encodedLabel = &ctx->cbuf[ctx->idx]; + } + ret = wc_CBOR_DecodeLabel(ctx, &label); if (ret == WOLFCOSE_SUCCESS) { /* crit MUST live in the protected bucket. */ - if (label == WOLFCOSE_HDR_CRIT) { + if (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_CRIT) != 0) { ret = WOLFCOSE_E_COSE_BAD_HDR; } - else if ((wolfCose_HdrStateContains(&unprotState, label) != 0) || - (wolfCose_HdrStateContains(hdrState, label) != 0)) { + else if ((wolfCose_HdrStateContainsLabel(&unprotState, + &label) != 0) || + (wolfCose_HdrStateContainsLabel(hdrState, + &label) != 0)) { ret = WOLFCOSE_E_CBOR_MALFORMED; } else { - ret = wolfCose_HdrStateAdd(&unprotState, label); + ret = wolfCose_HdrStateAddLabel(&unprotState, &label, + encodedLabel); } } - if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_HDR_KID)) { + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_KID) != 0)) { ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); if (ret == WOLFCOSE_SUCCESS) { hdr->kid = bstrData; @@ -453,7 +578,7 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_IV)) { + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_IV) != 0)) { ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); if (ret == WOLFCOSE_SUCCESS) { hdr->iv = bstrData; @@ -461,7 +586,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_PARTIAL_IV)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_PARTIAL_IV) != 0)) { ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); if (ret == WOLFCOSE_SUCCESS) { hdr->partialIv = bstrData; @@ -469,7 +595,7 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_ALG)) { + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { if ((ctx->idx < ctx->bufSz) && (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { ret = wc_CBOR_Skip(ctx); @@ -487,7 +613,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CONTENT_TYPE)) { + (wc_CBOR_LabelIsInt(&label, + WOLFCOSE_HDR_CONTENT_TYPE) != 0)) { hdr->flags |= WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED; if ((ctx->idx < ctx->bufSz) && (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { @@ -518,82 +645,26 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, ret = WOLFCOSE_E_COSE_BAD_HDR; } - if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateMerge(hdrState, &unprotState); - } } return ret; } #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ - defined(WOLFCOSE_MAC_VERIFY) -/* Decode only the algorithm from an unselected header map. Other labels and - * values are intentionally left to the application that selected the entry. */ -static int wolfCose_DecodeSkippedHdrAlg(WOLFCOSE_CBOR_CTX* ctx, - int32_t* alg, int* algFound) -{ - int ret; - size_t mapCount = 0u; - size_t i; - - if ((ctx == NULL) || (alg == NULL) || (algFound == NULL)) { - ret = WOLFCOSE_E_INVALID_ARG; - } - else { - ret = wc_CBOR_DecodeMapStart(ctx, &mapCount); - } - if ((ret == WOLFCOSE_SUCCESS) && (mapCount > ctx->bufSz)) { - ret = WOLFCOSE_E_CBOR_MALFORMED; - } - - for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - WOLFCOSE_CBOR_LABEL label; - - ret = wc_CBOR_DecodeLabel(ctx, &label); - if ((ret == WOLFCOSE_SUCCESS) && - (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { - if (*algFound != 0) { - ret = WOLFCOSE_E_CBOR_MALFORMED; - } - else { - int64_t algVal; - - *algFound = 1; - if ((ctx->idx < ctx->bufSz) && - (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { - ret = wc_CBOR_Skip(ctx); - } - else { - ret = wc_CBOR_DecodeInt(ctx, &algVal); - if ((ret == WOLFCOSE_SUCCESS) && - (wolfCose_InInt32Range(algVal) == 0)) { - ret = WOLFCOSE_E_COSE_BAD_ALG; - } - if (ret == WOLFCOSE_SUCCESS) { - *alg = (int32_t)algVal; - } - } - } - } - else if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_Skip(ctx); - } - else { - /* No action required */ - } - } - - return ret; -} - -/* Decode the three fields shared by COSE_Signature and COSE_recipient. */ + defined(WOLFCOSE_MAC_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) +/* Decode and validate the three fields shared by COSE_Signature and + * COSE_recipient. A signature requires a bstr value. A recipient permits a + * bstr or null value and may have a fourth nested-recipients field. */ static int wolfCose_DecodeSkippedHeaderEntry(WOLFCOSE_CBOR_CTX* ctx, - size_t maxArrayCount, size_t* arrayCount, int32_t* alg) + size_t maxArrayCount, size_t* arrayCount, int32_t* alg, + uint8_t isSignature) { int ret; const uint8_t* protectedData = NULL; + const uint8_t* valueData = NULL; size_t protectedLen = 0u; - int algFound = 0; + size_t valueLen = 0u; + WOLFCOSE_HDR hdr; + WOLFCOSE_HDR_STATE hdrState; if ((ctx == NULL) || (arrayCount == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; @@ -603,56 +674,58 @@ static int wolfCose_DecodeSkippedHeaderEntry(WOLFCOSE_CBOR_CTX* ctx, if (alg != NULL) { *alg = WOLFCOSE_ALG_UNSET; } + (void)XMEMSET(&hdr, 0, sizeof(hdr)); ret = wc_CBOR_DecodeArrayStart(ctx, arrayCount); } if ((ret == WOLFCOSE_SUCCESS) && ((*arrayCount < 3u) || (*arrayCount > maxArrayCount))) { ret = WOLFCOSE_E_CBOR_MALFORMED; } - if ((ret == WOLFCOSE_SUCCESS) && (alg == NULL)) { - size_t i; - - for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < 3u); i++) { - ret = wc_CBOR_Skip(ctx); - } - } - if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { + if (ret == WOLFCOSE_SUCCESS) { ret = wc_CBOR_DecodeBstr(ctx, &protectedData, &protectedLen); } - if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL) && - (protectedLen > 0u)) { - WOLFCOSE_CBOR_CTX protectedCtx; - - (void)XMEMSET(&protectedCtx, 0, sizeof(protectedCtx)); - protectedCtx.cbuf = protectedData; - protectedCtx.bufSz = protectedLen; - ret = wolfCose_DecodeSkippedHdrAlg(&protectedCtx, alg, &algFound); - if ((ret == WOLFCOSE_SUCCESS) && - (protectedCtx.idx != protectedCtx.bufSz)) { - ret = WOLFCOSE_E_CBOR_MALFORMED; - } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, + &hdr, &hdrState); } - if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { - ret = wolfCose_DecodeSkippedHdrAlg(ctx, alg, &algFound); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(ctx, &hdr, &hdrState); } if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { - ret = wc_CBOR_Skip(ctx); + *alg = hdr.alg; + } + if ((ret == WOLFCOSE_SUCCESS) && (isSignature != 0u)) { + ret = wc_CBOR_DecodeBstr(ctx, &valueData, &valueLen); + } + else if ((ret == WOLFCOSE_SUCCESS) && + (ctx->idx < ctx->bufSz) && + (ctx->cbuf[ctx->idx] == WOLFCOSE_CBOR_NULL)) { + ctx->idx++; + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(ctx, &valueData, &valueLen); + } + else { + /* No action required */ } + (void)valueData; + (void)valueLen; return ret; } -#if defined(WOLFCOSE_SIGN_VERIFY) +#if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) /* A COSE_Signature has exactly three fields. */ int wolfCose_DecodeSkippedSignature(WOLFCOSE_CBOR_CTX* ctx) { size_t arrayCount = 0u; - return wolfCose_DecodeSkippedHeaderEntry(ctx, 3u, &arrayCount, NULL); + return wolfCose_DecodeSkippedHeaderEntry(ctx, 3u, &arrayCount, NULL, 1u); } #endif -#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) +#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN) /* Structurally validate one non-selected COSE_recipient and every nested * recipient. Use an explicit bounded stack to avoid recursive C calls. */ int wolfCose_DecodeSkippedRecipient(WOLFCOSE_CBOR_CTX* ctx, @@ -677,7 +750,7 @@ int wolfCose_DecodeSkippedRecipient(WOLFCOSE_CBOR_CTX* ctx, int32_t decodedAlg = WOLFCOSE_ALG_UNSET; ret = wolfCose_DecodeSkippedHeaderEntry(ctx, 4u, &arrayCount, - &decodedAlg); + &decodedAlg, 0u); remaining--; if ((ret == WOLFCOSE_SUCCESS) && (firstRecipient != 0)) { *recipientAlg = decodedAlg; diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index e5918cb..593b81a 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -147,10 +147,14 @@ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_MAC[3]; WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_ENCRYPT0[8]; WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_ENCRYPT[7]; +#define WOLFCOSE_MAX_HEADER_LABELS WOLFCOSE_MAX_MAP_ITEMS + typedef struct WOLFCOSE_HDR_STATE { - uint32_t labelBits; - int64_t extraLabels[WOLFCOSE_MAX_MAP_ITEMS]; - size_t extraCount; + uint32_t labelBits; + int64_t extraIntegerLabels[WOLFCOSE_MAX_HEADER_LABELS]; + const uint8_t* extraTextLabels[WOLFCOSE_MAX_HEADER_LABELS]; + size_t extraIntegerCount; + size_t extraTextCount; } WOLFCOSE_HDR_STATE; /* ----- COSE internal helpers ----- */ @@ -313,13 +317,14 @@ WOLFCOSE_LOCAL int wolfCose_HmacType(int32_t alg, int* hmacType); * \param eccKey Caller-owned ECC key with private key. * \return WOLFCOSE_SUCCESS or negative error code. */ -#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) WOLFCOSE_LOCAL int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, size_t coordSz, enum wc_HashType hashType, WC_RNG* rng, ecc_key* eccKey); -#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ +#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN || COUNTERSIGN_SIGN */ /** @@ -416,7 +421,9 @@ WOLFCOSE_LOCAL int wolfCose_HmacCheckKeyLen(int32_t alg, size_t keyLen); #if defined(WOLFCOSE_HAVE_RSAPSS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) /* RsaPssCheckKey -- defined in wolfcose_alg.c */ WOLFCOSE_LOCAL int wolfCose_RsaPssCheckKey(const WOLFCOSE_KEY* key, size_t* modulusLen); @@ -442,40 +449,53 @@ WOLFCOSE_LOCAL int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, /* SkipIfTstrLabel -- defined in wolfcose_hdr.c */ WOLFCOSE_LOCAL int wolfCose_SkipIfTstrLabel(const WOLFCOSE_CBOR_CTX* ctx, int* skipped); +/* HdrStateContainsLabel -- defined in wolfcose_hdr.c */ +WOLFCOSE_LOCAL int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label); + +/* HdrStateAddLabel -- defined in wolfcose_hdr.c */ +WOLFCOSE_LOCAL int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label, const uint8_t* encodedLabel); + #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ - defined(WOLFCOSE_MAC_VERIFY) -#if defined(WOLFCOSE_SIGN_VERIFY) + defined(WOLFCOSE_MAC_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) +#if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) /* DecodeSkippedSignature -- defined in wolfcose_hdr.c */ WOLFCOSE_LOCAL int wolfCose_DecodeSkippedSignature(WOLFCOSE_CBOR_CTX* ctx); #endif #endif #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ - defined(WOLFCOSE_MAC_VERIFY) -#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) + defined(WOLFCOSE_MAC_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) +#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN) /* DecodeSkippedRecipient -- defined in wolfcose_hdr.c */ WOLFCOSE_LOCAL int wolfCose_DecodeSkippedRecipient(WOLFCOSE_CBOR_CTX* ctx, int32_t* recipientAlg); #endif #endif -#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) /* KeyCanSign -- defined in wolfcose_key.c */ WOLFCOSE_LOCAL int wolfCose_KeyCanSign(const WOLFCOSE_KEY* key); #endif #if defined(WOLFCOSE_KEY_ENCODE) || defined(WOLFCOSE_SIGN1_SIGN) || \ - defined(WOLFCOSE_SIGN_SIGN) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_COUNTERSIGN_SIGN) /* SizeAdd -- defined in wolfcose_key.c */ WOLFCOSE_LOCAL int wolfCose_SizeAdd(size_t* total, size_t add); /* CborStringSize -- defined in wolfcose_key.c */ WOLFCOSE_LOCAL int wolfCose_CborStringSize(size_t len, size_t* encodedLen); + +/* CborHeadSize -- defined in wolfcose_key.c */ +WOLFCOSE_LOCAL size_t wolfCose_CborHeadSize(uint64_t val); #endif #if (defined(WOLFCOSE_KEY_DECODE) || defined(WOLFCOSE_SIGN1) || \ - defined(WOLFCOSE_SIGN) || \ + defined(WOLFCOSE_SIGN) || defined(WOLFCOSE_COUNTERSIGN) || \ defined(WOLFCOSE_MAC0) || defined(WOLFCOSE_MAC) || \ defined(WOLFCOSE_ENCRYPT0) || defined(WOLFCOSE_ENCRYPT)) && \ defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) @@ -587,7 +607,9 @@ WOLFCOSE_LOCAL int wolfCose_UpdateRecipientMode(int32_t alg, int* commonMode); #if defined(WOLFCOSE_HAVE_MLDSA) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) /* MlDsaCheckKey -- defined in wolfcose_sign1.c */ WOLFCOSE_LOCAL int wolfCose_MlDsaCheckKey(const WOLFCOSE_KEY* key, int32_t alg); #endif @@ -595,7 +617,7 @@ WOLFCOSE_LOCAL int wolfCose_MlDsaCheckKey(const WOLFCOSE_KEY* key, int32_t alg); #if defined(WOLFCOSE_HAVE_LMS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ - defined(WOLFCOSE_EXT_SIGN)) + defined(WOLFCOSE_COUNTERSIGN_SIGN) || defined(WOLFCOSE_EXT_SIGN)) /* LmsCheckKey -- defined in wolfcose_sign1.c */ WOLFCOSE_LOCAL int wolfCose_LmsCheckKey(const WOLFCOSE_KEY* key); #endif @@ -606,7 +628,7 @@ WOLFCOSE_LOCAL int wolfCose_ExtSignAlg(int32_t alg, int* preHashes); #endif #if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ - defined(WOLFCOSE_EXT_SIGN) + defined(WOLFCOSE_COUNTERSIGN_SIGN) || defined(WOLFCOSE_EXT_SIGN) /* SignSigLen -- defined in wolfcose_sign1.c */ WOLFCOSE_LOCAL int wolfCose_SignSigLen(const WOLFCOSE_KEY* key, int32_t alg, size_t* expSigLen); @@ -650,7 +672,8 @@ WOLFCOSE_LOCAL void wolfCose_EccPrivateImportRollback(ecc_key* ecc, #if defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_SIGN_VERIFY) || \ defined(WOLFCOSE_ENCRYPT0_DECRYPT) || defined(WOLFCOSE_MAC0_VERIFY) || \ - defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) + defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) /* HdrClearOnFail -- defined in wolfcose_util.c */ WOLFCOSE_LOCAL void wolfCose_HdrClearOnFail(int ret, WOLFCOSE_HDR* hdr); #endif diff --git a/src/wolfcose_key.c b/src/wolfcose_key.c index 00c0eda..a4cf980 100644 --- a/src/wolfcose_key.c +++ b/src/wolfcose_key.c @@ -296,7 +296,8 @@ int wc_CoseKey_SetSymmetric(WOLFCOSE_KEY* key, const uint8_t* data, return ret; } -#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) /* Signing needs either private key material in wolfCOSE or a delegated signer; * hasPrivate alone conflates that with "the private key is here", which * governs whether wc_CoseKey_Encode may serialise it. */ @@ -314,7 +315,7 @@ int wolfCose_KeyCanSign(const WOLFCOSE_KEY* key) #endif return can; } -#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ +#endif /* signature creation */ #if defined(WOLFCOSE_EXT_SIGN) int wc_CoseKey_SetExtSigner(WOLFCOSE_KEY* key, WOLFCOSE_SIGN_CB cb, @@ -340,7 +341,7 @@ int wc_CoseKey_SetExtSigner(WOLFCOSE_KEY* key, WOLFCOSE_SIGN_CB cb, * Shared by the COSE_Key and COSE_Sign1 size queries. Every add is checked so * a size computation can never wrap into a too-small buffer request. */ #if defined(WOLFCOSE_KEY_ENCODE) || defined(WOLFCOSE_SIGN1_SIGN) || \ - defined(WOLFCOSE_SIGN_SIGN) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_COUNTERSIGN_SIGN) int wolfCose_SizeAdd(size_t* total, size_t add) { int ret = WOLFCOSE_SUCCESS; @@ -354,7 +355,7 @@ int wolfCose_SizeAdd(size_t* total, size_t add) return ret; } -static size_t wolfCose_CborHeadSize(uint64_t val) +size_t wolfCose_CborHeadSize(uint64_t val) { size_t len; @@ -387,7 +388,8 @@ int wolfCose_CborStringSize(size_t len, size_t* encodedLen) } return ret; } -#endif /* WOLFCOSE_KEY_ENCODE || WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ +#endif /* WOLFCOSE_KEY_ENCODE || WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN || + * WOLFCOSE_COUNTERSIGN_SIGN */ #if defined(WOLFCOSE_KEY_ENCODE) diff --git a/src/wolfcose_recipient.c b/src/wolfcose_recipient.c index 24ad74f..d4131e4 100644 --- a/src/wolfcose_recipient.c +++ b/src/wolfcose_recipient.c @@ -953,7 +953,7 @@ int wolfCose_UpdateRecipientMode(int32_t alg, int* commonMode) * platforms the cast cannot truncate, so the guard and helper are omitted (and * the condition would otherwise be a compile-time constant). */ #if (defined(WOLFCOSE_KEY_DECODE) || defined(WOLFCOSE_SIGN1) || \ - defined(WOLFCOSE_SIGN) || \ + defined(WOLFCOSE_SIGN) || defined(WOLFCOSE_COUNTERSIGN) || \ defined(WOLFCOSE_MAC0) || defined(WOLFCOSE_MAC) || \ defined(WOLFCOSE_ENCRYPT0) || defined(WOLFCOSE_ENCRYPT)) && \ defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) diff --git a/src/wolfcose_sign1.c b/src/wolfcose_sign1.c index bd66b04..9f77024 100644 --- a/src/wolfcose_sign1.c +++ b/src/wolfcose_sign1.c @@ -54,7 +54,9 @@ * operations that actually call them. */ #if defined(WOLFCOSE_HAVE_MLDSA) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ - defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_SIGN) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY)) /* Map an ML-DSA COSE algorithm to the FIPS 204 security level its key must * report, so a key of the wrong level cannot satisfy a higher-level alg. */ static int wolfCose_MlDsaAlgLevel(int32_t alg, byte* level) @@ -103,7 +105,7 @@ int wolfCose_MlDsaCheckKey(const WOLFCOSE_KEY* key, int32_t alg) #if defined(WOLFCOSE_HAVE_LMS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY) || \ - defined(WOLFCOSE_EXT_SIGN)) + defined(WOLFCOSE_COUNTERSIGN_SIGN) || defined(WOLFCOSE_EXT_SIGN)) /* RFC 8778: validate that the key is HSS-LMS-typed and was attached through * wc_CoseKey_SetLms() so the union member is known to be an LmsKey. */ int wolfCose_LmsCheckKey(const WOLFCOSE_KEY* key) @@ -119,7 +121,7 @@ int wolfCose_LmsCheckKey(const WOLFCOSE_KEY* key) #endif /* WOLFCOSE_HAVE_LMS */ #if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) || \ - defined(WOLFCOSE_EXT_SIGN) + defined(WOLFCOSE_COUNTERSIGN_SIGN) || defined(WOLFCOSE_EXT_SIGN) /* Exact signature length for this key and algorithm. wolfCose_SigSize() alone * 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. */ diff --git a/src/wolfcose_util.c b/src/wolfcose_util.c index 1b9f5b4..987bb3b 100644 --- a/src/wolfcose_util.c +++ b/src/wolfcose_util.c @@ -312,7 +312,8 @@ void wolfCose_EccPrivateImportRollback(ecc_key* ecc, * not exposed to callers that inspect hdr without gating on the return code. */ #if defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_SIGN_VERIFY) || \ defined(WOLFCOSE_ENCRYPT0_DECRYPT) || defined(WOLFCOSE_MAC0_VERIFY) || \ - defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) + defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) || \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) void wolfCose_HdrClearOnFail(int ret, WOLFCOSE_HDR* hdr) { if ((ret != WOLFCOSE_SUCCESS) && (hdr != NULL)) { diff --git a/tests/misra_consumer.c b/tests/misra_consumer.c index 6c8087b..bf83383 100644 --- a/tests/misra_consumer.c +++ b/tests/misra_consumer.c @@ -86,6 +86,14 @@ static size_t wolfCose_MisraUsePublicApis(void) #ifdef WOLFCOSE_SIGN1_VERIFY WOLFCOSE_MISRA_USE_API(wc_CoseSign1_Verify); #endif +#ifdef WOLFCOSE_COUNTERSIGN_SIGN + WOLFCOSE_MISRA_USE_API(wc_Cose_AddCounterSignature); + WOLFCOSE_MISRA_USE_API(wc_Cose_AddCounterSignature0); +#endif +#ifdef WOLFCOSE_COUNTERSIGN_VERIFY + WOLFCOSE_MISRA_USE_API(wc_Cose_VerifyCounterSignature); + WOLFCOSE_MISRA_USE_API(wc_Cose_VerifyCounterSignature0); +#endif #ifdef WOLFCOSE_ENCRYPT0_ENCRYPT WOLFCOSE_MISRA_USE_API(wc_CoseEncrypt0_Encrypt); #endif @@ -137,6 +145,7 @@ int main(void) WOLFCOSE_ALG_ECDH_ES_A128KW, WOLFCOSE_ALG_ECDH_ES_A192KW, WOLFCOSE_ALG_ECDH_ES_A256KW, + (int32_t)WOLFCOSE_TAG_COUNTERSIGNATURE, WOLFCOSE_CRV_ML_DSA_44, WOLFCOSE_CRV_ML_DSA_65, WOLFCOSE_CRV_ML_DSA_87, diff --git a/tests/test_cbor.c b/tests/test_cbor.c index 84edca9..893ba5d 100644 --- a/tests/test_cbor.c +++ b/tests/test_cbor.c @@ -1149,6 +1149,18 @@ static void test_cbor_decode_label(void) WOLFCOSE_CBOR_LABEL label; uint8_t buf[64]; static const uint8_t algText[] = "alg"; + static const uint8_t validUtf8[] = { + 0x6Du, 0xC2u, 0x80u, 0xE0u, 0xA0u, 0x80u, + 0xF0u, 0x90u, 0x80u, 0x80u, 0xF4u, 0x8Fu, 0xBFu, 0xBFu + }; + static const uint8_t truncatedUtf8[] = {0x61u, 0xC2u}; + static const uint8_t overlongUtf8[] = {0x62u, 0xC0u, 0xAFu}; + static const uint8_t surrogateUtf8[] = { + 0x63u, 0xEDu, 0xA0u, 0x80u + }; + static const uint8_t outOfRangeUtf8[] = { + 0x64u, 0xF4u, 0x90u, 0x80u, 0x80u + }; int ret; printf(" [Int-or-text labels]\n"); @@ -1201,6 +1213,33 @@ static void test_cbor_decode_label(void) "label negint matches"); } + (void)wc_CBOR_DecoderInit(&ctx, validUtf8, sizeof(validUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && label.isText == 1u && + label.textLen == (sizeof(validUtf8) - 1u), + "label accepts valid UTF-8 boundaries"); + + (void)wc_CBOR_DecoderInit(&ctx, truncatedUtf8, sizeof(truncatedUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "label rejects truncated UTF-8"); + + (void)wc_CBOR_DecoderInit(&ctx, overlongUtf8, sizeof(overlongUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "label rejects overlong UTF-8"); + + (void)wc_CBOR_DecoderInit(&ctx, surrogateUtf8, sizeof(surrogateUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "label rejects UTF-8 surrogate"); + + (void)wc_CBOR_DecoderInit(&ctx, outOfRangeUtf8, + sizeof(outOfRangeUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "label rejects UTF-8 above U+10FFFF"); + /* A label argument above INT64_MAX has no int64_t spelling, in either * sign. The negint boundary is one step further out: RFC 8949 encodes -n * as n-1, so argument INT64_MAX is INT64_MIN and still valid. */ diff --git a/tests/test_cose.c b/tests/test_cose.c index 2a4743d..f14e094 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -98,6 +98,223 @@ static int g_failures = 0; } \ } while (0) +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) +static int test_cose_countersign_roundtrip(WOLFCOSE_KEY* signKey, + const WOLFCOSE_KEY* verifyKey, int32_t alg, WC_RNG* rng, + const char* name) +{ + static const uint8_t target[] = { + 0xD2u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x78u, 0x41u, 0x00u + }; + static uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + static uint8_t out[WOLFCOSE_MAX_SCRATCH_SZ + 256u]; + WOLFCOSE_COUNTERSIGNATURE counterSigner; + WOLFCOSE_HDR hdr; + size_t outLen = 0u; + int ret; + + (void)name; + TEST_LOG(" [Countersignature %s]\n", name); + + counterSigner.algId = alg; + counterSigner.key = signKey; + counterSigner.kid = NULL; + counterSigner.kidLen = 0u; + ret = wc_Cose_AddCounterSignature(&counterSigner, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &outLen, rng); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && outLen > sizeof(target), + "algorithm countersignature creation"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_Cose_VerifyCounterSignature(verifyKey, 0u, + out, outLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == alg, + "algorithm countersignature verification"); + } + if (ret == WOLFCOSE_SUCCESS) { + WOLFCOSE_COUNTERSIGNATURE aliasSigner = counterSigner; + size_t aliasLen = 0u; + int aliasRet; + + /* A kid aliasing the output must be rejected: the attach rewrites out + * before the kid is encoded, so an overlap would read stale bytes. */ + aliasSigner.kid = out; + aliasSigner.kidLen = 4u; + aliasRet = wc_Cose_AddCounterSignature(&aliasSigner, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &aliasLen, rng); + TEST_ASSERT(aliasRet == WOLFCOSE_E_INVALID_ARG, + "reject countersignature kid aliasing output"); + } + if (ret == WOLFCOSE_SUCCESS) { + WOLFCOSE_COUNTERSIGNATURE lmsSigner = counterSigner; + size_t lmsLen = 0u; + int lmsRet; + + /* No HSS/LMS countersignature path exists; the algorithm is refused + * before the key or any signer is consulted. */ + lmsSigner.algId = WOLFCOSE_ALG_HSS_LMS; + lmsRet = wc_Cose_AddCounterSignature(&lmsSigner, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &lmsLen, rng); + TEST_ASSERT(lmsRet == WOLFCOSE_E_COSE_BAD_ALG, + "reject HSS/LMS countersignature algorithm"); + } + if (ret == WOLFCOSE_SUCCESS) { + size_t staleLen = 77u; + int nullRet; + + nullRet = wc_Cose_AddCounterSignature(NULL, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &staleLen, rng); + TEST_ASSERT(nullRet == WOLFCOSE_E_INVALID_ARG && staleLen == 0u, + "null countersigner clears output length"); + staleLen = 77u; + nullRet = wc_Cose_AddCounterSignature0(NULL, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &staleLen, rng); + TEST_ASSERT(nullRet == WOLFCOSE_E_INVALID_ARG && staleLen == 0u, + "null abbreviated countersigner clears output length"); + } + if (ret == WOLFCOSE_SUCCESS) { + /* Same target as above with an unprotected map carrying a registered + * countersignature label whose value has the wrong major type. */ + static const uint8_t badAbbrev[] = { + 0xD2u, 0x84u, 0x40u, 0xA1u, 0x0Cu, 0x00u, 0x41u, 0x78u, 0x41u, 0x00u + }; + static const uint8_t badFull[] = { + 0xD2u, 0x84u, 0x40u, 0xA1u, 0x0Bu, 0x00u, 0x41u, 0x78u, 0x41u, 0x00u + }; + WOLFCOSE_COUNTERSIGNATURE0 counterSigner0; + size_t badLen = 0u; + int badRet; + + counterSigner0.algId = alg; + counterSigner0.key = signKey; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + badAbbrev, sizeof(badAbbrev), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &badLen, rng); + TEST_ASSERT(badRet == WOLFCOSE_E_CBOR_MALFORMED, + "reject non-bstr abbreviated countersignature in target"); + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + badFull, sizeof(badFull), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &badLen, rng); + TEST_ASSERT(badRet == WOLFCOSE_E_CBOR_MALFORMED, + "reject non-array full countersignature in target"); + } +#ifdef WOLFCOSE_CHECK_WORD32_LEN + if (ret == WOLFCOSE_SUCCESS) { + size_t hugeLen = 0u; + int hugeRet; + + /* A capacity that cannot reach wolfCrypt must be refused before any + * per-byte overlap walk or scratch zeroing could act on it. */ + hugeRet = wc_Cose_AddCounterSignature(&counterSigner, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, (size_t)0xFFFFFFFFu + 2u, out, sizeof(out), &hugeLen, + rng); + TEST_ASSERT(hugeRet == WOLFCOSE_E_INVALID_ARG, + "reject over-word32 scratch capacity"); + } +#endif +#if (WOLFCOSE_MAX_MAP_ITEMS + 1u) < 24u + if (ret == WOLFCOSE_SUCCESS) { + /* Same target with an unprotected map declaring one entry more than + * WOLFCOSE_MAX_MAP_ITEMS; every entry is the pair 0: 0. */ + uint8_t wide[8u + (2u * (WOLFCOSE_MAX_MAP_ITEMS + 1u))]; + size_t wideLen = 0u; + size_t badLen = 0u; + size_t entries = (size_t)WOLFCOSE_MAX_MAP_ITEMS + 1u; + size_t i; + int badRet; + + wide[wideLen++] = 0xD2u; + wide[wideLen++] = 0x84u; + wide[wideLen++] = 0x40u; + wide[wideLen++] = (uint8_t)(0xA0u | entries); + for (i = 0u; i < entries; i++) { + wide[wideLen++] = 0x00u; + wide[wideLen++] = 0x00u; + } + wide[wideLen++] = 0x41u; + wide[wideLen++] = 0x78u; + wide[wideLen++] = 0x41u; + wide[wideLen++] = 0x00u; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + wide, wideLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &badLen, rng); + TEST_ASSERT(badRet == WOLFCOSE_E_CBOR_MALFORMED, + "reject target map above WOLFCOSE_MAX_MAP_ITEMS"); + } +#endif + return ret; +} +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) && defined(HAVE_ECC) && \ + (defined(WOLFCOSE_HAVE_ES384) || defined(WOLFCOSE_HAVE_ES512)) +static void test_cose_countersign_ecdsa_curves(void) +{ + WC_RNG rng; + int ret; + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "ecdsa curves rng"); +#if defined(WOLFCOSE_HAVE_ES384) + if (ret == 0) { + WOLFCOSE_KEY signKey; + ecc_key eccKey; + + ret = wc_ecc_init(&eccKey); + if (ret == 0) { + ret = wc_ecc_make_key_ex(&rng, 48, &eccKey, ECC_SECP384R1); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&signKey); + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&signKey, WOLFCOSE_CRV_P384, &eccKey); + } + TEST_ASSERT(ret == 0, "ES384 countersign key"); + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + WOLFCOSE_ALG_ES384, &rng, "ES384"); + wc_CoseKey_Free(&signKey); + } + (void)wc_ecc_free(&eccKey); + } +#endif +#if defined(WOLFCOSE_HAVE_ES512) + if (ret == 0) { + WOLFCOSE_KEY signKey; + ecc_key eccKey; + + ret = wc_ecc_init(&eccKey); + if (ret == 0) { + ret = wc_ecc_make_key_ex(&rng, 66, &eccKey, ECC_SECP521R1); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&signKey); + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&signKey, WOLFCOSE_CRV_P521, &eccKey); + } + TEST_ASSERT(ret == 0, "ES512 countersign key"); + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + WOLFCOSE_ALG_ES512, &rng, "ES512"); + wc_CoseKey_Free(&signKey); + } + (void)wc_ecc_free(&eccKey); + } +#endif + (void)wc_FreeRng(&rng); +} +#endif + #if (defined(WOLFCOSE_MAC) && defined(WOLFCOSE_HAVE_HMAC256)) || \ (defined(WOLFCOSE_ENCRYPT) && defined(WOLFCOSE_HAVE_AESGCM)) /* Locate the one-byte direct-alg value in a generated recipient header. */ @@ -969,6 +1186,14 @@ static void test_cose_sign1_ext_sign(void) TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_ES256, "ext-sign hdr alg"); } +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &verifyKey, + WOLFCOSE_ALG_ES256, NULL, "external ES256"); + } +#endif + /* Wrong-length ECDSA sig is rejected by alg size, even with kty unset. */ if (ret == 0) { WOLFCOSE_KEY badKey; @@ -1927,6 +2152,14 @@ static void test_cose_sign1_eddsa(void) TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_EDDSA, "sign1 eddsa hdr alg"); } +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + WOLFCOSE_ALG_EDDSA, &rng, "Ed25519"); + } +#endif + if (ret == 0) { WOLFCOSE_KEY wrongTypeKey = signKey; int wrongRet; @@ -2036,6 +2269,14 @@ static void test_cose_sign1_ed448(void) TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_EDDSA, "sign1 ed448 hdr alg"); } +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + WOLFCOSE_ALG_EDDSA, &rng, "Ed448"); + } +#endif + if (ret == 0) { WOLFCOSE_KEY wrongTypeKey = signKey; int wrongRet; @@ -2963,6 +3204,14 @@ static void test_cose_sign1_pss(const char* label, int32_t alg) TEST_ASSERT(hdr.alg == alg, "sign1 pss hdr alg"); } +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + alg, &rng, label); + } +#endif + if (ret == 0) { /* Wrong key should fail */ RsaKey rsaWrong; @@ -3410,6 +3659,14 @@ static void test_cose_sign1_ml_dsa(const char* label, int32_t alg, byte level) TEST_ASSERT(hdr.alg == alg, "sign1 ml-dsa hdr alg"); } +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) + if (ret == 0) { + ret = test_cose_countersign_roundtrip(&signKey, &signKey, + alg, &rng, label); + } +#endif + if (ret == 0) { /* Wrong key should fail */ wc_MlDsaKey dlWrong; @@ -17069,7 +17326,9 @@ static void test_cose_protected_hdr_empty_map(void) &hdrState); TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "DecodeProtectedHdr accepts serialized empty map"); - TEST_ASSERT((hdrState.labelBits == 0u) && (hdrState.extraCount == 0u), + TEST_ASSERT((hdrState.labelBits == 0u) && + (hdrState.extraIntegerCount == 0u) && + (hdrState.extraTextCount == 0u), "DecodeProtectedHdr empty map state"); } @@ -17259,8 +17518,8 @@ static void test_cose_sign_dup_signer_unprot_hdr(void) ret = wc_CoseSign_Verify(&key, 0, unselectedMsg, sizeof(unselectedMsg), NULL, 0, NULL, 0, scratch, sizeof(scratch), &hdr, &payload, &payloadLen); - TEST_ASSERT(ret == WOLFCOSE_E_COSE_SIG_FAIL, - "dup label in unselected signer ignored"); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "dup label in unselected signer rejected"); } if (eccInited != 0) { (void)wc_ecc_free(&eccKey); } @@ -17346,8 +17605,8 @@ static void test_cose_mac_dup_recipient_unprot_hdr(void) ret = wc_CoseMac_Verify(&recipient, 0, unselectedMsg, sizeof(unselectedMsg), NULL, 0, NULL, 0, scratch, sizeof(scratch), &hdr, &payload, &payloadLen); - TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, - "dup label in unselected recipient ignored (mac)"); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "dup label in unselected recipient rejected (mac)"); ret = wc_CoseMac_Verify(&recipient, 1, nestedSiblings, sizeof(nestedSiblings), NULL, 0, NULL, 0, @@ -17510,8 +17769,8 @@ static void test_cose_encrypt_dup_recipient_unprot_hdr(void) sizeof(unselectedMsg), NULL, 0, NULL, 0, scratch, sizeof(scratch), &hdr, plaintext, sizeof(plaintext), &plaintextLen); - TEST_ASSERT(ret == WOLFCOSE_E_COSE_DECRYPT_FAIL, - "dup label in unselected recipient ignored (encrypt)"); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "dup label in unselected recipient rejected (encrypt)"); ret = wc_CoseEncrypt_Decrypt(&recipient, 1, nestedSiblings, sizeof(nestedSiblings), NULL, 0, NULL, 0, @@ -18044,15 +18303,57 @@ static void test_cose_protected_hdr_tstr_label(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; + WOLFCOSE_CBOR_CTX enc; + uint8_t labelData[256]; + uint8_t dupBoundary[600]; + static const size_t boundaryLen[] = {0u, 23u, 24u, 255u, 256u}; + size_t i; /* {1: -7, "x": 0} : alg ES256, plus an unknown tstr label */ uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; + /* {"x": 0, "x": 1} */ + uint8_t dupTstrLabel[] = { + 0xA2u, 0x61u, 'x', 0x00u, 0x61u, 'x', 0x01u + }; TEST_LOG(" [Protected Header: tstr-labeled entry]\n"); XMEMSET(&hdr, 0, sizeof(hdr)); ret = wolfCose_DecodeProtectedHdr(tstrLabel, sizeof(tstrLabel), &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, + "DecodeProtectedHdr preserves unknown tstr label"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(dupTstrLabel, sizeof(dupTstrLabel), + &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, - "DecodeProtectedHdr rejects tstr labels"); + "DecodeProtectedHdr rejects duplicate tstr label"); + + XMEMSET(labelData, 'a', sizeof(labelData)); + for (i = 0u; i < (sizeof(boundaryLen) / sizeof(boundaryLen[0])); i++) { + enc.buf = dupBoundary; + enc.bufSz = sizeof(dupBoundary); + enc.idx = 0u; + ret = wc_CBOR_EncodeMapStart(&enc, 2u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&enc, labelData, boundaryLen[i]); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&enc, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&enc, labelData, boundaryLen[i]); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&enc, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(dupBoundary, enc.idx, + &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "reject duplicate tstr label at CBOR length boundary"); + } } static void test_cose_protected_hdr_dup_label(void) @@ -19465,25 +19766,40 @@ static void test_cose_decode_tstr_alg_values(void) static void test_cose_decode_unprotected_tstr_label(void) { - /* Cover the tstr-skip + dup-detection paths in - * wolfCose_DecodeUnprotectedHdr that the protected-hdr test - * exercised on the other side. */ int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; WOLFCOSE_CBOR_CTX ctx; /* {1: -7, "x": 0} */ uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; + /* {"x": 0} */ + uint8_t protectedTstr[] = {0xA1u, 0x61u, 'x', 0x00u}; + /* {"x": 1} */ + uint8_t duplicateTstr[] = {0xA1u, 0x61u, 'x', 0x01u}; - TEST_LOG(" [DecodeUnprotectedHdr: tstr label skipped]\n"); + TEST_LOG(" [DecodeUnprotectedHdr: tstr labels]\n"); memset(&hdr, 0, sizeof(hdr)); - memset(&hdrState, 0, sizeof(hdrState)); + ret = wolfCose_DecodeProtectedHdr(NULL, 0u, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "initialize tstr unprotected header state"); ctx.cbuf = tstrLabel; ctx.bufSz = sizeof(tstrLabel); ctx.idx = 0; ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, + "DecodeUnprotectedHdr preserves unknown tstr label"); + + memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedTstr, + sizeof(protectedTstr), &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "decode protected tstr label for cross-bucket test"); + ctx.cbuf = duplicateTstr; + ctx.bufSz = sizeof(duplicateTstr); + ctx.idx = 0; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, - "DecodeUnprotectedHdr rejects tstr label"); + "DecodeUnprotectedHdr rejects cross-bucket tstr duplicate"); } static void test_cose_key_decode_tstr_alg_rejected(void) @@ -24975,7 +25291,6 @@ static void test_cose_sign1_size_and_untagged(void) } #endif - /* ----- Per-file 100%-coverage tests for the split source layout ----- */ #if defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_ECC) @@ -25359,6 +25674,893 @@ static void test_multi_sign_mldsa65_roundtrip(void) } #endif /* WOLFCOSE_HAVE_MLDSA && WOLFCOSE_SIGN */ +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_cose_countersignatures(void) +{ + static const uint8_t payload[] = "signed release manifest"; + static const uint8_t detached[] = "detached software bill of materials"; + static const uint8_t counterKid1[] = "release-approval"; + static const uint8_t counterKid2[] = "transparency-log"; + static const uint8_t counterAad[] = "approval-policy-v1"; + static const uint8_t wrongAad[] = "wrong-policy"; + static const uint8_t encrypt0Target[] = { + 0xD0u, 0x83u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u + }; + static const uint8_t mac0Target[] = { + 0xD1u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x42u, 0x03u, 0x04u + }; + static const uint8_t sign1Target[] = { + 0xD2u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x42u, 0x03u, 0x04u + }; + static const uint8_t encryptTarget[] = { + 0xD8u, 0x60u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; + /* Same COSE_Encrypt with the recipient ciphertext detached (nil). */ + static const uint8_t encryptNullRecipientTarget[] = { + 0xD8u, 0x60u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0xF6u + }; + static const uint8_t macTarget[] = { + 0xD8u, 0x61u, 0x85u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x42u, 0x03u, 0x04u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; + static const uint8_t signTarget[] = { + 0xD8u, 0x62u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, + 0x81u, 0x83u, 0x40u, 0xA0u, 0x41u, 0x03u + }; + static const uint8_t textHeaderTarget[] = { + 0xD2u, 0x84u, 0x44u, 0xA1u, 0x61u, 'x', 0x00u, + 0xA1u, 0x61u, 'y', 0x01u, 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t duplicateHeaderTarget[] = { + 0xD2u, 0x84u, 0x40u, 0xA2u, 0x01u, 0x26u, 0x01u, 0x26u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t crossBucketDuplicateTarget[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, 0xA1u, 0x01u, + 0x26u, 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t protectedCounterTarget[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x0Bu, 0x80u, 0xA0u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t duplicateTextHeaderTarget[] = { + 0xD2u, 0x84u, 0x40u, + 0xA2u, 0x61u, 'x', 0x00u, 0x61u, 'x', 0x01u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t crossBucketTextDuplicateTarget[] = { + 0xD2u, 0x84u, 0x44u, 0xA1u, 0x61u, 'x', 0x00u, + 0xA1u, 0x61u, 'x', 0x01u, 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t protectedIvPairTarget[] = { + 0xD2u, 0x84u, + 0x47u, 0xA2u, 0x05u, 0x41u, 0x00u, 0x06u, 0x41u, 0x01u, + 0xA0u, 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t unprotectedIvPairTarget[] = { + 0xD2u, 0x84u, 0x40u, + 0xA2u, 0x05u, 0x41u, 0x00u, 0x06u, 0x41u, 0x01u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t crossBucketIvPairTarget[] = { + 0xD2u, 0x84u, + 0x44u, 0xA1u, 0x05u, 0x41u, 0x00u, + 0xA1u, 0x06u, 0x41u, 0x01u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t invalidIvTypeTarget[] = { + 0xD2u, 0x84u, 0x40u, 0xA1u, 0x05u, 0x00u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t invalidPartialIvTypeTarget[] = { + 0xD2u, 0x84u, 0x40u, 0xA1u, 0x06u, 0x00u, + 0x41u, 0x01u, 0x41u, 0x02u + }; + static const uint8_t emptySignaturesTarget[] = { + 0xD8u, 0x62u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x01u, 0x80u + }; + static const uint8_t malformedSignatureTarget[] = { + 0xD8u, 0x62u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x01u, + 0x81u, 0x00u + }; + static const uint8_t protectedNestedCounterTarget[] = { + 0xD8u, 0x62u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x01u, + 0x81u, 0x83u, 0x43u, 0xA1u, 0x0Bu, 0x80u, + 0xA0u, 0x41u, 0x03u + }; + static const uint8_t emptyRecipientsTarget[] = { + 0xD8u, 0x60u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x01u, 0x80u + }; + static const uint8_t malformedRecipientTarget[] = { + 0xD8u, 0x61u, 0x85u, 0x40u, 0xA0u, 0x41u, 0x01u, + 0x41u, 0x02u, 0x81u, 0x00u + }; + static const uint8_t emptyNestedRecipientsTarget[] = { + 0xD8u, 0x60u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x01u, + 0x81u, 0x84u, 0x40u, 0xA0u, 0x40u, 0x80u + }; + static const struct { + const uint8_t* message; + size_t messageLen; + } taggedTargets[] = { + { encrypt0Target, sizeof(encrypt0Target) }, + { mac0Target, sizeof(mac0Target) }, + { sign1Target, sizeof(sign1Target) }, + { encryptTarget, sizeof(encryptTarget) }, + { encryptNullRecipientTarget, sizeof(encryptNullRecipientTarget) }, + { macTarget, sizeof(macTarget) }, + { signTarget, sizeof(signTarget) }, + { textHeaderTarget, sizeof(textHeaderTarget) } + }; + static const struct { + const uint8_t* message; + size_t messageLen; + } malformedAggregateTargets[] = { + { emptySignaturesTarget, sizeof(emptySignaturesTarget) }, + { malformedSignatureTarget, sizeof(malformedSignatureTarget) }, + { protectedNestedCounterTarget, + sizeof(protectedNestedCounterTarget) }, + { emptyRecipientsTarget, sizeof(emptyRecipientsTarget) }, + { malformedRecipientTarget, sizeof(malformedRecipientTarget) }, + { emptyNestedRecipientsTarget, sizeof(emptyNestedRecipientsTarget) } + }; + WC_RNG rng; + ecc_key primaryEcc; + ecc_key counterEcc1; + ecc_key counterEcc2; + WOLFCOSE_KEY primaryKey; + WOLFCOSE_KEY counterKey1; + WOLFCOSE_KEY counterKey2; + WOLFCOSE_COUNTERSIGNATURE counterSigner; + WOLFCOSE_COUNTERSIGNATURE0 counterSigner0; + WOLFCOSE_HDR hdr; + const uint8_t* decoded = NULL; + size_t decodedLen = 0u; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[512]; + uint8_t counterMessage[768]; + uint8_t twoCounterMessage[1024]; + uint8_t abbreviatedMessage[768]; + uint8_t inPlace[768]; + uint8_t overlap[1536]; + uint8_t tampered[1024]; + uint8_t detachedMessage[512]; + uint8_t detachedCounterMessage[768]; + size_t messageLen = 0u; + size_t counterMessageLen = 0u; + size_t twoCounterMessageLen = 0u; + size_t threeCounterMessageLen = 0u; + size_t abbreviatedMessageLen = 0u; + size_t inPlaceLen = 0u; + size_t detachedMessageLen = 0u; + size_t detachedCounterMessageLen = 0u; + size_t targetIndex; + int rngInited = 0; + int primaryEccInited = 0; + int counterEcc1Inited = 0; + int counterEcc2Inited = 0; + int primaryKeyInited = 0; + int counterKey1Inited = 0; + int counterKey2Inited = 0; + int ret; + + TEST_LOG(" [RFC 9338 Countersignatures]\n"); + + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&primaryEcc); + } + if (ret == 0) { + primaryEccInited = 1; + ret = wc_ecc_init(&counterEcc1); + } + if (ret == 0) { + counterEcc1Inited = 1; + ret = wc_ecc_init(&counterEcc2); + } + if (ret == 0) { + counterEcc2Inited = 1; + ret = wc_ecc_make_key(&rng, 32, &primaryEcc); + } + if (ret == 0) { + ret = wc_ecc_make_key(&rng, 32, &counterEcc1); + } + if (ret == 0) { + ret = wc_ecc_make_key(&rng, 32, &counterEcc2); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&primaryKey); + primaryKeyInited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_Init(&counterKey1); + counterKey1Inited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_Init(&counterKey2); + counterKey2Inited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&primaryKey, WOLFCOSE_CRV_P256, + &primaryEcc); + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&counterKey1, WOLFCOSE_CRV_P256, + &counterEcc1); + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&counterKey2, WOLFCOSE_CRV_P256, + &counterEcc2); + } + TEST_ASSERT(ret == 0, "countersignature key setup"); + + if (ret == 0) { + ret = wc_CoseSign1_Sign(&primaryKey, WOLFCOSE_ALG_ES256, + NULL, 0u, payload, sizeof(payload) - 1u, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen, &rng); + TEST_ASSERT(ret == 0, "create countersignature target"); + } + + counterSigner.algId = WOLFCOSE_ALG_ES256; + counterSigner.key = &counterKey1; + counterSigner.kid = counterKid1; + counterSigner.kidLen = sizeof(counterKid1) - 1u; + if (ret == 0) { + ret = wc_Cose_AddCounterSignature(&counterSigner, + message, messageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), counterMessage, + sizeof(counterMessage), &counterMessageLen, &rng); + TEST_ASSERT(ret == 0 && counterMessageLen > messageLen, + "add full V2 countersignature"); + } + if (ret == 0) { + ret = wc_CoseSign1_Verify(&primaryKey, + counterMessage, counterMessageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr, &decoded, &decodedLen); + TEST_ASSERT(ret == 0 && decodedLen == sizeof(payload) - 1u && + memcmp(decoded, payload, decodedLen) == 0, + "primary signature survives countersigning"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + counterMessage, counterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0 && hdr.alg == WOLFCOSE_ALG_ES256 && + hdr.kidLen == sizeof(counterKid1) - 1u && + memcmp(hdr.kid, counterKid1, hdr.kidLen) == 0, + "verify full V2 countersignature headers"); + } + if (ret == 0) { + int badRet = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + counterMessage, counterMessageLen, NULL, 0u, + wrongAad, sizeof(wrongAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(badRet != 0, "countersignature rejects wrong AAD"); + } + + if (ret == 0) { + static const uint8_t context[] = "CounterSignatureV2"; + static const uint8_t targetPayload[] = {0x01u, 0x02u}; + static const uint8_t targetSignature[] = {0x03u, 0x04u}; + WOLFCOSE_CBOR_CTX enc; + uint8_t hash[WC_SHA256_DIGEST_SIZE]; + uint8_t signature[64]; + size_t signatureLen = sizeof(signature); + size_t tbsLen = 0u; + size_t unprotectedAlgMessageLen = 0u; + int pinnedRet; + int unpinnedRet; + + enc.buf = scratch; + enc.bufSz = sizeof(scratch); + enc.idx = 0u; + ret = wc_CBOR_EncodeArrayStart(&enc, 6u); + if (ret == 0) { + ret = wc_CBOR_EncodeTstr(&enc, context, sizeof(context) - 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, targetPayload, + sizeof(targetPayload)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, targetSignature, + sizeof(targetSignature)); + } + if (ret == 0) { + tbsLen = enc.idx; + ret = wc_Hash(WC_HASH_TYPE_SHA256, scratch, (word32)tbsLen, + hash, sizeof(hash)); + } + if (ret == 0) { + ret = wolfCose_EccSignRaw(hash, sizeof(hash), signature, + &signatureLen, 32u, WC_HASH_TYPE_SHA256, &rng, + &counterEcc1); + } + + enc.buf = tampered; + enc.bufSz = sizeof(tampered); + enc.idx = 0u; + if (ret == 0) { + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_SIGN1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, + WOLFCOSE_HDR_COUNTERSIGNATURE_V2); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ES256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, signature, signatureLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, targetPayload, + sizeof(targetPayload)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, targetSignature, + sizeof(targetSignature)); + } + if (ret == 0) { + unprotectedAlgMessageLen = enc.idx; + } + TEST_ASSERT(ret == 0, + "create countersignature with externally bound alg"); + + counterKey1.alg = WOLFCOSE_ALG_ES256; + pinnedRet = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + tampered, unprotectedAlgMessageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(pinnedRet == WOLFCOSE_SUCCESS, + "accept unprotected countersignature alg when pinned"); + + counterKey1.alg = WOLFCOSE_ALG_UNSET; + unpinnedRet = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + tampered, unprotectedAlgMessageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(unpinnedRet == WOLFCOSE_E_COSE_BAD_ALG, + "reject unprotected countersignature alg when unpinned"); + } + + if (ret == 0) { + counterSigner.key = &counterKey2; + counterSigner.kid = counterKid2; + counterSigner.kidLen = sizeof(counterKid2) - 1u; + ret = wc_Cose_AddCounterSignature(&counterSigner, + counterMessage, counterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), twoCounterMessage, + sizeof(twoCounterMessage), &twoCounterMessageLen, &rng); + TEST_ASSERT(ret == 0 && twoCounterMessageLen > counterMessageLen, + "append second full countersignature"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + twoCounterMessage, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0 && hdr.kidLen == sizeof(counterKid1) - 1u && + memcmp(hdr.kid, counterKid1, hdr.kidLen) == 0, + "verify first countersignature in array"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey2, 1u, + twoCounterMessage, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0 && hdr.kidLen == sizeof(counterKid2) - 1u && + memcmp(hdr.kid, counterKid2, hdr.kidLen) == 0, + "verify second countersignature in array"); + } + if (ret == 0) { + ret = wc_Cose_AddCounterSignature(&counterSigner, + twoCounterMessage, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), tampered, sizeof(tampered), + &threeCounterMessageLen, &rng); + TEST_ASSERT(ret == 0 && + threeCounterMessageLen > twoCounterMessageLen, + "append third full countersignature"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey2, 2u, + tampered, threeCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0 && hdr.kidLen == sizeof(counterKid2) - 1u && + memcmp(hdr.kid, counterKid2, hdr.kidLen) == 0, + "verify third countersignature in array"); + } + if (ret == 0) { + int badRet = wc_Cose_VerifyCounterSignature(&counterKey2, 2u, + twoCounterMessage, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG, + "reject missing countersignature index"); + } + if (ret == 0) { + WOLFCOSE_KEY wrongAlgKey = counterKey1; + int badRet; + + wrongAlgKey.alg = WOLFCOSE_ALG_ES384; + badRet = wc_Cose_VerifyCounterSignature(&wrongAlgKey, 0u, + twoCounterMessage, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_ALG, + "countersignature rejects wrong algorithm"); + } + if (ret == 0) { + (void)memcpy(tampered, twoCounterMessage, twoCounterMessageLen); + tampered[twoCounterMessageLen - 1u] ^= 1u; + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + tampered, twoCounterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr) != 0, + "countersignature covers primary signature"); + } + + if (ret == 0) { + (void)memcpy(inPlace, message, messageLen); + inPlaceLen = messageLen; + counterSigner.key = &counterKey1; + counterSigner.kid = counterKid1; + counterSigner.kidLen = sizeof(counterKid1) - 1u; + ret = wc_Cose_AddCounterSignature(&counterSigner, + inPlace, inPlaceLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), inPlace, + sizeof(inPlace), &inPlaceLen, &rng); + TEST_ASSERT(ret == 0 && inPlaceLen == counterMessageLen, + "add full countersignature in place"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + inPlace, inPlaceLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0, "verify in-place countersignature"); + } + if (ret == 0) { + size_t overlapLen = 1u; + int badRet; + + (void)memcpy(overlap, message, messageLen); + badRet = wc_Cose_AddCounterSignature(&counterSigner, + overlap, messageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &overlap[1], sizeof(overlap) - 1u, + &overlapLen, &rng); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && overlapLen == 0u, + "reject forward partial output overlap"); + + (void)memcpy(&overlap[1], message, messageLen); + overlapLen = 1u; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + &overlap[1], messageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), overlap, sizeof(overlap), + &overlapLen, &rng); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && overlapLen == 0u, + "reject reverse partial output overlap"); + + (void)memcpy(overlap, message, messageLen); + overlapLen = 1u; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + overlap, messageLen, NULL, 0u, NULL, 0u, + overlap, sizeof(overlap), tampered, sizeof(tampered), + &overlapLen, &rng); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && overlapLen == 0u && + memcmp(overlap, message, messageLen) == 0, + "reject scratch overlap with input without mutation"); + + (void)memset(overlap, 0xA5, sizeof(tampered)); + (void)memset(tampered, 0xA5, sizeof(tampered)); + overlapLen = 1u; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + message, messageLen, NULL, 0u, NULL, 0u, + overlap, sizeof(tampered), overlap, sizeof(tampered), + &overlapLen, &rng); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && overlapLen == 0u && + memcmp(overlap, tampered, sizeof(tampered)) == 0, + "reject scratch overlap with output without mutation"); + + (void)memcpy(overlap, counterMessage, counterMessageLen); + badRet = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + overlap, counterMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + overlap, sizeof(overlap), &hdr); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && + memcmp(overlap, counterMessage, counterMessageLen) == 0, + "reject verify scratch overlap without input mutation"); + } + + counterSigner0.algId = WOLFCOSE_ALG_ES256; + counterSigner0.key = &counterKey1; + if (ret == 0) { + ret = wc_Cose_AddCounterSignature0(&counterSigner0, + message, messageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), abbreviatedMessage, + sizeof(abbreviatedMessage), &abbreviatedMessageLen, &rng); + TEST_ASSERT(ret == 0 && abbreviatedMessageLen > messageLen, + "add abbreviated V2 countersignature"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature0(&counterSigner0, + abbreviatedMessage, abbreviatedMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch)); + TEST_ASSERT(ret == 0, "verify abbreviated V2 countersignature"); + } + if (ret == 0) { + int badRet; + + (void)memcpy(overlap, abbreviatedMessage, abbreviatedMessageLen); + badRet = wc_Cose_VerifyCounterSignature0(&counterSigner0, + overlap, abbreviatedMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + overlap, sizeof(overlap)); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG && + memcmp(overlap, abbreviatedMessage, + abbreviatedMessageLen) == 0, + "reject abbreviated verify scratch overlap"); + badRet = wc_Cose_VerifyCounterSignature0(&counterSigner0, + abbreviatedMessage, abbreviatedMessageLen, scratch, 1u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch)); + TEST_ASSERT(badRet == WOLFCOSE_E_INVALID_ARG, + "reject abbreviated verify detached payload in scratch"); + } + if (ret == 0) { + WOLFCOSE_COUNTERSIGNATURE0 wrongSigner = counterSigner0; + int badRet; + + wrongSigner.key = &counterKey2; + badRet = wc_Cose_VerifyCounterSignature0(&wrongSigner, + abbreviatedMessage, abbreviatedMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch)); + TEST_ASSERT(badRet != 0, + "abbreviated countersignature rejects wrong key"); + wrongSigner.key = &counterKey1; + wrongSigner.algId = WOLFCOSE_ALG_ES384; + badRet = wc_Cose_VerifyCounterSignature0(&wrongSigner, + abbreviatedMessage, abbreviatedMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch)); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_ALG, + "abbreviated countersignature rejects wrong algorithm"); + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + abbreviatedMessage, abbreviatedMessageLen, NULL, 0u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), tampered, sizeof(tampered), + &twoCounterMessageLen, &rng); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_HDR, + "reject duplicate abbreviated countersignature"); + } + + if (ret == 0) { + ret = wc_CoseSign1_Sign(&primaryKey, WOLFCOSE_ALG_ES256, + NULL, 0u, NULL, 0u, detached, sizeof(detached) - 1u, + NULL, 0u, scratch, sizeof(scratch), detachedMessage, + sizeof(detachedMessage), &detachedMessageLen, &rng); + TEST_ASSERT(ret == 0, "create detached countersignature target"); + } + if (ret == 0) { + counterSigner.key = &counterKey1; + counterSigner.kid = counterKid1; + counterSigner.kidLen = sizeof(counterKid1) - 1u; + ret = wc_Cose_AddCounterSignature(&counterSigner, + detachedMessage, detachedMessageLen, + detached, sizeof(detached) - 1u, NULL, 0u, + scratch, sizeof(scratch), detachedCounterMessage, + sizeof(detachedCounterMessage), &detachedCounterMessageLen, &rng); + TEST_ASSERT(ret == 0, "add countersignature to detached payload"); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + detachedCounterMessage, detachedCounterMessageLen, + detached, sizeof(detached) - 1u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + TEST_ASSERT(ret == 0, "verify detached payload countersignature"); + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + detachedCounterMessage, detachedCounterMessageLen, + payload, sizeof(payload) - 1u, NULL, 0u, + scratch, sizeof(scratch), &hdr) != 0, + "detached countersignature rejects wrong payload"); + } + + if (ret == 0) { + size_t shortLen = 123u; + + counterSigner.key = &counterKey1; + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + message, messageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), tampered, messageLen, + &shortLen, &rng) == WOLFCOSE_E_BUFFER_TOO_SMALL && + shortLen == 0u, "countersignature output bound checked"); + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + message, messageLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr) == WOLFCOSE_E_COSE_BAD_HDR, + "reject target without countersignature"); + shortLen = 123u; + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + message, messageLen, NULL, 1u, NULL, 0u, + scratch, sizeof(scratch), tampered, sizeof(tampered), + &shortLen, &rng) == WOLFCOSE_E_INVALID_ARG && shortLen == 0u, + "reject missing detached payload bytes"); + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + counterMessage, counterMessageLen, NULL, 1u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch), &hdr) == WOLFCOSE_E_INVALID_ARG, + "counter verification rejects missing detached payload bytes"); + TEST_ASSERT(wc_Cose_VerifyCounterSignature0(&counterSigner0, + abbreviatedMessage, abbreviatedMessageLen, NULL, 1u, + counterAad, sizeof(counterAad) - 1u, + scratch, sizeof(scratch)) == WOLFCOSE_E_INVALID_ARG, + "abbreviated verify rejects missing detached payload bytes"); + } + + for (targetIndex = 0u; + (ret == 0) && + (targetIndex < (sizeof(taggedTargets) / sizeof(taggedTargets[0]))); + targetIndex++) { + size_t taggedOutLen = 0u; + + ret = wc_Cose_AddCounterSignature(&counterSigner, + taggedTargets[targetIndex].message, + taggedTargets[targetIndex].messageLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &taggedOutLen, &rng); + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + tampered, taggedOutLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + } + } + TEST_ASSERT(ret == 0, + "countersign all tagged top-level message types"); + + if (ret == 0) { + size_t badOutLen = 0u; + + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + duplicateHeaderTarget, sizeof(duplicateHeaderTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_MALFORMED, + "reject duplicate target unprotected header"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + crossBucketDuplicateTarget, sizeof(crossBucketDuplicateTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_MALFORMED, + "reject duplicate target protected header"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + protectedCounterTarget, sizeof(protectedCounterTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_COSE_BAD_HDR, + "reject countersignature in protected header"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + duplicateTextHeaderTarget, sizeof(duplicateTextHeaderTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_MALFORMED, + "reject duplicate text target header"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + crossBucketTextDuplicateTarget, + sizeof(crossBucketTextDuplicateTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_MALFORMED, + "reject cross-bucket duplicate text target header"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + protectedIvPairTarget, sizeof(protectedIvPairTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_COSE_BAD_HDR, + "reject protected target with IV and Partial IV"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + unprotectedIvPairTarget, sizeof(unprotectedIvPairTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_COSE_BAD_HDR, + "reject unprotected target with IV and Partial IV"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + crossBucketIvPairTarget, sizeof(crossBucketIvPairTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_COSE_BAD_HDR, + "reject cross-bucket target with IV and Partial IV"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + invalidIvTypeTarget, sizeof(invalidIvTypeTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_TYPE, + "reject target with non-bstr IV"); + TEST_ASSERT(wc_Cose_AddCounterSignature(&counterSigner, + invalidPartialIvTypeTarget, sizeof(invalidPartialIvTypeTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng) == + WOLFCOSE_E_CBOR_TYPE, + "reject target with non-bstr Partial IV"); + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey1, 0u, + protectedCounterTarget, sizeof(protectedCounterTarget), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr) == + WOLFCOSE_E_COSE_BAD_HDR, + "reject protected countersignature verification"); + + for (targetIndex = 0u; + targetIndex < (sizeof(malformedAggregateTargets) / + sizeof(malformedAggregateTargets[0])); + targetIndex++) { + int badRet = wc_Cose_AddCounterSignature(&counterSigner, + malformedAggregateTargets[targetIndex].message, + malformedAggregateTargets[targetIndex].messageLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + tampered, sizeof(tampered), &badOutLen, &rng); + + TEST_ASSERT(badRet != WOLFCOSE_SUCCESS, + "reject malformed signer or recipient tree"); + } + } + + if (counterKey2Inited != 0) { + wc_CoseKey_Free(&counterKey2); + } + if (counterKey1Inited != 0) { + wc_CoseKey_Free(&counterKey1); + } + if (primaryKeyInited != 0) { + wc_CoseKey_Free(&primaryKey); + } + if (counterEcc2Inited != 0) { + (void)wc_ecc_free(&counterEcc2); + } + if (counterEcc1Inited != 0) { + (void)wc_ecc_free(&counterEcc1); + } + if (primaryEccInited != 0) { + (void)wc_ecc_free(&primaryEcc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && defined(WOLFCOSE_EXT_SIGN) && \ + defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_ECC) +/* A request that cannot be attached must fail before the signer runs, so a + * one-time-state (HSS/LMS or delegated) key is never consumed for nothing. */ +static void test_cose_countersign_preflight(void) +{ + static const uint8_t target[] = { + 0xD2u, 0x84u, 0x40u, 0xA0u, 0x41u, 0x78u, 0x41u, 0x00u + }; + static uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + static uint8_t out[WOLFCOSE_MAX_SCRATCH_SZ + 256u]; + static uint8_t small[sizeof(target)]; + WOLFCOSE_COUNTERSIGNATURE0 counterSigner0; + WOLFCOSE_KEY signKey; + test_ext_ctx ctx; + ecc_key eccKey; + WC_RNG rng; + size_t outLen = 0u; + size_t badLen = 0u; + int badRet; + int ret; + + TEST_LOG(" [Countersignature preflight before signer]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "preflight rng"); + if (ret == 0) { + ret = wc_ecc_init(&eccKey); + } + if (ret == 0) { + ret = wc_ecc_make_key(&rng, 32, &eccKey); + TEST_ASSERT(ret == 0, "preflight ecc keygen"); + } + ctx.rng = &rng; + ctx.key = &eccKey; + ctx.coordSz = 32u; + ctx.called = 0; + if (ret == 0) { + ret = wc_CoseKey_Init(&signKey); + } + if (ret == 0) { + signKey.kty = WOLFCOSE_KTY_EC2; + signKey.crv = WOLFCOSE_CRV_P256; + ret = wc_CoseKey_SetExtSigner(&signKey, test_ext_sign_cb, &ctx); + TEST_ASSERT(ret == 0, "preflight set ext signer"); + } + counterSigner0.algId = WOLFCOSE_ALG_ES256; + counterSigner0.key = &signKey; + + if (ret == 0) { + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), small, sizeof(small), &badLen, NULL); + TEST_ASSERT(badRet == WOLFCOSE_E_BUFFER_TOO_SMALL && ctx.called == 0, + "short output rejected before signer runs"); + } + if (ret == 0) { + ret = wc_Cose_AddCounterSignature0(&counterSigner0, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &outLen, NULL); + TEST_ASSERT(ret == 0 && ctx.called == 1, + "abbreviated countersignature via ext signer"); + } + if (ret == 0) { + ctx.called = 0; + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + out, outLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &badLen, NULL); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_HDR && ctx.called == 0, + "duplicate countersignature rejected before signer runs"); + } + if (ret == 0) { + ctx.called = 0; + counterSigner0.algId = WOLFCOSE_ALG_HSS_LMS; + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + target, sizeof(target), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &badLen, NULL); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_ALG && ctx.called == 0, + "HSS/LMS rejected before delegated signer runs"); + } + + (void)wc_ecc_free(&eccKey); + (void)wc_FreeRng(&rng); +} +#endif + int test_cose(void) { g_failures = 0; @@ -25368,6 +26570,21 @@ int test_cose(void) #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) test_cose_sign1_size_and_untagged(); #endif +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) + test_cose_countersignatures(); +#endif +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && defined(WOLFCOSE_EXT_SIGN) && \ + defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_ECC) + test_cose_countersign_preflight(); +#endif +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) && \ + defined(WOLFCOSE_COUNTERSIGN_VERIFY) && defined(HAVE_ECC) && \ + (defined(WOLFCOSE_HAVE_ES384) || defined(WOLFCOSE_HAVE_ES512)) + test_cose_countersign_ecdsa_curves(); +#endif /* Key tests */ test_cose_key_init(); diff --git a/tests/test_interop.c b/tests/test_interop.c index e49d0aa..d6b43b3 100644 --- a/tests/test_interop.c +++ b/tests/test_interop.c @@ -96,6 +96,170 @@ static const uint8_t sign1_vec1_keyD[] = { static const uint8_t sign1_vec1_payload[] = "This is the content."; +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +/* RFC 9338 Appendix A.1.1 COSE_Sign countersignature example. */ +static const uint8_t countersign_rfc9338_sign_key_x[] = { + 0xba, 0xc5, 0xb1, 0x1c, 0xad, 0x8f, 0x99, 0xf9, + 0xc7, 0x2b, 0x05, 0xcf, 0x4b, 0x9e, 0x26, 0xd2, + 0x44, 0xdc, 0x18, 0x9f, 0x74, 0x52, 0x28, 0x25, + 0x5a, 0x21, 0x9a, 0x86, 0xd6, 0xa0, 0x9e, 0xff +}; +static const uint8_t countersign_rfc9338_sign_key_y[] = { + 0x20, 0x13, 0x8b, 0xf8, 0x2d, 0xc1, 0xb6, 0xd5, + 0x62, 0xbe, 0x0f, 0xa5, 0x4a, 0xb7, 0x80, 0x4a, + 0x3a, 0x64, 0xb6, 0xd7, 0x2c, 0xcf, 0xed, 0x6b, + 0x6f, 0xb6, 0xed, 0x28, 0xbb, 0xfc, 0x11, 0x7e +}; +static const uint8_t countersign_rfc9338_sign_message[] = { + 0xd8, 0x62, 0x84, 0x40, 0xa1, 0x0b, 0x83, 0x43, + 0xa1, 0x01, 0x26, 0xa1, 0x04, 0x42, 0x31, 0x31, + 0x58, 0x40, 0x5a, 0xc0, 0x5e, 0x28, 0x9d, 0x5d, + 0x0e, 0x1b, 0x0a, 0x7f, 0x04, 0x8a, 0x5d, 0x2b, + 0x64, 0x38, 0x13, 0xde, 0xd5, 0x0b, 0xc9, 0xe4, + 0x92, 0x20, 0xf4, 0xf7, 0x27, 0x8f, 0x85, 0xf1, + 0x9d, 0x4a, 0x77, 0xd6, 0x55, 0xc9, 0xd3, 0xb5, + 0x1e, 0x80, 0x5a, 0x74, 0xb0, 0x99, 0xe1, 0xe0, + 0x85, 0xaa, 0xcd, 0x97, 0xfc, 0x29, 0xd7, 0x2f, + 0x88, 0x7e, 0x88, 0x02, 0xbb, 0x66, 0x50, 0xcc, + 0xeb, 0x2c, 0x54, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x81, + 0x83, 0x43, 0xa1, 0x01, 0x26, 0xa1, 0x04, 0x42, + 0x31, 0x31, 0x58, 0x40, 0xe2, 0xae, 0xaf, 0xd4, + 0x0d, 0x69, 0xd1, 0x9d, 0xfe, 0x6e, 0x52, 0x07, + 0x7c, 0x5d, 0x7f, 0xf4, 0xe4, 0x08, 0x28, 0x2c, + 0xbe, 0xfb, 0x5d, 0x06, 0xcb, 0xf4, 0x14, 0xaf, + 0x2e, 0x19, 0xd9, 0x82, 0xac, 0x45, 0xac, 0x98, + 0xb8, 0x54, 0x4c, 0x90, 0x8b, 0x45, 0x07, 0xde, + 0x1e, 0x90, 0xb7, 0x17, 0xc3, 0xd3, 0x48, 0x16, + 0xfe, 0x92, 0x6a, 0x2b, 0x98, 0xf5, 0x3a, 0xfd, + 0x2f, 0xa0, 0xf3, 0x0a +}; +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES512) +/* RFC 9338 Appendix A.2.1 COSE_Sign1 countersignature example. */ +static const uint8_t countersign_rfc9338_key_x[] = { + 0x00, 0x72, 0x99, 0x2c, 0xb3, 0xac, 0x08, 0xec, + 0xf3, 0xe5, 0xc6, 0x3d, 0xed, 0xec, 0x0d, 0x51, + 0xa8, 0xc1, 0xf7, 0x9e, 0xf2, 0xf8, 0x2f, 0x94, + 0xf3, 0xc7, 0x37, 0xbf, 0x5d, 0xe7, 0x98, 0x66, + 0x71, 0xea, 0xc6, 0x25, 0xfe, 0x82, 0x57, 0xbb, + 0xd0, 0x39, 0x46, 0x44, 0xca, 0xaa, 0x3a, 0xaf, + 0x8f, 0x27, 0xa4, 0x58, 0x5f, 0xbb, 0xca, 0xd0, + 0xf2, 0x45, 0x76, 0x20, 0x08, 0x5e, 0x5c, 0x8f, + 0x42, 0xad +}; +static const uint8_t countersign_rfc9338_key_y[] = { + 0x01, 0xdc, 0xa6, 0x94, 0x7b, 0xce, 0x88, 0xbc, + 0x57, 0x90, 0x48, 0x5a, 0xc9, 0x74, 0x27, 0x34, + 0x2b, 0xc3, 0x5f, 0x88, 0x7d, 0x86, 0xd6, 0x5a, + 0x08, 0x93, 0x77, 0xe2, 0x47, 0xe6, 0x0b, 0xaa, + 0x55, 0xe4, 0xe8, 0x50, 0x1e, 0x2a, 0xda, 0x57, + 0x24, 0xac, 0x51, 0xd6, 0x90, 0x90, 0x08, 0x03, + 0x3e, 0xbc, 0x10, 0xac, 0x99, 0x9b, 0x9d, 0x7f, + 0x5c, 0xc2, 0x51, 0x9f, 0x3f, 0xe1, 0xea, 0x1d, + 0x94, 0x75 +}; +static const uint8_t countersign_rfc9338_message[] = { + 0xd2, 0x84, 0x45, 0xa2, 0x01, 0x26, 0x03, 0x00, + 0xa2, 0x04, 0x42, 0x31, 0x31, 0x0b, 0x83, 0x44, + 0xa1, 0x01, 0x38, 0x23, 0xa1, 0x04, 0x58, 0x1e, + 0x62, 0x69, 0x6c, 0x62, 0x6f, 0x2e, 0x62, 0x61, + 0x67, 0x67, 0x69, 0x6e, 0x73, 0x40, 0x68, 0x6f, + 0x62, 0x62, 0x69, 0x74, 0x6f, 0x6e, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x58, 0x84, + 0x01, 0xb1, 0x29, 0x1b, 0x0e, 0x60, 0xa7, 0x9c, + 0x45, 0x9a, 0x4a, 0x91, 0x84, 0xa0, 0xd3, 0x93, + 0xe0, 0x34, 0xb3, 0x4a, 0xf0, 0x69, 0xa1, 0xcc, + 0xa3, 0x4f, 0x5a, 0x91, 0x3a, 0xff, 0xff, 0x69, + 0x80, 0x02, 0x29, 0x5f, 0xa9, 0xf8, 0xfc, 0xbf, + 0xb6, 0xfd, 0xff, 0x59, 0x13, 0x2f, 0xc0, 0xc4, + 0x06, 0xe9, 0x87, 0x54, 0xa9, 0x8f, 0x1f, 0xbf, + 0xe8, 0x1c, 0x03, 0x09, 0x5f, 0x48, 0x18, 0x56, + 0xbc, 0x47, 0x01, 0x70, 0x22, 0x72, 0x06, 0xfa, + 0x5b, 0xee, 0x3c, 0x04, 0x31, 0xc5, 0x6a, 0x66, + 0x82, 0x4e, 0x7a, 0xaf, 0x69, 0x29, 0x85, 0x95, + 0x2e, 0x31, 0x27, 0x14, 0x34, 0xb2, 0xba, 0x2e, + 0x47, 0xa3, 0x35, 0xc6, 0x58, 0xb5, 0xe9, 0x95, + 0xae, 0xb5, 0xd6, 0x3c, 0xf2, 0xd0, 0xce, 0xd3, + 0x67, 0xd3, 0xe4, 0xcc, 0x8f, 0xff, 0xd5, 0x3b, + 0x70, 0xd1, 0x15, 0xba, 0xa9, 0xe8, 0x69, 0x61, + 0xfb, 0xd1, 0xa5, 0xcf, 0x54, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2e, 0x58, 0x40, 0xbb, 0x58, 0x7d, 0x6b, 0x15, + 0xf4, 0x7b, 0xfd, 0x54, 0xd2, 0xcb, 0xfc, 0xec, + 0xef, 0x75, 0x45, 0x1e, 0x92, 0xb0, 0x8a, 0x51, + 0x4b, 0xd4, 0x39, 0xfa, 0x3a, 0xa6, 0x5c, 0x6a, + 0xc9, 0x2d, 0xf0, 0xd7, 0x32, 0x8c, 0x4a, 0x47, + 0x52, 0x9b, 0x32, 0xad, 0xd3, 0xdd, 0x1b, 0x4e, + 0x94, 0x00, 0x71, 0xc0, 0x21, 0xe9, 0xa8, 0xf2, + 0x64, 0x1f, 0x1d, 0x8e, 0x3b, 0x05, 0x3d, 0xdd, + 0x65, 0xae, 0x52 +}; +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_EDDSA) +static const uint8_t countersign_legacy_key[] = { + 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, + 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a, + 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, + 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a +}; +static const uint8_t countersign_legacy_message[] = { + 0xd2, 0x84, 0x45, 0xa2, 0x01, 0x27, 0x03, 0x00, + 0xa2, 0x07, 0x83, 0x43, 0xa1, 0x01, 0x27, 0xa1, + 0x04, 0x42, 0x31, 0x31, 0x58, 0x40, 0x6d, 0xae, + 0xd1, 0x58, 0xaf, 0xe4, 0x03, 0x2e, 0x8d, 0xd4, + 0x77, 0xd3, 0xd2, 0xb7, 0xf6, 0x67, 0xe7, 0x95, + 0x7a, 0xa8, 0x30, 0x2b, 0xb5, 0xe5, 0x68, 0xb4, + 0xdc, 0xbc, 0xce, 0x3c, 0xf0, 0xed, 0x5a, 0x90, + 0xf8, 0x31, 0x35, 0x1c, 0x85, 0xd6, 0x15, 0x5a, + 0x42, 0xa1, 0x7c, 0xa1, 0xf2, 0x5f, 0x50, 0x1c, + 0xc1, 0x3f, 0x67, 0x10, 0x8a, 0xe5, 0x3b, 0xda, + 0x92, 0xdb, 0x88, 0x27, 0x2e, 0x00, 0x04, 0x42, + 0x31, 0x31, 0x54, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x58, + 0x40, 0x71, 0x42, 0xfd, 0x2f, 0xf9, 0x6d, 0x56, + 0xdb, 0x85, 0xbe, 0xe9, 0x05, 0xa7, 0x6b, 0xa1, + 0xd0, 0xb7, 0x32, 0x1a, 0x95, 0xc8, 0xc4, 0xd3, + 0x60, 0x7c, 0x57, 0x81, 0x93, 0x2b, 0x7a, 0xfb, + 0x87, 0x11, 0x49, 0x7d, 0xfa, 0x75, 0x1b, 0xf4, + 0x0b, 0x58, 0xb3, 0xbc, 0xc3, 0x23, 0x00, 0xb1, + 0x48, 0x7f, 0x3d, 0xb3, 0x40, 0x85, 0xee, 0xf0, + 0x13, 0xbf, 0x08, 0xf4, 0xa4, 0x4d, 0x6f, 0xef, + 0x0d +}; +static const uint8_t countersign0_legacy_message[] = { + 0xd2, 0x84, 0x45, 0xa2, 0x01, 0x27, 0x03, 0x00, + 0xa2, 0x09, 0x58, 0x40, 0x58, 0xa1, 0x5e, 0x38, + 0xe6, 0xf0, 0x6d, 0x58, 0xef, 0xfe, 0x37, 0xfa, + 0x01, 0x1f, 0x74, 0x82, 0xd9, 0xfd, 0x58, 0xa4, + 0x8a, 0x71, 0x4e, 0x37, 0x33, 0x76, 0x28, 0x9a, + 0x07, 0x93, 0xec, 0x3f, 0xda, 0x92, 0x9d, 0xff, + 0xef, 0xa5, 0x71, 0xc9, 0x8c, 0x27, 0xe3, 0x74, + 0x9b, 0xff, 0xcd, 0xd0, 0x53, 0xb6, 0xc8, 0x4b, + 0x93, 0xdd, 0xa6, 0x07, 0xb6, 0x03, 0xda, 0xed, + 0xe2, 0x03, 0x0a, 0x08, 0x04, 0x42, 0x31, 0x31, + 0x54, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x58, 0x40, 0x71, + 0x42, 0xfd, 0x2f, 0xf9, 0x6d, 0x56, 0xdb, 0x85, + 0xbe, 0xe9, 0x05, 0xa7, 0x6b, 0xa1, 0xd0, 0xb7, + 0x32, 0x1a, 0x95, 0xc8, 0xc4, 0xd3, 0x60, 0x7c, + 0x57, 0x81, 0x93, 0x2b, 0x7a, 0xfb, 0x87, 0x11, + 0x49, 0x7d, 0xfa, 0x75, 0x1b, 0xf4, 0x0b, 0x58, + 0xb3, 0xbc, 0xc3, 0x23, 0x00, 0xb1, 0x48, 0x7f, + 0x3d, 0xb3, 0x40, 0x85, 0xee, 0xf0, 0x13, 0xbf, + 0x08, 0xf4, 0xa4, 0x4d, 0x6f, 0xef, 0x0d +}; +#endif + /* ----- COSE_Encrypt0 Test Vectors ----- */ /* @@ -1171,6 +1335,212 @@ static void test_interop_sign1_eddsa_with_aad(void) } #endif /* WOLFCOSE_HAVE_EDDSA */ +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_interop_countersign_rfc9338_sign(void) +{ + static const uint8_t expectedKid[] = "11"; + WOLFCOSE_KEY counterKey; + ecc_key eccKey; + WOLFCOSE_HDR hdr; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + int eccInited = 0; + int keyInited = 0; + int ret; + + printf(" [RFC 9338 Appendix A.1.1 COSE_Sign Countersignature]\n"); + + ret = wc_ecc_init(&eccKey); + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_import_unsigned(&eccKey, + countersign_rfc9338_sign_key_x, + countersign_rfc9338_sign_key_y, NULL, ECC_SECP256R1); + } + TEST_ASSERT(ret == 0, "import RFC 9338 COSE_Sign countersigner key"); + if (ret == 0) { + ret = wc_CoseKey_Init(&counterKey); + keyInited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&counterKey, WOLFCOSE_CRV_P256, + &eccKey); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey, 0u, + countersign_rfc9338_sign_message, + sizeof(countersign_rfc9338_sign_message), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr); + } + TEST_ASSERT(ret == 0, + "verify RFC 9338 COSE_Sign countersignature vector"); + if (ret == 0) { + TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_ES256 && + hdr.kidLen == sizeof(expectedKid) - 1u && + memcmp(hdr.kid, expectedKid, hdr.kidLen) == 0, + "decode RFC 9338 COSE_Sign countersigner headers"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&counterKey); + } + if (eccInited != 0) { + wc_ecc_free(&eccKey); + } +} +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES512) +static void test_interop_countersign_rfc9338_sign1(void) +{ + static const uint8_t expectedKid[] = + "bilbo.baggins@hobbiton.example"; + WOLFCOSE_KEY counterKey; + ecc_key eccKey; + WOLFCOSE_HDR hdr; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tampered[sizeof(countersign_rfc9338_message)]; + int eccInited = 0; + int keyInited = 0; + int ret; + + printf(" [RFC 9338 Appendix A.2.1 Countersignature]\n"); + + ret = wc_ecc_init(&eccKey); + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_import_unsigned(&eccKey, + countersign_rfc9338_key_x, countersign_rfc9338_key_y, + NULL, ECC_SECP521R1); + } + TEST_ASSERT(ret == 0, "import RFC 9338 countersigner key"); + if (ret == 0) { + ret = wc_CoseKey_Init(&counterKey); + keyInited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&counterKey, WOLFCOSE_CRV_P521, + &eccKey); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey, 0u, + countersign_rfc9338_message, + sizeof(countersign_rfc9338_message), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + } + TEST_ASSERT(ret == 0, "verify RFC 9338 countersignature vector"); + if (ret == 0) { + TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_ES512 && + hdr.kidLen == sizeof(expectedKid) - 1u && + memcmp(hdr.kid, expectedKid, hdr.kidLen) == 0, + "decode RFC 9338 countersigner headers"); + (void)memcpy(tampered, countersign_rfc9338_message, + sizeof(tampered)); + tampered[sizeof(tampered) - 1u] ^= 1u; + TEST_ASSERT(wc_Cose_VerifyCounterSignature(&counterKey, 0u, + tampered, sizeof(tampered), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr) != 0, + "RFC 9338 vector covers primary signature"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&counterKey); + } + if (eccInited != 0) { + wc_ecc_free(&eccKey); + } +} +#endif + +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_EDDSA) +static void test_interop_countersign_legacy(void) +{ + WOLFCOSE_KEY counterKey; + WOLFCOSE_COUNTERSIGNATURE0 counterSigner0; + WOLFCOSE_HDR hdr; + ed25519_key edKey; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + int edInited = 0; + int keyInited = 0; + int ret; + + printf(" [RFC 8152 Legacy Countersignatures]\n"); + + ret = wc_ed25519_init(&edKey); + if (ret == 0) { + edInited = 1; + ret = wc_ed25519_import_public(countersign_legacy_key, + (word32)sizeof(countersign_legacy_key), &edKey); + } + TEST_ASSERT(ret == 0, "import legacy countersigner key"); + if (ret == 0) { + ret = wc_CoseKey_Init(&counterKey); + keyInited = (ret == 0) ? 1 : 0; + } + if (ret == 0) { + ret = wc_CoseKey_SetEd25519(&counterKey, &edKey); + } + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature(&counterKey, 0u, + countersign_legacy_message, + sizeof(countersign_legacy_message), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr); + } + TEST_ASSERT(ret == 0 && hdr.alg == WOLFCOSE_ALG_EDDSA, + "verify deprecated label 7 countersignature"); + + counterSigner0.algId = WOLFCOSE_ALG_EDDSA; + counterSigner0.key = &counterKey; + if (ret == 0) { + ret = wc_Cose_VerifyCounterSignature0(&counterSigner0, + countersign0_legacy_message, + sizeof(countersign0_legacy_message), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch)); + } + TEST_ASSERT(ret == 0, + "verify deprecated label 9 countersignature"); + +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) + if (ret == 0) { + static uint8_t out[WOLFCOSE_MAX_SCRATCH_SZ + 256u]; + WOLFCOSE_COUNTERSIGNATURE counterSigner; + size_t outLen = 0u; + int badRet; + + /* Verification prefers a V2 label over a legacy one, so attaching V2 + * beside label 7 or 9 would hide the legacy signature; it is refused + * at the plan stage, before any signer could run. */ + counterSigner.algId = WOLFCOSE_ALG_EDDSA; + counterSigner.key = &counterKey; + counterSigner.kid = NULL; + counterSigner.kidLen = 0u; + badRet = wc_Cose_AddCounterSignature(&counterSigner, + countersign_legacy_message, sizeof(countersign_legacy_message), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + out, sizeof(out), &outLen, NULL); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_HDR, + "refuse V2 countersignature beside legacy label 7"); + badRet = wc_Cose_AddCounterSignature0(&counterSigner0, + countersign0_legacy_message, + sizeof(countersign0_legacy_message), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + out, sizeof(out), &outLen, NULL); + TEST_ASSERT(badRet == WOLFCOSE_E_COSE_BAD_HDR, + "refuse V2 countersignature beside legacy label 9"); + } +#endif + + if (keyInited != 0) { + wc_CoseKey_Free(&counterKey); + } + if (edInited != 0) { + wc_ed25519_free(&edKey); + } +} +#endif + /* ----- Multi-Signer Interop Tests ----- */ #if defined(WOLFCOSE_SIGN) && defined(WOLFCOSE_HAVE_ES256) static void test_interop_sign_multi_signer(void) @@ -1585,6 +1955,18 @@ int test_interop(void) test_interop_sign1_eddsa_roundtrip(); test_interop_sign1_eddsa_with_aad(); #endif +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) + test_interop_countersign_rfc9338_sign(); +#endif +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES512) + test_interop_countersign_rfc9338_sign1(); +#endif +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) && \ + defined(WOLFCOSE_HAVE_EDDSA) + test_interop_countersign_legacy(); +#endif printf("\n[Encrypt0 Tests]\n"); #ifdef WOLFCOSE_HAVE_AESGCM diff --git a/tools/wolfcose_tool.c b/tools/wolfcose_tool.c index f58ad81..224cbba 100644 --- a/tools/wolfcose_tool.c +++ b/tools/wolfcose_tool.c @@ -26,6 +26,8 @@ * keygen -a -o * sign -k -a -i -o * verify -k -i + * countersign -k -a -i -o + * counterverify -k -i * enc -k -a -i -o <cose_file> * dec -k <keyfile> -i <cose_file> -o <plaintext> * info -i <cose_file> @@ -78,6 +80,12 @@ #endif #endif +/* Countersigning grows a message by a signature, a kid, and CBOR framing; one + * capacity for input and output lets a countersigned file be countersigned or + * verified again by the next command. */ +#define WOLFCOSE_TOOL_MAX_COUNTER_MSG \ + (WOLFCOSE_TOOL_MAX_MSG + WOLFCOSE_TOOL_MAX_KEY + WOLFCOSE_MAX_SIG_SZ + 128u) + #define EXIT_USAGE 1 #define EXIT_CRYPTO 2 #define EXIT_IO 3 @@ -103,12 +111,16 @@ static void usage(void) " keygen -a <alg> -o <keyfile>\n" " sign -k <keyfile> -a <alg> -i <payload> -o <cose_file>\n" " verify -k <keyfile> -i <cose_file>\n" + " countersign -k <keyfile> -a <alg> -i <cose_file>" + " -o <cose_file>\n" + " counterverify -k <keyfile> -i <cose_file> [--index <n>]\n" " enc -k <keyfile> -a <alg> -i <plaintext> -o <cose_file>\n" " dec -k <keyfile> -i <cose_file> -o <plaintext>\n" " mac -k <keyfile> -a <alg> -i <payload> -o <cose_file>\n" " macverify -k <keyfile> -i <cose_file>\n" " info -i <cose_file>\n" " test [--all | -a <alg>] Round-trip self-test\n" + "\nCountersign options: -p <detached_payload> --aad <aad_file>\n" "\n" "Algorithms: ES256, EdDSA, Ed448, PS256, PS384, PS512,\n" " ML-DSA-44, ML-DSA-65, ML-DSA-87,\n" @@ -209,6 +221,23 @@ static int read_file(const char* path, uint8_t* buf, size_t bufSz, fprintf(stderr, "Read error: %s\n", path); return EXIT_IO; } + /* A full buffer with bytes still unread would otherwise be processed as a + * silently truncated message and fail later as malformed CBOR. */ + if (n == bufSz) { + int next = fgetc(f); + + if (ferror(f)) { + fclose(f); + fprintf(stderr, "Read error: %s\n", path); + return EXIT_IO; + } + if (next != EOF) { + fclose(f); + fprintf(stderr, "File too large (limit %zu bytes): %s\n", + bufSz, path); + return EXIT_IO; + } + } fclose(f); *outLen = n; return 0; @@ -782,6 +811,263 @@ static int tool_verify(const char* keyPath, const char* inPath) return ret; } +#if defined(WOLFCOSE_COUNTERSIGN) +static int tool_counter_apply(WOLFCOSE_KEY* key, int32_t alg, + int verify, size_t counterIndex, + const uint8_t* msg, size_t msgLen, + const uint8_t* detached, size_t detachedLen, + const uint8_t* aad, size_t aadLen, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen) +{ + int ret; + + if (verify != 0) { +#if defined(WOLFCOSE_COUNTERSIGN_VERIFY) + WOLFCOSE_HDR hdr; + + ret = wc_Cose_VerifyCounterSignature(key, counterIndex, + msg, msgLen, detached, detachedLen, aad, aadLen, + scratch, scratchSz, &hdr); + if (ret == 0) { + printf("Countersignature %zu verification OK", counterIndex); + if (hdr.kidLen != 0u) { + printf(". KID: %.*s", (int)hdr.kidLen, + (const char*)hdr.kid); + } + printf("\n"); + } +#else + (void)key; + (void)counterIndex; + (void)msg; + (void)msgLen; + (void)detached; + (void)detachedLen; + (void)aad; + (void)aadLen; + (void)scratch; + (void)scratchSz; + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + else { +#if defined(WOLFCOSE_COUNTERSIGN_SIGN) + WOLFCOSE_COUNTERSIGNATURE counterSigner; + WC_RNG rng; + + ret = wc_InitRng(&rng); + if (ret == 0) { + counterSigner.algId = alg; + counterSigner.key = key; + counterSigner.kid = key->kid; + counterSigner.kidLen = key->kidLen; + ret = wc_Cose_AddCounterSignature(&counterSigner, + msg, msgLen, detached, detachedLen, aad, aadLen, + scratch, scratchSz, out, outSz, outLen, &rng); + wc_FreeRng(&rng); + } +#else + (void)key; + (void)alg; + (void)msg; + (void)msgLen; + (void)detached; + (void)detachedLen; + (void)aad; + (void)aadLen; + (void)scratch; + (void)scratchSz; + (void)out; + (void)outSz; + (void)outLen; + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + return ret; +} + +static int tool_counter(const char* keyPath, int32_t alg, + int verify, size_t counterIndex, + const char* inPath, const char* outPath, + const char* detachedPath, const char* aadPath) +{ + int ret; + int keyMatched = 0; + int32_t kty = 0; + int32_t crv = 0; + int32_t keyAlg = 0; + uint8_t keyBuf[WOLFCOSE_TOOL_MAX_KEY]; + uint8_t msgBuf[WOLFCOSE_TOOL_MAX_COUNTER_MSG]; + uint8_t detachedBuf[WOLFCOSE_TOOL_MAX_MSG]; + uint8_t aadBuf[WOLFCOSE_TOOL_MAX_MSG]; + uint8_t outBuf[WOLFCOSE_TOOL_MAX_COUNTER_MSG]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + size_t keyLen = 0u; + size_t msgLen = 0u; + size_t detachedLen = 0u; + size_t aadLen = 0u; + size_t outLen = 0u; + const uint8_t* detached = NULL; + const uint8_t* aad = NULL; + WOLFCOSE_KEY coseKey; + + ret = read_file(keyPath, keyBuf, sizeof(keyBuf), &keyLen); + if (ret == 0) { + ret = read_file(inPath, msgBuf, sizeof(msgBuf), &msgLen); + } + if ((ret == 0) && (detachedPath != NULL)) { + ret = read_file(detachedPath, detachedBuf, + sizeof(detachedBuf), &detachedLen); + if (ret == 0) { + detached = detachedBuf; + } + } + if ((ret == 0) && (aadPath != NULL)) { + ret = read_file(aadPath, aadBuf, sizeof(aadBuf), &aadLen); + if (ret == 0) { + aad = aadBuf; + } + } + if (ret != 0) { + return ret; + } + + wc_CoseKey_Init(&coseKey); + (void)wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + kty = coseKey.kty; + crv = coseKey.crv; + keyAlg = coseKey.alg; + +#ifdef WOLFCOSE_HAVE_ECDSA + if (kty == WOLFCOSE_KTY_EC2) { + ecc_key ecc; + + keyMatched = 1; + wc_CoseKey_Init(&coseKey); + wc_ecc_init(&ecc); + ret = wc_CoseKey_SetEcc(&coseKey, crv, &ecc); + if (ret == 0) { + ret = wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + } + if (ret == 0) { + ret = tool_counter_apply(&coseKey, alg, verify, counterIndex, + msgBuf, msgLen, detached, detachedLen, aad, aadLen, + scratch, sizeof(scratch), outBuf, sizeof(outBuf), &outLen); + } + wc_ecc_free(&ecc); + } + else +#endif +#ifdef WOLFCOSE_HAVE_RSAPSS + if (kty == WOLFCOSE_KTY_RSA) { + RsaKey rsa; + + keyMatched = 1; + wc_CoseKey_Init(&coseKey); + wc_InitRsaKey(&rsa, NULL); + ret = wc_CoseKey_SetRsa(&coseKey, &rsa); + if (ret == 0) { + ret = wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + } + if (ret == 0) { + ret = tool_counter_apply(&coseKey, alg, verify, counterIndex, + msgBuf, msgLen, detached, detachedLen, aad, aadLen, + scratch, sizeof(scratch), outBuf, sizeof(outBuf), &outLen); + } + wc_FreeRsaKey(&rsa); + } + else +#endif +#ifdef WOLFCOSE_HAVE_EDDSA + if ((kty == WOLFCOSE_KTY_OKP) && + (crv == WOLFCOSE_CRV_ED25519)) { + ed25519_key ed; + + keyMatched = 1; + wc_CoseKey_Init(&coseKey); + wc_ed25519_init(&ed); + ret = wc_CoseKey_SetEd25519(&coseKey, &ed); + if (ret == 0) { + ret = wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + } + if (ret == 0) { + ret = tool_counter_apply(&coseKey, alg, verify, counterIndex, + msgBuf, msgLen, detached, detachedLen, aad, aadLen, + scratch, sizeof(scratch), outBuf, sizeof(outBuf), &outLen); + } + wc_ed25519_free(&ed); + } + else +#endif +#ifdef WOLFCOSE_HAVE_ED448 + if ((kty == WOLFCOSE_KTY_OKP) && (crv == WOLFCOSE_CRV_ED448)) { + ed448_key ed; + + keyMatched = 1; + wc_CoseKey_Init(&coseKey); + wc_ed448_init(&ed); + ret = wc_CoseKey_SetEd448(&coseKey, &ed); + if (ret == 0) { + ret = wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + } + if (ret == 0) { + ret = tool_counter_apply(&coseKey, alg, verify, counterIndex, + msgBuf, msgLen, detached, detachedLen, aad, aadLen, + scratch, sizeof(scratch), outBuf, sizeof(outBuf), &outLen); + } + wc_ed448_free(&ed); + } + else +#endif +#ifdef WOLFCOSE_HAVE_MLDSA + if (kty == WOLFCOSE_KTY_AKP) { + wc_MlDsaKey dl; + + keyMatched = 1; + wc_CoseKey_Init(&coseKey); + ret = wc_MlDsaKey_Init(&dl, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_CoseKey_SetMlDsa(&coseKey, keyAlg, &dl); + } + if (ret == 0) { + ret = wc_CoseKey_Decode(&coseKey, keyBuf, keyLen); + } + if (ret == 0) { + ret = tool_counter_apply(&coseKey, alg, verify, counterIndex, + msgBuf, msgLen, detached, detachedLen, aad, aadLen, + scratch, sizeof(scratch), outBuf, sizeof(outBuf), &outLen); + } + wc_MlDsaKey_Free(&dl); + } + else +#endif + { + (void)crv; + (void)keyAlg; + } + + if (keyMatched == 0) { + fprintf(stderr, "Unsupported countersigning key type\n"); + return EXIT_USAGE; + } + if (ret != 0) { + fprintf(stderr, "%s failed: %d\n", + (verify != 0) ? "Counter verification" : "Countersign", + ret); + return EXIT_CRYPTO; + } + if (verify == 0) { + ret = write_file(outPath, outBuf, outLen); + if (ret == 0) { + printf("Countersigned: %zu byte COSE object -> %zu bytes\n", + msgLen, outLen); + } + } + return ret; +} +#endif /* WOLFCOSE_COUNTERSIGN */ + /* ----- enc: COSE_Encrypt0 encrypt ----- */ #if defined(WOLFCOSE_HAVE_AESGCM) || defined(WOLFCOSE_HAVE_AESCCM) || \ (defined(WOLFCOSE_HAVE_CHACHA20)) @@ -1631,6 +1917,10 @@ int main(int argc, char* argv[]) const char* keyPath = NULL; const char* inPath = NULL; const char* outPath = NULL; + const char* detachedPath = NULL; + const char* aadPath = NULL; + size_t counterIndex = 0u; + int counterIndexSet = 0; int32_t alg = 0; int i; @@ -1659,6 +1949,21 @@ int main(int argc, char* argv[]) else if (strcmp(argv[i], "-o") == 0) { outPath = argv[++i]; } + else if (strcmp(argv[i], "-p") == 0) { + detachedPath = argv[++i]; + } + else if (strcmp(argv[i], "--aad") == 0) { + aadPath = argv[++i]; + } + else if (strcmp(argv[i], "--index") == 0) { + char extra; + + if (sscanf(argv[++i], "%zu%c", &counterIndex, &extra) != 1) { + fprintf(stderr, "Invalid countersignature index\n"); + return EXIT_USAGE; + } + counterIndexSet = 1; + } else { fprintf(stderr, "Unknown option: %s\n", argv[i]); usage(); @@ -1678,6 +1983,18 @@ int main(int argc, char* argv[]) } } + if (((detachedPath != NULL) || (aadPath != NULL)) && + (strcmp(cmd, "countersign") != 0) && + (strcmp(cmd, "counterverify") != 0)) { + fprintf(stderr, + "-p and --aad apply only to countersign and counterverify\n"); + return EXIT_USAGE; + } + if ((counterIndexSet != 0) && (strcmp(cmd, "counterverify") != 0)) { + fprintf(stderr, "--index applies only to counterverify\n"); + return EXIT_USAGE; + } + /* Dispatch */ if (strcmp(cmd, "test") == 0) { return tool_test(algStr); @@ -1705,6 +2022,27 @@ int main(int argc, char* argv[]) } return tool_verify(keyPath, inPath); } +#if defined(WOLFCOSE_COUNTERSIGN) + else if (strcmp(cmd, "countersign") == 0) { + if (keyPath == NULL || algStr == NULL || inPath == NULL || + outPath == NULL) { + fprintf(stderr, "countersign requires -k <key> -a <alg>" + " -i <input> -o <output>\n"); + return EXIT_USAGE; + } + return tool_counter(keyPath, alg, 0, 0u, inPath, outPath, + detachedPath, aadPath); + } + else if (strcmp(cmd, "counterverify") == 0) { + if (keyPath == NULL || inPath == NULL) { + fprintf(stderr, + "counterverify requires -k <key> -i <input>\n"); + return EXIT_USAGE; + } + return tool_counter(keyPath, alg, 1, counterIndex, inPath, NULL, + detachedPath, aadPath); + } +#endif #if defined(WOLFCOSE_HAVE_HMAC) else if (strcmp(cmd, "mac") == 0) { if (keyPath == NULL || algStr == NULL || inPath == NULL ||