diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml new file mode 100644 index 0000000..dd6ea74 --- /dev/null +++ b/.github/workflows/interop.yml @@ -0,0 +1,101 @@ +name: Interop + +on: + push: + branches: [ 'main', 'release/**' ] + pull_request: + branches: [ '**' ] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + TCOSE_SHA: ff4c5f7c6fbbe27bb582214ff1878bf58ebc6c43 + QCBOR_SHA: 930708bb86481e88879eb1d87fd4d664f1d69503 + +jobs: + discover: + uses: ./.github/workflows/_resolve-wolfssl.yml + + interop: + name: wolfCOSE <-> t_cose (OpenSSL, wolfSSL ${{ matrix.wolfssl-version }}) + needs: discover + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.discover.outputs.matrix) }} + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool libssl-dev + + - name: Cache wolfSSL (${{ matrix.wolfssl-version }}) + if: matrix.wolfssl-version != 'master' + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: wolfssl-interop-${{ matrix.wolfssl-version }}-v1 + + - name: Build wolfSSL (${{ matrix.wolfssl-version }}) + if: matrix.wolfssl-version == 'master' || steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} \ + https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-ecc --enable-ed25519 --enable-ed448 \ + --enable-curve25519 --enable-aesgcm --enable-aesccm \ + --enable-sha384 --enable-sha512 --enable-keygen \ + --enable-rsapss --enable-hkdf --enable-aeskeywrap \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + # QCBOR and t_cose are BSD-3-Clause; fetched at pinned SHAs, not vendored. + - name: Build QCBOR @ pinned SHA + run: | + cd ~ + mkdir -p interop-deps/QCBOR && cd interop-deps/QCBOR + git init -q + git fetch --depth 1 -q https://github.com/laurencelundblade/QCBOR.git $QCBOR_SHA + git checkout -q FETCH_HEAD + make libqcbor.a + + - name: Build t_cose @ pinned SHA (OpenSSL adapter) + run: | + cd ~/interop-deps + mkdir -p t_cose && cd t_cose + git init -q + git fetch --depth 1 -q https://github.com/laurencelundblade/t_cose.git $TCOSE_SHA + git checkout -q FETCH_HEAD + make -f Makefile.ossl libt_cose.a \ + QCBOR_INC="-I $HOME/interop-deps/QCBOR/inc" \ + QCBOR_LIB="$HOME/interop-deps/QCBOR/libqcbor.a" + + - name: Build wolfCOSE + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $WOLFSSL_DIR/include" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + + - name: Run t_cose interop + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make interop-tcose \ + TCOSE_DIR=$HOME/interop-deps/t_cose \ + QCBOR_DIR=$HOME/interop-deps/QCBOR \ + TCOSE_CRYPTO_LIB="-lcrypto" \ + CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $WOLFSSL_DIR/include" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" diff --git a/.gitignore b/.gitignore index f78be52..21aceb9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ tests/test_wolfcose tools/wolfcose_tool examples/lifecycle_demo +tests/interop/t_cose/interop_tcose +tests/interop/t_cose/QCBOR/ +tests/interop/t_cose/t_cose/ # Editor / OS *.swp diff --git a/Makefile b/Makefile index b3ee04f..4fe67a6 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,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 coverage tool tool-test cmdline-test demo demos comprehensive scenarios c99-check clean +.PHONY: all shared test coverage tool tool-test cmdline-test demo demos comprehensive scenarios interop-tcose c99-check clean # --- Core library --- all: $(LIB_A) @@ -164,6 +164,27 @@ scenarios: $(LIB_A) ./$(SCEN_BROADCAST) || exit 1 @echo "=== All scenario examples passed ===" +# --- t_cose wire-interop (t_cose on OpenSSL; t_cose + QCBOR fetched at pinned SHAs) --- +# The harness TU never includes OpenSSL headers (they collide with wolfSSL on +# SHA256 etc.); the t_cose-side key loader is a separate TU. CI overrides +# TCOSE_CRYPTO_INC / TCOSE_CRYPTO_LIB per platform (system libssl on Linux). +TCOSE_DIR ?= $(HOME)/interop-deps/t_cose +QCBOR_DIR ?= $(HOME)/interop-deps/QCBOR +TCOSE_CRYPTO_INC ?= +TCOSE_CRYPTO_LIB ?= -lcrypto +INTEROP_DIR = tests/interop/t_cose +INTEROP_BIN = $(INTEROP_DIR)/interop_tcose +INTEROP_CFLAGS = $(CFLAGS) -std=c99 -I$(TCOSE_DIR)/inc -I$(QCBOR_DIR)/inc + +interop-tcose: $(LIB_A) + $(CC) $(INTEROP_CFLAGS) -DT_COSE_USE_OPENSSL_CRYPTO -c $(INTEROP_DIR)/interop_tcose.c -o $(INTEROP_DIR)/interop_tcose.o + $(CC) -std=c99 -Wall -Wextra -I$(TCOSE_DIR)/inc -I$(QCBOR_DIR)/inc $(TCOSE_CRYPTO_INC) \ + -c $(INTEROP_DIR)/interop_key_ossl.c -o $(INTEROP_DIR)/interop_key.o + $(CC) -o $(INTEROP_BIN) $(INTEROP_DIR)/interop_tcose.o $(INTEROP_DIR)/interop_key.o \ + $(LIB_A) $(TCOSE_DIR)/libt_cose.a $(QCBOR_DIR)/libqcbor.a \ + $(TCOSE_CRYPTO_LIB) $(LDFLAGS) -lm + ./$(INTEROP_BIN) + # --- C99 conformance gate --- # Compiles every translation unit (core, tests, tool, examples) under strict # ISO C99 with -pedantic-errors -Werror so any non-C99 construct fails the @@ -195,5 +216,6 @@ clean: rm -f $(OBJ) $(TEST_BIN) $(TOOL_BIN) $(DEMO_BIN) $(ENC_DEMO) $(MAC_DEMO) \ $(SIGN1_DEMO) $(COMP_SIGN) $(COMP_ENCRYPT) $(COMP_MAC) $(COMP_ERRORS) \ $(SCEN_FIRMWARE) $(SCEN_MULTIPARTY) $(SCEN_IOTFLEET) $(SCEN_SENSOR) $(SCEN_BROADCAST) \ + $(INTEROP_DIR)/*.o $(INTEROP_DIR)/*.su $(INTEROP_BIN) \ $(LIB_A) $(LIB_SO) src/*.su tests/*.su examples/comprehensive/*.su examples/scenarios/*.su \ src/*.gcno src/*.gcda tests/*.gcno tests/*.gcda *.gcov diff --git a/docs/Testing.md b/docs/Testing.md index fe6f8a6..d4f2d18 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -50,6 +50,26 @@ Runs real-world scenario examples: - Sensor attestation - Group broadcast MAC +### Interoperability (t_cose) + +```bash +make interop-tcose \ + TCOSE_DIR=/path/to/t_cose QCBOR_DIR=/path/to/QCBOR \ + TCOSE_CRYPTO_LIB="-lcrypto" +``` + +Proves RFC 9052 wire interoperability between wolfCOSE (on wolfCrypt) and +[t_cose](https://github.com/laurencelundblade/t_cose) (on OpenSSL): each library +produces COSE messages the other consumes, both directions, across every +algorithm both implement — ES256/384/512, PS256/384/512, EdDSA (Ed25519, Ed448), +HMAC 256/384/512, and AES-GCM 128/192/256. The bytes on the wire are the only +interface; the two APIs are never reconciled. Each primitive class also exercises +a tamper case that wolfCOSE must reject. + +t_cose and QCBOR are BSD-3-Clause and are not vendored; the +[Interop CI job](../.github/workflows/interop.yml) fetches them at pinned +SHAs. See `tests/interop/t_cose/README.md` for the fixed test-key provenance. + --- ## Code Coverage diff --git a/tests/interop/t_cose/README.md b/tests/interop/t_cose/README.md new file mode 100644 index 0000000..21bf0b0 --- /dev/null +++ b/tests/interop/t_cose/README.md @@ -0,0 +1,91 @@ +# wolfCOSE ⇄ t_cose Interop + +A wire-interoperability conformance suite: wolfCOSE (on wolfCrypt) and +[t_cose](https://github.com/laurencelundblade/t_cose) (on OpenSSL) each produce +COSE messages the other consumes, in both directions, for every algorithm both +implement. The COSE bytes on the wire are the only interface — the two libraries' +APIs are never reconciled. This demonstrates RFC 9052/9053 conformance, not a +performance or feature comparison. + +## What is covered + +| Message | Algorithms (both directions) | +|-------------|------------------------------------------------------| +| COSE_Sign1 | ES256/384/512, PS256/384/512, EdDSA (Ed25519, Ed448) | +| COSE_Mac0 | HMAC 256/384/512 | +| COSE_Encrypt0 | AES-GCM 128/192/256 | + +Each primitive class also runs a negative case — a tampered signature, MAC tag, +or AEAD tag — that wolfCOSE must reject. + +CBOR byte-for-byte equality is an explicit non-goal: CBOR permits multiple valid +encodings, so the suite verifies that each side *reconstructs and validates* the +other's output, never that the two producers emit identical bytes. + +## Dependencies (not vendored) + +t_cose and QCBOR are BSD-3-Clause. They are **not** redistributed here; CI fetches +them at pinned SHAs and links them: + +- t_cose `dev` @ `ff4c5f7c6fbbe27bb582214ff1878bf58ebc6c43` +- QCBOR @ `930708bb86481e88879eb1d87fd4d664f1d69503` + +wolfCOSE itself stays zero-allocation; this harness is test-only. + +## Building locally + +Fetch the pinned SHAs (same sequence CI uses) and build, from this directory: + +```bash +# QCBOR @ pinned SHA +mkdir -p QCBOR && cd QCBOR && git init -q && \ + git fetch --depth 1 -q https://github.com/laurencelundblade/QCBOR.git \ + 930708bb86481e88879eb1d87fd4d664f1d69503 && \ + git checkout -q FETCH_HEAD && make libqcbor.a && cd .. + +# t_cose @ pinned SHA (OpenSSL adapter) +mkdir -p t_cose && cd t_cose && git init -q && \ + git fetch --depth 1 -q https://github.com/laurencelundblade/t_cose.git \ + ff4c5f7c6fbbe27bb582214ff1878bf58ebc6c43 && \ + git checkout -q FETCH_HEAD && \ + make -f Makefile.ossl libt_cose.a \ + QCBOR_INC="-I ../QCBOR/inc" QCBOR_LIB="../QCBOR/libqcbor.a" && cd .. + +# the suite (from the repo root) +make interop-tcose \ + TCOSE_DIR=$PWD/tests/interop/t_cose/t_cose \ + QCBOR_DIR=$PWD/tests/interop/t_cose/QCBOR \ + TCOSE_CRYPTO_LIB="-lcrypto" +``` + +## Files + +| File | Role | +|-----------------------|---------------------------------------------------------------| +| `interop_tcose.c` | Table-driven harness; wolfCrypt side; both directions per case | +| `interop_key_ossl.c` | t_cose-side OpenSSL key loader (isolated TU — wolfSSL and OpenSSL headers collide on `SHA256`) | +| `interop_cases.h` | Shared key-id enum so a key means the same on both sides | +| `interop_keys.h` | Fixed DER/symmetric test-key fixtures (provenance below) | + +## Test-key provenance (fixtures, not production keys) + +Committed for determinism. Generated once with OpenSSL: + +```bash +# EC private keys (SEC1/RFC 5915 DER) +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -outform DER +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -outform DER +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -outform DER + +# EdDSA private keys (PKCS#8 DER) +openssl genpkey -algorithm ED25519 -outform DER +openssl genpkey -algorithm ED448 -outform DER + +# RSA-2048 private key (PKCS#1 DER) +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | \ + openssl rsa -outform DER -traditional +``` + +OpenSSL loads all of these via `d2i_AutoPrivateKey`; wolfCrypt via the matching +`wc_{Ecc,Ed25519,Ed448,Rsa}PrivateKeyDecode`. Symmetric keys are fixed bytes, +loaded backend-agnostically through `t_cose_key_init_symmetric`. diff --git a/tests/interop/t_cose/interop_cases.h b/tests/interop/t_cose/interop_cases.h new file mode 100644 index 0000000..8ef4895 --- /dev/null +++ b/tests/interop/t_cose/interop_cases.h @@ -0,0 +1,23 @@ +/* interop_cases.h — shared identifiers for the wolfCOSE<->t_cose interop matrix. + * Both the wolfCrypt-side harness and the t_cose-side key loader include this so + * a key id means the same thing on both sides. Test-only. + */ +#ifndef WOLFCOSE_INTEROP_CASES_H +#define WOLFCOSE_INTEROP_CASES_H + +/* Which fixed key fixture a case uses (see interop_keys.h). */ +enum it_key { + IT_KEY_P256 = 0, + IT_KEY_P384, + IT_KEY_P521, + IT_KEY_ED25519, + IT_KEY_ED448, + IT_KEY_RSA2048, + IT_KEY_SYM16, + IT_KEY_SYM24, + IT_KEY_SYM32, + IT_KEY_SYM48, + IT_KEY_SYM64 +}; + +#endif /* WOLFCOSE_INTEROP_CASES_H */ diff --git a/tests/interop/t_cose/interop_key_ossl.c b/tests/interop/t_cose/interop_key_ossl.c new file mode 100644 index 0000000..dbe97a4 --- /dev/null +++ b/tests/interop/t_cose/interop_key_ossl.c @@ -0,0 +1,53 @@ +/* interop_key_ossl.c — t_cose-side asymmetric key loader, OpenSSL backend (test-only). + * + * Isolated from the wolfCrypt side: this TU includes OpenSSL + t_cose's + * backend-agnostic key header ONLY (no wolfSSL), so the two crypto stacks never + * collide in one translation unit (they both define SHA256 etc.). Loads the same + * shared DER bytes the wolfCrypt side uses, via d2i_AutoPrivateKey (SEC1, PKCS#1 + * and PKCS#8 are all auto-detected). Symmetric keys are handled in the harness + * via the backend-agnostic t_cose_key_init_symmetric(). + * + * Copyright (C) 2026 wolfSSL Inc. GPL-3.0-or-later (see wolfCOSE LICENSE). + */ + +#include "t_cose/t_cose_key.h" +#include +#include + +#include "interop_cases.h" +#include "interop_keys.h" + +struct t_cose_key interop_tcose_load(int key_id); +void interop_tcose_free(struct t_cose_key key); + +static const unsigned char* der_for(int key_id, long* len) +{ + switch (key_id) { + case IT_KEY_P256: *len = (long)p256_der_len; return p256_der; + case IT_KEY_P384: *len = (long)p384_der_len; return p384_der; + case IT_KEY_P521: *len = (long)p521_der_len; return p521_der; + case IT_KEY_ED25519: *len = (long)ed25519_der_len; return ed25519_der; + case IT_KEY_ED448: *len = (long)ed448_der_len; return ed448_der; + case IT_KEY_RSA2048: *len = (long)rsa2048_der_len; return rsa2048_der; + default: *len = 0; return NULL; + } +} + +struct t_cose_key interop_tcose_load(int key_id) +{ + struct t_cose_key k; + long len = 0; + const unsigned char* der = der_for(key_id, &len); + const unsigned char* p = der; + + k.key.ptr = NULL; + if (der != NULL) { + k.key.ptr = d2i_AutoPrivateKey(NULL, &p, len); + } + return k; +} + +void interop_tcose_free(struct t_cose_key key) +{ + EVP_PKEY_free((EVP_PKEY*)key.key.ptr); +} diff --git a/tests/interop/t_cose/interop_keys.h b/tests/interop/t_cose/interop_keys.h new file mode 100644 index 0000000..4f0927a --- /dev/null +++ b/tests/interop/t_cose/interop_keys.h @@ -0,0 +1,201 @@ +/* interop_keys.h — fixed test key fixtures for wolfCOSE<->t_cose interop (test-only). + * Generated once and committed for determinism; NOT for production use. + * EC (SEC1/RFC5915): openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-XXX -outform DER + * Ed25519/Ed448 (PKCS#8): openssl genpkey -algorithm ED25519|ED448 -outform DER + * RSA-2048 (PKCS#1): openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl rsa -outform DER -traditional + * OpenSSL loads all via d2i_AutoPrivateKey; wolfCrypt via wc_{Ecc,Ed25519,Ed448,Rsa}PrivateKeyDecode. + * Symmetric keys are fixed bytes (sized for AES-128/192/256 and HMAC-256/384/512). + */ +#ifndef WOLFCOSE_INTEROP_KEYS_H +#define WOLFCOSE_INTEROP_KEYS_H +#include + +static const unsigned char p256_der[] = { + 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x03, 0x1c, 0xec, 0x64, 0x39, + 0xa3, 0x1a, 0x44, 0xfc, 0x9a, 0xe4, 0xd9, 0x2f, 0x9f, 0xc5, 0x3e, 0x88, + 0xcd, 0xd4, 0x08, 0x54, 0x16, 0xfb, 0x7f, 0xec, 0x21, 0xd6, 0x2e, 0xa2, + 0x79, 0xff, 0x77, 0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x87, 0x0e, 0x0e, + 0xb7, 0x93, 0x6a, 0x95, 0x9c, 0xf2, 0x8c, 0x50, 0xe7, 0xbe, 0x6a, 0x87, + 0xb9, 0x33, 0x74, 0x54, 0x10, 0x72, 0xe2, 0x2b, 0x21, 0x5f, 0x90, 0x47, + 0x7f, 0xc8, 0xb9, 0xc6, 0x20, 0xb7, 0x6f, 0xdb, 0x6f, 0x76, 0xc4, 0x16, + 0x1e, 0x15, 0xee, 0x1d, 0xa1, 0xc0, 0x1b, 0xb4, 0x09, 0x5c, 0xb7, 0xfe, + 0x4e, 0x68, 0x56, 0x4a, 0xb1, 0x4a, 0xa1, 0x76, 0x21, 0x38, 0xc6, 0x23, + 0x00 +}; +static const unsigned int p256_der_len = 121; + +static const unsigned char p384_der[] = { + 0x30, 0x81, 0xa4, 0x02, 0x01, 0x01, 0x04, 0x30, 0x2f, 0x18, 0x44, 0x11, + 0xc3, 0x32, 0xd0, 0x10, 0x11, 0xc4, 0x17, 0xa7, 0x9f, 0xeb, 0x34, 0xda, + 0x5d, 0x59, 0x95, 0x2a, 0x30, 0x2e, 0xc8, 0xb1, 0xf0, 0x17, 0xb8, 0x70, + 0x78, 0xf5, 0x3c, 0x4a, 0xd8, 0x73, 0x4d, 0xf5, 0xf2, 0xaf, 0x80, 0xbb, + 0x11, 0xc6, 0x5c, 0x2d, 0xde, 0x74, 0x07, 0xea, 0xa0, 0x07, 0x06, 0x05, + 0x2b, 0x81, 0x04, 0x00, 0x22, 0xa1, 0x64, 0x03, 0x62, 0x00, 0x04, 0x33, + 0xcb, 0x50, 0x32, 0xe1, 0x7a, 0x85, 0xcf, 0x42, 0xa0, 0x67, 0x7c, 0x88, + 0xfe, 0xfd, 0x1c, 0x80, 0xc2, 0x6c, 0xfe, 0xbd, 0x63, 0x40, 0x99, 0x03, + 0x63, 0xf7, 0x71, 0x75, 0x8c, 0xe1, 0xd2, 0xc3, 0xa2, 0x44, 0x2e, 0x15, + 0xb1, 0xae, 0x43, 0xe6, 0xf2, 0x45, 0xed, 0x06, 0x7f, 0xcb, 0x79, 0x58, + 0xfd, 0xf7, 0x1c, 0xce, 0xdf, 0x0b, 0x43, 0x55, 0x20, 0xa9, 0x6f, 0xf5, + 0x1b, 0x37, 0x0f, 0x0a, 0x0d, 0xe1, 0x8e, 0xfc, 0xfc, 0x61, 0xdc, 0x10, + 0x68, 0x09, 0x75, 0x74, 0x19, 0x6e, 0xd2, 0x57, 0x9a, 0x10, 0xd9, 0x45, + 0x6b, 0x54, 0x69, 0xa8, 0x57, 0x2d, 0x74, 0x80, 0xf0, 0x7f, 0x3b +}; +static const unsigned int p384_der_len = 167; + +static const unsigned char p521_der[] = { + 0x30, 0x81, 0xdc, 0x02, 0x01, 0x01, 0x04, 0x42, 0x00, 0x31, 0x05, 0x49, + 0x01, 0x10, 0x44, 0xab, 0x6f, 0x91, 0x93, 0xbc, 0x77, 0x1e, 0x17, 0x46, + 0xe6, 0xf6, 0xa3, 0xaf, 0xf0, 0x58, 0x49, 0x16, 0xbd, 0x48, 0x34, 0x2b, + 0x88, 0x69, 0xef, 0x81, 0xbe, 0x4c, 0xa9, 0x57, 0xb4, 0x0d, 0x80, 0x9e, + 0xd0, 0x84, 0x69, 0xa8, 0x24, 0x9a, 0xd4, 0x3b, 0xa2, 0x63, 0x2d, 0x23, + 0xee, 0x96, 0x9c, 0x4c, 0xa7, 0x0e, 0xab, 0x58, 0xb1, 0xe8, 0x00, 0xaa, + 0x3c, 0xdc, 0xa0, 0x07, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0xa1, + 0x81, 0x89, 0x03, 0x81, 0x86, 0x00, 0x04, 0x01, 0x14, 0x05, 0xce, 0xe7, + 0x29, 0x19, 0x78, 0x6a, 0xc2, 0x8b, 0xe8, 0x83, 0xb7, 0x37, 0x9e, 0x1b, + 0x89, 0xda, 0x3f, 0x08, 0x39, 0x42, 0x05, 0xee, 0xa3, 0xd3, 0x64, 0x0a, + 0xe1, 0x30, 0x0f, 0x13, 0x49, 0x00, 0x89, 0x3d, 0x7e, 0x31, 0xd4, 0xf9, + 0x7d, 0x9f, 0xa8, 0x49, 0xd9, 0xa3, 0x15, 0xfa, 0x94, 0x45, 0x50, 0x51, + 0x6d, 0xfa, 0xe9, 0x52, 0x9d, 0x61, 0x4a, 0xf9, 0x3b, 0x82, 0xd7, 0x6a, + 0xae, 0x01, 0xc3, 0xf3, 0x96, 0xad, 0x48, 0x3e, 0xcb, 0x2e, 0x93, 0xd5, + 0x5b, 0x22, 0x53, 0xf5, 0x07, 0x36, 0x3f, 0x34, 0x00, 0xc7, 0xb4, 0xb1, + 0x91, 0x3f, 0xe2, 0x30, 0x9d, 0xd5, 0x4c, 0x75, 0x34, 0x21, 0xe3, 0x10, + 0xe1, 0x3e, 0xe1, 0x14, 0x1f, 0xd2, 0x16, 0x8c, 0xbe, 0x04, 0xa5, 0xb9, + 0xb5, 0xc6, 0xfe, 0xc3, 0x2d, 0x3c, 0xe2, 0x36, 0x4b, 0x1a, 0x6f, 0xd1, + 0x7f, 0x80, 0xe0, 0x1f, 0xb6, 0xd0, 0x8c +}; +static const unsigned int p521_der_len = 223; + +static const unsigned char ed25519_der[] = { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, + 0x04, 0x22, 0x04, 0x20, 0x35, 0x05, 0x66, 0xd8, 0xc4, 0x2a, 0x7a, 0x73, + 0x46, 0x04, 0xb0, 0x40, 0xab, 0x1b, 0x69, 0x0a, 0xd9, 0xfe, 0x0a, 0x85, + 0x19, 0x5d, 0x36, 0xa4, 0x60, 0x15, 0x6e, 0x99, 0x23, 0xdf, 0x8a, 0x6e +}; +static const unsigned int ed25519_der_len = 48; + +static const unsigned char ed448_der[] = { + 0x30, 0x47, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x71, + 0x04, 0x3b, 0x04, 0x39, 0x6f, 0x3d, 0x77, 0xb0, 0xea, 0xb5, 0x2b, 0xee, + 0xea, 0x72, 0x54, 0x7f, 0x4c, 0xee, 0xca, 0xde, 0x33, 0xff, 0xbe, 0x51, + 0xf9, 0xcb, 0xd6, 0xf9, 0xce, 0x46, 0x77, 0xee, 0x02, 0x11, 0xc2, 0x3a, + 0xbb, 0xfa, 0x28, 0x72, 0xfd, 0x51, 0xf3, 0x95, 0xb2, 0xa4, 0x6d, 0x6b, + 0xdc, 0x99, 0xa5, 0x1f, 0xef, 0xe5, 0xa7, 0x69, 0x15, 0xcf, 0x68, 0x94, + 0x87 +}; +static const unsigned int ed448_der_len = 73; + +static const unsigned char rsa2048_der[] = { + 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, + 0xd3, 0xc3, 0xbf, 0x23, 0x16, 0x75, 0x33, 0xd8, 0x45, 0x25, 0x49, 0x29, + 0xc1, 0xe7, 0xbd, 0x71, 0x09, 0x22, 0x3d, 0xc6, 0x3e, 0x63, 0x2b, 0xfd, + 0xa3, 0x9c, 0x86, 0xf4, 0x8c, 0x59, 0x5a, 0xfd, 0xd4, 0x91, 0x12, 0xfa, + 0x06, 0x7f, 0xc6, 0xa6, 0x30, 0x86, 0x6d, 0xff, 0xb1, 0x31, 0xa7, 0x8e, + 0xbc, 0x49, 0x66, 0x98, 0x9f, 0xe6, 0xa2, 0xc9, 0x21, 0x12, 0xa1, 0x57, + 0x37, 0xc7, 0x48, 0xd3, 0xd4, 0x25, 0x2b, 0x72, 0x49, 0xf8, 0x19, 0xe8, + 0xa7, 0xa8, 0x2a, 0x5b, 0xb3, 0x26, 0x67, 0xc9, 0x4d, 0x89, 0x8c, 0x81, + 0x09, 0xd4, 0x24, 0x07, 0x5d, 0xf7, 0xe9, 0xeb, 0xe9, 0xfe, 0x94, 0xc2, + 0x05, 0x0d, 0x59, 0x92, 0xae, 0x9c, 0x60, 0x8a, 0x94, 0xf5, 0xbe, 0x2b, + 0xe2, 0x0d, 0x42, 0xf6, 0x84, 0x64, 0x1f, 0x54, 0x3d, 0xd9, 0x1d, 0xf1, + 0x64, 0x96, 0xe4, 0xaa, 0xc0, 0xd1, 0xce, 0x27, 0x6d, 0x15, 0x04, 0x08, + 0x71, 0x10, 0x4d, 0xc1, 0x4a, 0xd4, 0x82, 0x63, 0x4d, 0x8a, 0x47, 0xc6, + 0xa5, 0x41, 0x38, 0x62, 0x5c, 0x24, 0xec, 0x6f, 0x69, 0xfe, 0xff, 0x9e, + 0x98, 0xc1, 0x24, 0x01, 0x1f, 0x24, 0xde, 0x41, 0x34, 0x66, 0xd2, 0xb5, + 0xc6, 0x63, 0x95, 0x23, 0x35, 0xf9, 0x70, 0xb5, 0xca, 0x81, 0x8b, 0xd8, + 0xf6, 0xbb, 0xdf, 0x38, 0xd7, 0x21, 0x0e, 0xe2, 0x55, 0x22, 0xe8, 0x9a, + 0xb7, 0x0d, 0x05, 0x19, 0xdf, 0xc8, 0x87, 0xc0, 0x12, 0x00, 0x29, 0xc9, + 0x0f, 0x46, 0xae, 0xe8, 0x2f, 0x66, 0xee, 0xd2, 0x39, 0x2c, 0x0d, 0x96, + 0xb7, 0xc2, 0x76, 0xfa, 0xa5, 0x97, 0x09, 0xdc, 0xab, 0xfc, 0x1b, 0x53, + 0x7e, 0xa3, 0x50, 0x48, 0x09, 0x9c, 0x67, 0xca, 0xec, 0x0a, 0xe9, 0x47, + 0x3d, 0xc2, 0xbf, 0x4f, 0x68, 0x4d, 0x60, 0x76, 0xee, 0x45, 0x08, 0x8b, + 0x58, 0x33, 0xcb, 0x1f, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, + 0x00, 0x41, 0x7b, 0xe4, 0x9d, 0x4b, 0x12, 0x8f, 0x27, 0xaf, 0xaa, 0x2a, + 0x07, 0xb9, 0xa8, 0x99, 0xd2, 0x5c, 0x8a, 0xb4, 0x23, 0x9b, 0xa3, 0x3b, + 0x35, 0x41, 0xe4, 0xe3, 0x6e, 0xb7, 0xff, 0xe1, 0xbd, 0x60, 0x80, 0x44, + 0x28, 0x00, 0xde, 0x7e, 0x65, 0x61, 0xd8, 0x10, 0x27, 0xe3, 0x79, 0x81, + 0x61, 0x08, 0x24, 0x82, 0x8f, 0x6b, 0xd2, 0xfc, 0x3f, 0xc9, 0xf4, 0x62, + 0xe3, 0xff, 0xa1, 0xa6, 0x89, 0xaa, 0xae, 0x9b, 0xd2, 0xc9, 0xc0, 0xe8, + 0xf2, 0x22, 0xf6, 0x43, 0xb2, 0xfb, 0xa1, 0x42, 0xb2, 0xe3, 0x41, 0x47, + 0xab, 0x97, 0x88, 0x7c, 0x63, 0xb2, 0xd1, 0x39, 0x0a, 0xf4, 0xde, 0xfe, + 0xcd, 0x48, 0x96, 0xaa, 0x3e, 0x4b, 0xb4, 0x9b, 0xbd, 0xfe, 0xf6, 0x47, + 0x0a, 0x76, 0xea, 0xb6, 0xec, 0x58, 0x60, 0x45, 0xb7, 0xdd, 0x30, 0x48, + 0xac, 0x1e, 0xf8, 0xf2, 0x59, 0x43, 0x2b, 0x1d, 0xde, 0x4f, 0x2f, 0xfb, + 0x99, 0x1a, 0xc7, 0x62, 0xa6, 0x4c, 0x4b, 0xc7, 0xd3, 0xd6, 0x41, 0xfc, + 0xec, 0x53, 0x4e, 0x7f, 0x93, 0xf4, 0xc2, 0x4d, 0x47, 0x7a, 0x1b, 0x14, + 0x11, 0x1f, 0xbe, 0xa0, 0xdd, 0xd9, 0x8d, 0xf6, 0x6a, 0xf3, 0xe5, 0x65, + 0x45, 0xae, 0xef, 0x2c, 0x0f, 0xb9, 0x24, 0xbe, 0xe7, 0xf9, 0x89, 0xed, + 0x18, 0xbd, 0x8d, 0x9a, 0x6e, 0xec, 0xc5, 0x5b, 0xb4, 0x39, 0x6b, 0x26, + 0x42, 0x98, 0x2e, 0x51, 0xc6, 0x1e, 0xad, 0xf2, 0x67, 0x8e, 0x07, 0x49, + 0xdd, 0x93, 0x8d, 0xa7, 0x47, 0x74, 0x39, 0xc8, 0xf7, 0x29, 0x83, 0x19, + 0x2d, 0x2f, 0xd7, 0xe6, 0x22, 0x15, 0xaa, 0xdf, 0x03, 0x4b, 0x7d, 0xc6, + 0xe4, 0x60, 0x16, 0x48, 0xf6, 0xa4, 0x4c, 0x06, 0x61, 0x0b, 0x16, 0x45, + 0xaf, 0x82, 0x6c, 0x64, 0xe7, 0x6e, 0x31, 0x39, 0x75, 0xe4, 0xff, 0x48, + 0x5c, 0xc0, 0xf9, 0x10, 0x81, 0x02, 0x81, 0x81, 0x00, 0xf0, 0x68, 0xaa, + 0xbc, 0x20, 0x3f, 0x3a, 0x9f, 0x36, 0x62, 0x47, 0xb8, 0x22, 0x11, 0x07, + 0xde, 0xf6, 0xcf, 0x00, 0x82, 0xb9, 0x19, 0xf6, 0xb2, 0x89, 0x2f, 0xe6, + 0xb7, 0xf7, 0xfe, 0x7f, 0xb7, 0x8a, 0xe3, 0xfb, 0x4f, 0x84, 0xbb, 0x1c, + 0xd7, 0x45, 0xeb, 0x2e, 0xf7, 0xa8, 0x73, 0x0e, 0x87, 0x08, 0x1a, 0x33, + 0xc6, 0xdc, 0x43, 0x2d, 0x2f, 0xbe, 0xaa, 0xd4, 0xdb, 0x47, 0xcd, 0x68, + 0xe4, 0x97, 0x03, 0x02, 0x8c, 0x01, 0x9b, 0x92, 0x36, 0xf8, 0xaa, 0x8e, + 0x06, 0x7a, 0x0a, 0xc7, 0xdb, 0x1f, 0xd5, 0x77, 0xdd, 0xcb, 0x7f, 0x8a, + 0xcb, 0x90, 0xb1, 0x8d, 0x5d, 0xab, 0x77, 0x63, 0x95, 0x3f, 0xe2, 0xb7, + 0xe8, 0x09, 0x50, 0x64, 0xb8, 0x01, 0xc8, 0xeb, 0xbe, 0xa3, 0x17, 0xda, + 0x66, 0x6d, 0xf1, 0x9d, 0xa6, 0xdc, 0x3b, 0x9f, 0x30, 0x8f, 0xc4, 0x11, + 0xc9, 0x7a, 0x6b, 0x77, 0x7f, 0x02, 0x81, 0x81, 0x00, 0xe1, 0x7f, 0x85, + 0x44, 0xdb, 0xa9, 0x51, 0x02, 0x35, 0xc4, 0xb6, 0xeb, 0xf8, 0x8b, 0x2c, + 0x11, 0x9d, 0xe3, 0x49, 0xe8, 0x05, 0x30, 0xad, 0xb6, 0x84, 0xf7, 0xa4, + 0xcf, 0x70, 0x21, 0x59, 0x45, 0xc8, 0xc1, 0x20, 0x79, 0xc1, 0x0c, 0x47, + 0xa7, 0x6c, 0x79, 0xc6, 0xaa, 0xd1, 0x4d, 0x63, 0x28, 0xaa, 0x47, 0x1f, + 0xd9, 0x94, 0x64, 0x21, 0x77, 0xe3, 0x60, 0xec, 0x52, 0x1f, 0x5a, 0xe8, + 0xd0, 0x17, 0x49, 0x45, 0x86, 0x01, 0x4a, 0xae, 0x5f, 0x88, 0xc3, 0x61, + 0xbe, 0x38, 0x38, 0xc2, 0x61, 0x04, 0xf1, 0x12, 0xe5, 0xbe, 0x76, 0xfe, + 0x3a, 0xd4, 0xf1, 0xc3, 0x29, 0x5f, 0x1d, 0xf2, 0xc0, 0x40, 0xe8, 0xd3, + 0x7d, 0x27, 0xee, 0x95, 0x66, 0x94, 0xea, 0xc4, 0xf8, 0xbc, 0xc4, 0xa9, + 0xe6, 0x98, 0x8a, 0x60, 0x23, 0x71, 0x4e, 0x77, 0xfe, 0xc3, 0xe6, 0x07, + 0x27, 0x35, 0x66, 0x7c, 0x61, 0x02, 0x81, 0x81, 0x00, 0xb6, 0x36, 0x1e, + 0x71, 0xc7, 0xdf, 0x24, 0x87, 0x57, 0xa8, 0xd5, 0xc2, 0xf1, 0xcf, 0x06, + 0xb0, 0x2f, 0x50, 0x65, 0x8a, 0xae, 0xd0, 0xc6, 0xf2, 0x3a, 0x98, 0x5b, + 0xbe, 0x43, 0xf0, 0x58, 0xcc, 0xbc, 0x30, 0x5f, 0x61, 0xbd, 0xb0, 0x34, + 0x03, 0xd5, 0xb2, 0x93, 0x3e, 0x92, 0x25, 0xe2, 0x74, 0xe5, 0xe7, 0x36, + 0x27, 0x1a, 0xfa, 0xaf, 0x5c, 0xe8, 0x9e, 0x82, 0x06, 0x51, 0x3d, 0x96, + 0xaa, 0xea, 0xea, 0x15, 0x1c, 0x18, 0x7b, 0xd9, 0x2e, 0x60, 0xfa, 0xa3, + 0xfa, 0xb4, 0xb5, 0x47, 0x15, 0x05, 0xe3, 0xbd, 0x9e, 0x15, 0x86, 0xed, + 0xa3, 0xac, 0x5f, 0x66, 0x4e, 0x00, 0x8f, 0xd4, 0xa2, 0x62, 0x71, 0x6c, + 0x02, 0x2a, 0x4d, 0x0c, 0x89, 0x8a, 0x90, 0xc6, 0x5d, 0x0f, 0x30, 0x1d, + 0x10, 0x55, 0x74, 0x33, 0x91, 0x2c, 0x8a, 0xe3, 0xf4, 0xe2, 0x82, 0x31, + 0x19, 0x0b, 0xf8, 0x37, 0xf1, 0x02, 0x81, 0x80, 0x0b, 0x0c, 0x5f, 0x70, + 0x37, 0x3e, 0xfd, 0x7c, 0x19, 0x3c, 0x51, 0x26, 0x10, 0xa0, 0x69, 0x01, + 0x57, 0xf6, 0x09, 0xe9, 0xf7, 0x4f, 0x22, 0x43, 0xbe, 0x12, 0x10, 0x1e, + 0x25, 0xc5, 0x4c, 0x85, 0x71, 0xc7, 0x9c, 0x9a, 0xba, 0x8d, 0xaa, 0x79, + 0x16, 0x84, 0x84, 0xea, 0x5b, 0xa4, 0xea, 0x05, 0xd5, 0x09, 0xf5, 0x12, + 0x89, 0x05, 0xba, 0xea, 0x0f, 0xd6, 0xf0, 0xdd, 0x39, 0x32, 0x10, 0x14, + 0x19, 0xff, 0xfa, 0x0b, 0x0d, 0xc7, 0x25, 0xf2, 0x02, 0x56, 0x68, 0x54, + 0x94, 0x96, 0x9b, 0x57, 0x7b, 0x91, 0x80, 0x36, 0x87, 0x75, 0x77, 0x11, + 0x54, 0xdb, 0x9f, 0x8f, 0x48, 0x5c, 0xc4, 0x47, 0x0d, 0x27, 0x1b, 0x2d, + 0x97, 0xa6, 0x45, 0xe5, 0xa7, 0xc8, 0x34, 0xec, 0x66, 0xfb, 0x4f, 0xc3, + 0xbf, 0x97, 0x90, 0x0c, 0x4c, 0x81, 0xe6, 0xb9, 0x31, 0xc9, 0xf4, 0x71, + 0xc4, 0x59, 0x08, 0x21, 0x02, 0x81, 0x80, 0x62, 0xb0, 0x3b, 0x69, 0xf4, + 0x48, 0x7c, 0x7b, 0x2f, 0x74, 0x28, 0x8b, 0x9e, 0xe3, 0xb1, 0x9f, 0x41, + 0x1f, 0x11, 0xb9, 0xac, 0x37, 0x60, 0x25, 0x94, 0x51, 0xfa, 0xba, 0xb7, + 0x4a, 0x96, 0x46, 0xe5, 0xfc, 0xe6, 0xdf, 0x66, 0x03, 0x29, 0xa0, 0x0c, + 0x32, 0x85, 0xa8, 0xec, 0xf4, 0x01, 0x87, 0x19, 0x30, 0xcf, 0xd5, 0x48, + 0xfd, 0x26, 0x85, 0x67, 0x14, 0xae, 0xaf, 0x76, 0x4e, 0x4f, 0x07, 0xfa, + 0xa9, 0x06, 0x74, 0x0b, 0xe7, 0x98, 0x9d, 0x85, 0x5d, 0xfe, 0x77, 0xb1, + 0x5a, 0x76, 0xd5, 0x04, 0x73, 0xe0, 0x42, 0xa5, 0x9a, 0x37, 0x98, 0xb0, + 0x2f, 0x0f, 0xb1, 0x6a, 0xf3, 0x3e, 0x36, 0xfe, 0x45, 0xbc, 0x65, 0x86, + 0xfb, 0xcf, 0x45, 0xa5, 0xb8, 0xa5, 0xe1, 0xfe, 0xe6, 0x69, 0x88, 0x59, + 0x74, 0x1f, 0xee, 0x91, 0xba, 0xb5, 0xff, 0xd1, 0xe9, 0x57, 0xbf, 0xcc, + 0x13, 0x76, 0x4b +}; +static const unsigned int rsa2048_der_len = 1191; + +/* Fixed symmetric keys (deterministic). Use a prefix of sym_key_64 with the + * length the algorithm needs (AES-128/192/256, HMAC-256/384/512). */ +static const unsigned char sym_key_64[64] = { + 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10, + 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20, + 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30, + 0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40 +}; + +#endif /* WOLFCOSE_INTEROP_KEYS_H */ diff --git a/tests/interop/t_cose/interop_tcose.c b/tests/interop/t_cose/interop_tcose.c new file mode 100644 index 0000000..faec412 --- /dev/null +++ b/tests/interop/t_cose/interop_tcose.c @@ -0,0 +1,392 @@ +/* interop_tcose.c — wolfCOSE <-> t_cose wire-interop harness (test-only). + * + * Proves RFC 9052 interop: wolfCOSE (on wolfCrypt) and t_cose (on OpenSSL) produce + * and consume each other's COSE messages, both directions, across every algorithm + * both implement. The interface is the bytes on the wire; the two APIs are never + * reconciled. wolfCrypt and OpenSSL must not meet in one TU (their headers collide + * on SHA256 etc.), so the t_cose-side asymmetric key loading lives in + * interop_key_ossl.c. Symmetric keys use t_cose's backend-agnostic helper. + * + * Copyright (C) 2026 wolfSSL Inc. GPL-3.0-or-later (see wolfCOSE LICENSE). + * t_cose and QCBOR are fetched at pinned SHAs by CI (BSD-3-Clause, not vendored). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "t_cose/t_cose_common.h" +#include "t_cose/t_cose_key.h" +#include "t_cose/t_cose_sign1_sign.h" +#include "t_cose/t_cose_sign1_verify.h" +#include "t_cose/t_cose_mac_compute.h" +#include "t_cose/t_cose_mac_validate.h" +#include "t_cose/t_cose_encrypt_enc.h" +#include "t_cose/t_cose_encrypt_dec.h" +#include "t_cose/t_cose_standard_constants.h" +#include "t_cose/q_useful_buf.h" + +#include +#include +#include "interop_cases.h" +#include "interop_keys.h" + +/* t_cose-side asymmetric key loader (interop_key_ossl.c; no wolfSSL there). */ +struct t_cose_key interop_tcose_load(int key_id); +void interop_tcose_free(struct t_cose_key key); + +static int g_fail = 0; + +#define OK(cond, name) do { \ + if (!(cond)) { printf(" FAIL: %s (line %d)\n", (name), __LINE__); \ + g_fail++; } \ + else { printf(" pass: %s\n", (name)); } \ +} while (0) + +static const unsigned char g_payload[] = "wolfCOSE<->t_cose interop payload"; +static const size_t g_payloadLen = sizeof(g_payload) - 1u; + +/* ---- wolfCrypt key set (owns the underlying wolfCrypt key for cleanup) ---- */ +typedef struct { + WOLFCOSE_KEY ck; + int kind; /* 0 ecc, 1 rsa, 2 ed25519, 3 ed448 */ + ecc_key ec; + RsaKey rsa; + ed25519_key ed; + ed448_key ed4; +} wc_keyset; + +static void wc_free(wc_keyset* ks) +{ + if (ks->kind == 0) wc_ecc_free(&ks->ec); + else if (ks->kind == 1) wc_FreeRsaKey(&ks->rsa); + else if (ks->kind == 2) wc_ed25519_free(&ks->ed); + else if (ks->kind == 3) wc_ed448_free(&ks->ed4); + wc_CoseKey_Free(&ks->ck); +} + +/* kind stays at the sentinel -1 until the matching wolfCrypt init succeeds, so + * wc_free() on any failure path frees exactly what was initialized. */ +static int wc_load(wc_keyset* ks, int key_id) +{ + word32 idx = 0; + int ret = 0; + memset(ks, 0, sizeof(*ks)); + ks->kind = -1; + if (wc_CoseKey_Init(&ks->ck) != 0) return -1; + + switch (key_id) { + case IT_KEY_P256: + case IT_KEY_P384: + case IT_KEY_P521: { + const unsigned char* der; word32 dlen; int crv; + if (key_id == IT_KEY_P256) { der = p256_der; dlen = p256_der_len; crv = WOLFCOSE_CRV_P256; } + else if (key_id == IT_KEY_P384) { der = p384_der; dlen = p384_der_len; crv = WOLFCOSE_CRV_P384; } + else { der = p521_der; dlen = p521_der_len; crv = WOLFCOSE_CRV_P521; } + if (wc_ecc_init(&ks->ec) != 0) { ret = -1; break; } + ks->kind = 0; + if (wc_EccPrivateKeyDecode(der, &idx, &ks->ec, dlen) != 0) { ret = -1; break; } + ret = wc_CoseKey_SetEcc(&ks->ck, crv, &ks->ec); + break; + } + case IT_KEY_RSA2048: + if (wc_InitRsaKey(&ks->rsa, NULL) != 0) { ret = -1; break; } + ks->kind = 1; + if (wc_RsaPrivateKeyDecode(rsa2048_der, &idx, &ks->rsa, rsa2048_der_len) != 0) { ret = -1; break; } + ret = wc_CoseKey_SetRsa(&ks->ck, &ks->rsa); + break; + case IT_KEY_ED25519: { + /* PKCS#8 EdDSA carries only the private seed; derive the public key. */ + byte pub[ED25519_PUB_KEY_SIZE]; + if (wc_ed25519_init(&ks->ed) != 0) { ret = -1; break; } + ks->kind = 2; + if (wc_Ed25519PrivateKeyDecode(ed25519_der, &idx, &ks->ed, ed25519_der_len) != 0) { ret = -1; break; } + if (wc_ed25519_make_public(&ks->ed, pub, sizeof(pub)) != 0) { ret = -1; break; } + if (wc_ed25519_import_public(pub, sizeof(pub), &ks->ed) != 0) { ret = -1; break; } + ret = wc_CoseKey_SetEd25519(&ks->ck, &ks->ed); + break; + } + case IT_KEY_ED448: { + byte pub[ED448_PUB_KEY_SIZE]; + if (wc_ed448_init(&ks->ed4) != 0) { ret = -1; break; } + ks->kind = 3; + if (wc_Ed448PrivateKeyDecode(ed448_der, &idx, &ks->ed4, ed448_der_len) != 0) { ret = -1; break; } + if (wc_ed448_make_public(&ks->ed4, pub, sizeof(pub)) != 0) { ret = -1; break; } + if (wc_ed448_import_public(pub, sizeof(pub), &ks->ed4) != 0) { ret = -1; break; } + ret = wc_CoseKey_SetEd448(&ks->ck, &ks->ed4); + break; + } + default: + ret = -1; + break; + } + + if (ret != 0) { + wc_free(ks); + } + return ret; +} + +/* ---- COSE_Sign1 round-trip for one (wc_alg, tc_alg, key), both directions ---- */ +static void sign1_case(const char* name, int32_t wc_alg, int32_t tc_alg, + int key_id, int needs_aux, int with_negative) +{ + wc_keyset ks; + struct t_cose_key tk; + struct t_cose_sign1_sign_ctx sctx; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t wbuf[2048]; size_t wlen = 0; + uint8_t aux[2048]; + Q_USEFUL_BUF_MAKE_STACK_UB(tbuf, 2048); + struct q_useful_buf_c payload = { g_payload, g_payloadLen }; + struct q_useful_buf_c tmsg = { NULL, 0 }; + WOLFCOSE_HDR hdr; const uint8_t* dec = NULL; size_t decLen = 0; + enum t_cose_err_t terr; + WC_RNG rng; + int rc; + + printf(" [Sign1 / %s]\n", name); + if (wc_load(&ks, key_id) != 0) { OK(0, "wolfCrypt key load"); return; } + tk = interop_tcose_load(key_id); + if (tk.key.ptr == NULL) { OK(0, "t_cose key load"); wc_free(&ks); return; } + if (wc_InitRng(&rng) != 0) { OK(0, "rng"); interop_tcose_free(tk); wc_free(&ks); return; } + + /* w->t */ + rc = wc_CoseSign1_Sign(&ks.ck, wc_alg, NULL, 0, + g_payload, g_payloadLen, NULL, 0, NULL, 0, + scratch, sizeof(scratch), wbuf, sizeof(wbuf), &wlen, &rng); + OK(rc == 0 && wlen > 0, "wolfCOSE produced COSE_Sign1"); + if (rc == 0) { + struct t_cose_sign1_verify_ctx vctx; + struct q_useful_buf_c sign1 = { wbuf, wlen }; + struct q_useful_buf_c vp = { NULL, 0 }; + t_cose_sign1_verify_init(&vctx, 0); + if (needs_aux) { + struct q_useful_buf auxb = { aux, sizeof(aux) }; + t_cose_sign1_verify_set_auxiliary_buffer(&vctx, auxb); + } + t_cose_sign1_set_verification_key(&vctx, tk); + terr = t_cose_sign1_verify(&vctx, sign1, &vp, NULL); + OK(terr == T_COSE_SUCCESS, "t_cose verified wolfCOSE output (w->t)"); + OK(terr == T_COSE_SUCCESS && vp.len == g_payloadLen && + memcmp(vp.ptr, g_payload, g_payloadLen) == 0, "payload matches (w->t)"); + } + + /* t->w */ + t_cose_sign1_sign_init(&sctx, 0, tc_alg); + if (needs_aux) { + struct q_useful_buf auxb = { aux, sizeof(aux) }; + t_cose_sign1_sign_set_auxiliary_buffer(&sctx, auxb); + } + t_cose_sign1_set_signing_key(&sctx, tk, NULL_Q_USEFUL_BUF_C); + terr = t_cose_sign1_sign(&sctx, payload, tbuf, &tmsg); + OK(terr == T_COSE_SUCCESS && tmsg.len > 0, "t_cose produced COSE_Sign1"); + if (terr == T_COSE_SUCCESS) { + rc = wc_CoseSign1_Verify(&ks.ck, tmsg.ptr, tmsg.len, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &dec, &decLen); + OK(rc == 0, "wolfCOSE verified t_cose output (t->w)"); + OK(rc == 0 && decLen == g_payloadLen && + memcmp(dec, g_payload, g_payloadLen) == 0, "payload matches (t->w)"); + } + + /* negative: corrupt the signature tail -> wolfCOSE must reject */ + if (with_negative && wlen > 0 && wlen <= sizeof(wbuf)) { + uint8_t bad[2048]; memcpy(bad, wbuf, wlen); + bad[wlen - 1u] ^= 0xFFu; + rc = wc_CoseSign1_Verify(&ks.ck, bad, wlen, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &dec, &decLen); + OK(rc != 0, "wolfCOSE rejects tampered signature (negative)"); + } + + interop_tcose_free(tk); + wc_free(&ks); + wc_FreeRng(&rng); +} + +/* ---- COSE_Mac0 (HMAC) round-trip, both directions. Symmetric key is + * backend-agnostic (t_cose_key_init_symmetric), so it lives in this TU. ---- */ +static void mac0_case(const char* name, int32_t wc_alg, int32_t tc_alg, size_t keyLen) +{ + WOLFCOSE_KEY ck; + struct t_cose_key tk; + struct t_cose_mac_calculate_ctx cctx; + struct q_useful_buf_c keyb = { sym_key_64, keyLen }; + struct q_useful_buf_c payload = { g_payload, g_payloadLen }; + struct q_useful_buf_c tmsg = { NULL, 0 }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t wbuf[512]; size_t wlen = 0; + Q_USEFUL_BUF_MAKE_STACK_UB(tbuf, 512); + WOLFCOSE_HDR hdr; const uint8_t* dec = NULL; size_t decLen = 0; + enum t_cose_err_t terr; + int rc; + + printf(" [Mac0 / %s]\n", name); + if (wc_CoseKey_Init(&ck) != 0) { OK(0, "wolfCOSE key"); return; } + if (wc_CoseKey_SetSymmetric(&ck, sym_key_64, keyLen) != 0) { + OK(0, "wolfCOSE key"); wc_CoseKey_Free(&ck); return; } + if (t_cose_key_init_symmetric(tc_alg, keyb, &tk) != T_COSE_SUCCESS) { + OK(0, "t_cose key"); wc_CoseKey_Free(&ck); return; } + + rc = wc_CoseMac0_Create(&ck, wc_alg, NULL, 0, g_payload, g_payloadLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + wbuf, sizeof(wbuf), &wlen); + OK(rc == 0 && wlen > 0, "wolfCOSE produced COSE_Mac0"); + if (rc == 0) { + struct t_cose_mac_validate_ctx vctx; + struct q_useful_buf_c msg = { wbuf, wlen }; + struct q_useful_buf_c vp = { NULL, 0 }; + uint64_t tags[T_COSE_MAX_TAGS_TO_RETURN]; + t_cose_mac_validate_init(&vctx, T_COSE_OPT_MESSAGE_TYPE_MAC0); + t_cose_mac_set_validate_key(&vctx, tk); + terr = t_cose_mac_validate_msg(&vctx, msg, NULL_Q_USEFUL_BUF_C, &vp, NULL, tags); + OK(terr == T_COSE_SUCCESS, "t_cose validated wolfCOSE output (w->t)"); + OK(terr == T_COSE_SUCCESS && vp.len == g_payloadLen && + memcmp(vp.ptr, g_payload, g_payloadLen) == 0, "payload matches (w->t)"); + } + + /* t->w */ + t_cose_mac_compute_init(&cctx, T_COSE_OPT_MESSAGE_TYPE_MAC0, tc_alg); + t_cose_mac_set_computing_key(&cctx, tk, NULL_Q_USEFUL_BUF_C); + terr = t_cose_mac_compute(&cctx, NULL_Q_USEFUL_BUF_C, payload, tbuf, &tmsg); + OK(terr == T_COSE_SUCCESS && tmsg.len > 0, "t_cose produced COSE_Mac0"); + if (terr == T_COSE_SUCCESS) { + rc = wc_CoseMac0_Verify(&ck, tmsg.ptr, tmsg.len, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &dec, &decLen); + OK(rc == 0, "wolfCOSE verified t_cose output (t->w)"); + OK(rc == 0 && decLen == g_payloadLen && + memcmp(dec, g_payload, g_payloadLen) == 0, "payload matches (t->w)"); + } + + /* negative: corrupt the tag tail -> wolfCOSE must reject */ + if (wlen > 0 && wlen <= sizeof(wbuf)) { + uint8_t bad[512]; memcpy(bad, wbuf, wlen); + bad[wlen - 1u] ^= 0xFFu; + rc = wc_CoseMac0_Verify(&ck, bad, wlen, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &dec, &decLen); + OK(rc != 0, "wolfCOSE rejects tampered tag (negative)"); + } + t_cose_key_free_symmetric(tk); + wc_CoseKey_Free(&ck); +} + +/* ---- COSE_Encrypt0 (AES-GCM) round-trip. IV travels in the message. ---- */ +static void enc0_case(const char* name, int32_t wc_alg, int32_t tc_alg, size_t keyLen) +{ + WOLFCOSE_KEY ck; + struct t_cose_key tk; + struct t_cose_encrypt_enc ectx; + struct q_useful_buf_c keyb = { sym_key_64, keyLen }; + struct q_useful_buf_c payload = { g_payload, g_payloadLen }; + struct q_useful_buf_c tmsg = { NULL, 0 }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t wbuf[512]; size_t wlen = 0; + Q_USEFUL_BUF_MAKE_STACK_UB(tbuf, 512); + uint8_t iv[12]; + uint8_t pt[256]; size_t ptLen = 0; + WOLFCOSE_HDR hdr; + enum t_cose_err_t terr; + WC_RNG rng; + int rc; + + printf(" [Encrypt0 / %s]\n", name); + if (wc_CoseKey_Init(&ck) != 0) { OK(0, "wolfCOSE key"); return; } + if (wc_CoseKey_SetSymmetric(&ck, sym_key_64, keyLen) != 0) { + OK(0, "wolfCOSE key"); wc_CoseKey_Free(&ck); return; } + if (t_cose_key_init_symmetric(tc_alg, keyb, &tk) != T_COSE_SUCCESS) { + OK(0, "t_cose key"); wc_CoseKey_Free(&ck); return; } + if (wc_InitRng(&rng) != 0) { + OK(0, "rng"); t_cose_key_free_symmetric(tk); wc_CoseKey_Free(&ck); return; } + if (wc_RNG_GenerateBlock(&rng, iv, sizeof(iv)) != 0) { + OK(0, "iv"); wc_FreeRng(&rng); t_cose_key_free_symmetric(tk); wc_CoseKey_Free(&ck); return; } + + rc = wc_CoseEncrypt0_Encrypt(&ck, wc_alg, iv, sizeof(iv), + g_payload, g_payloadLen, NULL, 0, NULL, NULL, 0, + scratch, sizeof(scratch), wbuf, sizeof(wbuf), &wlen); + OK(rc == 0 && wlen > 0, "wolfCOSE produced COSE_Encrypt0"); + if (rc == 0) { + struct t_cose_encrypt_dec_ctx dctx; + struct q_useful_buf_c msg = { wbuf, wlen }; + struct q_useful_buf ptb = { pt, sizeof(pt) }; + struct q_useful_buf_c out = { NULL, 0 }; + uint64_t tags[T_COSE_MAX_TAGS_TO_RETURN]; + t_cose_encrypt_dec_init(&dctx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT0); + t_cose_encrypt_dec_set_cek(&dctx, tk); + terr = t_cose_encrypt_dec_msg(&dctx, msg, NULL_Q_USEFUL_BUF_C, ptb, &out, NULL, tags); + OK(terr == T_COSE_SUCCESS, "t_cose decrypted wolfCOSE output (w->t)"); + OK(terr == T_COSE_SUCCESS && out.len == g_payloadLen && + memcmp(out.ptr, g_payload, g_payloadLen) == 0, "plaintext matches (w->t)"); + } + + /* t->w */ + t_cose_encrypt_enc_init(&ectx, T_COSE_OPT_MESSAGE_TYPE_ENCRYPT0, tc_alg); + t_cose_encrypt_set_cek(&ectx, tk); + terr = t_cose_encrypt_enc(&ectx, payload, NULL_Q_USEFUL_BUF_C, tbuf, &tmsg); + OK(terr == T_COSE_SUCCESS && tmsg.len > 0, "t_cose produced COSE_Encrypt0"); + if (terr == T_COSE_SUCCESS) { + rc = wc_CoseEncrypt0_Decrypt(&ck, tmsg.ptr, tmsg.len, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + pt, sizeof(pt), &ptLen); + OK(rc == 0, "wolfCOSE decrypted t_cose output (t->w)"); + OK(rc == 0 && ptLen == g_payloadLen && + memcmp(pt, g_payload, g_payloadLen) == 0, "plaintext matches (t->w)"); + } + + /* negative: corrupt the AEAD tag tail -> wolfCOSE must reject */ + if (wlen > 0 && wlen <= sizeof(wbuf)) { + uint8_t bad[512]; memcpy(bad, wbuf, wlen); + bad[wlen - 1u] ^= 0xFFu; + rc = wc_CoseEncrypt0_Decrypt(&ck, bad, wlen, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + pt, sizeof(pt), &ptLen); + OK(rc != 0, "wolfCOSE rejects tampered ciphertext (negative)"); + } + t_cose_key_free_symmetric(tk); + wc_CoseKey_Free(&ck); + wc_FreeRng(&rng); +} + +struct sign1_row { + const char* name; + int32_t wc_alg; + int32_t tc_alg; + int key_id; + int needs_aux; /* EdDSA needs the t_cose auxiliary buffer */ +}; + +static const struct sign1_row SIGN1_CASES[] = { + { "ES256", WOLFCOSE_ALG_ES256, T_COSE_ALGORITHM_ES256, IT_KEY_P256, 0 }, + { "ES384", WOLFCOSE_ALG_ES384, T_COSE_ALGORITHM_ES384, IT_KEY_P384, 0 }, + { "ES512", WOLFCOSE_ALG_ES512, T_COSE_ALGORITHM_ES512, IT_KEY_P521, 0 }, + { "PS256", WOLFCOSE_ALG_PS256, T_COSE_ALGORITHM_PS256, IT_KEY_RSA2048, 0 }, + { "PS384", WOLFCOSE_ALG_PS384, T_COSE_ALGORITHM_PS384, IT_KEY_RSA2048, 0 }, + { "PS512", WOLFCOSE_ALG_PS512, T_COSE_ALGORITHM_PS512, IT_KEY_RSA2048, 0 }, + { "EdDSA/Ed25519", WOLFCOSE_ALG_EDDSA, T_COSE_ALGORITHM_EDDSA, IT_KEY_ED25519, 1 }, + { "EdDSA/Ed448", WOLFCOSE_ALG_EDDSA, T_COSE_ALGORITHM_EDDSA, IT_KEY_ED448, 1 }, +}; + +int main(void) +{ + size_t i; + printf("=== wolfCOSE <-> t_cose interop (t_cose backend: OpenSSL) ===\n\n"); + + for (i = 0; i < sizeof(SIGN1_CASES)/sizeof(SIGN1_CASES[0]); i++) { + const struct sign1_row* r = &SIGN1_CASES[i]; + sign1_case(r->name, r->wc_alg, r->tc_alg, r->key_id, r->needs_aux, + /*with_negative=*/ i == 0); + } + + mac0_case("HMAC256", WOLFCOSE_ALG_HMAC_256_256, T_COSE_ALGORITHM_HMAC256, 32); + mac0_case("HMAC384", WOLFCOSE_ALG_HMAC_384_384, T_COSE_ALGORITHM_HMAC384, 48); + mac0_case("HMAC512", WOLFCOSE_ALG_HMAC_512_512, T_COSE_ALGORITHM_HMAC512, 64); + + enc0_case("A128GCM", WOLFCOSE_ALG_A128GCM, T_COSE_ALGORITHM_A128GCM, 16); + enc0_case("A192GCM", WOLFCOSE_ALG_A192GCM, T_COSE_ALGORITHM_A192GCM, 24); + enc0_case("A256GCM", WOLFCOSE_ALG_A256GCM, T_COSE_ALGORITHM_A256GCM, 32); + + printf("\n=== Results: %d failure(s) ===\n", g_fail); + return g_fail == 0 ? 0 : 1; +}