Skip to content

Commit f530d32

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
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.
1 parent 5ae0cc4 commit f530d32

5 files changed

Lines changed: 679 additions & 3 deletions

File tree

src/internal.c

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,216 @@ int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId)
27082708
#endif /* WOLFSSH_TPM */
27092709

27102710

2711+
#ifdef WOLFSSH_CERTS
2712+
2713+
/* Finds needle in the first inSz bytes of in. Unlike WSTRNSTR() an embedded
2714+
NUL does not end the search, the buffer being length delimited. */
2715+
static const byte* FindInBuffer(const byte* in, word32 inSz, const char* needle)
2716+
{
2717+
word32 needleSz;
2718+
word32 i;
2719+
2720+
needleSz = (word32)WSTRLEN(needle);
2721+
if (needleSz == 0 || inSz < needleSz) {
2722+
return NULL;
2723+
}
2724+
2725+
for (i = 0; i <= inSz - needleSz; i++) {
2726+
if (WMEMCMP(in + i, needle, needleSz) == 0) {
2727+
return in + i;
2728+
}
2729+
}
2730+
2731+
return NULL;
2732+
}
2733+
2734+
2735+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2736+
/* Reports whether the buffer holds only trusted-certificate PEM. A plain
2737+
certificate anywhere in it wins, the way wc_PemToDer() finds one first. */
2738+
int IsTrustedCertPem(const byte* in, word32 inSz)
2739+
{
2740+
const char* certHeader = NULL;
2741+
const char* trustedHeader = NULL;
2742+
2743+
if (wc_PemGetHeaderFooter(CA_TYPE, &certHeader, NULL) != 0
2744+
|| certHeader == NULL
2745+
|| wc_PemGetHeaderFooter(TRUSTED_CERT_TYPE, &trustedHeader,
2746+
NULL) != 0
2747+
|| trustedHeader == NULL) {
2748+
return 0;
2749+
}
2750+
2751+
return FindInBuffer(in, inSz, certHeader) == NULL
2752+
&& FindInBuffer(in, inSz, trustedHeader) != NULL;
2753+
}
2754+
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
2755+
2756+
2757+
/* Loads every PEM certificate block in the buffer as a root CA, skipping the
2758+
ones that fail, the way wolfSSL's own chain loader treats a CA file. */
2759+
static int LoadRootCaPemBuffer(WOLFSSH_CTX* ctx, const byte* in, word32 inSz)
2760+
{
2761+
DerBuffer* der = NULL;
2762+
const char* certHeader = NULL;
2763+
const char* certFooter = NULL;
2764+
const char* footer;
2765+
const byte* found;
2766+
const byte* foundEnd;
2767+
const byte* nextHeader;
2768+
const byte* nextCert;
2769+
word32 certHeaderSz;
2770+
word32 certFooterSz;
2771+
word32 headerSz;
2772+
word32 footerSz;
2773+
word32 bodyStart;
2774+
word32 searchSz;
2775+
word32 blockSz;
2776+
word32 used = 0;
2777+
word32 loaded = 0;
2778+
word32 failed = 0;
2779+
int wcType = CA_TYPE;
2780+
int ret;
2781+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2782+
const char* trustedHeader = NULL;
2783+
const char* trustedFooter = NULL;
2784+
const byte* nextTrusted;
2785+
word32 trustedHeaderSz;
2786+
word32 trustedFooterSz;
2787+
#endif
2788+
2789+
if (ctx->certMan == NULL) {
2790+
WLOG(WS_LOG_DEBUG, "Error no cert manager set");
2791+
return WS_MEMORY_E;
2792+
}
2793+
2794+
if (wc_PemGetHeaderFooter(CA_TYPE, &certHeader, &certFooter) != 0
2795+
|| certHeader == NULL || certFooter == NULL) {
2796+
return WS_BAD_FILE_E;
2797+
}
2798+
certHeaderSz = (word32)WSTRLEN(certHeader);
2799+
certFooterSz = (word32)WSTRLEN(certFooter);
2800+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2801+
if (wc_PemGetHeaderFooter(TRUSTED_CERT_TYPE, &trustedHeader,
2802+
&trustedFooter) != 0
2803+
|| trustedHeader == NULL || trustedFooter == NULL) {
2804+
return WS_BAD_FILE_E;
2805+
}
2806+
trustedHeaderSz = (word32)WSTRLEN(trustedHeader);
2807+
trustedFooterSz = (word32)WSTRLEN(trustedFooter);
2808+
#endif
2809+
2810+
/* Each form's header is re-sought only from behind the last one found. */
2811+
nextCert = FindInBuffer(in, inSz, certHeader);
2812+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2813+
nextTrusted = FindInBuffer(in, inSz, trustedHeader);
2814+
#endif
2815+
2816+
while (used < inSz) {
2817+
found = nextCert;
2818+
footer = certFooter;
2819+
headerSz = certHeaderSz;
2820+
footerSz = certFooterSz;
2821+
wcType = CA_TYPE;
2822+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2823+
/* wc_PemToDer() takes either form, but only finds the one its type
2824+
names first, so whichever header leads picks the type. */
2825+
if (found == NULL || (nextTrusted != NULL && nextTrusted < found)) {
2826+
found = nextTrusted;
2827+
footer = trustedFooter;
2828+
headerSz = trustedHeaderSz;
2829+
footerSz = trustedFooterSz;
2830+
wcType = TRUSTED_CERT_TYPE;
2831+
}
2832+
#endif
2833+
2834+
if (found == NULL) {
2835+
break;
2836+
}
2837+
used = (word32)(found - in);
2838+
bodyStart = used + headerSz;
2839+
2840+
/* Step this form's cursor past the header being read. */
2841+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2842+
if (wcType == TRUSTED_CERT_TYPE) {
2843+
nextTrusted = FindInBuffer(in + bodyStart, inSz - bodyStart,
2844+
trustedHeader);
2845+
}
2846+
else {
2847+
nextCert = FindInBuffer(in + bodyStart, inSz - bodyStart,
2848+
certHeader);
2849+
}
2850+
#else
2851+
nextCert = FindInBuffer(in + bodyStart, inSz - bodyStart, certHeader);
2852+
#endif
2853+
2854+
nextHeader = (nextCert != NULL && nextCert >= in + bodyStart) ?
2855+
nextCert : NULL;
2856+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2857+
if (nextTrusted != NULL && nextTrusted >= in + bodyStart
2858+
&& (nextHeader == NULL || nextTrusted < nextHeader)) {
2859+
nextHeader = nextTrusted;
2860+
}
2861+
#endif
2862+
2863+
/* A block runs to its footer, and the next header caps the search
2864+
for one. Bounding the search keeps the walk linear. */
2865+
searchSz = (nextHeader != NULL) ?
2866+
(word32)(nextHeader - (in + bodyStart)) : inSz - bodyStart;
2867+
foundEnd = FindInBuffer(in + bodyStart, searchSz, footer);
2868+
2869+
if (foundEnd == NULL) {
2870+
WLOG(WS_LOG_ERROR, "Skipping CA %u, nothing closes its header",
2871+
loaded + failed);
2872+
failed++;
2873+
used = bodyStart;
2874+
continue;
2875+
}
2876+
blockSz = (word32)(foundEnd - found) + footerSz;
2877+
2878+
if (wc_PemToDer(found, (long)blockSz, wcType, &der, ctx->heap,
2879+
NULL, NULL) != 0) {
2880+
/* A bad body is reported after the buffer is allocated. */
2881+
wc_FreeDer(&der);
2882+
WLOG(WS_LOG_ERROR, "Skipping CA %u, PEM to DER failed",
2883+
loaded + failed);
2884+
failed++;
2885+
}
2886+
else {
2887+
ret = wolfSSH_CERTMAN_LoadRootCA_buffer(ctx->certMan,
2888+
der->buffer, der->length);
2889+
wc_FreeDer(&der);
2890+
2891+
if (ret != WS_SUCCESS) {
2892+
WLOG(WS_LOG_ERROR, "Skipping CA %u, error %d loading it",
2893+
loaded + failed, ret);
2894+
failed++;
2895+
}
2896+
else {
2897+
loaded++;
2898+
}
2899+
}
2900+
2901+
used += blockSz;
2902+
}
2903+
2904+
if (loaded > 0) {
2905+
ret = WS_SUCCESS;
2906+
}
2907+
else if (failed > 0) {
2908+
ret = WS_PARSE_E;
2909+
}
2910+
else {
2911+
WLOG(WS_LOG_ERROR, "No certificate in the CA buffer");
2912+
ret = WS_BAD_FILE_E;
2913+
}
2914+
2915+
return ret;
2916+
}
2917+
2918+
#endif /* WOLFSSH_CERTS */
2919+
2920+
27112921
int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
27122922
const byte* in, word32 inSz,
27132923
int format, int type)
@@ -2784,6 +2994,20 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
27842994
}
27852995
}
27862996
else if (format == WOLFSSH_FORMAT_PEM) {
2997+
#ifdef WOLFSSH_CERTS
2998+
if (type == BUFTYPE_CA) {
2999+
/* A CA buffer may hold a bundle, so every block is loaded. */
3000+
return LoadRootCaPemBuffer(ctx, in, inSz);
3001+
}
3002+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
3003+
if (type == BUFTYPE_CERT && IsTrustedCertPem(in, inSz)) {
3004+
/* The trust data behind the certificate means nothing to a peer. */
3005+
WLOG(WS_LOG_DEBUG, "Trusted certificate form is for root CAs");
3006+
return WS_BAD_FILETYPE_E;
3007+
}
3008+
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
3009+
#endif /* WOLFSSH_CERTS */
3010+
27873011
/* The der size will be smaller than the pem size. */
27883012
der = (byte*)WMALLOC(inSz, heap, dynamicType);
27893013
if (der == NULL)

src/ssh.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2350,12 +2350,17 @@ int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format,
23502350
#ifdef WOLFSSH_CERTS
23512351
static const char* CertBeginPrefix = "-----BEGIN CERTIFICATE-----";
23522352
#endif
2353+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2354+
static const char* TrustedCertBeginPrefix =
2355+
"-----BEGIN TRUSTED CERTIFICATE-----";
2356+
#endif
23532357

23542358
/* Longest algorithm name is ecdsa-sha2-nistp521-cert-v01@openssh.com. */
23552359
#define WOLFSSH_MAX_CERT_ALGO_NAME_SZ 48
23562360

23572361
/* Identifies a certificate from its content, without decoding or allocating.
2358-
An x509v3-* line holds a wire chain, not a certificate, so it is declined. */
2362+
An x509v3-* line holds a wire chain, not a certificate, so it is declined.
2363+
The trusted form is PEM too; the decoders decide who takes it. */
23592364
static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
23602365
byte* certId)
23612366
{
@@ -2375,6 +2380,12 @@ static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
23752380
*flavor = WOLFSSH_CERT_FLAVOR_X509;
23762381
ret = WOLFSSH_FORMAT_PEM;
23772382
}
2383+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2384+
else if (WSTRNSTR((const char*)in, TrustedCertBeginPrefix, inSz) != NULL) {
2385+
*flavor = WOLFSSH_CERT_FLAVOR_X509;
2386+
ret = WOLFSSH_FORMAT_PEM;
2387+
}
2388+
#endif /* WOLFSSH_HAVE_TRUSTED_CERT_PEM */
23782389
#endif /* WOLFSSH_CERTS */
23792390

23802391
#ifdef WOLFSSH_OSSH_CERTS
@@ -2421,6 +2432,14 @@ static int DoPemCert(const byte* in, word32 inSz, byte** out, word32* outSz,
24212432
word32 derSz = 0;
24222433
int ret;
24232434

2435+
#ifdef WOLFSSH_HAVE_TRUSTED_CERT_PEM
2436+
if (IsTrustedCertPem(in, inSz)) {
2437+
/* The trust data behind the certificate means nothing to a peer. */
2438+
WLOG(WS_LOG_DEBUG, "Trusted certificate form is for root CAs");
2439+
return WS_BAD_FILETYPE_E;
2440+
}
2441+
#endif
2442+
24242443
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
24252444
if (der == NULL) {
24262445
return WS_MEMORY_E;

0 commit comments

Comments
 (0)