mirror of https://github.com/wolfSSL/wolfssh.git
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
parent
5ae0cc4c93
commit
f530d32548
224
src/internal.c
224
src/internal.c
|
|
@ -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)
|
||||
|
|
|
|||
21
src/ssh.c
21
src/ssh.c
|
|
@ -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;
|
||||
|
|
|
|||
418
tests/api.c
418
tests/api.c
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue