Skip to content

Commit 6c8603f

Browse files
committed
preTBS: address review of BC/EJBCA CSR interop commit
Followup to a56735e. Tightens the new CSR-attribute decoder path and GenerateCsrPreTBS based on review: DecodeCertReqAttrValue (X9.146 cases) - Propagate the underlying decoder error instead of clobbering with ASN_PARSE_E. Callers can now distinguish MEMORY_E, ASN_BITSTR_E, ASN_OBJECT_ID_E, etc. from a generic parse failure. - New CheckSinglePkcs10AttrValue() helper enforces that the SET OF AttributeValue holds exactly one TLV. Rejects an over-stuffed SET that would silently feed only the first value to the decoder. - Updated function docstring to enumerate the X9.146 cases and the new ASN_OBJECT_ID_E return when an X9.146 attribute appears at both the top level and nested inside extensionRequest (already detected by VERIFY_AND_SET_OID; just wasn't documented). OidFromId / oidCsrAttrType - Register SUBJ_ALT_PUB_KEY_INFO_OID, ALT_SIG_ALG_OID and ALT_SIG_VAL_OID under oidCsrAttrType so the OID validation in GetObjectId can match them against the canonical bytes. Previously the cases worked by accident because GetObjectId silently accepts unknown OIDs (per the existing TODO at line ~7348); if that TODO is ever resolved the new path would have broken. GenerateCsrPreTBS - Renamed the encoding selector from `bcMode` (int 0/1, two semantically-distinct states) to `topLevelAttr` with a doc comment. - Added an explicit comment in the topLevelAttr branch explaining why the lamps-draft "altSigValue must be last" enforcement is *not* applied here (PKCS#10 attributes is SET OF, ordering is determined by DER's SET OF rule, removing one element leaves the SET sorted). - Added a sanity check on the lamps-draft branch: if the cert claims the lamps-draft encoding (no top-level altSigValue attribute) but the parser didn't populate dCert->extensions / extensionsIdx, fail with ASN_PARSE_E rather than walking off the end of an incomplete DecodedCert. Interop coverage unchanged but re-validated: - BC -> wolfSSL: cert + CSR alt sigs verify (BouncyCastle 1.80.2 artefacts, the JAR EJBCA-CE 9.x bundles). - wolfSSL CSR -> BC issuer -> wolfSSL: BC issues a dual-alg cert from a wolfSSL CSR; wolfSSL verifies the issued cert. - All 9 self-tests under --enable-dual-alg-certs --enable-experimental --enable-dilithium --enable-mldsa --enable-certreq --enable-certgen --enable-keygen still pass (default config: clean -Werror, 0 fail). Note: a wolfSSL self-test that exercises the BC top-level encoding in isolation still requires either a hand-crafted (~4 KB) DER fixture or a runtime BouncyCastle dependency, neither of which are appropriate for the in-tree test suite right now. The integration test tooling under /tmp/ejbca-interop/ remains the reproducer.
1 parent a56735e commit 6c8603f

1 file changed

Lines changed: 109 additions & 28 deletions

File tree

wolfcrypt/src/asn.c

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5184,6 +5184,15 @@ static const byte attrDnQualifier[] = {85, 4, 46};
51845184
static const byte attrInitals[] = {85, 4, 43};
51855185
static const byte attrSurname[] = {85, 4, 4};
51865186
static const byte attrGivenName[] = {85, 4, 42};
5187+
#ifdef WOLFSSL_DUAL_ALG_CERTS
5188+
/* X9.146 hybrid attributes can appear at the top level of a PKCS#10
5189+
* CSR (BouncyCastle / EJBCA encoding) in addition to nested inside
5190+
* extensionRequest. The OID bytes are the same as the corresponding
5191+
* X.509 extension OIDs (2.5.29.72/73/74). */
5192+
static const byte attrSubjAltPubKeyInfoOid[] = {85, 29, 72};
5193+
static const byte attrAltSigAlgOid[] = {85, 29, 73};
5194+
static const byte attrAltSigValOid[] = {85, 29, 74};
5195+
#endif
51875196
#endif
51885197
#endif
51895198

@@ -6722,6 +6731,20 @@ const byte* OidFromId(word32 id, word32 type, word32* oidSz)
67226731
oid = attrExtensionRequestOid;
67236732
*oidSz = sizeof(attrExtensionRequestOid);
67246733
break;
6734+
#ifdef WOLFSSL_DUAL_ALG_CERTS
6735+
case SUBJ_ALT_PUB_KEY_INFO_OID:
6736+
oid = attrSubjAltPubKeyInfoOid;
6737+
*oidSz = sizeof(attrSubjAltPubKeyInfoOid);
6738+
break;
6739+
case ALT_SIG_ALG_OID:
6740+
oid = attrAltSigAlgOid;
6741+
*oidSz = sizeof(attrAltSigAlgOid);
6742+
break;
6743+
case ALT_SIG_VAL_OID:
6744+
oid = attrAltSigValOid;
6745+
*oidSz = sizeof(attrAltSigValOid);
6746+
break;
6747+
#endif
67256748
default:
67266749
break;
67276750
}
@@ -21107,15 +21130,56 @@ static const byte strAttrChoice[] = {
2110721130
ASN_PRINTABLE_STRING, ASN_IA5_STRING, ASN_UTF8STRING, 0
2110821131
};
2110921132

21133+
#ifdef WOLFSSL_DUAL_ALG_CERTS
21134+
/* PKCS#10 attributes are SET OF AttributeValue. Several attribute OIDs
21135+
* (notably the X9.146 ones we accept here) only ever carry a single
21136+
* value, but the wire format would let an attacker append more. Confirm
21137+
* that the SET content is exactly one TLV item. Returns 0 on success. */
21138+
static int CheckSinglePkcs10AttrValue(const byte* input, word32 sz)
21139+
{
21140+
word32 idx = 0;
21141+
int len;
21142+
byte tag;
21143+
int ret;
21144+
21145+
ret = GetASNTag(input, &idx, &tag, sz);
21146+
if (ret < 0) {
21147+
return ret;
21148+
}
21149+
ret = GetLength(input, &idx, &len, sz);
21150+
if (ret < 0) {
21151+
return ret;
21152+
}
21153+
if (idx + (word32)len != sz) {
21154+
WOLFSSL_MSG("PKCS#10 attribute value SET must contain exactly one"
21155+
" value");
21156+
WOLFSSL_ERROR_VERBOSE(ASN_PARSE_E);
21157+
return ASN_PARSE_E;
21158+
}
21159+
return 0;
21160+
}
21161+
#endif /* WOLFSSL_DUAL_ALG_CERTS */
21162+
2111021163
/* Decode a certificate request attribute's value.
21164+
*
21165+
* Recognised attribute OIDs:
21166+
* PKCS#9: contentType, challengePassword, serialNumber,
21167+
* unstructuredName, extensionRequest.
21168+
* X9.146 dual-alg (when WOLFSSL_DUAL_ALG_CERTS is enabled):
21169+
* subjectAltPublicKeyInfo, altSignatureAlgorithm,
21170+
* altSignatureValue. These can also appear nested inside
21171+
* extensionRequest (the IETF lamps draft style); both
21172+
* encodings populate the same DecodedCert fields.
2111121173
*
2111221174
* @param [in] cert Certificate request object.
2111321175
* @param [out] criticalExt Critical extension return code.
2111421176
* @param [in] oid OID describing which attribute was found.
2111521177
* @param [in] aIdx Index into certificate source to start parsing.
21116-
* @param [in] input Attribute value data.
21117-
* @param [in] maxIdx Maximum index to parse to.
21178+
* @param [in] input Attribute value data (the SET's content).
21179+
* @param [in] maxIdx Length of attribute value data, in bytes.
2111821180
* @return 0 on success.
21181+
* @return ASN_OBJECT_ID_E when the same X9.146 attribute appears more
21182+
* than once (top-level + nested or two top-level copies).
2111921183
* @return ASN_PARSE_E when BER encoded data does not match ASN.1 items or
2112021184
* is invalid.
2112121185
*/
@@ -21226,37 +21290,33 @@ static int DecodeCertReqAttrValue(DecodedCert* cert, int* criticalExt,
2122621290
break;
2122721291

2122821292
#ifdef WOLFSSL_DUAL_ALG_CERTS
21229-
/* X9.146 dual-algorithm CSR attributes appear in two encodings
21230-
* in the wild:
21231-
* - Nested inside the extensionRequest attribute above (the
21232-
* IETF lamps draft style; how wolfSSL emits via
21233-
* wc_SetCustomExtension).
21234-
* - As top-level PKCS#10 attributes whose OID is the X.509
21235-
* extension OID (2.5.29.72/73/74) - BouncyCastle's
21236-
* PKCS10CertificationRequestBuilder.build(signer, altPubKey,
21237-
* altSigner) emits this. EJBCA inherits the BC encoding.
21238-
* Accept both. The 'input'/'maxIdx' span here is the SET's
21239-
* content, which contains the value SEQUENCE / BIT STRING
21240-
* directly. */
21293+
/* See the docstring above for the two X9.146 CSR encodings we
21294+
* accept here. The 'input'/'maxIdx' span at this point is the
21295+
* SET's content for the attribute value - it must contain
21296+
* exactly one TLV (SubjectAltPublicKeyInfo / AlgorithmIdentifier
21297+
* / BIT STRING). VERIFY_AND_SET_OID detects an X9.146 attribute
21298+
* that already arrived via the other encoding (or twice via
21299+
* the same encoding) and returns ASN_OBJECT_ID_E before any
21300+
* decoder field is overwritten. */
2124121301
case SUBJ_ALT_PUB_KEY_INFO_OID:
2124221302
VERIFY_AND_SET_OID(cert->extSapkiSet);
21243-
ret = DecodeSubjAltPubKeyInfo(input, (int)maxIdx, cert);
21244-
if (ret < 0) {
21245-
ret = ASN_PARSE_E;
21303+
ret = CheckSinglePkcs10AttrValue(input, maxIdx);
21304+
if (ret == 0) {
21305+
ret = DecodeSubjAltPubKeyInfo(input, (int)maxIdx, cert);
2124621306
}
2124721307
break;
2124821308
case ALT_SIG_ALG_OID:
2124921309
VERIFY_AND_SET_OID(cert->extAltSigAlgSet);
21250-
ret = DecodeAltSigAlg(input, (int)maxIdx, cert);
21251-
if (ret < 0) {
21252-
ret = ASN_PARSE_E;
21310+
ret = CheckSinglePkcs10AttrValue(input, maxIdx);
21311+
if (ret == 0) {
21312+
ret = DecodeAltSigAlg(input, (int)maxIdx, cert);
2125321313
}
2125421314
break;
2125521315
case ALT_SIG_VAL_OID:
2125621316
VERIFY_AND_SET_OID(cert->extAltSigValSet);
21257-
ret = DecodeAltSigVal(input, (int)maxIdx, cert);
21258-
if (ret < 0) {
21259-
ret = ASN_PARSE_E;
21317+
ret = CheckSinglePkcs10AttrValue(input, maxIdx);
21318+
if (ret == 0) {
21319+
ret = DecodeAltSigVal(input, (int)maxIdx, cert);
2126021320
}
2126121321
break;
2126221322
#endif /* WOLFSSL_DUAL_ALG_CERTS */
@@ -30330,7 +30390,12 @@ static int GenerateCsrPreTBS(DecodedCert* dCert, byte* der, int derSz)
3033030390
word32 outIdx;
3033130391

3033230392
/* Excised range, in two flavours. */
30333-
int bcMode = 0; /* if set, excise an entire top-level Attribute */
30393+
/* Encoding selector for the excision step:
30394+
* 0 -> lamps-draft (extensionRequest-nested) - excise the
30395+
* altSigValue Extension from inside extensionRequest.
30396+
* 1 -> BC / EJBCA top-level - excise the altSigValue Attribute
30397+
* SEQUENCE wholesale from the CRInfo's attributes [0] SET. */
30398+
int topLevelAttr = 0;
3033430399
word32 excisedAttrStart = 0;
3033530400
word32 excisedAttrEnd = 0;
3033630401
/* lamps-draft (extensionRequest) mode: */
@@ -30434,7 +30499,7 @@ static int GenerateCsrPreTBS(DecodedCert* dCert, byte* der, int derSz)
3043430499

3043530500
if (oid == ALT_SIG_VAL_OID) {
3043630501
/* (A) BC encoding - excise this entire Attribute SEQUENCE. */
30437-
bcMode = 1;
30502+
topLevelAttr = 1;
3043830503
excisedAttrStart = attrTagOff;
3043930504
excisedAttrEnd = attrContentEnd;
3044030505
break;
@@ -30448,10 +30513,19 @@ static int GenerateCsrPreTBS(DecodedCert* dCert, byte* der, int derSz)
3044830513
walkIdx = attrContentEnd;
3044930514
}
3045030515

30451-
if (bcMode) {
30516+
if (topLevelAttr) {
3045230517
/* BC encoding: only the wrapping CRInfo SEQUENCE and attributes
3045330518
* [0] header lengths shrink; the altSigValue Attribute is
30454-
* removed wholesale. */
30519+
* removed wholesale.
30520+
*
30521+
* Note: there is no "altSigValue must be last" enforcement here
30522+
* (unlike the certificate path and the lamps-draft branch
30523+
* below). PKCS#10 attributes is SET OF Attribute, and DER's
30524+
* SET OF ordering rule means the encoder controls position.
30525+
* Removing one element from a sorted SET still leaves it
30526+
* sorted, so emitting the remaining attributes in their
30527+
* original wire order is DER-conformant regardless of where
30528+
* altSigValue sat. */
3045530529
attrsPrefixLen = excisedAttrStart - attrsContentStart;
3045630530
attrsSuffixLen = attrsContentEnd - excisedAttrEnd;
3045730531
newAttrsContentLen = attrsPrefixLen + attrsSuffixLen;
@@ -30465,6 +30539,13 @@ static int GenerateCsrPreTBS(DecodedCert* dCert, byte* der, int derSz)
3046530539
return ASN_PARSE_E;
3046630540
}
3046730541

30542+
if (dCert->extensions == NULL || dCert->extensionsSz <= 0 ||
30543+
dCert->extensionsIdx == 0) {
30544+
WOLFSSL_MSG("CSR has lamps-draft altSigValue but extensions"
30545+
" bookkeeping is missing");
30546+
return ASN_PARSE_E;
30547+
}
30548+
3046830549
idx = setTagOff;
3046930550
ret = GetSet(dCert->source, &idx, &len, extReqAttrContentEnd);
3047030551
if (ret < 0) {
@@ -30559,7 +30640,7 @@ static int GenerateCsrPreTBS(DecodedCert* dCert, byte* der, int derSz)
3055930640
der[outIdx++] = ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0;
3056030641
outIdx += SetLength(newAttrsContentLen, der + outIdx);
3056130642

30562-
if (bcMode) {
30643+
if (topLevelAttr) {
3056330644
/* Emit the attributes SET with the altSigValue Attribute SEQUENCE
3056430645
* removed (everything else stays exactly where it was). */
3056530646
if (attrsPrefixLen > 0) {

0 commit comments

Comments
 (0)