Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions manpages/wolfssl-verify.1
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ revocation.
.SH OPTIONS
-CAfile file trusted CA certificate to verify against (PEM only).
.br
Must be a self-signed root CA unless \-partial_chain
May be a bundle; must contain a self-signed root CA
.br
is given; an intermediate CA causes "CA file is not
.br
root CA".
unless \-partial_chain is given. See NOTES.
.br
.LP
-untrusted file intermediate certificate file. Only one intermediate
Expand Down Expand Up @@ -52,11 +50,19 @@ wolfssl verify -CAfile ca-and-crl.pem -crl_check server-cert.pem
.BR wolfssl-x509(1) ", " wolfssl-crl(1)
.SH NOTES
The certificate to verify is given as the final argument. -CAfile must be
a PEM file. Unless -partial_chain is given, -CAfile must be a self-signed
root CA; supplying an intermediate CA without -partial_chain fails with
"CA file is not root CA". With -crl_check, revocation data must already be
present in -CAfile (for example by concatenating the CRL PEM after the CA
certificate). Available only when wolfSSL is built with filesystem support.
a PEM file, and may contain a bundle of multiple certificates; the bundle
is scanned in any order for a self-signed root CA. Every candidate CA
certificate, including the self-signed root, must assert basicConstraints
CA:TRUE; certificates without it are skipped when searching for a root
unless -partial_chain is given. This is intentional hardening versus
older wolfssl-clu releases, which accepted a self-signed root lacking
basicConstraints. Unless -partial_chain is given, the bundle must contain
a self-signed root CA; if none is found, verification fails with "CA file
does not contain a self-signed root CA (use -partial_chain to trust an
intermediate)". With -crl_check, revocation
data must already be present in -CAfile (for example by concatenating the
CRL PEM after the CA certificate). Available only when wolfSSL is built
with filesystem support.
.SH BUGS
No known bugs at this time.
.SH AUTHOR
Expand Down
278 changes: 228 additions & 50 deletions src/sign-verify/clu_x509_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static const struct option verify_options[] = {
{"-untrusted", required_argument, 0, WOLFCLU_INTERMEDIATE },
{"-crl_check", no_argument, 0, WOLFCLU_CHECK_CRL },
{"-partial_chain", no_argument, 0, WOLFCLU_PARTIAL_CHAIN },
{"-inform", required_argument, 0, WOLFCLU_INFORM },
{"-help", no_argument, 0, WOLFCLU_HELP },
{"-h", no_argument, 0, WOLFCLU_HELP },

Expand All @@ -43,15 +44,56 @@ static void wolfCLU_x509VerifyHelp(void)
{
WOLFCLU_LOG(WOLFCLU_L0, "./wolfssl verify -CAfile <ca file name> "
"[-untrusted <intermidate file>] [-crl_check] "
"[-partial_chain] <cert to verify>");
"[-partial_chain] [-inform pem|der] <cert to verify>");

WOLFCLU_LOG(WOLFCLU_L0, "Note: Current support only allows for loading "
"1 cert as -untrusted");
WOLFCLU_LOG(WOLFCLU_L0, "Note: -inform is accepted for compatibility "
"and ignored; input format is auto-detected");
}

/*Returns 1 if cert is a self-signed root, 0 otherwise (or on hard error, with *hardErr set to a non-WOLFCLU_SUCCESS code). */
static int cert_is_self_signed_root(WOLFSSL_X509* cert, int* hardErr) {
WOLFSSL_X509_NAME* subj = wolfSSL_X509_get_subject_name(cert);
WOLFSSL_X509_NAME* issu = wolfSSL_X509_get_issuer_name(cert);

*hardErr = WOLFCLU_SUCCESS;

if (subj == NULL || issu == NULL ||
wolfSSL_X509_NAME_cmp(subj, issu) != 0) {
return 0;
}

{
WOLFSSL_EVP_PKEY* pubKey = wolfSSL_X509_get_pubkey(cert);
int isRoot;
if (pubKey == NULL) {
*hardErr = WOLFCLU_FATAL_ERROR;
return 0;
}
isRoot = (wolfSSL_X509_verify(cert, pubKey) == 1);
wolfSSL_EVP_PKEY_free(pubKey);
return isRoot;
}
}

/*Returns 1 if cert should be skipped as a non-CA trust anchor/issuer, 0 if it's acceptable to add to the CA bundle's trust store. */
static int should_skip_non_ca_cert(WOLFSSL_X509* cert) {
if (wolfSSL_X509_get_isCA(cert) == 1) {
return 0;
}
if (wolfSSL_X509_ext_isSet_by_NID(cert, WC_NID_basic_constraints)) {
wolfCLU_Log(WOLFCLU_L0, "Skipping CA bundle cert that explicitly "
"asserts basicConstraints CA:FALSE");
return 1;
}
wolfCLU_Log(WOLFCLU_L0, "Warning: CA bundle cert has no basicConstraints "
"extension (pre-dates RFC 5280); treating it as a legacy CA "
"rather than rejecting it");
return 0;
}
#endif

static X509* load_cert_from_file(const char* filename) {
static WOLFSSL_X509* load_cert_from_file(const char* filename) {
WOLFSSL_BIO* bio = NULL;
WOLFSSL_X509* cert = NULL;

Expand All @@ -73,12 +115,12 @@ static X509* load_cert_from_file(const char* filename) {

return cert;
}
#endif /* !WOLFCLU_NO_FILESYSTEM */

int wolfCLU_x509Verify(int argc, char** argv)
{
#ifndef WOLFCLU_NO_FILESYSTEM
int ret = WOLFCLU_SUCCESS;
int inForm = PEM_FORM;
int crlCheck = 0;
int partialChain = 0;
int longIndex = 1;
Expand All @@ -87,11 +129,15 @@ int wolfCLU_x509Verify(int argc, char** argv)
char* verifyCert = NULL;
char* intermCert = NULL;
WOLFSSL_X509_STORE* store = NULL;
WOLFSSL_X509_LOOKUP* lookup = NULL;
WOLFSSL_X509_STORE_CTX* ctx = NULL;
WOLFSSL_X509* cert = NULL;
WOLFSSL_X509* intermediate = NULL;
STACK_OF(WOLFSSL_X509)* intermStack = NULL;
int loaded = 0;
int foundRoot = 0;
int hardErr = WOLFCLU_SUCCESS;
int rootErr = WOLFCLU_SUCCESS;
WOLFSSL_X509* caX509 = NULL;

/* last parameter is the certificate to verify */
if (XSTRNCMP("-h", argv[argc-1], 2) == 0) {
Expand Down Expand Up @@ -137,7 +183,14 @@ int wolfCLU_x509Verify(int argc, char** argv)
break;

case WOLFCLU_INFORM:
inForm = wolfCLU_checkInform(optarg);
/* Format is auto-detected; -inform is a compat no-op. */
if (optarg != NULL &&
XSTRNCMP(optarg, "pem", 4) != 0 &&
XSTRNCMP(optarg, "PEM", 4) != 0) {
WOLFCLU_LOG(WOLFCLU_L0,
"Warning: -inform %s is ignored; "
"verify auto-detects PEM then DER", optarg);
}
break;

case WOLFCLU_HELP:
Expand All @@ -155,15 +208,17 @@ int wolfCLU_x509Verify(int argc, char** argv)
}
}

cert = load_cert_from_file(verifyCert);
if (!cert) {
wolfCLU_LogError("Failed to load cert: %s\n", verifyCert);
ret = WOLFCLU_FATAL_ERROR;
if (ret == WOLFCLU_SUCCESS) {
cert = load_cert_from_file(verifyCert);
if (cert == NULL) {
wolfCLU_LogError("Failed to load cert: %s\n", verifyCert);
ret = WOLFCLU_FATAL_ERROR;
}
}

if (ret == WOLFCLU_SUCCESS && intermCert) {
if (ret == WOLFCLU_SUCCESS && intermCert != NULL) {
intermediate = load_cert_from_file(intermCert);
if (!intermediate) {
if (intermediate == NULL) {
wolfCLU_LogError("Failed to load cert: %s\n", intermCert);
ret = WOLFCLU_FATAL_ERROR;
}
Expand All @@ -176,53 +231,176 @@ int wolfCLU_x509Verify(int argc, char** argv)
}
}

if (ret == WOLFCLU_SUCCESS) {
if (inForm != PEM_FORM) {
wolfCLU_LogError("Only handling PEM CA files");
ret = WOLFCLU_FATAL_ERROR;
}
}

if (ret == WOLFCLU_SUCCESS) {
lookup = wolfSSL_X509_STORE_add_lookup(store,
wolfSSL_X509_LOOKUP_file());
if (lookup == NULL) {
wolfCLU_LogError("Failed to setup lookup");
/* Require -CAfile to contain a self-signed root CA unless -partial_chain. */
if (ret == WOLFCLU_SUCCESS && caCert != NULL) {
byte* pemBuf = NULL;
int pemSz = 0;
long max_sz = 10 * 1024 * 1024; /* 10 MB limit for CA bundle */
loaded = 0;

wolfSSL_ERR_clear_error();
if (wolfCLU_ReadFileToBuffer(caCert, max_sz, &pemBuf, &pemSz) != WOLFCLU_SUCCESS) {
wolfCLU_LogError("Failed to open or read CA file %s", caCert);
ret = WOLFCLU_FATAL_ERROR;
}
}

/* Confirm CA file is root CA unless partialChain enabled */
if (ret == WOLFCLU_SUCCESS){
if (!partialChain && caCert != NULL){
int error;

error = wolfSSL_CertManagerVerify(store->cm, caCert,
WOLFSSL_FILETYPE_PEM);
else {
char* pem = (char*)pemBuf;
long sz = (long)pemSz;

if (ret == WOLFCLU_SUCCESS && pem != NULL) {
char* curr = pem;
char* nextCert = XSTRSTR(curr, "-----BEGIN CERTIFICATE-----");
char* nextTrusted = XSTRSTR(curr, "-----BEGIN TRUSTED CERTIFICATE-----");
char* nextCrl = XSTRSTR(curr, "-----BEGIN X509 CRL-----");

while (curr && curr < pem + sz && ret == WOLFCLU_SUCCESS) {
char* best = NULL;
int isCert = 0;
int isCrl = 0;
int isTrusted = 0;

if (nextCert && nextCert < curr) nextCert = XSTRSTR(curr, "-----BEGIN CERTIFICATE-----");
if (nextTrusted && nextTrusted < curr) nextTrusted = XSTRSTR(curr, "-----BEGIN TRUSTED CERTIFICATE-----");
if (nextCrl && nextCrl < curr) nextCrl = XSTRSTR(curr, "-----BEGIN X509 CRL-----");

if (nextCert) { best = nextCert; isCert = 1; }
if (nextTrusted && (!best || nextTrusted < best)) { best = nextTrusted; isCert = 1; isCrl = 0; isTrusted = 1; }
if (nextCrl && (!best || nextCrl < best)) { best = nextCrl; isCrl = 1; isCert = 0; isTrusted = 0; }

if (!best) {
break;
}

if (error != ASN_SELF_SIGNED_E) {
wolfCLU_LogError("CA file is not root CA");
ret = WOLFCLU_FATAL_ERROR;
if (isCert) {
long remain = pem + sz - best;
WOLFSSL_BIO* memBio = wolfSSL_BIO_new_mem_buf(best,
(remain > (long)INT_MAX) ? INT_MAX : (int)remain);
if (memBio) {
/*TRUSTED CERTIFICATE blocks carry trailing trust attributes after the DER; only the _AUX reader understands that footer/format. */
caX509 = isTrusted ?
wolfSSL_PEM_read_bio_X509_AUX(memBio, NULL, NULL, NULL) :
wolfSSL_PEM_read_bio_X509(memBio, NULL, NULL, NULL);
if (caX509 != NULL) {
int skipCert = 0;
/*Counts certs found in the file (to decide whether to fall back to a DER parse below), not certs added to the trust store. */
loaded++;
if (!partialChain &&
should_skip_non_ca_cert(caX509)) {
skipCert = 1;
}
if (!skipCert && !partialChain && !foundRoot) {
if (cert_is_self_signed_root(caX509, &hardErr)) {
foundRoot = 1;
}
if (hardErr != WOLFCLU_SUCCESS && rootErr == WOLFCLU_SUCCESS) {
rootErr = hardErr;
}
}
if (!skipCert && wolfSSL_X509_STORE_add_cert(store, caX509) != WOLFSSL_SUCCESS) {
wolfCLU_LogError("Failed to add CA cert to trust store");
ret = WOLFCLU_FATAL_ERROR;
}
wolfSSL_X509_free(caX509);
} else {
wolfCLU_LogError("CA bundle contains corrupt or truncated certificate; aborting verification");
ret = WOLFCLU_FATAL_ERROR;
}
wolfSSL_BIO_free(memBio);
} else {
wolfCLU_LogError("Failed to allocate memory BIO for CA certificate");
ret = WOLFCLU_FATAL_ERROR;
}

char* footer = XSTRSTR(best, "-----END ");
if (footer) {
curr = footer + 9;
} else {
curr = pem + sz;
}
}
else if (isCrl) {
#ifdef HAVE_CRL
if (crlCheck) {
long remain = pem + sz - best;
WOLFSSL_BIO* memBio = wolfSSL_BIO_new_mem_buf(best,
(remain > (long)INT_MAX) ? INT_MAX : (int)remain);
if (memBio) {
WOLFSSL_X509_CRL* crl = wolfSSL_PEM_read_bio_X509_CRL(memBio, NULL, NULL, NULL);
if (crl != NULL) {
if (wolfSSL_X509_STORE_add_crl(store, crl) != WOLFSSL_SUCCESS) {
wolfCLU_LogError("Failed to add CRL to trust store");
ret = WOLFCLU_FATAL_ERROR;
}
wolfSSL_X509_CRL_free(crl);
} else {
wolfCLU_LogError("CRL data in CA file is corrupt or truncated; aborting verification");
ret = WOLFCLU_FATAL_ERROR;
}
wolfSSL_BIO_free(memBio);
} else {
wolfCLU_LogError("Failed to allocate memory BIO for CRL");
ret = WOLFCLU_FATAL_ERROR;
}
}
#endif /* HAVE_CRL */
char* footer = XSTRSTR(best, "-----END ");
if (footer) {
curr = footer + 9;
} else {
curr = pem + sz;
}
}
}
}
if (pem) {
XFREE(pem, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
}
else {
/*
* We're expecting these errors, since root certs are
* self-signed so remove them from the error queue.
*/
if (wolfSSL_ERR_peek_error() == -ASN_NO_SIGNER_E) {
wolfSSL_ERR_get_error();
if (wolfSSL_ERR_peek_error() == -ASN_SELF_SIGNED_E) {
wolfSSL_ERR_get_error();

/* fall back to DER if file opened but had no PEM certs. */
if (ret == WOLFCLU_SUCCESS && loaded == 0) {
caX509 = load_cert_from_file(caCert);
if (caX509 == NULL) {
wolfCLU_LogError("Failed to load CA file %s", caCert);
ret = WOLFCLU_FATAL_ERROR;
}
/* Same CA:TRUE requirement as the PEM bundle path above. */
if (ret == WOLFCLU_SUCCESS && !partialChain &&
should_skip_non_ca_cert(caX509)) {
wolfCLU_LogError("CA file does not assert "
"basicConstraints CA:TRUE");
ret = WOLFCLU_FATAL_ERROR;
}
if (ret == WOLFCLU_SUCCESS && !partialChain && !foundRoot) {
if (cert_is_self_signed_root(caX509, &hardErr)) {
foundRoot = 1;
}
/* Defer: a later cert may still be a valid root. */
if (hardErr != WOLFCLU_SUCCESS &&
rootErr == WOLFCLU_SUCCESS) {
rootErr = hardErr;
}
}
if (ret == WOLFCLU_SUCCESS &&
wolfSSL_X509_STORE_add_cert(store, caX509)
!= WOLFSSL_SUCCESS) {
wolfCLU_LogError("Failed to add CA cert to trust store");
ret = WOLFCLU_FATAL_ERROR;
}
wolfSSL_X509_free(caX509);
}
}
}

if (ret == WOLFCLU_SUCCESS && caCert != NULL) {
if (wolfSSL_X509_LOOKUP_load_file(lookup, caCert, X509_FILETYPE_PEM)
!= WOLFSSL_SUCCESS) {
wolfCLU_LogError("Failed to load CA file via lookup");
if (ret == WOLFCLU_SUCCESS && !partialChain && caCert != NULL &&
!foundRoot) {
if (rootErr != WOLFCLU_SUCCESS) {
wolfCLU_LogError("Error while checking CA bundle for a "
"self-signed root CA");
ret = rootErr;
}
else {
wolfCLU_LogError("CA file does not contain a self-signed root CA "
"(use -partial_chain to trust an intermediate)");
ret = WOLFCLU_FATAL_ERROR;
}
}
Expand Down Expand Up @@ -276,5 +454,5 @@ int wolfCLU_x509Verify(int argc, char** argv)
(void)argv;
WOLFCLU_LOG(WOLFCLU_E0, "No filesystem support");
return WOLFCLU_FATAL_ERROR;
#endif
#endif /* !WOLFCLU_NO_FILESYSTEM */
}
Loading
Loading