Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions include/wolfcose/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ extern "C" {
#endif
#endif /* WOLFCOSE_LEAN_MLDSA */

/* ----- Integration seams -----
*
* Opt-in in every build, including a full one. These are not algorithms, so
* there is no "wolfSSL provides the primitive" clause to auto-enable them and a
* default build is byte-identical to one built without them.
* ----- */

/* External/delegated signing: the caller supplies the signature over the
* to-be-signed bytes, so no private key material enters wolfCOSE — extension. */
#if defined(WOLFCOSE_ENABLE_EXT_SIGN)
#define WOLFCOSE_EXT_SIGN
#endif

/* ----- Signature algorithms ----- */

/* ES256 — core (on whenever wolfSSL has ECC) */
Expand Down Expand Up @@ -503,6 +516,11 @@ extern "C" {
#error "wolfCOSE: ML-DSA enabled but WOLFCOSE_MAX_SCRATCH_SZ too small"
#endif

#if defined(WOLFCOSE_EXT_SIGN) && !defined(WOLFCOSE_SIGN1_SIGN) && \
!defined(WOLFCOSE_SIGN_SIGN)
#error "WOLFCOSE_ENABLE_EXT_SIGN requires an enabled signing operation"
#endif

#ifdef __cplusplus
}
#endif
Expand Down
56 changes: 55 additions & 1 deletion include/wolfcose/wolfcose.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,33 @@ typedef struct WOLFCOSE_HDR {
/** \brief Flag indicating payload is detached (RFC 9052 Section 2) */
#define WOLFCOSE_HDR_FLAG_DETACHED 0x01u

#if defined(WOLFCOSE_EXT_SIGN)
/**
* \brief Caller-supplied signature callback (RFC 9052 Section 4.4).
*
* Receives what the algorithm's signature primitive consumes: the digest of the
* Sig_structure for pre-hashed algorithms (ES256/384/512, PS256/384/512), or the
* Sig_structure itself for EdDSA/Ed448/ML-DSA.
*
* Returns the COSE signature bytes exactly as they appear in the message: for
* ECDSA that is fixed-width r||s (RFC 9053 Section 2.1), which is what
* psa_sign_hash() already produces. wolfCOSE performs no conversion, but does
* check the returned length.
*
* \param cbCtx Opaque caller context, passed through untouched.
* \param alg WOLFCOSE_ALG_* being signed with.
* \param tbs To-be-signed bytes (digest, or Sig_structure for EdDSA).
* \param tbsSz Length of tbs.
* \param sig Output buffer for the signature.
* \param sigSz Capacity of sig.
* \param sigLen Output: bytes written to sig.
* \return 0 on success, non-zero to fail the operation.
*/
typedef int (*WOLFCOSE_SIGN_CB)(void* cbCtx, int32_t alg,
const uint8_t* tbs, size_t tbsSz,
uint8_t* sig, size_t sigSz, size_t* sigLen);
#endif /* WOLFCOSE_EXT_SIGN */

/**
* \brief COSE key structure. Pointers to caller-owned wolfCrypt key structs.
*
Expand Down Expand Up @@ -343,7 +370,15 @@ typedef struct WOLFCOSE_KEY {
const uint8_t* mldsaSeed; /**< RFC 9964 ML-DSA private seed (32B), caller-owned */
size_t mldsaSeedLen; /**< ML-DSA private seed length */
#endif
uint8_t hasPrivate; /**< 1 if private key material present */
uint8_t hasPrivate; /**< 1 if the key can produce signatures. With
* signCb set this means the external signer holds
* the private key, not wolfCOSE. */
#if defined(WOLFCOSE_EXT_SIGN)
/* Appended at the end: absent unless the seam is enabled, so the struct
* layout is unchanged in a default build. */
WOLFCOSE_SIGN_CB signCb; /**< NULL: sign locally with wolfCrypt */
void* signCtx; /**< Opaque, passed to signCb untouched */
#endif
} WOLFCOSE_KEY;

/**
Expand Down Expand Up @@ -631,6 +666,25 @@ WOLFCOSE_API int wc_CoseKey_SetRsa(WOLFCOSE_KEY* key, RsaKey* rsaKey);
WOLFCOSE_API int wc_CoseKey_SetSymmetric(WOLFCOSE_KEY* key,
const uint8_t* data, size_t dataLen);

#if defined(WOLFCOSE_EXT_SIGN)
/**
* \brief Delegate signing to a caller-supplied callback.
*
* Does not touch kty/crv or the key union, so it composes with
* wc_CoseKey_SetEcc() and friends. Sets hasPrivate: for a delegated key that
* means the key can produce signatures, not that wolfCOSE holds the private
* material. With a callback set, wc_CoseSign1_Sign() accepts a NULL rng
* because the external signer owns its own randomness.
*
* \param key COSE key (must be initialized).
* \param cb Signature callback. Must not be NULL.
* \param cbCtx Opaque context passed to cb (may be NULL).
* \return WOLFCOSE_SUCCESS or negative error code.
*/
WOLFCOSE_API int wc_CoseKey_SetExtSigner(WOLFCOSE_KEY* key,
WOLFCOSE_SIGN_CB cb, void* cbCtx);
#endif

#if defined(WOLFCOSE_KEY_ENCODE)
/**
* \brief Encode a WOLFCOSE_KEY to CBOR COSE_Key map format.
Expand Down
169 changes: 169 additions & 0 deletions src/wolfcose.c
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,25 @@ int wc_CoseKey_SetSymmetric(WOLFCOSE_KEY* key, const uint8_t* data,
return ret;
}

#if defined(WOLFCOSE_EXT_SIGN)
int wc_CoseKey_SetExtSigner(WOLFCOSE_KEY* key, WOLFCOSE_SIGN_CB cb,
void* cbCtx)
{
int ret;

if ((key == NULL) || (cb == NULL)) {
ret = WOLFCOSE_E_INVALID_ARG;
}
else {
key->signCb = cb;
key->signCtx = cbCtx;
key->hasPrivate = 1;
ret = WOLFCOSE_SUCCESS;
}
return ret;
}
#endif

#if defined(WOLFCOSE_KEY_ENCODE)
static size_t wolfCose_KeyOptionalEntries(const WOLFCOSE_KEY* key)
{
Expand Down Expand Up @@ -3506,6 +3525,100 @@ static int wolfCose_MlDsaCheckKey(const WOLFCOSE_KEY* key, int32_t alg)
}
#endif /* WOLFCOSE_HAVE_MLDSA */

#if defined(WOLFCOSE_EXT_SIGN)
/* EdDSA and ML-DSA primitives consume the Sig_structure whole; everything else
* signs a digest of it. Not derivable from wolfCose_AlgToHashType, which
* returns a stand-in hash for EdDSA. */
static int wolfCose_AlgPreHashes(int32_t alg)
{
int preHashes;

switch (alg) {
case WOLFCOSE_ALG_EDDSA:
case WOLFCOSE_ALG_ML_DSA_44:
case WOLFCOSE_ALG_ML_DSA_65:
case WOLFCOSE_ALG_ML_DSA_87:
preHashes = 0;
break;
default:
preHashes = 1;
break;
}
Comment on lines +3536 to +3546
return preHashes;
}

int wolfCose_ExtSign(const WOLFCOSE_KEY* key, int32_t alg,
const uint8_t* sigStruct, size_t sigStructLen,
uint8_t* sig, size_t sigSz, size_t* sigLen)
{
int ret = WOLFCOSE_SUCCESS;
enum wc_HashType hashType = WC_HASH_TYPE_NONE;
uint8_t hashBuf[WC_MAX_DIGEST_SIZE];
int digestSz = 0;
const uint8_t* tbs = sigStruct;
size_t tbsLen = sigStructLen;
size_t coordSz = 0;
int cbRet = 0;

if ((key == NULL) || (key->signCb == NULL) || (sigStruct == NULL) ||
(sig == NULL) || (sigLen == NULL) || (sigSz == 0u)) {
ret = WOLFCOSE_E_INVALID_ARG;
}

if ((ret == WOLFCOSE_SUCCESS) && (wolfCose_AlgPreHashes(alg) != 0)) {
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, sigStruct, (word32)sigStructLen,
hashBuf, (word32)digestSz));
if (ret != 0) {
ret = WOLFCOSE_E_CRYPTO;
}
}
if (ret == WOLFCOSE_SUCCESS) {
tbs = hashBuf;
tbsLen = (size_t)digestSz;
}
}

if (ret == WOLFCOSE_SUCCESS) {
*sigLen = 0;
INJECT_FAILURE(WOLF_FAIL_EXT_SIGN, -1,
cbRet = key->signCb(key->signCtx, alg, tbs, tbsLen,
sig, sigSz, sigLen));
if (cbRet != 0) {
ret = WOLFCOSE_E_CRYPTO;
}
}

/* A callback is caller code: do not trust its length. */
if ((ret == WOLFCOSE_SUCCESS) &&
((*sigLen == 0u) || (*sigLen > sigSz))) {
ret = WOLFCOSE_E_CRYPTO;
}

/* ECDSA is fixed-width r||s (RFC 9053 Section 2.1); a short or long
* signature here would produce a malformed COSE message. */
if ((ret == WOLFCOSE_SUCCESS) && (key->kty == WOLFCOSE_KTY_EC2)) {
ret = wolfCose_CrvKeySize(key->crv, &coordSz);

if ((ret == WOLFCOSE_SUCCESS) && (*sigLen != (2u * coordSz))) {
ret = WOLFCOSE_E_CRYPTO;
}
}
Comment on lines +3607 to +3615

wolfCose_ForceZero(hashBuf, sizeof(hashBuf));
return ret;
}
#endif /* WOLFCOSE_EXT_SIGN */

#if defined(WOLFCOSE_SIGN1_SIGN)
int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg,
const uint8_t* kid, size_t kidLen,
Expand Down Expand Up @@ -3542,10 +3655,22 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg,
isDetached = 0u;
}

#if defined(WOLFCOSE_EXT_SIGN)
if ((key == NULL) || (sigPayload == NULL) || (scratch == NULL) ||
(out == NULL) || (outLen == NULL)) {
ret = WOLFCOSE_E_INVALID_ARG;
}
/* An external signer owns its own randomness, so rng is required only when
* wolfCrypt does the signing. */
if ((ret == WOLFCOSE_SUCCESS) && (key->signCb == NULL) && (rng == NULL)) {
ret = WOLFCOSE_E_INVALID_ARG;
}
#else
if ((key == NULL) || (sigPayload == NULL) || (scratch == NULL) ||
(out == NULL) || (outLen == NULL) || (rng == NULL)) {
ret = WOLFCOSE_E_INVALID_ARG;
}
#endif
#ifdef WOLFCOSE_CHECK_WORD32_LEN
if ((ret == WOLFCOSE_SUCCESS) &&
((wolfCose_LenFitsWord32(payloadLen) == 0) ||
Expand Down Expand Up @@ -3593,6 +3718,28 @@ int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg,
}

/* Sign based on algorithm */
#if defined(WOLFCOSE_EXT_SIGN)
if ((ret == WOLFCOSE_SUCCESS) && (key->signCb != NULL)) {
size_t extSigLen = 0;

/* The signature lands after the Sig_structure in scratch, so no extra
* stack is needed and the capacity is whatever scratch has left rather
* than sigBuf's fixed 132. */
if (scratchSz <= sigStructLen) {
ret = WOLFCOSE_E_BUFFER_TOO_SMALL;
}
if (ret == WOLFCOSE_SUCCESS) {
ret = wolfCose_ExtSign(key, alg, scratch, sigStructLen,
&scratch[sigStructLen],
scratchSz - sigStructLen, &extSigLen);
}
if (ret == WOLFCOSE_SUCCESS) {
sigPtr = &scratch[sigStructLen];
sigSz = extSigLen;
}
}
else
#endif
#if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448)
if ((ret == WOLFCOSE_SUCCESS) && (alg == WOLFCOSE_ALG_EDDSA)) {
word32 edSigLen = (word32)sizeof(sigBuf);
Expand Down Expand Up @@ -4506,6 +4653,28 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount,
}

/* Sign the hash */
#if defined(WOLFCOSE_EXT_SIGN)
if ((ret == WOLFCOSE_SUCCESS) && (signer->key->signCb != NULL)) {
size_t extSigLen = 0;

/* Signature goes after the Sig_structure in scratch; sigBuf is
* reserved for the local-signing branches. */
if (scratchSz <= sigStructLen) {
ret = WOLFCOSE_E_BUFFER_TOO_SMALL;
}
if (ret == WOLFCOSE_SUCCESS) {
Comment on lines +4656 to +4665
ret = wolfCose_ExtSign(signer->key, signer->algId,
scratch, sigStructLen,
&scratch[sigStructLen],
scratchSz - sigStructLen, &extSigLen);
}
if (ret == WOLFCOSE_SUCCESS) {
sigPtr = &scratch[sigStructLen];
sigSz = extSigLen;
}
}
else
#endif
#ifdef WOLFCOSE_HAVE_ECDSA
if ((ret == WOLFCOSE_SUCCESS) &&
((signer->algId == WOLFCOSE_ALG_ES256) ||
Expand Down
21 changes: 21 additions & 0 deletions src/wolfcose_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,27 @@ WOLFCOSE_LOCAL int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen,
WC_RNG* rng, ecc_key* eccKey);
#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */

#if defined(WOLFCOSE_EXT_SIGN)
/**
* \brief Produce a signature via the key's external signer callback.
* Pre-hashes the Sig_structure for algorithms whose primitive takes a
* digest, then validates the length the callback reports.
* \param key Key with signCb set.
* \param alg WOLFCOSE_ALG_* being signed with.
* \param sigStruct Encoded Sig_structure.
* \param sigStructLen Sig_structure length.
* \param sig Output buffer for the signature.
* \param sigSz Capacity of sig.
* \param sigLen Output: bytes written to sig.
* \return WOLFCOSE_SUCCESS or negative error code.
*/
WOLFCOSE_LOCAL int wolfCose_ExtSign(const WOLFCOSE_KEY* key, int32_t alg,
const uint8_t* sigStruct,
size_t sigStructLen,
uint8_t* sig, size_t sigSz,
size_t* sigLen);
#endif /* WOLFCOSE_EXT_SIGN */

/**
* \brief Verify a raw r||s ECC signature.
* Converts raw r||s -> DER then calls wc_ecc_verify_hash.
Expand Down
3 changes: 3 additions & 0 deletions tests/force_failure.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ typedef enum {
/* Hash failures */
WOLF_FAIL_HASH, /* wc_Hash */

/* External signer failures */
WOLF_FAIL_EXT_SIGN, /* WOLFCOSE_SIGN_CB */

WOLF_FAIL_COUNT
} WolfForceFailure;

Expand Down
Loading