diff --git a/src/internal.h b/src/internal.h index c6c636e..49306e7 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). The + * issuer Name is the envelope-target cert's own subject when it is a CA, + * otherwise its issuer; the subject Name comes 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); 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 7116734..8047f9e 100644 --- a/src/scep/scep_msg.c +++ b/src/scep/scep_msg.c @@ -787,23 +787,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) { @@ -821,14 +821,25 @@ 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.extBasicConstSet && 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; + size_t inner = (size_t)issuer_name_len + (size_t)sc.subjectRawLen; size_t cap = inner + 8; uint8_t* buf = (uint8_t*)WOLFCERT_XMALLOC(cap, heap); if (buf == NULL) { @@ -847,8 +858,8 @@ 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, issuer_name, (size_t)issuer_name_len); + off += (size_t)issuer_name_len; memcpy(buf + off, sc.subjectRaw, (size_t)sc.subjectRawLen); off += (size_t)sc.subjectRawLen; diff --git a/tests/unit/test_scep_msg.c b/tests/unit/test_scep_msg.c index 5508346..5f75971 100644 --- a/tests/unit/test_scep_msg.c +++ b/tests/unit/test_scep_msg.c @@ -141,6 +141,96 @@ 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. Ownership + * of the returned buffer passes to the caller (free 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, + 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->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()). */ @@ -541,6 +631,120 @@ static int test_signer_subject_matches_csr(void) return 0; } +/* Build the IssuerAndSubject for `ra_der` and require its issuer Name to be + * the subject DN of `name_der`, followed by the CSR subject DN. */ +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; + size_t hdr = 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; + + /* Outer SEQUENCE: tag octet plus short- or long-form length octets. */ + if (rc == 0) { + if (ias.len < 2 || ias.data[0] != 0x30) + rc = 1; + else if ((ias.data[1] & 0x80) == 0) + hdr = 2; + else + hdr = 2 + (size_t)(ias.data[1] & 0x7F); + } + + if (rc == 0 && ias.len != hdr + (size_t)nc.subjectRawLen + + (size_t)sc.subjectRawLen) + rc = 1; + + if (rc == 0 && memcmp(ias.data + hdr, nc.subjectRaw, + (size_t)nc.subjectRawLen) != 0) + rc = 1; + + if (rc == 0 && memcmp(ias.data + hdr + (size_t)nc.subjectRawLen, + sc.subjectRaw, (size_t)sc.subjectRawLen) != 0) + 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* csr_der = NULL; + size_t csr_len = 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, + &ra_der, &ra_len) == 0); + REQUIRE(make_signed_cert(ca_der, ca_len, ca_key_der, ca_key_len, + "wolfCert Test Sub CA", 1, + &sub_der, &sub_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); + + free(csr_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 @@ -1276,6 +1480,8 @@ int main(void) return 1; if (test_signer_subject_matches_csr()) 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())