Load every PEM block in a root CA buffer

- LoadRootCaPemBuffer() loads every block a PEM CA buffer holds as a
  root CA, skipping the ones that fail. It returns WS_SUCCESS when
  any loaded, WS_PARSE_E when all failed, and WS_BAD_FILE_E when the
  buffer holds no block.
- A block runs header to footer with the next header capping the
  footer search, so wc_PemToDer() gets the block rather than the rest
  of the buffer. A header that nothing closes is skipped and the walk
  resumes at its end; each form's header is re-sought only from
  behind the one just read.
- A block takes the plain or the trusted form, whichever header leads
  picking the type. FindInBuffer() searches a length-delimited
  buffer, so an embedded NUL does not end the search.
- wolfSSH_ProcessBuffer() routes a PEM BUFTYPE_CA there and, like
  DoPemCert(), gives WS_BAD_FILETYPE_E for the trusted form as a
  certificate; SniffCertForm() reads its header as X.509 PEM.
- internal.h defines WOLFSSH_HAVE_TRUSTED_CERT_PEM under
  WOLFSSH_CERTS with wolfSSL 5.8.0 or newer and declares
  IsTrustedCertPem(); ssh.h documents the cert buffer calls.
- tests/api.c adds catBuffers(), makeTrustedPem() and
  assertCaInstalled(), with tests for the bundle, trusted file and
  trusted ReadCert paths.
pull/1183/head
Yosuke Shimizu 2026-08-06 16:43:01 +09:00 committed by John Safranek
parent 5ae0cc4c93
commit f530d32548
5 changed files with 679 additions and 3 deletions

View File

@ -2708,6 +2708,216 @@ int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId)
#endif /* WOLFSSH_TPM */
#ifdef WOLFSSH_CERTS
/* Finds needle in the first inSz bytes of in. Unlike WSTRNSTR() an embedded
NUL does not end the search, the buffer being length delimited. */
static const byte* FindInBuffer(const byte* in, word32 inSz, const char* needle)
{
word32 needleSz;
word32 i;
needleSz = (word32)WSTRLEN(needle);
if (needleSz == 0 || inSz < needleSz) {
return NULL;
}
for (i = 0; i <= inSz - needleSz; i++) {
if (WMEMCMP(in + i, needle, needleSz) == 0) {
return in + i;
}
}
return NULL;
}
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
/* Reports whether the buffer holds only trusted-certificate PEM. A plain
certificate anywhere in it wins, the way wc_PemToDer() finds one first. */
int IsTrustedCertPem(const byte* in, word32 inSz)
{
const char* certHeader = NULL;
const char* trustedHeader = NULL;
if (wc_PemGetHeaderFooter(CA_TYPE, &certHeader, NULL) != 0
|| certHeader == NULL
|| wc_PemGetHeaderFooter(TRUSTED_CERT_TYPE, &trustedHeader,
NULL) != 0
|| trustedHeader == NULL) {
return 0;
}
return FindInBuffer(in, inSz, certHeader) == NULL
&& FindInBuffer(in, inSz, trustedHeader) != NULL;
}
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
/* Loads every PEM certificate block in the buffer as a root CA, skipping the
ones that fail, the way wolfSSL's own chain loader treats a CA file. */
static int LoadRootCaPemBuffer(WOLFSSH_CTX* ctx, const byte* in, word32 inSz)
{
DerBuffer* der = NULL;
const char* certHeader = NULL;
const char* certFooter = NULL;
const char* footer;
const byte* found;
const byte* foundEnd;
const byte* nextHeader;
const byte* nextCert;
word32 certHeaderSz;
word32 certFooterSz;
word32 headerSz;
word32 footerSz;
word32 bodyStart;
word32 searchSz;
word32 blockSz;
word32 used = 0;
word32 loaded = 0;
word32 failed = 0;
int wcType = CA_TYPE;
int ret;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
const char* trustedHeader = NULL;
const char* trustedFooter = NULL;
const byte* nextTrusted;
word32 trustedHeaderSz;
word32 trustedFooterSz;
#endif
if (ctx->certMan == NULL) {
WLOG(WS_LOG_DEBUG, "Error no cert manager set");
return WS_MEMORY_E;
}
if (wc_PemGetHeaderFooter(CA_TYPE, &certHeader, &certFooter) != 0
|| certHeader == NULL || certFooter == NULL) {
return WS_BAD_FILE_E;
}
certHeaderSz = (word32)WSTRLEN(certHeader);
certFooterSz = (word32)WSTRLEN(certFooter);
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (wc_PemGetHeaderFooter(TRUSTED_CERT_TYPE, &trustedHeader,
&trustedFooter) != 0
|| trustedHeader == NULL || trustedFooter == NULL) {
return WS_BAD_FILE_E;
}
trustedHeaderSz = (word32)WSTRLEN(trustedHeader);
trustedFooterSz = (word32)WSTRLEN(trustedFooter);
#endif
/* Each form's header is re-sought only from behind the last one found. */
nextCert = FindInBuffer(in, inSz, certHeader);
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
nextTrusted = FindInBuffer(in, inSz, trustedHeader);
#endif
while (used < inSz) {
found = nextCert;
footer = certFooter;
headerSz = certHeaderSz;
footerSz = certFooterSz;
wcType = CA_TYPE;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
/* wc_PemToDer() takes either form, but only finds the one its type
names first, so whichever header leads picks the type. */
if (found == NULL || (nextTrusted != NULL && nextTrusted < found)) {
found = nextTrusted;
footer = trustedFooter;
headerSz = trustedHeaderSz;
footerSz = trustedFooterSz;
wcType = TRUSTED_CERT_TYPE;
}
#endif
if (found == NULL) {
break;
}
used = (word32)(found - in);
bodyStart = used + headerSz;
/* Step this form's cursor past the header being read. */
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (wcType == TRUSTED_CERT_TYPE) {
nextTrusted = FindInBuffer(in + bodyStart, inSz - bodyStart,
trustedHeader);
}
else {
nextCert = FindInBuffer(in + bodyStart, inSz - bodyStart,
certHeader);
}
#else
nextCert = FindInBuffer(in + bodyStart, inSz - bodyStart, certHeader);
#endif
nextHeader = (nextCert != NULL && nextCert >= in + bodyStart) ?
nextCert : NULL;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (nextTrusted != NULL && nextTrusted >= in + bodyStart
&& (nextHeader == NULL || nextTrusted < nextHeader)) {
nextHeader = nextTrusted;
}
#endif
/* A block runs to its footer, and the next header caps the search
for one. Bounding the search keeps the walk linear. */
searchSz = (nextHeader != NULL) ?
(word32)(nextHeader - (in + bodyStart)) : inSz - bodyStart;
foundEnd = FindInBuffer(in + bodyStart, searchSz, footer);
if (foundEnd == NULL) {
WLOG(WS_LOG_ERROR, "Skipping CA %u, nothing closes its header",
loaded + failed);
failed++;
used = bodyStart;
continue;
}
blockSz = (word32)(foundEnd - found) + footerSz;
if (wc_PemToDer(found, (long)blockSz, wcType, &der, ctx->heap,
NULL, NULL) != 0) {
/* A bad body is reported after the buffer is allocated. */
wc_FreeDer(&der);
WLOG(WS_LOG_ERROR, "Skipping CA %u, PEM to DER failed",
loaded + failed);
failed++;
}
else {
ret = wolfSSH_CERTMAN_LoadRootCA_buffer(ctx->certMan,
der->buffer, der->length);
wc_FreeDer(&der);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_ERROR, "Skipping CA %u, error %d loading it",
loaded + failed, ret);
failed++;
}
else {
loaded++;
}
}
used += blockSz;
}
if (loaded > 0) {
ret = WS_SUCCESS;
}
else if (failed > 0) {
ret = WS_PARSE_E;
}
else {
WLOG(WS_LOG_ERROR, "No certificate in the CA buffer");
ret = WS_BAD_FILE_E;
}
return ret;
}
#endif /* WOLFSSH_CERTS */
int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
const byte* in, word32 inSz,
int format, int type)
@ -2784,6 +2994,20 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
}
}
else if (format == WOLFSSH_FORMAT_PEM) {
#ifdef WOLFSSH_CERTS
if (type == BUFTYPE_CA) {
/* A CA buffer may hold a bundle, so every block is loaded. */
return LoadRootCaPemBuffer(ctx, in, inSz);
}
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (type == BUFTYPE_CERT && IsTrustedCertPem(in, inSz)) {
/* The trust data behind the certificate means nothing to a peer. */
WLOG(WS_LOG_DEBUG, "Trusted certificate form is for root CAs");
return WS_BAD_FILETYPE_E;
}
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
#endif /* WOLFSSH_CERTS */
/* The der size will be smaller than the pem size. */
der = (byte*)WMALLOC(inSz, heap, dynamicType);
if (der == NULL)

View File

@ -2350,12 +2350,17 @@ int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format,
#ifdef WOLFSSH_CERTS
static const char* CertBeginPrefix = "-----BEGIN CERTIFICATE-----";
#endif
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
static const char* TrustedCertBeginPrefix =
"-----BEGIN TRUSTED CERTIFICATE-----";
#endif
/* Longest algorithm name is ecdsa-sha2-nistp521-cert-v01@openssh.com. */
#define WOLFSSH_MAX_CERT_ALGO_NAME_SZ 48
/* Identifies a certificate from its content, without decoding or allocating.
An x509v3-* line holds a wire chain, not a certificate, so it is declined. */
An x509v3-* line holds a wire chain, not a certificate, so it is declined.
The trusted form is PEM too; the decoders decide who takes it. */
static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
byte* certId)
{
@ -2375,6 +2380,12 @@ static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_PEM;
}
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
else if (WSTRNSTR((const char*)in, TrustedCertBeginPrefix, inSz) != NULL) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_PEM;
}
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
@ -2421,6 +2432,14 @@ static int DoPemCert(const byte* in, word32 inSz, byte** out, word32* outSz,
word32 derSz = 0;
int ret;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
if (IsTrustedCertPem(in, inSz)) {
/* The trust data behind the certificate means nothing to a peer. */
WLOG(WS_LOG_DEBUG, "Trusted certificate form is for root CAs");
return WS_BAD_FILETYPE_E;
}
#endif
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (der == NULL) {
return WS_MEMORY_E;

View File

@ -1328,6 +1328,86 @@ static void test_wolfSSH_CTX_SetWindowPacketSize(void)
}
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
!defined(WOLFSSH_NO_ECDSA)
/* Joins two buffers so a multi-block PEM can be built in memory. Returns 0 on
* success. */
static int catBuffers(const byte* a, word32 aSz, const byte* b, word32 bSz,
byte** out, word32* outSz)
{
byte* buf;
int ret = -1;
*out = NULL;
*outSz = 0;
buf = (byte*)malloc(aSz + bSz);
if (buf != NULL) {
memcpy(buf, a, aSz);
memcpy(buf + aSz, b, bSz);
*out = buf;
*outSz = aSz + bSz;
ret = 0;
}
return ret;
}
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
/* Relabels a certificate PEM as the trusted form. The body is the plain
* certificate, without the trust settings OpenSSL appends, so this covers
* how the header is read rather than what follows it. Returns 0 on success. */
static int makeTrustedPem(const byte* pem, word32 pemSz, byte** out,
word32* outSz)
{
static const char begin[] = "-----BEGIN CERTIFICATE-----";
static const char end[] = "-----END CERTIFICATE-----";
static const char tBegin[] = "-----BEGIN TRUSTED CERTIFICATE-----";
static const char tEnd[] = "-----END TRUSTED CERTIFICATE-----\n";
const char* b;
const char* e;
byte* buf;
word32 bodySz;
word32 sz;
*out = NULL;
*outSz = 0;
b = WSTRNSTR((const char*)pem, begin, pemSz);
e = WSTRNSTR((const char*)pem, end, pemSz);
if (b == NULL || e == NULL) {
return -1;
}
b += sizeof(begin) - 1;
if (e <= b) {
return -1;
}
bodySz = (word32)(e - b);
sz = (word32)(sizeof(tBegin) - 1) + bodySz + (word32)(sizeof(tEnd) - 1);
buf = (byte*)malloc(sz);
if (buf == NULL) {
return -1;
}
memcpy(buf, tBegin, sizeof(tBegin) - 1);
memcpy(buf + sizeof(tBegin) - 1, b, bodySz);
memcpy(buf + sizeof(tBegin) - 1 + bodySz, tEnd, sizeof(tEnd) - 1);
*out = buf;
*outSz = sz;
return 0;
}
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_ECDSA)
/* Build the length-prefixed single-cert chain buffer that
* wolfSSH_CERTMAN_VerifyCerts_buffer expects. Caller frees *chain. */
@ -1471,6 +1551,341 @@ static void test_wolfSSH_CertMan(void)
}
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
!defined(WOLFSSH_NO_ECDSA)
/* The CA signed fred's certificate, so a chain check tells an installed CA
* from a missing one. */
static void assertCaInstalled(WOLFSSH_CTX* ctx)
{
byte* leafDer = NULL;
byte* chain = NULL;
word32 leafDerSz = 0;
word32 chainSz = 0;
AssertIntEQ(0, load_file("./keys/fred-cert.der", &leafDer, &leafDerSz));
AssertIntEQ(0, certman_make_chain(leafDer, leafDerSz, &chain, &chainSz));
#ifdef WOLFSSH_NO_FPKI
AssertIntEQ(WS_SUCCESS,
wolfSSH_CERTMAN_VerifyCerts_buffer(ctx->certMan, chain, chainSz, 1));
#else
/* An FPKI build rejects this leaf's profile, but only after finding its
* signer, so the code still tells the CA apart from a missing one. */
AssertIntEQ(WS_CERT_PROFILE_E,
wolfSSH_CERTMAN_VerifyCerts_buffer(ctx->certMan, chain, chainSz, 1));
#endif
free(chain);
free(leafDer);
}
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
/* A CA buffer may hold a bundle, so every PEM block in it is loaded. A block
* that will not load is skipped rather than failing the bundle. */
static void test_wolfSSH_CTX_AddRootCert_bundle(void)
{
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
!defined(WOLFSSH_NO_ECDSA)
static const char junk[] = "Bag Attributes: not a certificate\n";
static const char badPem[] =
"-----BEGIN CERTIFICATE-----\n"
"$$$$ not base64 $$$$\n"
"-----END CERTIFICATE-----\n";
/* Valid base64, but no certificate, so the manager is what refuses it. */
static const char notACertPem[] =
"-----BEGIN CERTIFICATE-----\n"
"bm90IGEgY2VydGlmaWNhdGUgYXQgYWxsLCBqdXN0IHRleHQ=\n"
"-----END CERTIFICATE-----\n";
/* A header with nothing closing it, so the walk has to resume from behind
* it rather than let it swallow the block that follows. */
static const char headerOnlyPem[] =
"-----BEGIN CERTIFICATE-----\n";
/* Same block behind text holding a NUL. */
static const char nulBadPem[] =
"Bag Attributes\0more text\n"
"-----BEGIN CERTIFICATE-----\n"
"$$$$ not base64 $$$$\n"
"-----END CERTIFICATE-----\n";
WOLFSSH_CTX* ctx = NULL;
WOLFSSH_CTX* ctxOne = NULL;
byte* ca = NULL;
byte* leaf = NULL;
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
byte* trusted = NULL;
byte* trustedLeaf = NULL;
word32 trustedSz = 0;
word32 trustedLeafSz = 0;
#endif
byte* bundle = NULL;
word32 caSz = 0;
word32 leafSz = 0;
word32 bundleSz = 0;
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.pem", &ca, &caSz));
AssertIntEQ(0, load_file("./keys/server-cert.pem", &leaf, &leafSz));
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
/* The CA is the second block, so anything it signs verifies only if the
* walk got past the first. */
AssertIntEQ(0, catBuffers(leaf, leafSz, ca, caSz, &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctx);
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
/* A trusted-certificate block names a CA too, alone and beside a plain
* one. */
AssertIntEQ(0, makeTrustedPem(ca, caSz, &trusted, &trustedSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, trusted, trustedSz,
WOLFSSH_FORMAT_PEM));
/* A presented certificate carries no trust data, so the certificate
* path declines the form however the caller reached it. A plain block
* ahead of a trusted one is the certificate, so it is still taken. */
AssertIntEQ(WS_BAD_FILETYPE_E,
wolfSSH_CTX_UseCert_buffer(ctx, trusted, trustedSz,
WOLFSSH_FORMAT_PEM));
AssertIntEQ(0, catBuffers(leaf, leafSz, trusted, trustedSz, &bundle,
&bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_UseCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
AssertIntEQ(0, catBuffers(trusted, trustedSz, ca, caSz, &bundle,
&bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
/* Two trusted blocks with the CA second, so only a walk that looks for
* a trusted header again after passing the first installs it. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, makeTrustedPem(leaf, leafSz, &trustedLeaf, &trustedLeafSz));
AssertIntEQ(0, catBuffers(trustedLeaf, trustedLeafSz, trusted, trustedSz,
&bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
/* A trusted block behind a bad plain one. Only a walk that stepped over
* the failure reaches it, and installing the CA is what shows it did. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
trusted, trustedSz, &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
/* The CA leads this bundle rather than closing it, so a walk that kept
* only the last block would leave the leaf without a signer. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, catBuffers(ca, caSz, leaf, leafSz, &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
/* A block the manager turns down leaves the ones around it installed. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, catBuffers((const byte*)notACertPem,
(word32)(sizeof(notACertPem) - 1), ca, caSz, &bundle,
&bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
/* An unterminated header ahead of the CA. The block it opens has no end,
* so only a walk that steps past the header alone reaches the CA. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, catBuffers((const byte*)headerOnlyPem,
(word32)(sizeof(headerOnlyPem) - 1), ca, caSz, &bundle,
&bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
/* A block that will not decode is skipped wherever it sits, and the walk
* carries on from behind its header. */
ctxOne = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctxOne);
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
ca, caSz, &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctxOne, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
assertCaInstalled(ctxOne);
wolfSSH_CTX_free(ctxOne);
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)badPem,
(word32)(sizeof(badPem) - 1), &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
/* A NUL is not the end of the buffer, so the bad block behind it is
* still read. */
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)nulBadPem,
(word32)(sizeof(nulBadPem) - 1), &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
/* Text around the blocks is not a certificate, so it is stepped over. */
AssertIntEQ(0, catBuffers((const byte*)junk, (word32)(sizeof(junk) - 1),
ca, caSz, &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
AssertIntEQ(0, catBuffers(ca, caSz, (const byte*)junk,
(word32)(sizeof(junk) - 1), &bundle, &bundleSz));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
bundle = NULL;
/* Nothing installed is what fails the call. Blocks that all fail are a
* parse error, and a buffer holding no block at all is a bad file. */
AssertIntEQ(WS_PARSE_E,
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)badPem,
(word32)(sizeof(badPem) - 1), WOLFSSH_FORMAT_PEM));
AssertIntEQ(0, catBuffers((const byte*)badPem, (word32)(sizeof(badPem) - 1),
(const byte*)notACertPem, (word32)(sizeof(notACertPem) - 1),
&bundle, &bundleSz));
AssertIntEQ(WS_PARSE_E,
wolfSSH_CTX_AddRootCert_buffer(ctx, bundle, bundleSz,
WOLFSSH_FORMAT_PEM));
free(bundle);
AssertIntEQ(WS_BAD_FILE_E,
wolfSSH_CTX_AddRootCert_buffer(ctx, (const byte*)junk,
(word32)(sizeof(junk) - 1), WOLFSSH_FORMAT_PEM));
wolfSSH_CTX_free(ctx);
free(ca);
free(leaf);
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
free(trusted);
free(trustedLeaf);
#endif
#endif /* WOLFSSH_CERTS && !NO_WOLFSSH_SERVER && !WOLFSSH_NO_ECDSA */
}
/* A CA file may be in the trusted form, which the certificate path still
* declines. */
static void test_wolfSSH_CTX_AddRootCert_file_trusted(void)
{
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
!defined(WOLFSSH_NO_ECDSA) && defined(WOLFSSH_HAVE_TRUSTED_CERT_PEM) && \
!defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
static const char trustedPath[] = "./trusted-ca-test.pem";
WOLFSSH_CTX* ctx = NULL;
WFILE* fp = NULL;
byte* ca = NULL;
byte* trusted = NULL;
word32 caSz = 0;
word32 trustedSz = 0;
AssertIntEQ(0, load_file("./keys/ca-cert-ecc.pem", &ca, &caSz));
AssertIntEQ(0, makeTrustedPem(ca, caSz, &trusted, &trustedSz));
AssertIntEQ(WFOPEN(NULL, &fp, trustedPath, "wb"), 0);
AssertNotNull(fp);
AssertIntEQ((int)WFWRITE(NULL, trusted, 1, trustedSz, fp), (int)trustedSz);
WFCLOSE(NULL, fp);
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
AssertIntEQ(WS_SUCCESS, wolfSSH_CTX_AddRootCert_file(ctx, trustedPath));
assertCaInstalled(ctx);
/* A presented certificate carries no trust data, so this form is not one
* of the certificate path's. */
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_CTX_UseCert_file(ctx, trustedPath));
wolfSSH_CTX_free(ctx);
AssertIntEQ(0, remove(trustedPath));
free(trusted);
free(ca);
#endif
}
/* Trust settings say what a root may be trusted for, which means nothing in
* a certificate sent to a peer, so the readers name the form and decline it. */
static void test_wolfSSH_ReadCert_buffer_trusted(void)
{
#if defined(WOLFSSH_CERTS) && !defined(NO_WOLFSSH_SERVER) && \
!defined(WOLFSSH_NO_ECDSA) && defined(WOLFSSH_HAVE_TRUSTED_CERT_PEM)
byte* cert = NULL;
byte* trusted = NULL;
byte* out = NULL;
const byte* outType = NULL;
word32 certSz = 0;
word32 trustedSz = 0;
word32 outSz = 0;
word32 outTypeSz = 0;
byte flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
AssertIntEQ(0, load_file("./keys/server-cert.pem", &cert, &certSz));
AssertIntEQ(0, makeTrustedPem(cert, certSz, &trusted, &trustedSz));
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(trusted, trustedSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
free(trusted);
free(cert);
#endif
}
#define KEY_BUF_SZ 2048
#ifndef WOLFSSH_NO_RSA
@ -6498,6 +6913,9 @@ int wolfSSH_ApiTest(int argc, char** argv)
test_wolfSSH_CTX_UseCert_buffer();
test_wolfSSH_CTX_UseCert_file();
test_wolfSSH_CTX_AddRootCert_file();
test_wolfSSH_CTX_AddRootCert_bundle();
test_wolfSSH_CTX_AddRootCert_file_trusted();
test_wolfSSH_ReadCert_buffer_trusted();
test_wolfSSH_ReadCert_buffer();
test_wolfSSH_ReadCert_file();
test_wolfSSH_CTX_UsePrivateKey_buffer_pem();

View File

@ -112,8 +112,15 @@ extern "C" {
#define WOLFSSL_V5_0_0 0x05000000
#define WOLFSSL_V5_7_0 0x05007000
#define WOLFSSL_V5_7_2 0x05007002
#define WOLFSSL_V5_8_0 0x05008000
#define WOLFSSL_V5_9_2 0x05009002
/* wolfSSL 5.8.0 added the trusted-certificate PEM header, both the
* TRUSTED_CERT_TYPE enum and PemToDer()'s fallback to it. */
#if defined(WOLFSSH_CERTS) && (LIBWOLFSSL_VERSION_HEX >= WOLFSSL_V5_8_0)
#define WOLFSSH_HAVE_TRUSTED_CERT_PEM
#endif
/* wc_MlDsaKey_* / WC_MLDSA_* naming replaced the wc_Dilithium_* API in
* wolfSSL 5.9.2. HAVE_DILITHIUM alone doesn't distinguish the two, so
* require the version that has the new API too. */
@ -1291,6 +1298,9 @@ WOLFSSH_LOCAL int ChannelCreditWindow(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel,
WOLFSSH_LOCAL int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
const byte* in, word32 inSz,
int format, int type);
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
WOLFSSH_LOCAL int IsTrustedCertPem(const byte* in, word32 inSz);
#endif
#ifdef WOLFSSH_TPM
WOLFSSH_LOCAL int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId);
#endif

View File

@ -106,8 +106,9 @@ WOLFSSH_API int wolfSSH_ReadKey_file(const char* name,
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
/* Decodes a PEM/DER X.509 cert or OpenSSH cert line, detected from content.
* Caller frees out via heap; on failure every out param is cleared. A body
* that will not decode is WS_PARSE_E; WS_BAD_FILE_E is from _file alone. */
* Of several PEM certs, only the first is read. Caller frees out via heap; on
* failure out params are cleared. An undecodable body gives WS_PARSE_E,
* WS_BAD_FILE_E comes from _file alone. */
WOLFSSH_API int wolfSSH_ReadCert_buffer(const byte* in, word32 inSz,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap);
@ -535,8 +536,12 @@ WOLFSSH_API int wolfSSH_CTX_UsePrivateKey_buffer(WOLFSSH_CTX* ctx,
const byte* in, word32 inSz,
int format);
#ifdef WOLFSSH_CERTS
/* Takes the leaf; of several PEM certs, only the first is read. The
* trusted form is for root CAs, so it is declined here. */
WOLFSSH_API int wolfSSH_CTX_UseCert_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format);
/* Loads every PEM cert, plain or trusted (wolfSSL 5.8.0+), so a bundle
* installs all its CAs. A failing block is skipped; no CA fails the call. */
WOLFSSH_API int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format);
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)