@@ -291,11 +291,24 @@ Set symmetric key material in a COSE key structure.
291291### wc_CoseKey_Encode
292292
293293```c
294- int wc_CoseKey_Encode(const WOLFCOSE_KEY* key, uint8_t* buf, size_t bufSz, size_t* outLen);
294+ int wc_CoseKey_Encode(WOLFCOSE_KEY* key, uint8_t* buf, size_t bufSz, size_t* outLen);
295295```
296296
297297Encode a COSE key to CBOR format.
298298
299+ > ** Warning: this serialises the private key when the key has one.**
300+ > ` wc_CoseKey_SetEcc() ` sets ` key->hasPrivate ` whenever the attached ` ecc_key `
301+ > is a keypair, and ` wc_CoseKey_Encode() ` then emits the ` -4: d ` entry. The
302+ > output is a perfectly valid ` COSE_Key ` , only longer - for P-256 with ` alg `
303+ > set, 112 bytes / ` map(6) ` instead of 77 bytes / ` map(5) ` - so nothing fails
304+ > loudly. The same applies to RSA (` -3: d ` plus the CRT factors), Ed25519 and
305+ > Ed448 (` -4: d ` ), an RFC 9964 AKP key (` -2: priv ` seed), and symmetric keys
306+ > (` -1: k ` is the whole key). Anything that publishes a public key derived
307+ > from a live keypair - WebAuthn/CTAP2 attestation ` authData ` , ECDH key
308+ > agreement, JWK-style publication - must use
309+ > [ ` wc_CoseKey_Encode_ex() ` ] ( #wc_cosekey_encode_ex ) with
310+ > ` WOLFCOSE_KEY_PUBLIC_ONLY ` .
311+
299312** Parameters:**
300313| Name | Description |
301314| ------| -------------|
@@ -308,6 +321,107 @@ Encode a COSE key to CBOR format.
308321
309322---
310323
324+ ### wc_CoseKey_Encode_ex
325+
326+ ``` c
327+ int wc_CoseKey_Encode_ex (WOLFCOSE_KEY* key, uint8_t* buf, size_t bufSz,
328+ size_t* outLen, uint32_t flags);
329+ ```
330+
331+ As `wc_CoseKey_Encode()`, plus output options. Passing `flags = 0` is
332+ equivalent to calling `wc_CoseKey_Encode()`.
333+
334+ | Flag | Effect |
335+ |------|--------|
336+ | `WOLFCOSE_KEY_PUBLIC_ONLY` | Emit the public half only. No `-4: d` for EC2/OKP, no `-3: d` or CRT factors for RSA, no `-2: priv` seed for an AKP key. |
337+
338+ Unknown flag bits are rejected with `WOLFCOSE_E_INVALID_ARG`.
339+
340+ A symmetric key has no public half - `-1: k` *is* the key - so
341+ `WOLFCOSE_KEY_PUBLIC_ONLY` on `WOLFCOSE_KTY_SYMMETRIC` returns
342+ `WOLFCOSE_E_COSE_KEY_TYPE` rather than emitting a key with no key material.
343+
344+ The key structure is not modified; the flag affects only this call, so the
345+ same key can still sign afterwards.
346+
347+ ```c
348+ /* Publish the attestation public key without the private scalar. */
349+ ret = wc_CoseKey_Encode_ex(&key, cose, sizeof(cose), &coseLen,
350+ WOLFCOSE_KEY_PUBLIC_ONLY);
351+ ```
352+
353+ ** Returns:** ` WOLFCOSE_SUCCESS ` or error code
354+
355+ ---
356+
357+ ### wc_CoseKey_EncodeEccRaw
358+
359+ ``` c
360+ int wc_CoseKey_EncodeEccRaw (int32_t crv,
361+ const uint8_t* x, const uint8_t* y,
362+ const uint8_t* d, size_t coordLen,
363+ const uint8_t* kid, size_t kidLen, int32_t alg,
364+ uint8_t* out, size_t outSz, size_t* outLen);
365+ ```
366+
367+ Encode an EC2 `COSE_Key` straight from raw affine coordinates. For callers
368+ that hold only the coordinates - re-emitting a stored credential record, or
369+ echoing a peer key - this avoids `wc_ecc_import_unsigned()`, which pays a full
370+ point import, an on-curve check, and an `ecc_key` worth of stack purely to
371+ serialise bytes that are already in hand.
372+
373+ The output is byte-identical to what `wc_CoseKey_Encode_ex()` produces for the
374+ same `crv`/`kid`/`alg` and the same coordinates.
375+
376+ **Parameters:**
377+ | Name | Description |
378+ |------|-------------|
379+ | `crv` | `WOLFCOSE_CRV_P256` / `P384` / `P521` (EC2 curves only) |
380+ | `x`, `y` | Coordinates, each exactly `coordLen` bytes, big-endian, zero-padded |
381+ | `d` | Private scalar (`coordLen` bytes), or `NULL` for a public-only key |
382+ | `coordLen` | Must equal the curve size: 32, 48, or 66 |
383+ | `kid`, `kidLen` | Optional key identifier (`NULL`, 0 to omit) |
384+ | `alg` | Algorithm for the `3: alg` entry, or `WOLFCOSE_ALG_UNSET` to omit |
385+ | `out`, `outSz`, `outLen` | Output buffer and encoded length |
386+
387+ **Returns:** `WOLFCOSE_SUCCESS` or error code
388+
389+ Nothing here checks that `(x, y)` is on the curve: the bytes are copied into
390+ the map as supplied. Encoding an unvalidated point is safe; *using* one is
391+ not, so import it through wolfCrypt before any ECDH or verify operation.
392+
393+ There is deliberately no `wc_CoseKey_SetEccRaw()`. `WOLFCOSE_KEY` holds a
394+ single pointer-sized union member for the key, which cannot carry three
395+ independent buffers (`x`, `y`, `d`), and widening the structure would change
396+ `sizeof(WOLFCOSE_KEY)` and break the ABI for already-compiled callers. The
397+ free function above is the supported raw-coordinate path.
398+
399+ ---
400+
401+ ### wc_CoseKey_EncodeSize / wc_CoseKey_EncodeSize_ex
402+
403+ ```c
404+ int wc_CoseKey_EncodeSize(const WOLFCOSE_KEY* key, size_t* outLen);
405+ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen,
406+ uint32_t flags);
407+ ```
408+
409+ Compute the ** exact** number of bytes ` wc_CoseKey_Encode() ` /
410+ ` wc_CoseKey_Encode_ex() ` would write, without encoding into a buffer. This is
411+ the ` COSE_Key ` counterpart of
412+ [ ` wc_CoseSign1_SignSize_ex() ` ] ( #wc_cosesign1_signsize_ex ) : nothing is written
413+ and no key material is exported, only the component lengths of the attached
414+ key are read. The result is exact, not an upper bound, so it can size a buffer
415+ or reject an oversized key before committing storage.
416+
417+ ` flags ` takes the same ` WOLFCOSE_KEY_* ` values as ` wc_CoseKey_Encode_ex() ` , so
418+ ` WOLFCOSE_KEY_PUBLIC_ONLY ` sizes the public-only encoding. ` flags = 0 ` sizes
419+ the private encoding - see the warning on ` wc_CoseKey_Encode() ` .
420+
421+ ** Returns:** ` WOLFCOSE_SUCCESS ` or error code
422+
423+ ---
424+
311425### wc_CoseKey_Decode
312426
313427``` c
@@ -331,9 +445,71 @@ Attach the wolfCrypt key with `wc_CoseKey_SetEcc()`, `wc_CoseKey_SetEd25519()`,
331445assigning the `key.*` union directly does not, and no key material is imported.
332446
333447The decoded `kty`/`crv` must name the attached key type or
334- `WOLFCOSE_E_COSE_KEY_TYPE` is returned before any importer runs. Decoding into a
335- key with nothing attached runs no importer and yields metadata only, which is
336- how to inspect an untrusted `COSE_Key` before choosing a key object for it.
448+ `WOLFCOSE_E_COSE_KEY_TYPE` is returned before any importer runs. To learn which
449+ key type a buffer holds before attaching anything, use
450+ [`wc_CoseKey_PeekInfo()`](#wc_cosekey_peekinfo).
451+
452+ Decoding is strict: preferred CBOR only, integer labels only, no duplicate
453+ labels, and `bufSz` must be exactly the encoded length. See
454+ [Getting Started - Strict
455+ decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization).
456+
457+ ---
458+
459+ ### wc_CoseKey_PeekInfo
460+
461+ ```c
462+ typedef struct WOLFCOSE_KEY_INFO {
463+ int32_t kty; /* WOLFCOSE_KTY_*, always set on success */
464+ int32_t alg; /* WOLFCOSE_ALG_*, WOLFCOSE_ALG_UNSET if absent */
465+ int32_t crv; /* WOLFCOSE_CRV_*, 0 if absent or N/A */
466+ const uint8_t* kid; /* Key ID, zero-copy pointer, NULL if absent */
467+ size_t kidLen;
468+ } WOLFCOSE_KEY_INFO;
469+
470+ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz,
471+ WOLFCOSE_KEY_INFO* info);
472+ ```
473+
474+ Read ` kty ` , ` alg ` , ` crv ` , and ` kid ` out of a ` COSE_Key ` buffer without
475+ importing any key material and without needing a wolfCrypt key object.
476+
477+ ` wc_CoseKey_Decode() ` requires the caller to have attached a key of the
478+ matching type up front and returns ` WOLFCOSE_E_COSE_KEY_TYPE ` otherwise, so a
479+ parser that accepts more than one key type would have to guess and retry.
480+ Peek first, then attach once:
481+
482+ ``` c
483+ WOLFCOSE_KEY_INFO info;
484+
485+ ret = wc_CoseKey_PeekInfo(in, inSz, &info);
486+ if (ret == WOLFCOSE_SUCCESS ) {
487+ if (info.kty == WOLFCOSE_KTY_EC2) {
488+ (void)wc_ecc_init(&ecc);
489+ ret = wc_CoseKey_SetEcc(&key, info.crv, &ecc);
490+ }
491+ else if (info.kty == WOLFCOSE_KTY_OKP) {
492+ (void)wc_ed25519_init(&ed);
493+ ret = wc_CoseKey_SetEd25519(&key, &ed);
494+ }
495+ /* ... */
496+ if (ret == WOLFCOSE_SUCCESS) {
497+ ret = wc_CoseKey_Decode(&key, in, inSz);
498+ }
499+ }
500+ ```
501+
502+ ` in ` is not modified and nothing is consumed, so the call is repeatable. ` kid `
503+ points into ` in ` , so it stays valid only as long as that buffer does.
504+
505+ The same structural checks ` wc_CoseKey_Decode() ` applies are applied here -
506+ integer labels only, no duplicate labels, ` kty ` required, no trailing bytes -
507+ so a buffer that peeks successfully will not be rejected by the decoder for
508+ those reasons. Label ` -1 ` is ` crv ` for EC2/OKP but ` k ` /` n ` for symmetric/RSA
509+ keys; the value is dispatched on its CBOR type, so ` crv ` stays 0 for the
510+ latter. On any error every field of ` info ` is cleared.
511+
512+ ** Returns:** ` WOLFCOSE_SUCCESS ` or error code
337513
338514---
339515
@@ -788,10 +964,38 @@ Verify a COSE_Mac message as a specific recipient.
788964
789965Basic CBOR encoding/decoding functions in ` wolfcose.h ` :
790966
967+ ### Context Setup
968+
969+ ` WOLFCOSE_CBOR_CTX ` carries both a mutable ` buf ` (encode) and a const ` cbuf `
970+ (decode). These set the right one, clear the other, set ` bufSz ` , and zero
971+ ` idx ` in a single call:
972+
973+ ``` c
974+ int wc_CBOR_EncoderInit (WOLFCOSE_CBOR_CTX* ctx, uint8_t* buf, size_t bufSz);
975+ int wc_CBOR_DecoderInit(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* buf, size_t bufSz);
976+ ```
977+
978+ Both are `static inline` in the header, so they cost nothing over assigning
979+ the fields by hand.
980+
981+ ```c
982+ WOLFCOSE_CBOR_CTX ctx;
983+
984+ (void)wc_CBOR_EncoderInit(&ctx, out, sizeof(out));
985+ ret = wc_CBOR_EncodeMapStart(&ctx, 2);
986+ ...
987+ (void)wc_CBOR_DecoderInit(&ctx, in, inSz);
988+ ret = wc_CBOR_DecodeMapStart(&ctx, &count);
989+ ```
990+
991+ ** Returns:** ` WOLFCOSE_SUCCESS ` , or ` WOLFCOSE_E_INVALID_ARG ` if ` ctx ` or ` buf `
992+ is ` NULL ` .
993+
791994### Encoding Functions
792995
793996| Function | Description |
794997| ----------| -------------|
998+ | ` wc_CBOR_EncoderInit(ctx, buf, bufSz) ` | Initialize an encode context |
795999| ` wc_CBOR_EncodeUint(ctx, val) ` | Encode unsigned integer |
7961000| ` wc_CBOR_EncodeInt(ctx, val) ` | Encode signed integer |
7971001| ` wc_CBOR_EncodeBstr(ctx, data, len) ` | Encode byte string |
@@ -806,16 +1010,115 @@ Basic CBOR encoding/decoding functions in `wolfcose.h`:
8061010
8071011| Function | Description |
8081012| ----------| -------------|
1013+ | ` wc_CBOR_DecoderInit(ctx, buf, bufSz) ` | Initialize a decode context |
8091014| ` wc_CBOR_DecodeUint(ctx, val) ` | Decode unsigned integer |
8101015| ` wc_CBOR_DecodeInt(ctx, val) ` | Decode signed integer |
8111016| ` wc_CBOR_DecodeBstr(ctx, data, len) ` | Decode byte string (zero-copy) |
8121017| ` wc_CBOR_DecodeTstr(ctx, str, len) ` | Decode text string (zero-copy) |
8131018| ` wc_CBOR_DecodeArrayStart(ctx, count) ` | Decode array header |
8141019| ` wc_CBOR_DecodeMapStart(ctx, count) ` | Decode map header |
8151020| ` wc_CBOR_DecodeTag(ctx, tag) ` | Decode CBOR tag |
1021+ | ` wc_CBOR_DecodeLabel(ctx, label) ` | Decode an int-or-text map label |
8161022| ` wc_CBOR_Skip(ctx) ` | Skip over any CBOR item |
1023+ | ` wc_CBOR_SkipItem(ctx, data, dataLen) ` | Skip an item and capture its raw bytes |
8171024| ` wc_CBOR_PeekType(ctx) ` | Peek at next item's major type |
8181025
1026+ > ** Decoding is strict by design.** Every decode entry point requires
1027+ > RFC 8949 Section 4.2.1 preferred (shortest-form) arguments and rejects
1028+ > indefinite lengths. See [ Getting Started - Strict
1029+ > decoding] ( Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization )
1030+ > before debugging an interop failure.
1031+
1032+ ### wc_CBOR_SkipItem
1033+
1034+ ``` c
1035+ int wc_CBOR_SkipItem (WOLFCOSE_CBOR_CTX* ctx,
1036+ const uint8_t** data, size_t* dataLen);
1037+ ```
1038+
1039+ As `wc_CBOR_Skip()`, but also reports the raw encoded bytes of the item that
1040+ was skipped, zero-copy into the decoder input. This is the deferred/nested
1041+ parse primitive: capture a sub-item now, parse it later or with a different
1042+ decoder. It replaces the hand-rolled
1043+
1044+ ```c
1045+ start = ctx.idx;
1046+ ret = wc_CBOR_Skip(&ctx);
1047+ ptr = ctx.cbuf + start;
1048+ len = ctx.idx - start;
1049+ ```
1050+
1051+ that every integrator ends up writing - CTAP2 ` allowList ` entries, a
1052+ ` COSE_Key ` embedded in an extension map, ` keyAgreement ` in
1053+ ` authenticatorClientPIN ` .
1054+
1055+ ``` c
1056+ const uint8_t * sub;
1057+ size_t subLen;
1058+
1059+ ret = wc_CBOR_SkipItem(&ctx, &sub, &subLen);
1060+ if (ret == WOLFCOSE_SUCCESS ) {
1061+ ret = wc_CoseKey_Decode(&peerKey, sub, subLen); /* parse it later */
1062+ }
1063+ ```
1064+
1065+ ` wc_CBOR_Skip() ` is unchanged. On failure the outputs are untouched.
1066+
1067+ ** Returns:** ` WOLFCOSE_SUCCESS ` or error code
1068+
1069+ ---
1070+
1071+ ### wc_CBOR_DecodeLabel
1072+
1073+ ``` c
1074+ typedef struct WOLFCOSE_CBOR_LABEL {
1075+ int64_t val; /* Integer label, valid when isText == 0 */
1076+ const uint8_t* text; /* Text label, points into the input buffer */
1077+ size_t textLen;
1078+ uint8_t isText;
1079+ } WOLFCOSE_CBOR_LABEL ;
1080+
1081+ int wc_CBOR_DecodeLabel (WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label);
1082+ int wc_CBOR_LabelIsInt(const WOLFCOSE_CBOR_LABEL* label, int64_t val);
1083+ int wc_CBOR_LabelIsText(const WOLFCOSE_CBOR_LABEL* label,
1084+ const uint8_t* text, size_t textLen);
1085+ ```
1086+
1087+ RFC 9052 defines `label = int / tstr`, and real COSE and CTAP2 maps use both
1088+ spellings for the same field (`3` vs `"alg"`, `1` vs `"type"`, `2` vs `"id"`).
1089+ `wc_CBOR_DecodeLabel()` consumes one item and reports whichever form it found,
1090+ so a parser writes the dispatch once instead of duplicating a
1091+ `wc_CBOR_PeekType()` branch at every map.
1092+
1093+ Major types 0 and 1 fill `val` with `isText == 0`; major type 3 fills
1094+ `text`/`textLen` with `isText == 1` and no copy. Anything else returns
1095+ `WOLFCOSE_E_CBOR_TYPE`.
1096+
1097+ `wc_CBOR_LabelIsInt()` and `wc_CBOR_LabelIsText()` return 1 on match and 0
1098+ otherwise, including for a `NULL` label. Text comparison is byte-exact: no
1099+ Unicode normalization or case folding, matching how CTAP2 and COSE compare
1100+ labels.
1101+
1102+ ```c
1103+ WOLFCOSE_CBOR_LABEL label;
1104+ static const uint8_t algText[] = "alg";
1105+
1106+ ret = wc_CBOR_DecodeLabel(&ctx, &label);
1107+ if (wc_CBOR_LabelIsInt(&label, 3) || wc_CBOR_LabelIsText(&label, algText, 3)) {
1108+ ret = wc_CBOR_DecodeInt(&ctx, &alg);
1109+ }
1110+ else {
1111+ ret = wc_CBOR_Skip(&ctx);
1112+ }
1113+ ```
1114+
1115+ Note that ` wc_CoseKey_Decode() ` and the COSE header parsers accept integer
1116+ labels only, by design: silently skipping text labels would break their
1117+ duplicate-label enforcement. ` wc_CBOR_DecodeLabel() ` is for caller-written
1118+ parsers of protocol maps such as CTAP2.
1119+
1120+ ** Returns:** ` WOLFCOSE_SUCCESS ` or error code
1121+
8191122---
8201123
8211124## Error Codes
0 commit comments