From 29e0f479adba1c381a1bf539c17b63621c35d41c Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Fri, 21 Aug 2026 13:03:17 +0900 Subject: [PATCH] scep: name the issuing CA in the GetCertInitial IssuerAndSubject - wolfcert_scep_issuer_and_subject takes the issuer Name from the envelope-target certificate's own subject when it sets the basic constraints CA flag, and from its issuer otherwise. The Name validation and the length arithmetic follow the selected Name. - Each Name is written through a new enc_seq(), so the result encodes as SEQUENCE { issuer Name, subject Name }; enc_seq_len() sizes the outer SEQUENCE from the two TLVs. A length that cannot be encoded returns WOLFCERT_ERR_MEMORY, as does a failed write, which frees the output buffer first. - The parameter is renamed ra_cert_der/ra_cert_len, the function and its src/internal.h declaration are WOLFCERT_TEST_VIS, and both doc comments carry the selection rule. internal.h also states that an RA certificate is assumed to come from the CA that issues the requested certificate. - tests: make_signed_cert() issues a certificate under the make_ca CA with its own subject, an is_ca flag and a with_bc flag, and seq_content() decodes one DER SEQUENCE. - tests: test_issuer_and_subject_issuer_name() runs check_issuer_and_subject() over an RA certificate, the CA, a sub-CA and a certificate carrying no basic constraints, comparing both decoded Names byte for byte, and requires WOLFCERT_ERR_BAD_ARG for a NULL ra_cert_der, csr_der and out_der. Issue: F-8022 --- src/internal.h | 11 +- src/scep/scep_msg.c | 96 ++++++++++--- tests/unit/test_scep_msg.c | 275 +++++++++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+), 23 deletions(-) diff --git a/src/internal.h b/src/internal.h index c6c636e..66ee43c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -352,11 +352,12 @@ typedef struct { const char* fail_info; } WolfCertScepAttrs; -/* Build a DER-encoded IssuerAndSubject SEQUENCE (RFC 8894 section 3.3.2) from - * the raw Name bytes of the RA/CA cert (-> issuer) and a CSR (-> subject). - * Used as the enveloped content of a GetCertInitial pkiMessage. */ -int wolfcert_scep_issuer_and_subject(const uint8_t* issuer_cert_der, size_t issuer_cert_len, - const uint8_t* csr_der, size_t csr_len, +/* Build the GetCertInitial IssuerAndSubject (RFC 8894 section 3.3.2) from an + * envelope-target cert and a CSR. A CA target names itself; an RA names its + * issuer, which assumes that is the CA issuing the requested cert. */ +WOLFCERT_TEST_VIS int wolfcert_scep_issuer_and_subject( + const uint8_t* ra_cert_der, size_t ra_cert_len, + const uint8_t* csr_der, size_t csr_len, WolfCertBuffer* out_der, void* heap); WOLFCERT_TEST_VIS int wolfcert_scep_envelop(const uint8_t* ra_cert_der, diff --git a/src/scep/scep_msg.c b/src/scep/scep_msg.c index 615e115..fb3bba7 100644 --- a/src/scep/scep_msg.c +++ b/src/scep/scep_msg.c @@ -119,6 +119,32 @@ static int enc_printable(const char* s, byte* out, size_t cap) return enc_printable_n((const byte*)s, strlen(s), out, cap); } +/* Wrap `vl` bytes in a SEQUENCE. Returns total bytes written, or -1 if `cap` + * is too small. */ +static int enc_seq(const byte* v, size_t vl, byte* out, size_t cap) +{ + if (cap < 1) + return -1; + + out[0] = 0x30; + int ll = der_put_len(out + 1, cap - 1, vl); + if (ll < 0 || 1 + (size_t)ll + vl > cap) + return -1; + + memcpy(out + 1 + ll, v, vl); + return (int)(1 + (size_t)ll + vl); +} + +/* Size of the TLV that enc_seq writes for `vl` content bytes, or 0 when the + * length cannot be encoded. */ +static size_t enc_seq_len(size_t vl) +{ + byte tmp[8]; + int ll = der_put_len(tmp, sizeof(tmp), vl); + + return (ll < 0) ? 0 : (size_t)(1 + (size_t)ll + vl); +} + static int enc_octet(const byte* v, size_t vl, byte* out, size_t cap) { if (cap < 1) @@ -795,23 +821,23 @@ WOLFCERT_TEST_VIS int wolfcert_scep_parse_pki_message(const uint8_t* pki_der, return WOLFCERT_OK; } -/* Build RFC 8894 section 3.3.2 IssuerAndSubject: - * IssuerAndSubject ::= SEQUENCE { issuer Name, subject Name } - * where the issuer Name is copied from the RA/CA cert and the subject - * Name is copied from the CSR. This is the enveloped content of a - * GetCertInitial (messageType 20) pkiMessage - it lets the server - * locate the pending request by DN when transactionID matching is - * ambiguous. */ -int wolfcert_scep_issuer_and_subject(const uint8_t* issuer_cert_der, size_t issuer_cert_len, - const uint8_t* csr_der, size_t csr_len, +/* Build the GetCertInitial (messageType 20) enveloped content, RFC 8894 + * section 3.3.2 IssuerAndSubject ::= SEQUENCE { issuer Name, subject Name }: + * the Name of the issuing CA, then the subject Name from the CSR. */ +WOLFCERT_TEST_VIS int wolfcert_scep_issuer_and_subject( + const uint8_t* ra_cert_der, size_t ra_cert_len, + const uint8_t* csr_der, size_t csr_len, WolfCertBuffer* out_der, void* heap) { - if (issuer_cert_der == NULL || csr_der == NULL || out_der == NULL) + const uint8_t* issuer_name; + int issuer_name_len; + + if (ra_cert_der == NULL || csr_der == NULL || out_der == NULL) return WOLFCERT_ERR_BAD_ARG; DecodedCert ic; - wc_InitDecodedCert(&ic, (byte*)issuer_cert_der, - (word32)issuer_cert_len, heap); + wc_InitDecodedCert(&ic, (byte*)ra_cert_der, + (word32)ra_cert_len, heap); int rc = wc_ParseCert(&ic, CERT_TYPE, NO_VERIFY, NULL); if (rc != 0) { @@ -829,15 +855,37 @@ int wolfcert_scep_issuer_and_subject(const uint8_t* issuer_cert_der, size_t issu return WOLFCERT_ERR_PARSE; } - if (ic.subjectRaw == NULL || ic.subjectRawLen <= 0 || + /* A CA certificate issues under its own name. An RA certificate is an + * end entity, so the CA that will issue is the one that issued it. */ + if (ic.isCA) { + issuer_name = ic.subjectRaw; + issuer_name_len = ic.subjectRawLen; + } + else { + issuer_name = ic.issuerRaw; + issuer_name_len = ic.issuerRawLen; + } + + if (issuer_name == NULL || issuer_name_len <= 0 || sc.subjectRaw == NULL || sc.subjectRawLen <= 0) { wc_FreeDecodedCert(&ic); wc_FreeDecodedCert(&sc); return WOLFCERT_ERR_PARSE; } - size_t inner = (size_t)ic.subjectRawLen + (size_t)sc.subjectRawLen; + /* Give each Name its own SEQUENCE, so the result decodes as + * IssuerAndSubject ::= SEQUENCE { issuer Name, subject Name }. */ + size_t issuer_tlv = enc_seq_len((size_t)issuer_name_len); + size_t subject_tlv = enc_seq_len((size_t)sc.subjectRawLen); + size_t inner = issuer_tlv + subject_tlv; size_t cap = inner + 8; + + if (issuer_tlv == 0 || subject_tlv == 0) { + wc_FreeDecodedCert(&ic); + wc_FreeDecodedCert(&sc); + return WOLFCERT_ERR_MEMORY; + } + uint8_t* buf = (uint8_t*)WOLFCERT_XMALLOC(cap, heap); if (buf == NULL) { wc_FreeDecodedCert(&ic); @@ -855,10 +903,22 @@ int wolfcert_scep_issuer_and_subject(const uint8_t* issuer_cert_der, size_t issu } size_t off = 1 + (size_t)ll; - memcpy(buf + off, ic.subjectRaw, (size_t)ic.subjectRawLen); - off += (size_t)ic.subjectRawLen; - memcpy(buf + off, sc.subjectRaw, (size_t)sc.subjectRawLen); - off += (size_t)sc.subjectRawLen; + int n = enc_seq(issuer_name, (size_t)issuer_name_len, + buf + off, cap - off); + if (n > 0) { + off += (size_t)n; + n = enc_seq(sc.subjectRaw, (size_t)sc.subjectRawLen, + buf + off, cap - off); + } + + if (n < 0) { + WOLFCERT_XFREE(buf, heap); + wc_FreeDecodedCert(&ic); + wc_FreeDecodedCert(&sc); + return WOLFCERT_ERR_MEMORY; + } + + off += (size_t)n; wc_FreeDecodedCert(&ic); wc_FreeDecodedCert(&sc); diff --git a/tests/unit/test_scep_msg.c b/tests/unit/test_scep_msg.c index 9caa2f9..f6914dc 100644 --- a/tests/unit/test_scep_msg.c +++ b/tests/unit/test_scep_msg.c @@ -141,6 +141,97 @@ static int make_ca(uint8_t** cert_out, size_t* cert_out_len, return ret; } +/* Issue a certificate signed by the make_ca CA cert/key, so its issuer and + * subject names differ. `is_ca` sets the basic constraints CA flag; `with_bc` + * 0 omits the extension entirely. Caller frees *cert_out with free(). */ +static int make_signed_cert(const uint8_t* ca_der, size_t ca_der_len, + const uint8_t* ca_key_der, size_t ca_key_len, + const char* cn, int is_ca, int with_bc, + uint8_t** cert_out, size_t* cert_out_len) +{ + RsaKey ca_key; + RsaKey sub_key; + WC_RNG rng; + Cert* cert = NULL; + uint8_t* der = NULL; + word32 idx = 0; + int ret = 0; + int body_n = 0; + int sign_n = 0; + + if (wc_InitRng(&rng) != 0) + return -1; + if (wc_InitRsaKey(&ca_key, NULL) != 0) { + wc_FreeRng(&rng); + return -1; + } + if (wc_InitRsaKey(&sub_key, NULL) != 0) { + wc_FreeRsaKey(&ca_key); + wc_FreeRng(&rng); + return -1; + } + + if (wc_RsaPrivateKeyDecode(ca_key_der, &idx, &ca_key, + (word32)ca_key_len) != 0) + ret = -1; + + if (ret == 0 && wc_MakeRsaKey(&sub_key, 2048, WC_RSA_EXPONENT, &rng) != 0) + ret = -1; + + if (ret == 0) { + der = (uint8_t*)malloc(4096); + if (der == NULL) + ret = -1; + } + + if (ret == 0) { + cert = wc_CertNew(NULL); + if (cert == NULL) + ret = -1; + } + + if (ret == 0) { + wc_InitCert_ex(cert, NULL, INVALID_DEVID); + strncpy(cert->subject.commonName, cn, CTC_NAME_SIZE - 1); + cert->subject.commonName[CTC_NAME_SIZE - 1] = '\0'; + cert->isCA = is_ca; + cert->basicConstSet = with_bc; /* CA:FALSE when is_ca is 0 */ + cert->selfSigned = 0; + cert->sigType = CTC_SHA256wRSA; + cert->daysValid = 2; + + if (wc_SetIssuerBuffer(cert, ca_der, (int)ca_der_len) != 0) + ret = -1; + } + + if (ret == 0) { + body_n = wc_MakeCert(cert, der, 4096, &sub_key, NULL, &rng); + if (body_n <= 0) + ret = -1; + } + + if (ret == 0) { + sign_n = wc_SignCert(cert->bodySz, cert->sigType, der, 4096, &ca_key, + NULL, &rng); + if (sign_n <= 0) + ret = -1; + } + + if (ret == 0) { + *cert_out = der; + *cert_out_len = (size_t)sign_n; + der = NULL; /* ownership transferred */ + } + + if (cert != NULL) + wc_CertFree(cert); + free(der); + wc_FreeRsaKey(&sub_key); + wc_FreeRsaKey(&ca_key); + wc_FreeRng(&rng); + return ret; +} + #ifdef HAVE_ECC /* Generate a throwaway self-signed ECC (P-256) CA cert, DER. Ownership of the * returned buffer passes to the caller (free with free()). */ @@ -584,6 +675,188 @@ static int test_signer_key_usage(void) return 0; } +/* Content of the DER SEQUENCE at `p`, or NULL when it is not one or does not + * fit in `len`. `content_len` and `total` receive the content and TLV sizes. */ +static const uint8_t* seq_content(const uint8_t* p, size_t len, + size_t* content_len, size_t* total) +{ + size_t hdr; + size_t clen; + size_t nb; + size_t i; + + if (len < 2 || p[0] != 0x30) + return NULL; + + if ((p[1] & 0x80) == 0) { + hdr = 2; + clen = p[1]; + } + else { + nb = (size_t)(p[1] & 0x7F); + if (nb == 0 || nb > 4 || len < 2 + nb) + return NULL; + + clen = 0; + for (i = 0; i < nb; i++) + clen = (clen << 8) | p[2 + i]; + hdr = 2 + nb; + } + + if (clen > len - hdr) + return NULL; + + *content_len = clen; + *total = hdr + clen; + return p + hdr; +} + +/* Build the IssuerAndSubject for `ra_der` and require it to decode as + * SEQUENCE { issuer Name, subject Name }, with the issuer Name carrying the + * subject DN of `name_der` and the subject Name that of the CSR. */ +static int check_issuer_and_subject(const uint8_t* ra_der, size_t ra_len, + const uint8_t* name_der, size_t name_len, + const uint8_t* csr_der, size_t csr_len) +{ + WolfCertBuffer ias = { 0 }; + DecodedCert nc; + DecodedCert sc; + const uint8_t* outer = NULL; + const uint8_t* dn = NULL; + size_t outer_len = 0; + size_t dn_len = 0; + size_t total = 0; + size_t total2 = 0; + int rc = 0; + + if (wolfcert_scep_issuer_and_subject(ra_der, ra_len, csr_der, csr_len, + &ias, NULL) != WOLFCERT_OK) + return 1; + + wc_InitDecodedCert(&nc, name_der, (word32)name_len, NULL); + wc_InitDecodedCert(&sc, csr_der, (word32)csr_len, NULL); + + if (wc_ParseCert(&nc, CERT_TYPE, NO_VERIFY, NULL) != 0 || + wc_ParseCert(&sc, CERTREQ_TYPE, NO_VERIFY, NULL) != 0) + rc = 1; + + if (rc == 0 && (nc.subjectRaw == NULL || nc.subjectRawLen <= 0 || + sc.subjectRaw == NULL || sc.subjectRawLen <= 0)) + rc = 1; + + /* SEQUENCE { issuer Name, subject Name }, nothing before or after. */ + if (rc == 0) { + outer = seq_content(ias.data, ias.len, &outer_len, &total); + if (outer == NULL || total != ias.len) + rc = 1; + } + + if (rc == 0) { + dn = seq_content(outer, outer_len, &dn_len, &total); + if (dn == NULL || dn_len != (size_t)nc.subjectRawLen || + memcmp(dn, nc.subjectRaw, dn_len) != 0) + rc = 1; + } + + if (rc == 0) { + dn = seq_content(outer + total, outer_len - total, &dn_len, &total2); + if (dn == NULL || dn_len != (size_t)sc.subjectRawLen || + memcmp(dn, sc.subjectRaw, dn_len) != 0) + rc = 1; + else if (total + total2 != outer_len) + rc = 1; + } + + wc_FreeDecodedCert(&nc); + wc_FreeDecodedCert(&sc); + wolfcert_buffer_free(&ias); + return rc; +} + +/* RFC 8894 section 3.3.2: the IssuerAndSubject issuer Name identifies the CA + * that issues the requested cert - an RA contributes its issuer's name, a CA + * (including a sub-CA under an offline root) its own subject. */ +static int test_issuer_and_subject_issuer_name(void) +{ + RsaKey key; + WC_RNG rng; + uint8_t* ca_der = NULL; + size_t ca_len = 0; + uint8_t* ca_key_der = NULL; + size_t ca_key_len = 0; + uint8_t* ra_der = NULL; + size_t ra_len = 0; + uint8_t* sub_der = NULL; + size_t sub_len = 0; + uint8_t* nobc_der = NULL; + size_t nobc_len = 0; + uint8_t* csr_der = NULL; + size_t csr_len = 0; + WolfCertBuffer bad = { 0 }; + int rc = 0; + + REQUIRE(wc_InitRng(&rng) == 0); + REQUIRE(wc_InitRsaKey(&key, NULL) == 0); + REQUIRE(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng) == 0); + + REQUIRE(make_ca(&ca_der, &ca_len, &ca_key_der, &ca_key_len) == 0); + REQUIRE(make_signed_cert(ca_der, ca_len, ca_key_der, ca_key_len, + "wolfCert Test RA Encryption", 0, 1, + &ra_der, &ra_len) == 0); + REQUIRE(make_signed_cert(ca_der, ca_len, ca_key_der, ca_key_len, + "wolfCert Test Sub CA", 1, 1, + &sub_der, &sub_len) == 0); + REQUIRE(make_signed_cert(ca_der, ca_len, ca_key_der, ca_key_len, + "wolfCert Test No BC", 0, 0, + &nobc_der, &nobc_len) == 0); + REQUIRE(make_csr(&key, &rng, "device-4711.example.org", "Widgets Inc", + &csr_der, &csr_len) == 0); + + /* Split RA/CA: the RA is an end entity, so its issuer names the CA. */ + rc = check_issuer_and_subject(ra_der, ra_len, ca_der, ca_len, + csr_der, csr_len); + + /* Single self-signed CA as the envelope target. */ + if (rc == 0) + rc = check_issuer_and_subject(ca_der, ca_len, ca_der, ca_len, + csr_der, csr_len); + + /* Sub-CA with no RA: it issues, so it names itself. */ + if (rc == 0) + rc = check_issuer_and_subject(sub_der, sub_len, sub_der, sub_len, + csr_der, csr_len); + + /* No basic constraints at all: taken for an end entity, so its issuer + * supplies the Name whatever the certificate was meant to be. */ + if (rc == 0) + rc = check_issuer_and_subject(nobc_der, nobc_len, ca_der, ca_len, + csr_der, csr_len); + + /* Each NULL argument is refused before any parsing. */ + if (rc == 0 && wolfcert_scep_issuer_and_subject(NULL, ra_len, csr_der, + csr_len, &bad, NULL) != WOLFCERT_ERR_BAD_ARG) + rc = 1; + if (rc == 0 && wolfcert_scep_issuer_and_subject(ra_der, ra_len, NULL, + csr_len, &bad, NULL) != WOLFCERT_ERR_BAD_ARG) + rc = 1; + if (rc == 0 && wolfcert_scep_issuer_and_subject(ra_der, ra_len, csr_der, + csr_len, NULL, NULL) != WOLFCERT_ERR_BAD_ARG) + rc = 1; + + wolfcert_buffer_free(&bad); + free(csr_der); + free(nobc_der); + free(sub_der); + free(ra_der); + free(ca_key_der); + free(ca_der); + wc_FreeRsaKey(&key); + wc_FreeRng(&rng); + + REQUIRE(rc == 0); + return 0; +} + /* RFC 8894: a CertRep must be signed by the CA/RA certificate the client * fetched via GetCACert. A response signed by any other certificate, as a * MITM or rogue server would forge, must be rejected before the client @@ -1321,6 +1594,8 @@ int main(void) return 1; if (test_signer_key_usage()) return 1; + if (test_issuer_and_subject_issuer_name()) + return 1; if (test_cert_rep_signer_trust()) return 1; if (test_cert_rep_txid_and_type())