diff --git a/docs/EMBEDDED.md b/docs/EMBEDDED.md index abb466f..e9cb263 100644 --- a/docs/EMBEDDED.md +++ b/docs/EMBEDDED.md @@ -194,7 +194,7 @@ Two related buffers are intentionally **not** exposed as knobs: PKCS#7 signed-attribute encoding; shrinking it risks breaking SCEP signing rather than saving meaningful RAM. -## 3. SCEP pkiMessage encode buffer +## 3. SCEP and PKCS#7 message limits Encoding a SCEP SignedData pkiMessage allocates a one-shot heap buffer sized `envelope + signer-cert + WOLFCERT_SCEP_PKI_SLACK`. The slack bounds @@ -235,6 +235,15 @@ constrained targets. Example: #define WOLFCERT_SCEP_MAX_MSG_SZ (16 * 1024) ``` +### Certificate bundles (EST and SCEP) + +| Macro | Default | Bounds | +|-------|---------|--------| +| `WOLFCERT_PKCS7_MAX_CERTS` | `16` | certificates read from one certs-only PKCS#7 bundle; one more is refused with `WOLFCERT_ERR_UNSUPPORTED` | + +A bundle is read up to `WOLFCERT_PKCS7_MAX_CERTS` certificates whatever +wolfSSL's `MAX_PKCS7_CERTS` is. + ## 4. Heap / static-memory pools Every wolfCert allocation carries a `heap` hint that threads through to diff --git a/src/internal.h b/src/internal.h index e11fcbb..f39bb6a 100644 --- a/src/internal.h +++ b/src/internal.h @@ -106,6 +106,12 @@ #define WOLFCERT_SCEP_MAX_MSG_SZ (64 * 1024) #endif +/* Max number of certificates accepted from one certs-only PKCS#7 bundle. + * See docs/EMBEDDED.md. */ +#ifndef WOLFCERT_PKCS7_MAX_CERTS +#define WOLFCERT_PKCS7_MAX_CERTS 16 +#endif + /* Upper bound on the length of a base64/percent-encoded PKIOperation GET URL. * RFC 8894 section 4.1 lets a client fall back to HTTP GET when the CA does not * advertise POSTPKIOperation, carrying the pkiMessage in the `message` query diff --git a/src/pkcs7_util.c b/src/pkcs7_util.c index acc83ca..1c7ba74 100644 --- a/src/pkcs7_util.c +++ b/src/pkcs7_util.c @@ -23,8 +23,8 @@ * concatenated as PEM or DER. * - build a certs-only SignedData around a set of DER certificates. * - * Both directions go through wolfSSL's wc_PKCS7 API: extraction via - * wc_PKCS7_VerifySignedData(), encoding via a DEGENERATE_SID SignedData + * Both directions go through wolfSSL's wc_PKCS7 API: extraction is validated + * by wc_PKCS7_VerifySignedData(), encoding uses a DEGENERATE_SID SignedData * (wc_PKCS7_EncodeSignedData() with no signer). Heap hints thread through for * wolfSSL static-memory builds. */ @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -44,9 +45,8 @@ /* ---- certs-only (degenerate) SignedData extraction --------------------- * * - * wc_PKCS7_VerifySignedData validates the structure and populates - * pkcs7->cert[] / certSz[]. The bundles we parse are degenerate certs-only - * SignedData, so there is no signature to verify. */ + * wc_PKCS7_VerifySignedData validates the structure; the certificates are then + * read from the input itself. */ /* Append `n` bytes to a growable WolfCertBuffer. */ static int acc_append(WolfCertBuffer* acc, size_t* cap, const uint8_t* data, @@ -95,41 +95,198 @@ static int append_cert(WolfCertBuffer* acc, size_t* cap, const uint8_t* der, return rc; } -static int pkcs7_certs_extract(const uint8_t* p7_der, size_t p7_der_len, - WolfCertBuffer* out, void* heap, int as_pem) +/* Read the TLV header at *idx. Returns 0 with *len set, 1 for an indefinite + * length, or -1 when it is malformed. */ +static int tlv_header(const uint8_t* der, word32* idx, word32 max, byte* tag, + int* len) { - if (p7_der == NULL || p7_der_len == 0 || out == NULL) - return WOLFCERT_ERR_BAD_ARG; + if (GetASNTag(der, idx, tag, max) < 0 || *idx >= max) + return -1; - PKCS7* p7 = wc_PKCS7_New(heap, WOLFCERT_DEVID_SOFTWARE); - if (p7 == NULL) - return WOLFCERT_ERR_MEMORY; + if (der[*idx] == ASN_INDEF_LENGTH) { + (*idx)++; + *len = 0; + return 1; + } - int rc = wc_PKCS7_VerifySignedData(p7, (byte*)p7_der, (word32)p7_der_len); - if (rc != 0) { - wc_PKCS7_Free(p7); - return WOLFCERT_ERR_WC(rc, "pkcs7", "VerifySignedData"); + if (GetLength(der, idx, len, max) < 0) + return -1; + + return 0; +} + +/* Move *idx past the end-of-contents marker that closes the indefinite-length + * element whose header was just read. */ +static int skip_indef(const uint8_t* der, word32* idx, word32 max) +{ + int depth = 1; + int hr; + byte tag; + int len; + + while (depth > 0) { + hr = tlv_header(der, idx, max, &tag, &len); + if (hr < 0) + return -1; + + if (hr == 1) + depth++; + else if (tag == 0 && len == 0) + depth--; + else + *idx += (word32)len; + } + + return 0; +} + +/* Find the certificate list inside the bundle. + * Returns -1 when no certificate list is found. */ +static int pkcs7_cert_set(const uint8_t* der, word32 der_len, word32* start, + word32* end) +{ + /* Headers from the start of the bundle down to its certificate list. + * enter = 1 steps inside the element, 0 skips over it. */ + static const struct { byte tag; byte enter; } path[] = { + { ASN_CONSTRUCTED | ASN_SEQUENCE, 1 }, + { ASN_OBJECT_ID, 0 }, + { ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC, 1 }, + { ASN_CONSTRUCTED | ASN_SEQUENCE, 1 }, + { ASN_INTEGER, 0 }, + { ASN_CONSTRUCTED | ASN_SET, 0 }, + { ASN_CONSTRUCTED | ASN_SEQUENCE, 0 }, + { ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC, 1 }, + }; + const size_t n = sizeof(path) / sizeof(path[0]); + word32 idx = 0; + size_t i; + byte tag; + int len = 0; + int hr = 0; + + for (i = 0; i < n; i++) { + hr = tlv_header(der, &idx, der_len, &tag, &len); + if (hr < 0 || tag != path[i].tag) + return -1; + + if (!path[i].enter && hr == 0) + idx += (word32)len; + else if (!path[i].enter && skip_indef(der, &idx, der_len) != 0) + return -1; + } + + *start = idx; + if (hr == 0) { + *end = idx + (word32)len; } + else { + /* An indefinite-length list ends just before its end-of-contents. */ + if (skip_indef(der, &idx, der_len) != 0) + return -1; + *end = idx - ASN_INDEF_END_SZ; + } + + return 0; +} + +/* Is [idx, end) a certificate body: tbsCertificate, signatureAlgorithm and + * signatureValue, with nothing after? Returns 0 when it is. */ +static int cert_shape(const uint8_t* der, word32 idx, word32 end) +{ + static const byte parts[] = { + ASN_CONSTRUCTED | ASN_SEQUENCE, + ASN_CONSTRUCTED | ASN_SEQUENCE, + ASN_BIT_STRING + }; + size_t i; + byte tag; + int len; + + for (i = 0; i < sizeof(parts); i++) { + if (tlv_header(der, &idx, end, &tag, &len) != 0 || tag != parts[i]) + return -1; + + idx += (word32)len; + } + + return (idx == end) ? 0 : -1; +} + +/* Append every certificate in the certificates field at [idx, end) of `der`, + * skipping the tagged CertificateChoices alternatives [0] to [3]. */ +static int append_cert_set(WolfCertBuffer* acc, size_t* cap, const uint8_t* der, + word32 idx, word32 end, int as_pem, void* heap) +{ + size_t count = 0; + int rc = WOLFCERT_OK; + word32 at; + byte tag = 0; + int len; + + while (rc == WOLFCERT_OK && idx < end) { + at = idx; + if (tlv_header(der, &idx, end, &tag, &len) != 0) + rc = WOLFCERT_ERR(WOLFCERT_ERR_PARSE, "pkcs7", + "malformed certificates field"); + else if (tag == (ASN_CONSTRUCTED | ASN_SEQUENCE) && + cert_shape(der, idx, idx + (word32)len) != 0) + rc = WOLFCERT_ERR(WOLFCERT_ERR_PARSE, "pkcs7", + "certificates field holds a non-certificate"); + else if (tag != (ASN_CONSTRUCTED | ASN_SEQUENCE) && + (tag < (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC) || + tag > (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC | 3))) + rc = WOLFCERT_ERR(WOLFCERT_ERR_PARSE, "pkcs7", + "certificates field holds an unknown entry"); + else + idx += (word32)len; + + if (rc == WOLFCERT_OK && tag == (ASN_CONSTRUCTED | ASN_SEQUENCE) && + ++count > WOLFCERT_PKCS7_MAX_CERTS) + rc = WOLFCERT_ERR(WOLFCERT_ERR_UNSUPPORTED, "pkcs7", + "bundle holds more than WOLFCERT_PKCS7_MAX_CERTS " + "certificates"); + + if (rc == WOLFCERT_OK && tag == (ASN_CONSTRUCTED | ASN_SEQUENCE)) + rc = append_cert(acc, cap, der + at, idx - at, as_pem, heap); + } + + return rc; +} + +static int pkcs7_certs_extract(const uint8_t* p7_der, size_t p7_der_len, + WolfCertBuffer* out, void* heap, int as_pem) +{ WolfCertBuffer acc = { .heap = heap }; size_t cap = 0; + PKCS7* p7; + word32 start; + word32 end; + int rc; - for (int i = 0; i < MAX_PKCS7_CERTS; ++i) { - if (p7->cert[i] == NULL || p7->certSz[i] == 0) - continue; + if (p7_der == NULL || p7_der_len == 0 || out == NULL) + return WOLFCERT_ERR_BAD_ARG; - rc = append_cert(&acc, &cap, p7->cert[i], p7->certSz[i], as_pem, heap); - if (rc != WOLFCERT_OK) { - WOLFCERT_XFREE(acc.data, heap); - wc_PKCS7_Free(p7); - return rc; - } - } + p7 = wc_PKCS7_New(heap, WOLFCERT_DEVID_SOFTWARE); + if (p7 == NULL) + return WOLFCERT_ERR_MEMORY; + + rc = wc_PKCS7_VerifySignedData(p7, (byte*)p7_der, (word32)p7_der_len); + if (rc != 0) + rc = WOLFCERT_ERR_WC(rc, "pkcs7", "VerifySignedData"); + else if (pkcs7_cert_set(p7_der, (word32)p7_der_len, &start, &end) != 0) + rc = WOLFCERT_ERR(WOLFCERT_ERR_PARSE, "pkcs7", + "cannot locate the certificate list in bundle"); + else + rc = append_cert_set(&acc, &cap, p7_der, start, end, as_pem, heap); wc_PKCS7_Free(p7); - if (acc.len == 0) { + if (rc == WOLFCERT_OK && acc.len == 0) + rc = WOLFCERT_ERR_NOT_FOUND; + + if (rc != WOLFCERT_OK) { WOLFCERT_XFREE(acc.data, heap); - return WOLFCERT_ERR_NOT_FOUND; + return rc; } *out = acc; diff --git a/tests/unit/test_parse_negative.c b/tests/unit/test_parse_negative.c index 8f771c9..eac9438 100644 --- a/tests/unit/test_parse_negative.c +++ b/tests/unit/test_parse_negative.c @@ -34,6 +34,11 @@ #include #include +#if defined(WOLFCERT_HAVE_EST) || defined(WOLFCERT_HAVE_SCEP) +#include "../integration/tls_test_util.h" +#include +#endif + #if defined(WOLFCERT_HAVE_SCEP) && defined(WOLFCERT_HAVE_RSA) #include #include @@ -105,6 +110,296 @@ static int test_pkcs7(void) == WOLFCERT_ERR_PARSE); return 0; } + +/* Mint a self-signed certificate named `cn` as DER into `out`. */ +static int mint_cert_der(const char* cn, uint8_t* out, size_t cap, + size_t* out_len) +{ + uint8_t* cert_pem = NULL; + uint8_t* key_pem = NULL; + size_t cert_len = 0; + size_t key_len = 0; + DerBuffer* der = NULL; + int rc; + + rc = mint_self_id(cn, 1, &cert_pem, &cert_len, &key_pem, &key_len); + if (rc == 0) + rc = wc_PemToDer(cert_pem, (long)cert_len, CERT_TYPE, &der, NULL, NULL, + NULL); + if (rc == 0 && der->length > cap) + rc = -1; + if (rc == 0) { + memcpy(out, der->buffer, der->length); + *out_len = der->length; + } + + wc_FreeDer(&der); + free(cert_pem); + free(key_pem); + return rc; +} + +/* Header size of the definite-length TLV at p. */ +static size_t tlv_hdr_len(const uint8_t* p) +{ + return (p[1] & 0x80) ? 2 + (size_t)(p[1] & 0x7F) : 2; +} + +/* BER forms bundle_to_ber() writes. */ +enum { BER_WRAPPERS, BER_STREAMED, BER_CERT_LIST }; + +/* Re-encode a certs-only bundle with indefinite lengths on its outer wrappers + * and, per `form`, on encapContentInfo (carrying content, as a streaming + * encoder writes it) or on the certificate list. The caller frees *out. */ +static int bundle_to_ber(const uint8_t* der, size_t der_len, int form, + uint8_t** out, size_t* out_len) +{ + static const uint8_t wrap[] = { 0xA0, 0x80, 0x30, 0x80 }; + static const uint8_t list_open[] = { 0xA0, 0x80 }; + static const uint8_t signer_infos[] = { 0x31, 0x00 }; + static const uint8_t eoc[6] = { 0 }; + /* encapContentInfo as the encoder writes it, after version and an empty + * digestAlgorithms, and its streamed form holding the content "A". */ + static const uint8_t encap[] = { + 0x30, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x07, 0x01 + }; + static const uint8_t encap_streamed[] = { + 0x30, 0x80, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, + 0x07, 0x01, 0xA0, 0x80, 0x24, 0x80, 0x04, 0x01, 0x41, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + const size_t encap_at = 5; + const size_t list_at = encap_at + sizeof(encap); + const uint8_t* oid = der + tlv_hdr_len(der); + size_t oid_len = 2 + (size_t)oid[1]; + const uint8_t* body = oid + oid_len + tlv_hdr_len(oid + oid_len); + const uint8_t* piece[5]; + size_t piece_len[5]; + size_t pieces = 0; + size_t body_len; + size_t list_hdr; + size_t i; + uint8_t* p; + + body += tlv_hdr_len(body); + body_len = (size_t)(der + der_len - body); + if (form != BER_WRAPPERS && + (body_len < list_at + 4 || + memcmp(body + encap_at, encap, sizeof(encap)) != 0 || + body[list_at] != 0xA0 || + memcmp(body + body_len - sizeof(signer_infos), signer_infos, + sizeof(signer_infos)) != 0)) + return -1; + + if (form == BER_WRAPPERS) { + piece[0] = body; + piece_len[0] = body_len; + pieces = 1; + } + else if (form == BER_STREAMED) { + piece[0] = body; + piece_len[0] = encap_at; + piece[1] = encap_streamed; + piece_len[1] = sizeof(encap_streamed); + piece[2] = body + list_at; + piece_len[2] = body_len - list_at; + pieces = 3; + } + else { + list_hdr = tlv_hdr_len(body + list_at); + piece[0] = body; + piece_len[0] = list_at; + piece[1] = list_open; + piece_len[1] = sizeof(list_open); + piece[2] = body + list_at + list_hdr; + piece_len[2] = body_len - list_at - list_hdr - sizeof(signer_infos); + piece[3] = eoc; + piece_len[3] = 2; + piece[4] = signer_infos; + piece_len[4] = sizeof(signer_infos); + pieces = 5; + } + + *out_len = 2 + oid_len + sizeof(wrap) + sizeof(eoc); + for (i = 0; i < pieces; i++) + *out_len += piece_len[i]; + *out = (uint8_t*)malloc(*out_len); + if (*out == NULL) + return -1; + + p = *out; + *p++ = 0x30; + *p++ = 0x80; + memcpy(p, oid, oid_len); + p += oid_len; + memcpy(p, wrap, sizeof(wrap)); + p += sizeof(wrap); + for (i = 0; i < pieces; i++) { + memcpy(p, piece[i], piece_len[i]); + p += piece_len[i]; + } + memcpy(p, eoc, sizeof(eoc)); + return 0; +} + +/* Does extracting `bundle` as DER give certs[0..count) in order? */ +static int extracts_in_order(const uint8_t* bundle, size_t bundle_len, + const uint8_t* const* certs, const size_t* lens, + size_t count) +{ + WolfCertBuffer out = { 0 }; + size_t off = 0; + size_t i; + int ok; + + ok = wolfcert_pkcs7_certs_to_der(bundle, bundle_len, &out, NULL) + == WOLFCERT_OK; + for (i = 0; ok && i < count; i++) { + ok = off + lens[i] <= out.len && + memcmp(out.data + off, certs[i], lens[i]) == 0; + off += lens[i]; + } + ok = ok && off == out.len; + + wolfcert_buffer_free(&out); + return ok; +} + +/* Is `bundle` refused with `want`, with nothing handed back? */ +static int refused_with(const uint8_t* bundle, size_t bundle_len, int want) +{ + WolfCertBuffer out = { 0 }; + int rc = wolfcert_pkcs7_certs_to_der(bundle, bundle_len, &out, NULL); + int ok = (rc == want && out.data == NULL); + + wolfcert_buffer_free(&out); + return ok; +} + +/* Does this wolfSSL build accept the bundle at all? */ +static int wolfssl_accepts(const uint8_t* bundle, size_t bundle_len) +{ + PKCS7* p7 = wc_PKCS7_New(NULL, INVALID_DEVID); + int ok; + + if (p7 == NULL) + return 0; + + ok = wc_PKCS7_VerifySignedData(p7, (byte*)bundle, (word32)bundle_len) == 0; + wc_PKCS7_Free(p7); + return ok; +} + +/* Re-encode `p7` as BER and check it as extracts_in_order() does. A BER form + * this wolfSSL build does not accept counts as a pass. */ +static int ber_extracts_in_order(const WolfCertBuffer* p7, int form, + const uint8_t* const* certs, + const size_t* lens, size_t count) +{ + uint8_t* ber = NULL; + size_t ber_len = 0; + int ok; + + ok = bundle_to_ber(p7->data, p7->len, form, &ber, &ber_len) == 0; + if (ok && wolfssl_accepts(ber, ber_len)) + ok = extracts_in_order(ber, ber_len, certs, lens, count); + + free(ber); + return ok; +} + +/* DER and BER bundles come back whole and in order up to the limit; one more, + * a non-certificate or an unknown entry is refused. Each case frees before + * asserting so a failed REQUIRE cannot leak. */ +static int test_pkcs7_bundle(void) +{ + static const uint8_t not_cert[] = { 0x30, 0x03, 0x02, 0x01, 0x00 }; + static const uint8_t empty_seq[] = { 0x30, 0x00 }; + const uint8_t* certs[WOLFCERT_PKCS7_MAX_CERTS + 1]; + size_t lens[WOLFCERT_PKCS7_MAX_CERTS + 1]; + uint8_t a[2048]; + uint8_t b[2048]; + size_t a_len = 0; + size_t b_len = 0; + WolfCertBuffer p7 = { 0 }; + uint8_t* last = NULL; + int built; + int der_ok; + int ber_ok; + int streamed_ok; + int accepted; + int refused; + size_t i; + + REQUIRE(mint_cert_der("bundle cert A", a, sizeof(a), &a_len) == 0); + REQUIRE(mint_cert_der("bundle cert B", b, sizeof(b), &b_len) == 0); + for (i = 0; i < WOLFCERT_PKCS7_MAX_CERTS + 1; i++) { + certs[i] = (i % 2 == 0) ? a : b; + lens[i] = (i % 2 == 0) ? a_len : b_len; + } + + built = wolfcert_pkcs7_build_certs_only(certs, lens, + WOLFCERT_PKCS7_MAX_CERTS, &p7, NULL) == WOLFCERT_OK; + der_ok = built && extracts_in_order(p7.data, p7.len, certs, lens, + WOLFCERT_PKCS7_MAX_CERTS); + ber_ok = built && ber_extracts_in_order(&p7, BER_WRAPPERS, certs, lens, + WOLFCERT_PKCS7_MAX_CERTS); + streamed_ok = built && ber_extracts_in_order(&p7, BER_STREAMED, certs, + lens, + WOLFCERT_PKCS7_MAX_CERTS); + wolfcert_buffer_free(&p7); + REQUIRE(built); + REQUIRE(der_ok); + REQUIRE(ber_ok); + REQUIRE(streamed_ok); + + built = wolfcert_pkcs7_build_certs_only(certs, lens, 1, &p7, NULL) + == WOLFCERT_OK; + ber_ok = built && ber_extracts_in_order(&p7, BER_CERT_LIST, certs, lens, 1); + wolfcert_buffer_free(&p7); + REQUIRE(built); + REQUIRE(ber_ok); + + built = wolfcert_pkcs7_build_certs_only(certs, lens, + WOLFCERT_PKCS7_MAX_CERTS + 1, &p7, NULL) == WOLFCERT_OK; + der_ok = built && refused_with(p7.data, p7.len, WOLFCERT_ERR_UNSUPPORTED); + wolfcert_buffer_free(&p7); + REQUIRE(built); + REQUIRE(der_ok); + + certs[1] = not_cert; + lens[1] = sizeof(not_cert); + built = wolfcert_pkcs7_build_certs_only(certs, lens, 2, &p7, NULL) + == WOLFCERT_OK; + accepted = built && wolfssl_accepts(p7.data, p7.len); + refused = accepted && refused_with(p7.data, p7.len, WOLFCERT_ERR_PARSE); + wolfcert_buffer_free(&p7); + REQUIRE(built); + REQUIRE(accepted); + REQUIRE(refused); + + /* Turn an empty SEQUENCE after A into an empty OCTET STRING, which no + * CertificateChoices alternative allows. */ + certs[1] = empty_seq; + lens[1] = sizeof(empty_seq); + built = wolfcert_pkcs7_build_certs_only(certs, lens, 2, &p7, NULL) + == WOLFCERT_OK; + if (built) { + last = p7.data + p7.len - 2 - sizeof(empty_seq); + built = memcmp(last, empty_seq, sizeof(empty_seq)) == 0; + } + if (built) + last[0] = 0x04; + accepted = built && wolfssl_accepts(p7.data, p7.len); + refused = accepted && refused_with(p7.data, p7.len, WOLFCERT_ERR_PARSE); + wolfcert_buffer_free(&p7); + REQUIRE(built); + REQUIRE(accepted); + REQUIRE(refused); + + return 0; +} #endif #if defined(WOLFCERT_HAVE_SCEP) && defined(WOLFCERT_HAVE_RSA) @@ -264,6 +559,8 @@ int main(void) #if defined(WOLFCERT_HAVE_EST) || defined(WOLFCERT_HAVE_SCEP) if (test_pkcs7()) return 1; + if (test_pkcs7_bundle()) + return 1; #endif if (test_csr_pem()) return 1; diff --git a/wolfcert/client.h b/wolfcert/client.h index 9cf3b95..7359e64 100644 --- a/wolfcert/client.h +++ b/wolfcert/client.h @@ -45,7 +45,9 @@ WOLFCERT_API void wolfcert_client_free(WolfCertClient* client); * single-CA-cert case this is exactly that cert's DER, ready to load as * WOLFSSL_FILETYPE_ASN1. With more than one cert it is a concatenation, so * a single ASN.1 load consumes only the first; walk it (each cert is a - * complete DER SEQUENCE) or use PEM for multi-cert chains. */ + * complete DER SEQUENCE) or use PEM for multi-cert chains. + * Returns WOLFCERT_ERR_UNSUPPORTED when the chain holds more than + * WOLFCERT_PKCS7_MAX_CERTS certificates (see docs/EMBEDDED.md). */ WOLFCERT_API int wolfcert_client_get_ca(WolfCertClient* client, const WolfCertServerCfg* srv, WolfCertEncoding encoding, diff --git a/wolfcert/est.h b/wolfcert/est.h index c130fa2..f9b1010 100644 --- a/wolfcert/est.h +++ b/wolfcert/est.h @@ -27,7 +27,9 @@ extern "C" { #endif -/* GET /.well-known/est/cacerts - returns the CA chain as PEM. */ +/* GET /.well-known/est/cacerts - returns the CA chain as PEM. Returns + * WOLFCERT_ERR_UNSUPPORTED when the chain holds more than + * WOLFCERT_PKCS7_MAX_CERTS certificates (see docs/EMBEDDED.md). */ WOLFCERT_API int wolfcert_est_get_cacerts(const WolfCertServerCfg* srv, WolfCertBuffer* out_ca_pem); diff --git a/wolfcert/scep.h b/wolfcert/scep.h index 231fbf5..3c3a756 100644 --- a/wolfcert/scep.h +++ b/wolfcert/scep.h @@ -45,6 +45,9 @@ typedef struct { WOLFCERT_API int wolfcert_scep_get_ca_caps(const WolfCertServerCfg* srv, WolfCertScepCaps* out_caps); +/* GetCACert, returning the CA/RA certificate(s) as PEM. Returns + * WOLFCERT_ERR_UNSUPPORTED when a bundle holds more than + * WOLFCERT_PKCS7_MAX_CERTS certificates (see docs/EMBEDDED.md). */ WOLFCERT_API int wolfcert_scep_get_ca_cert(const WolfCertServerCfg* srv, WolfCertBuffer* out_ca_pem); @@ -256,7 +259,8 @@ WOLFCERT_API int wolfcert_scep_get_cert(const WolfCertServerCfg* srv, * of the current CA's expiry, so the device can install the new trust * anchor before the old one stops being honored. Returns * WOLFCERT_ERR_NOT_FOUND when the server has no roll-over configured - * (HTTP 404). + * (HTTP 404), and WOLFCERT_ERR_UNSUPPORTED when the response holds more than + * WOLFCERT_PKCS7_MAX_CERTS certificates (see docs/EMBEDDED.md). * * The response is a CMS SignedData signed by the current CA. current_ca_der is * the current CA certificate(s) in DER (one or more concatenated DER certs,