Skip to content

Add OpenSSL compatibility fixes and X509_verify_cert chain building for xmlsec - #12041

Open
julek-wolfssl wants to merge 31 commits into
wolfSSL:masterfrom
julek-wolfssl:xmlsec-compat-review
Open

julek-wolfssl wants to merge 31 commits into
wolfSSL:masterfrom
julek-wolfssl:xmlsec-compat-review

Conversation

@julek-wolfssl

Copy link
Copy Markdown
Member

Make wolfSSL's OpenSSL compatibility layer behave like OpenSSL 3.0 for the APIs xmlsec relies on, and rework X509_verify_cert() to build and check chains from X509 objects as OpenSSL does, failing closed where OpenSSL would try another candidate.

Chain verification

  • X509_verify_cert() builds the chain from X509 objects, picking issuers as OpenSSL's build_chain() does. It reports each error at its cert and depth to the verify callback. It covers CA checks, path lengths, CA key sizes, host and IP, keyUsage, signatures, dates, name constraints and revocation.
  • CRL handling follows OpenSSL: one CRL is checked per cert, chosen by issuer, authority key id and dates. X509_CRL_verify is implemented, and CheckCertCRLFromCm() takes an issuer and a check time.
  • Internal errors, such as out of memory, fail the verification without reaching the callback.
  • Add X509_VERIFY_PARAM_set_depth and X509_STORE_CTX_set0_param. The depth and owned members of X509_STORE_CTX are removed, which is an API change.
  • SSL_get0_verified_chain() returns the chain X509_verify_cert() builds, and caches it per peer cert.
  • CheckCAKeySize() compares modulus and curve size, so ECC minimums are now enforced. P-192 CAs no longer load with the default minimum.
  • Store changes: X509_STORE_set_default_paths and X509_LOOKUP_add_dir default handling, X509_STORE_CTX_get_by_subject, a working X509_OBJECT_free_contents, and X509_STORE_add_cert accepting expired self-signed certs for a store of its own. WOLFSSL_SIGNER_DER_CERT is now set in every OPENSSL_EXTRA build.
  • The commit message lists known differences from OpenSSL 3.0.13.

Crypto and API changes

  • DSA now behaves as OpenSSL does. This adds DSA_size and DSA_up_ref, handles any size of q, rejects r/s longer than q, and leaves the private key NULL for public-only keys. EVP_PKEY_sign and EVP_PKEY_verify use DER signatures for DSA, so raw signatures no longer verify through EVP_PKEY_verify.
  • Add RSA_padding_add/check_PKCS1_OAEP_mgf1, ERR_load_strings, ERR_get_next_error_library, CRYPTO_get_mem_functions, EVP_PKEY_RSA2, EVP_PKEY_RSA_PSS, the prime curve short names and B_ASN1_OCTET_STRING.
  • Private key BIGNUMs and the cipher context are zeroed when freed.
  • Fix i2d_PUBKEY for EC keys, EC PKCS#8 loading, and i2d_ECDSA_SIG allocation, which is now enabled with --enable-opensslall.

ASN1, X509 and stacks

  • Fix ASN1_INTEGER handling: dup, get, cmp, d2i, and magnitude-based ASN1_STRING_length/get0_data/type dispatch.
  • Fix X509/X509_REQ DER encoding so the stored encoding is written, and fix leaks in the d2i/PEM read functions with a non-NULL *x.
  • X509_chain_up_ref now references certs instead of copying them. sk_X509_CRL_free frees only the stack.
  • Fix several ASN1, OBJ and X509_NAME functions: OBJ_cmp, X509_NAME_oneline, X509_NAME_hash and the subjectKeyIdentifier copy in X509V3_EXT_d2i.
  • Add X509_NAME_ENTRY_dup, stack sorting, sk_X509_insert, sk_X509_delete_ptr, X509_STORE_CTX_set/get_app_data, and PKCS#12 X509_alias_get0/X509_keyid_get0.

Tests and CI

  • Add test certificates under certs/intermediate/trust_anchor/ with generation scripts, and table-driven tests for chain verification, including an out-of-memory test.
  • Add an xmlsec workflow that builds against --enable-all and compares failures with a known DSA list.
  • Adjust existing upstream tests to OpenSSL 3.0.13 results, and drop the branch's X509_get0_pubkey cache in favour of upstream's.

Applications with their own error library get a library number with
ERR_get_next_error_library, register strings with ERR_load_strings and
build codes with ERR_PACK, as xmlsec does. Add these, ERR_unload_strings
and ERR_STRING_DATA.

Library numbers start at 128, as in OpenSSL. There is no string table
for them, so errors put with these library numbers are not queued.
xmlsec signs and verifies DSA through the OpenSSL API. Several DSA
functions were missing or differed from OpenSSL:

- Add DSA_size: the largest DER encoding of a DSA signature for the key,
  with r and s as large as q. Callers size signature buffers with it.
  It returns -1 when q is not set.
- DSA_do_sign and DSA_do_verify truncate the digest to the size of q,
  as FIPS 186-4, 4.6 does, also where wolfCrypt's DSA does not.
- DSA_do_sign supports any size of q. It parsed the raw signature with
  d2i_DSA_SIG, whose raw form takes only 40 or 64 bytes, so a key with a
  224-bit q, as OpenSSL 3 makes for 2048-bit DSA by default, got no
  signature. The DSA_SIG is now built from the two halves of the raw
  signature, each the size of q.
- DSA_do_verify rejects r or s longer than q. Both were taken up to 32
  bytes and written one after the other at the size of q, so with a
  shorter q, r * 256 + x verified like r, and the same for s. As in
  OpenSSL, which needs 0 < r, s < q, a value longer than q now fails.
- A public key or parameters only have no private key. DSA_get0_key
  returned a zero BIGNUM as the private key and DSA_do_sign signed with
  x = 0. The private key is now NULL and DSA_do_sign fails without one.
- DSA objects are reference counted; add DSA_up_ref.
  EVP_PKEY_get1_DSA rebuilt the DSA from the stored DER encoding, which
  a key set with EVP_PKEY_assign_DSA does not have, so it returned NULL.
  It now returns the held key with a new reference, as OpenSSL does,
  and EVP_PKEY_set1_DSA takes one, so the caller and the EVP_PKEY can
  free the key in any order. A pkey that has only the DER encoding
  decodes it once and keeps the key. set1 also frees the encoding of a
  key it replaces, which leaked, and clears the PKCS#8 header size that
  came with it.
- EVP_PKEY_sign with a DSA key writes the DER encoding of r and s, of
  at most DSA_size() bytes, and signs all of the digest it is given.
  EVP_PKEY_verify takes only the DER encoding: the signature must decode
  and encode again to the same bytes, as OpenSSL's DSA_verify checks.
  Before, both used raw r and s. This changes behaviour: raw signatures no longer verify with
  EVP_PKEY_verify. EVP_SignFinal still writes raw r and s for DSA.
  EVP_PKEY_sign and EVP_PKEY_verify do not support DSA keys in builds
  with HAVE_SELFTEST, which do not have i2d_DSA_SIG, d2i_DSA_SIG,
  DSA_do_sign and DSA_do_verify.
The private key BIGNUM of DSA, DH and EC keys was freed without being
zeroed, so the key stayed in freed memory. Use BN_clear_free for it in
DSA_free, DH_free and EC_KEY_free, and where a private key is replaced.

The cipher union of an EVP_CIPHER_CTX, which holds the key schedule, was
left as it was after cleanup, and EVP_CIPHER_CTX_free freed the context
without zeroing it. A DES key schedule stayed in freed memory. Zero the
union in cleanup and the whole context before it is freed.
Applications that use OAEP with a label build the encoding themselves
and encrypt with RSA_NO_PADDING, as xmlsec does for every OAEP key
transport. Add RSA_padding_add_PKCS1_OAEP_mgf1 and
RSA_padding_check_PKCS1_OAEP_mgf1 on top of the wolfCrypt OAEP encoding.
They take the hash of the label and the hash of MGF1. As in OpenSSL, a
NULL md is SHA-1 and a NULL mgf1md is md. A hash that wolfCrypt OAEP
does not support fails. RSA_padding_add_PKCS1_OAEP and
RSA_padding_check_PKCS1_OAEP call them with NULL hashes.

The OAEP seed comes from the global RNG, as for BN_rand, instead of a
new DRBG for each call. The RNG is shared, so the padding holds its
lock. As in RAND_bytes, the RNG is looked up under the lock, since
another thread can free it with RAND_Cleanup.

The check takes an encoded message without its leading zero bytes, as
OpenSSL does, and to may be from. Differences from OpenSSL: the block is
at most RSA_MAX_SIZE bits, and an empty message fails, since wolfCrypt
reports it as it reports a decoding error.
OpenSSL code handles all RSA key types together, as xmlsec does with
case labels next to EVP_PKEY_RSA. Add them with OpenSSL's values:
EVP_PKEY_RSA2 is 19, OpenSSL's NID_rsa, and EVP_PKEY_RSA_PSS is
NID_rsassaPss. wolfSSL's own NID_rsa equals EVP_PKEY_RSA, so the value
is given as a number. wolfSSL RSA keys are always EVP_PKEY_RSA and never
have these types.

Add SN_X9_62_prime192v1 to SN_X9_62_prime256v1, SN_secp224r1,
SN_secp384r1 and SN_secp521r1, as in OpenSSL. Each is the name that
OBJ_nid2sn returns for the curve's NID. xmlsec uses them in its table of
named curves.
i2d_PUBKEY and i2d_PublicKey of an EC private key decoded the key's DER
as a public key. That failed for a key loaded from PKCS#8, whose DER has
no curve, and for a key without the optional public key. OpenSSL encodes
the public key in both cases, and xmlsec needs it to write the key of a
PKCS#12 file. Encode the public key from the EC key, computing the
public point when the key has none. EC_KEY loading from PKCS#8 decoded
the key after its header, so the key took the default curve. Decode the
whole PKCS#8 so the curve is kept, as a brainpool key needs.

X509_get0_pubkey and X509_get_pubkey now come from master; the tests
of this commit expect X509_get_pubkey to return the X509's key with a
new reference.

OpenSSL's i2d_ECDSA_SIG(sig, &p) with p NULL allocates the encoding.
wolfSSL does this only with WOLFSSL_I2D_ECDSA_SIG_ALLOC, which no build
option set, so xmlsec's ASN.1 ECDSA signatures failed. Define it with
--enable-opensslall in configure and the matching CMake bundles, and
remove it from the known macro extras. Other builds still return only
the length, as the API docs now say. When the encoding fails after the
buffer was allocated, free it and set p back to NULL, as OpenSSL does.
i2d_X509_bio and i2d_X509_REQ_bio (and through it i2d_X509_REQ) encoded
the certificate or request again from its parsed fields. The new
encoding can order or drop extensions and attributes, so it no longer
matched the signature, and a DSA request could not be written at all.
Both were also only built with certificate generation, though declared
for every build. Write the stored encoding, as i2d_X509 and
PEM_write_bio_X509_REQ do. Without a stored encoding, encode from the
fields when certificate generation is built. The oversized key
identifier tests now re-encode with X509_sign and X509_REQ_sign.

As in OpenSSL 3.0.13, a certificate keeps its stored encoding until it
is signed again. The request setters X509_REQ_set_version,
X509_REQ_set_subject_name, X509_REQ_set_pubkey and
X509_REQ_add1_attr_by_NID encode the request again with its old
signature, as OpenSSL's do, so X509_REQ_verify fails until it is signed
again. A setter still succeeds when the request cannot be encoded again,
as for DSA or Ed448; then no encoding is kept and i2d fails. A parsed
request now keeps the value of its version field, as OpenSSL. It was one
higher, so each re-encoding raised the version.

X509_REQ_add_extensions replaced the stored encoding with only the
unsigned request info, so functions that parse it, such as
X509_get_ext_count, X509_REQ_get_extensions and X509_REQ_verify, worked
on the wrong data. A signed request is now encoded again with its old
signature, as OpenSSL does. A request never signed keeps no encoding.
Extensions can now be added before the public key is set. The encoding
buffer has room for the largest signature, and the encoding fails when
the signature cannot be encoded instead of returning a negative length.

With a non-NULL *x, d2i_X509_bio, d2i_X509_CRL_bio and PEM_read_bio_X509
leaked the object that was there. Free it and store the new one. As
OpenSSL, when what d2i_X509_bio reads does not decode, it frees *x and
sets it to NULL. When nothing is read *x is kept. Two tests that passed
a freed certificate in *x now clear the pointer.
ASN1_INTEGER_dup of an INTEGER too long for the inline buffer, such as a
20 byte certificate serial, did not set the length of the copy.
ASN1_INTEGER_get of a negative INTEGER from d2i_ASN1_INTEGER returned
its magnitude. d2i_ASN1_INTEGER took an encoding with a redundant
leading octet, which OpenSSL rejects.
In OpenSSL an ASN1_INTEGER is an ASN1_STRING. _Generic dispatched
ASN1_STRING_length and ASN1_STRING_get0_data on one with C11 only: GCC
and clang without C11 passed the ASN1_INTEGER to the ASN1_STRING
functions and failed with -Werror. Choose at compile time with
__builtin_choose_expr there, keeping the argument type checked.

ASN1_STRING_type did not take an ASN1_INTEGER at all. Dispatch it the
same way to the new wolfSSL_ASN1_INTEGER_get_type, which returns
V_ASN1_NEG_INTEGER for a negative INTEGER and V_ASN1_INTEGER otherwise,
as OpenSSL. It reads the negative flag, as the other INTEGER functions
do: the type member is 0 in an INTEGER from ASN1_INTEGER_new. The
ASN1_STRING_type macro moves from openssl/asn1.h, which includes
openssl/ssl.h, to the others there.

As with _Generic, these are function-like macros: a function pointer is
taken of the wolfSSL_ASN1_STRING_* or wolfSSL_ASN1_INTEGER_* function.

test_wolfSSL_ASN1_INTEGER_STRING calls the three on INTEGERs, const or
not: on NULL, for the type of INTEGERs from ASN1_INTEGER_new,
ASN1_INTEGER_set, BN_to_ASN1_INTEGER, d2i_ASN1_INTEGER and
ASN1_INTEGER_dup, and for the length and data of 256 and of a cert
serial.
ASN1_STRING_length and ASN1_STRING_get0_data on an ASN1_INTEGER made by
ASN1_INTEGER_set, BN_to_ASN1_INTEGER or d2i_ASN1_INTEGER returned the
DER content, with the zero octet before a high bit. OpenSSL returns the
magnitude. Return it from wolfSSL_ASN1_INTEGER_get_length and get0_data,
which those macros call on an ASN1_INTEGER, whatever made the INTEGER,
and print it with i2a_ASN1_INTEGER.

X509_get_serialNumber encoded a serial with the high bit set as a
negative INTEGER. Add the zero octet, and set the type to
V_ASN1_INTEGER as the other INTEGERs made by the library have it.
Serials are taken as positive, as RFC 5280 requires: a negative one
reads as positive.

test_wolfSSL_ASN1_INTEGER_STRING checks the length and data of each
INTEGER it makes, the type member, and the DER and i2a output of the
serial.
X509_CRL_verify was a stub that returned 0, so applications that drop
CRLs that do not verify, as xmlsec does for CRLs in a signed document,
dropped every CRL and accepted the certificates they revoke. Check the
CRL signature with the public key. As in OpenSSL, return 1 when it
verifies, 0 when it does not, and -1 when it cannot be checked: a key of
the wrong type, an algorithm that is not compiled in, or a CRL with
nothing signed. The check uses the to-be-signed bytes and signature kept
for a CRL that was not verified on load, as with d2i and PEM. Key
handling is shared with X509_verify.

As in OpenSSL, X509_verify and X509_CRL_verify also take a private key
and verify with its public part. An RSA or EC private key gave 0 for a
valid signature, as the key DER kept for it is the private key. When
that public key cannot be encoded, as for DSA, -1 is returned. An Ed448
key is taken as an Ed25519 key is, its public key made from the private
key when it has none; it gave -1. wolfSSL makes no EVP_PKEY of type
RSA-PSS (it reads neither such a key nor the key of such a
certificate), so that type stays -1.

X509_CRL_get_REVOKED built entry serials from the content bytes, while
X509_get_serialNumber holds the DER encoding unless WOLFSSL_QT or
WOLFSSL_HAPROXY is defined. ASN1_INTEGER_to_BN and ASN1_INTEGER_get
failed on entry serials. The INTEGER functions had to guess the form:
content bytes that read as a DER INTEGER, such as 02 01 05, were taken
for one, so ASN1_INTEGER_get read serial 0x020105 as 5, and a revoked
certificate with that serial did not match its CRL entry. Store the DER
encoding of entry serials in every build, with the zero octet before a
high bit as X509_get_serialNumber has it, so they read the same in all
of them.

X509_CRL_add_revoked took the bytes of the INTEGER, so the serial of
X509_get_serialNumber went in with its DER header and the cert was not
found revoked. Take the value of an INTEGER made by the library, the
magnitude, as the serials of parsed CRLs and certs are held. The bytes
of one built by hand are still taken as they are.

sk_X509_CRL_free also freed the CRLs on the stack. OpenSSL frees the
stack only, and callers that keep their own references then freed the
CRLs twice. Use sk_X509_CRL_pop_free with X509_CRL_free to free both.
- ASN1_INTEGER_cmp compared the stored bytes: the DER encoding, or only
  the content in an INTEGER built by hand. A negative value is stored as
  the encoding of its magnitude, which BN_to_ASN1_INTEGER and
  d2i_ASN1_INTEGER can start with a zero byte that ASN1_INTEGER_set
  leaves out. So equal values compared unequal, as -128 from
  ASN1_INTEGER_set and from BN_to_ASN1_INTEGER. Compare the magnitudes,
  as ASN1_STRING_get0_data and ASN1_STRING_length give them. An empty
  INTEGER still does not equal 0, as in OpenSSL.
- OBJ_nid2obj gives an object with only the tag and a zero length for a
  NID without an OID encoding here, as for name attribute types. OBJ_cmp
  compared those bytes, so any two such types compared equal, and
  commonName did not equal 2.5.4.3 made with OBJ_txt2obj. Compare the
  NIDs when either object has no encoding.
- For a subjectKeyIdentifier extension X509V3_EXT_d2i copied
  extSubjKeyIdSz bytes from extSubjKeyId. That array holds a hash of the
  identifier when it is not KEYID_SIZE bytes long, so a short identifier
  came back as the start of the hash, and a longer one read past the end
  of the array. Copy the identifier from the extension instead.
- X509_NAME_oneline left the caller's buffer untouched for an empty or
  NULL name, so the caller read whatever was there. It now writes an
  empty string first, as OpenSSL does. X509_NAME_hash returned 0 for an
  empty name. OpenSSL hashes the empty encoding, which gives 0xeea339da
  with SHA-1. Do the same, so lookups by hash find certificates with an
  empty subject or issuer.
- Add X509_NAME_ENTRY_dup and sk_X509_NAME_ENTRY_pop_free. Applications
  copy the entries of a name to sort and compare them, as xmlsec does.
  X509_NAME_ENTRY_dup copies the object and the value, so the copy
  outlives the name it came from, as in OpenSSL.
  sk_X509_NAME_ENTRY_pop_free frees the stack and each entry with the
  given function.
- Add B_ASN1_OCTET_STRING, the type mask bit of an OCTET STRING, 0x0200
  as in OpenSSL. xmlsec passes it to X509_NAME_add_entry_by_txt for a
  name value given as '#' and hex, the raw encoding of the value.
  wolfSSL keeps the bytes as the entry value, as it does for any type
  that is not an MBSTRING_* type. OpenSSL 3.0 fails to add such an
  entry, so xmlsec '#hex' subject and issuer names find a cert only with
  wolfSSL. This is lookup only; the cert is still verified.
Applications sort the entries of two names to compare them regardless
of order, as xmlsec does. With OPENSSL_ALL, keep a comparator in the
stack, as OpenSSL does, and keep the one given to
sk_X509_NAME_ENTRY_new. Add wolfSSL_sk_set_cmp_func, mapped to
OPENSSL_sk_set_cmp_func, which sets the comparator and returns the
previous one, and wolfSSL_sk_sort, mapped to OPENSSL_sk_sort, a stable
insertion sort. Each node's hash moves with its data. Cipher stacks hold
their data in the node and are left as they are. Add
sk_X509_NAME_ENTRY_set_cmp_func and sk_X509_NAME_ENTRY_sort.

Applications reorder a cert stack in place, as xmlsec does to put the
key cert first and to drop duplicates. Add wolfSSL_sk_delete_ptr, which
removes the first node that holds the pointer and returns it, or NULL
when the pointer is not in the stack, as OpenSSL's sk_delete_ptr does.
The data is not freed. Cipher stacks hold copies, not pointers, so they
are not searched. Map sk_X509_insert to wolfSSL_sk_insert and add
sk_X509_delete_ptr.
X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT) asks for the
default certificate directory. It failed on the NULL path. Use
$SSL_CERT_DIR as OpenSSL does, and add nothing when it is not set,
since there is no built-in directory.

X509_STORE_set_default_paths failed when neither $SSL_CERT_FILE nor
$SSL_CERT_DIR was set, or when they did not load. OpenSSL treats the
default locations as optional and returns success. Do the same: load
what is set and ignore what does not load. A NULL store still fails.

Add X509_STORE_CTX_get_by_subject. Applications look up a cert in the
store by subject name, as xmlsec does to find the issuer of a CRL.
Search the certs added to the store, then the CAs loaded into the cert
manager. Return 1 with a reference in the object, 0 when none is found.
CRLs are not looked up. X509_STORE_get_by_subject was a stub and now
calls it, as in OpenSSL.

X509_OBJECT_free_contents was a stub, so a caller that releases the
object with it, as the OpenSSL 1.0 API does, leaked that reference.
Implement it: free what the object holds by its type and leave the
object empty (type X509_LU_NONE), so a later X509_OBJECT_free does not
free it again. X509_OBJECT_free uses it. The OCSP test called it on an
object of the store's list, which would free the store's cert; call it
on NULL there.

The cert manager keeps the DER of its CAs only with
WOLFSSL_SIGNER_DER_CERT, which only OPENSSL_ALL set, so
X509_STORE_CTX_get_by_subject() could not find them in other
OPENSSL_EXTRA builds. Set it in every OPENSSL_EXTRA build: one DER copy
per loaded CA, and Signer grows, so a PERSIST_CERT_CACHE file of an
older such build is rejected. As with OPENSSL_ALL, those builds then
also have X509_STORE_get1_certs() and wolfSSL_CertManagerGetCerts(),
and X509_STORE_CTX_get1_issuer() and the issuer that X509_verify_cert()
puts on top of its chain come from the cert manager.
OpenSSL's PKCS12_parse keeps the friendlyName and localKeyId bag
attributes in the certs, and X509_alias_get0 and X509_keyid_get0 return
them. Applications name the key from them, as xmlsec does.

wc_PKCS12_parse_ex2, internal to the library, also returns the
attributes of the cert that matches the key. PKCS12_parse keeps them in
that cert: the friendlyName when it is ASCII, as the low byte of each
BMPString character, and the localKeyId bytes. Both are NUL terminated
after their length, as ASN1_STRING data is. Any other friendlyName is
left out, where OpenSSL converts it to UTF-8. xmlsec then names the key
from X509_keyid_get0. Add X509_alias_get0 and X509_keyid_get0.

Only the key cert gets them; OpenSSL also sets them in the CA certs.
X509_dup does not copy them, as in OpenSSL. Malformed bag attributes are
skipped as before; OpenSSL's PKCS12_parse rejects the file.
Returns what CRYPTO_set_mem_functions set, else functions using the C
library. These can be wrapped and the wrappers passed to
CRYPTO_set_mem_functions without recursion. Getting them changes
nothing. Where CRYPTO_set_mem_functions is not supported they allocate
as OPENSSL_malloc does.
OpenSSL's X509_chain_up_ref returns a new stack of the same certs, each
with its reference count raised; wolfSSL's copied every cert. Code
ported from OpenSSL expects the same objects. Changes to a returned cert
now apply to the original, as in OpenSSL. X509_STORE_CTX_get1_chain
uses it, so it returns the chain's certs, as in OpenSSL.
Map them to X509_STORE_CTX ex_data index 1. OpenSSL uses index 0, but
wolfSSL keeps the SSL there in a TLS verify callback, so
get_ex_data(ctx, 0) is not the app data. They need HAVE_EX_DATA. Init
and cleanup clear the app data. Unlike in OpenSSL, it does not carry
over to the next TLS verify callback of a handshake, as wolfSSL gives
each cert its own context.
Certificates, keys and CRLs under certs/intermediate/trust_anchor/ for
table-driven X509_verify_cert() tests: trust anchors and their copies,
roots and intermediates with fixed validity periods, cross-certs, twins
with the same name or key id, CAs without basicConstraints or
keyCertSign, v1 roots, forged issuers and CRLs.

gen_certs.sh makes certs valid for 30 years from when it runs; the
tests use them at the current time only. gen_dated_certs.sh makes the
certs with fixed dates. It gives each cert the serial it has in the set,
where some serials repeat, and issues each with a CA database of its
own. check_dates.sh lists the ones that are expired on purpose.
check_cert_chains.sh checks the certs whose issuer is kept and in date.
No code changes.
AddCA() checks the key of a CA against the cert manager's minimum key
sizes for each key type. Move that switch into a function of its own,
CheckCAKeySize().

The check compared the DER length of the public key with the minimums,
which are in bytes of key size. For ECC that length is of the whole
SubjectPublicKeyInfo, 91 bytes for P-256, so no ECC minimum up to 521
bits was enforced. For RSA it is the RSAPublicKey DER, so keys about
100 bits short of the minimum passed. Compare the modulus size for RSA
and the curve size for ECC. OpenSSL has no CA-only minimum; its auth
level checks the real key strength (EVP_PKEY_get_security_bits()), as
this now does for the wolfSSL minimums.

This changes AddCA(): a CA whose curve is below the minimum ECC key
size, such as P-192 with the default of 224 bits, no longer loads.
test_wolfSSL_CertManagerCAKeySize loads CAs into an SSL_CTX that
verifies peers: a CA with a 2048-bit modulus loads with a minimum of
2048 bits but not of 2056, and with a minimum of 384 bits a P-256 CA
does not load and a P-384 CA does.
X509_verify_cert() verified each cert with the cert manager and picked
issuers through lookups, retries and alternate chains on top of it. The
cert manager finds signers by name and key id only, so it accepted
chains OpenSSL rejects and rejected chains OpenSSL accepts.

Build the chain from X509 objects, as OpenSSL 3.0's build_chain() does,
and fail closed where OpenSSL would try another candidate:
- The issuer is OpenSSL's pick: among the certs whose names and key ids
  match and whose key type fits the signature, the first one valid at
  the verification time, else the one that expires last. Its key is not
  checked when picked; a signature it did not make is reported later
  (error 7). Trusted certs are searched in the order they were added,
  as OpenSSL's store keeps them (the new store->added). Failing closed,
  a cert without its DER, made in memory, is never picked, trusted or
  not, so every cert on the chain has its DER and is checked.
- Without X509_V_FLAG_TRUSTED_FIRST the untrusted certs are searched
  first, in their order, else the trusted ones. X509_STORE_CTX_init()
  sets the flag, as OpenSSL's default param does. Above a trusted cert
  only trusted certs issue. A trusted stack replaces the store's
  trusted certs and its cert manager CAs.
- A cert is self-signed by its names and key ids. It is trusted when it
  was found among the trusted certs, or when it is self-signed and is
  the trusted cert picked as its own issuer: a copy is a mimic.
- The first issuer found is final: no other candidate is tried when a
  later check fails. Where OpenSSL would take an alternate chain,
  wolfSSL takes it only when its trusted cert is the same cert as the
  untrusted one above; otherwise the missing issuer is final, also when
  the verify callback accepts it.
- A trusted self-signed cert ends the chain, and any trusted cert with
  X509_V_FLAG_PARTIAL_CHAIN. As OpenSSL's last resort, a trusted leaf
  ends a partial chain only when no issuer is found, also above an
  untrusted self-signed cert. A trusted issuer already on the chain
  gives X509_V_ERR_CERT_CHAIN_TOO_LONG, as OpenSSL takes it again until
  the chain is too long. An untrusted self-signed top gives 18 or 19, a
  missing issuer 2 or 20.
- X509_V_FLAG_PARTIAL_CHAIN is read from the ctx param only, which init
  inherits from the store and X509_STORE_CTX_set0_param() replaces.
- The CAs of the cert manager, as in an SSL_CTX's store, are trusted
  certs searched after the others: only those the application loaded,
  not CAs that TLS cached from a peer's chain or temporary ones. Such a
  CA goes on the chain from its DER and the chain goes on to its issuer
  there, so its path length, name constraints, CA flag, dates and
  revocation by its root are checked. As in wolfSSL's handshake, a CA
  without an issuer there ends the chain. Without its DER, or when its
  key did not sign the cert, the cert has no issuer. A cert that breaks
  the name constraints of such a CA gets 47 or 48 on the chain. The
  comment where settings.h sets WOLFSSL_SIGNER_DER_CERT names this use.
- The depth comes from the ctx param, as in OpenSSL; the leaf and the
  trust anchor do not count, also a CA of the cert manager that ends the
  chain, as the anchor of a partial chain in OpenSSL. A negative depth,
  unset included, rejects every chain. As OpenSSL, the verify callback
  is told of a chain too long; failing closed, the error is final.
  X509_STORE_CTX_set_depth() sets the param depth. The depth member of
  X509_STORE_CTX is removed, as OpenSSL's X509_STORE_CTX is opaque: only
  the setter sets the depth, and the ctx of the TLS verify callback no
  longer carries it; test.h's callback uses error_depth instead. This is
  an API change for code that read or wrote the member. So is the
  removal of the owned member, which only the old verification filled.
- No temporary CAs are loaded into the cert manager, so none are
  unloaded after the verification.

xmlsec sets the verify depth on a param and gives the param to the
X509_STORE_CTX: add X509_VERIFY_PARAM_set_depth and
X509_STORE_CTX_set0_param. The param depth is the maximum number of
intermediate CAs, -1 when unset. X509_VERIFY_PARAM_set1 and inherit
copy it only when set, so a store depth reaches the context at init.
X509_STORE_CTX_init() sets the param depth from the store, or to 100
when the store has none, as OpenSSL does. X509_STORE_CTX_set0_param
frees the context's param and takes the new one, replacing what came
from the store. A NULL param is ignored, and so is the context's own
param, which OpenSSL frees and keeps a dangling pointer to. TLS
verification does not use the param depth. The context allocates and
frees its param as X509_VERIFY_PARAM_new() and X509_VERIFY_PARAM_free()
do, without the context's heap, as OpenSSL's context uses
X509_VERIFY_PARAM_new(). So a param of X509_VERIFY_PARAM_new() given to
set0_param is freed with the heap it was allocated with, also with
static memory.

Then, as OpenSSL, each error reported at its cert and depth to the
verify callback:
- Every cert above the leaf is a CA as X509_check_ca() decides (79).
  Without basicConstraints only the top cert can be one, and not with
  X509_V_FLAG_X509_STRICT. WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE
  still lets an untrusted intermediate without CA:TRUE through.
- Path lengths as check_extensions(): every non-self-issued cert above
  the leaf counts, CA or not, and 25 is reported at the CA whose
  pathLenConstraint is exceeded.
- The minimum CA key size of every issuer, the anchor included (67).
- Host and IP of the leaf.
- Revocation, below.
- Top down as internal_verify(): the issuer's keyUsage allows
  keyCertSign (32), the signature (7) and the dates of each cert at the
  verification time: the param time with X509_V_FLAG_USE_CHECK_TIME,
  none with X509_V_FLAG_NO_CHECK_TIME, else now. As X509_cmp_time(), a
  cert has expired at its notAfter. Then the callback is called for the
  cert with ok set.
- Name constraints as check_name_constraints(): the names of each cert,
  but a self-issued one above the leaf, against every cert above it,
  the anchor included (47, 48, 51). The CN of the leaf, CA or not, is a
  DNS name when it has no dNSName SAN. CheckCertNameConstraints() runs
  wolfCrypt's checker on the DER of a CA and of a cert.

Revocation:
- CRLs are checked when the cert manager has CRL checking on, or when
  X509_V_FLAG_CRL_CHECK(_ALL) is on the ctx param, as OpenSSL reads it.
  X509_STORE_set_flags() puts them on the store's param. Without
  HAVE_CRL the flags are not ignored: no CRL is found, so each cert they
  check gives 3, as in OpenSSL without a CRL.
- As OpenSSL, the loaded CRLs are checked also for a cert without a CRL
  distribution point: WOLFSSL_CRL_ALLOW_MISSING_CDP now skips the check
  only in the handshake (CheckCertCRL_ex()).
- The CRL of a cert must be signed by the cert's issuer on the chain:
  CheckCertCRLFromCm() takes that issuer and, as crl_akid_check(), skips
  a CRL whose authority key id is not the issuer's subject key id. A CRL
  verified when it was loaded, after its CA, keeps no signature: it is
  used when the CA that verified it has the issuer's key.
- As in OpenSSL, a self-issued top cert has its own CRLs: as the leaf,
  or with X509_V_FLAG_CRL_CHECK_ALL, a trust anchor is checked against
  them too. Another top cert has no CRL without its issuer, so it gives
  3 where it is checked. The CRL checks of the cert manager alone leave
  the trust anchor out, self-issued or not, as in TLS.
- As OpenSSL's get_crl_sk(), one CRL is checked per cert, among those
  of the issuer's name and key: a current one over one out of date,
  then the latest thisUpdate, the first on a tie. An older CRL of
  another key, or a revocation that only an older CRL lists, is not
  looked at.
- A CRL that does not verify gives X509_V_ERR_CRL_SIGNATURE_FAILURE
  (8), final also when the callback accepts it, as its entries are not
  verified. A missing CRL gives 3.
- As OpenSSL's check_crl_time(), a CRL is current from its thisUpdate
  until its nextUpdate, if any, at the time the cert dates are checked
  at: the param time with X509_V_FLAG_USE_CHECK_TIME, none with
  X509_V_FLAG_NO_CHECK_TIME, else now. CheckCertCRLFromCm() takes that
  time; the handshake keeps its nextUpdate check at the current time.
  Without a current CRL, one not yet valid gives 11, else an expired one
  12, at the cert's depth: OpenSSL takes the CRL of the latest
  thisUpdate. Once accepted, its entries are still checked. As in
  OpenSSL, the dates of a CRL are checked before its signature.
  Loading a CRL into an X509_STORE does not check its dates.
- As in OpenSSL, the CRLs of X509_STORE_CTX_set0_crls() are used over
  the store's, also when they are out of date: the store's only when
  none of them is of the cert's issuer.
- An issuer whose keyUsage lacks cRLSign gives 35, before the CRL dates
  as OpenSSL's check_crl(); once accepted, the CRL is used.

As OpenSSL, an internal error is not a verify error: the verify
callback is not asked, so it cannot accept it, and X509_verify_cert()
fails with X509_V_ERR_OUT_OF_MEM when memory ran out, else
X509_V_ERR_UNSPECIFIED. Such are a failed allocation, a cert that does
not decode again, a mutex error, and any result of the CRL checks but
theirs. An error of the signature check, by the cert manager or with
the key of an X509, other than a verification result is one: the cert
decoded before. A bad signature, or a signature algorithm not compiled
in, stays a verification result. VerifyCRL_Signature() returns MEMORY_E
instead of ASN_CRL_CONFIRM_E when memory ran out.
After a failure the X509_STORE_CTX error is never X509_V_OK, also for a
leaf without its DER (-1). Add the X509_V_ERR values used here.

The store follows OpenSSL too:
- X509_STORE_add_cert() of a store of its own takes a self-signed cert
  that is expired or not yet valid, also with X509_V_FLAG_NO_CHECK_TIME.
  It stays out of the cert manager, so TLS never trusts it, and is
  dropped when the store goes to an SSL_CTX. X509_STORE_get0_objects(),
  X509_STORE_get1_certs(), X509_STORE_CTX_get_by_subject() and
  X509_STORE_CTX_get1_issuer() find it. Adding a cert already there is
  a no-op.
- X509_STORE_CTX_set_flags() sets the param flags.
  X509_STORE_set_flags() sets X509_V_FLAG_X509_STRICT there too, and the
  CRL flags, but not X509_V_FLAG_NO_CHECK_TIME: on the store of an
  SSL_CTX it would let CAs out of date into the cert manager, which TLS
  trusts. The flags of a connection that are copied to the store of its
  SSL_CTX leave X509_STRICT out.

SSL_get0_verified_chain() returns the chain that X509_verify_cert()
builds, from the peer's cert to the trust anchor, as OpenSSL returns
the chain its handshake verified, instead of the peer's certs followed
by the CA chain. After a full handshake that verified the peer
(verification on and no error, also none that a verify callback
accepted), the dates of a CA the application loaded into the cert
manager, and whether it is a CA that may sign certs, do not fail it
where that CA, the same cert by its DER, verifies the cert below it:
the handshake trusts such a CA without these checks. So a CA that
expired after it was loaded, or was loaded with its date errors
allowed, still gives the chain, also below a root of the cert manager.
Every other check applies. A cert that only a store of its own trusts,
outside its cert manager, gets every check, and so does every cert
after a resumed handshake, which verifies no cert, or after one that
did not verify the peer. Without WOLFSSL_SIGNER_DER_CERT a CA of the
cert manager cannot go on the chain, so there is none for a peer that
only such a CA verifies. As OpenSSL keeps the chain of its handshake,
the chain is built once per peer cert and kept: later calls return
the same stack without verifying again. A new peer chain and
wolfSSL_clear() drop it, and it is built again when its first cert is
not the peer's. Before, each call verified again and freed the stack
the previous one returned.

Upstream tests changed, each to what OpenSSL 3.0.13 gives:
- X509_STORE_check_time: CRL_CHECK on the ctx param with no CRL gives
  3, so it now passes certs/crl/crl.pem, which does not list the cert;
  without HAVE_CRL it expects 3.
- X509_STORE_CTX_ex10: a store PARTIAL_CHAIN flag set after init
  reaches the ctx only at the next init; before that, 2.
- partial_chain_cb_reject: the partial chain ends at the first trusted
  cert, int2, which the callback rejects.
- untrusted_inter_depth_trusted_stack: init drops the ctx flag, so
  depth 0 is too long again (22).
- untrusted_inter tests: a tampered intermediate found first is the
  issuer and fails the chain (7) instead of wolfSSL retrying the
  genuine one; with only the tampered cert as trusted stack, 2 at
  depth 1. The comments say so; the function names are kept.
- selfsigned_anchor_no_keycertsign: a self-signed anchor without
  keyCertSign fails with 79 at depth 1. ca_no_keycertsign also checks
  79 at depth 1.
- X509_STORE_untrusted: an untrusted root without a loaded CA gives 19,
  as "openssl verify" does, not 20.
- X509_STORE_InvalidCa and _CtxCallback: the callback sees 79, then 32,
  which is left on the ctx.
New tests cover the param flags and depth as xmlsec sets them, the
X509_STRICT of a connection not reaching the store, in-memory certs
without DER, CRL dates at a verification time, CRLs loaded after their
CA, internal errors not reaching the callback, the verified chain of a
connection and its caching, and the CRL checks of an SSL_CTX's cert
manager. Tests that need WOLFSSL_SIGNER_DER_CERT expect no verified
chain or a failure without it.

Known differences from OpenSSL 3.0.13:
- Failing closed, no alternate chain through another trusted cert:
  some chains that OpenSSL verifies are rejected.
- A CRL that does not verify stays an error once accepted; OpenSSL then
  checks its entries. So does a chain too long; OpenSSL then reports the
  missing issuer.
- A CA of an SSL_CTX without its issuer there ends the chain, as in the
  handshake; OpenSSL gives 2.
- A CA:TRUE leaf whose CN is not a hostname, below DNS constraints, is
  rejected, as a CA:FALSE one already was. The TLS path does not check
  the CN of a CA leaf; it is not changed here.
- The serial in an authority key id is not compared, as before, so
  some chains that OpenSSL rejects verify.
- CA key sizes: OpenSSL has no CA-only minimum.
- SSL_get0_verified_chain() accepts the dates and CA flag of a CA of the
  cert manager after a full handshake, which trusts it without them;
  OpenSSL fails such a handshake. After a resumed handshake it builds
  the chain from the session's certs and checks all of it; OpenSSL has
  none.
- A CRL is picked by its name, authority key id and dates; OpenSSL
  also scores its scope, critical extensions and delta CRLs.
- wolfSSL cannot load a CRL without nextUpdate; OpenSSL takes it as
  never expired, as the date check here would.
- Running out of memory inside X509_check_host(), X509_check_ip_asc()
  or the key size checks of CheckCAKeySize() (WOLFSSL_SMALL_STACK) is
  taken as a mismatch or a small key, and inside the math of a signature
  check, as with sp_int, as a bad signature, as OpenSSL does.
- After a failure X509_STORE_CTX_get_current_cert() is the leaf: wolfSSL
  keeps the cert to verify there. The verify callback sees the cert of
  each error.
- X509_STORE_set_flags() leaves X509_V_FLAG_NO_CHECK_TIME out, above;
  X509_VERIFY_PARAM_set_flags() on X509_STORE_get0_param() sets it.
- With X509_STORE_add_crl() the cert manager checks every cert below
  the trust anchor, also without X509_V_FLAG_CRL_CHECK, as before.
- A verification time of 0 is the current time, as in wolfCrypt;
  OpenSSL takes it as 1970.
- A CRL date that does not parse, as one with a time zone offset, makes
  the CRL not yet valid (11); OpenSSL gives 15 or 16.
test_X509_verify_cert_chains runs the verifications of the table
x509VfyCases. Each row gives the trusted certs (store, file, lookup or
SSL_CTX), a trusted stack, the untrusted certs, the leaf, CRLs, flags,
the param (as xmlsec sets it, without X509_V_FLAG_TRUSTED_FIRST), the
time, the depth and the verify callback, with the expected return,
error, error depth and, where it matters, the chain and every error the
callback saw. One runner builds each case. It also checks that the
current cert of each callback error is the chain cert at its depth, and
that the caller's stacks are left as they were.

The rows cover CA key sizes (RSA, P-256), self-signed copies and
self-issued anchors, trusted certs that are not CAs, v1 roots and
X509_V_FLAG_X509_STRICT, the search order with and without
TRUSTED_FIRST, dead ends and twins, forged and wrong signers, trusted
stacks and their CRLs, issuer key ids and key types, the dates of
anchors, intermediates and CRLs at a verification time, partial chains
and where the flag is read, SSL_CTX stores and the roots of their CAs,
the depth when a CA of an SSL_CTX ends the chain, a chain too long and
the callback, path lengths, untrusted roots, name constraints, also of a
CA of an SSL_CTX, CRL checks without CRLs (also without HAVE_CRL), CRL
signatures, stale CRLs, with and without X509_V_FLAG_NO_CHECK_TIME, CRLs
of the ctx over the store's, the pick among CRLs of one name, the CRLs
of a self-signed anchor, and issuers without cRLSign. The CRL rows also
run with WOLFSSL_CRL_ALLOW_MISSING_CDP. A few certs and CRLs are made at
run time with the X509 API; the rest are in
certs/intermediate/trust_anchor/.

More tests:
- test_X509_verify_cert_oom fails each allocation of X509_verify_cert()
  in turn for a leaf without a trusted issuer, a chain through a
  pathlen:0 CA of an SSL_CTX, a cert that a set0_crls CRL revokes and an
  expired self-signed CA of an SSL_CTX; it never verifies. With a
  callback that accepts every error, a failed allocation either has no
  effect or fails the verification with X509_V_ERR_OUT_OF_MEM or
  X509_V_ERR_UNSPECIFIED, the callback seeing only the errors it sees
  without a failure. Some failed allocation gives X509_V_ERR_OUT_OF_MEM.
  Where verifying an RSA signature allocates, as with sp_int math, a
  failure there is a bad signature: then only that the chain still
  fails without a callback is checked.
- test_X509_verify_cert_tls_cached_ca: after a handshake whose callback
  waives the missing issuer, the peer's CA cached in the SSL_CTX does
  not anchor X509_verify_cert() on its store (20).
- test_X509_STORE_add_cert_expired: the store API and TLS side of a
  store that takes an expired self-signed cert.

Rows that give another result with OpenSSL 3.0.13 have a comment:
- key_size: OpenSSL has no CA-only minimum; at auth level 3 or 4 it
  first reports X509_V_ERR_EE_KEY_TOO_SMALL for the leaf, so the row
  whose callback accepts every 67 verifies only in wolfSSL.
- v1 root with X509_STRICT: OpenSSL first reports
  X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER at depth 0, wolfSSL 79 at
  depth 1.
- dead_end: OpenSSL takes an alternate chain via a trusted cert and
  verifies (once after its callback accepts 9) or gives 79 at depth 1;
  wolfSSL fails closed with 2 at depth 2.
- trusted_cert_error: OpenSSL gives 7 at depth 1 through a trusted twin;
  wolfSSL fails closed with 20 at depth 1.
- crl_dates bad CRL signature, callback accepts: OpenSSL checks the
  CRL's entries and gives 23; wolfSSL keeps 8.
- depth, callback accepts: OpenSSL reports VE_LOCAL at depth 1 and
  verifies; wolfSSL keeps 22.
- ctx_store_ca CA without its root: wolfSSL trusts the SSL_CTX CA, as
  its handshake does; OpenSSL gives 2 at depth 2, and with a depth limit
  22 at depth 1 or 2.
Build wolfSSL with --enable-all, build xmlsec with the osp patch and
run its key, signature and encryption test scripts. The failed tests
must match the per-version list of known DSA failures. Add the xmlsec
build dependencies to the ubuntu-24.04-full apt package list.
Upstream added callers of the old three-argument form. NULL issuer and
check time keep the handshake's CRL checks.
Upstream caches the key in key.pkey and keeps the peer cert for the
whole handshake. The pubKeyPKey member and the static SetPeerCert are
left over and clash with upstream's SetPeerCert.
Upstream added a test of the same name. Its X509_get_pubkey returns a
new reference to the cached key, not a new key.
…h test

EVP_PKEY_sign writes and EVP_PKEY_verify takes only DER for DSA, so the
upstream test needs a buffer for DER and the raw EVP_SignFinal signature
encoded.
As in OpenSSL, the chain reports the trust anchor to the callback too,
a leaf without an issuer gets error 21 after 20, and a CRL that is not
yet valid is reported before the revocation.
…test

The chain is built from X509 objects, not verified with the cert
manager, so its verify callback is not asked about the CRL or the dates.
A callback set with wolfSSL_CertManagerSetVerify on the store's
CertManager, as an SSL_CTX shares with its X509_STORE, is now asked
about each error instead of the X509_STORE_CTX or X509_STORE callback;
only one of them is called. Restore the CertManager callback checks of
test_wolfSSL_X509_verify_cert_crl_date_callback. As with the store
callback, revocation is reported before the dates, so waiving the first
error leaves the date error to reject.
X509_verify_cert now asks the CertManager callback first, so an app
callback set with wolfSSL_CertManagerSetVerify replaced VerifiedChainCb
when wolfSSL_get0_verified_chain built the chain. A callback wolfSSL
installs for itself wins: VerifiedChainCb is returned ahead of the
CertManager callback. Test that the app callback is not called while
the chain is built.
Copilot AI balanced review requested due to automatic review settings November 6, 2026 14:25
@julek-wolfssl julek-wolfssl self-assigned this Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text +312 B (+0.4%, 71,031 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m3

  • FLASH: .text +392 B (+0.3%, 132,069 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4

  • FLASH: .text +448 B (+0.2%, 209,420 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +256 B (+0.3%, 73,719 B / 262,144 B, total: 28% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +896 B (+0.5%, 182,565 B / 262,144 B, total: 70% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +384 B (+0.2%, 196,312 B / 1,048,576 B, total: 19% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +320 B (+0.5%, 68,313 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .rodata -16 B, .text +6,400 B (+0.8%, 807,060 B / 1,048,576 B, total: 77% used)
  • RAM: .data +8 B (+0.0%, 121,780 B / 262,144 B, total: 46% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +320 B (+0.1%, 223,060 B / 262,144 B, total: 85% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +384 B (+0.1%, 313,168 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +640 B (+0.2%, 344,272 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +320 B (+0.5%, 68,313 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +384 B (+0.3%, 132,817 B / 262,144 B, total: 51% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +448 B (+0.2%, 249,954 B / 262,144 B, total: 95% used)

gcc-arm-cortex-m7

  • FLASH: .text +448 B (+0.2%, 209,420 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +384 B (+0.1%, 314,000 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +448 B (+0.2%, 249,954 B / 262,144 B, total: 95% used)

stm32-sim-stm32h753

  • FLASH: .text +984 B (+0.5%, 204,740 B / 2,097,152 B, total: 10% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata +8 B, text +2,176 B (+0.8%, 284,640 B / 67,108,864 B, total: 0% used)

Catch memory regressions in CI with MemBrowse. Free for open source.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The multithreaded fallback has an unsynchronized counter that can return duplicate library numbers, and err.h leaves the new API type and declarations dependent on header include order.

2 open issues
What changed in this PR

This PR improves wolfSSL’s OpenSSL compatibility for xmlsec, focusing on X509 chain verification, cryptographic APIs, and object ownership.

Changes:

  • Reworks certificate-chain verification and CRL handling, with trust-anchor fixtures and regression coverage.
  • Extends compatibility APIs and fixes encoding, reference counting, and sensitive-data cleanup.
  • Updates build options, documentation, and CI dependencies.
File Description
wolfssl/​wolfcrypt/​settings.h Retains signer DER in OpenSSL-extra builds.
wolfssl/​wolfcrypt/​pkcs12.h Declares parsing support for bag attributes.
wolfssl/​wolfcrypt/​asn.h Declares signature and name-constraint helpers.
wolfssl/​test.h Uses error depth in certificate display callbacks.
wolfssl/​openssl/​rsa.h Exposes OAEP padding compatibility APIs.
wolfssl/​openssl/​evp.h Adds RSA key-type compatibility constants.
wolfssl/​openssl/​err.h Adds error-registration aliases and packing macro.
wolfssl/​openssl/​ec.h Adds prime-curve short names.
wolfssl/​openssl/​dsa.h Adds DSA reference counting and size API.
wolfssl/​openssl/​crypto.h Exposes memory-function retrieval aliases.
wolfssl/​openssl/​compat_types.h Defines the stack comparator callback type.
wolfssl/​openssl/​asn1.h Updates ASN1 compatibility aliases.
wolfssl/​internal.h Adds verification helpers and object metadata.
wolfssl/​crl.h Adds issuer and time parameters for CRL checks.
tests/​unit-mcdc/​test_crl_whitebox.c Updates CRL helper calls.
tests/​api/​test_x509.h Registers encoding and object-reuse tests.
tests/​api/​test_tls.h Registers parameter-isolation coverage.
tests/​api/​test_tls.c Tests connection flags remaining separate from store flags.
tests/​api/​test_ssl_cert.h Registers verified-chain anchor coverage.
tests/​api/​test_ossl_x509.c Updates empty-name hash expectations.
tests/​api/​test_ossl_x509_str.h Registers chain, CRL, store, and allocation-failure tests.
tests/​api/​test_ossl_x509_pk.h Registers public-key retrieval tests.
tests/​api/​test_ossl_x509_name.h Registers name-entry and sorting tests.
tests/​api/​test_ossl_x509_ext.h Registers subject-key-identifier decoding coverage.
tests/​api/​test_ossl_x509_crypto.h Registers signature and revoked-serial tests.
tests/​api/​test_ossl_sk.h Registers stack operations and chain-reference tests.
tests/​api/​test_ossl_rsa.h Registers OAEP padding tests.
tests/​api/​test_ossl_p7p12.h Registers PKCS#12 attribute tests.
tests/​api/​test_ossl_obj.c Tests comparisons between distinct and equivalent OIDs.
tests/​api/​test_ossl_ec.h Registers curve-name and allocation-failure tests.
tests/​api/​test_ossl_dsa.h Registers DSA size, digest, key, and reference tests.
tests/​api/​test_ossl_asn1.h Registers ASN1 integer/string compatibility tests.
tests/​api/​test_evp_pkey.h Registers key-type, ownership, and encoding tests.
tests/​api/​test_evp_cipher.h Registers cipher cleanup coverage.
tests/​api/​test_evp_cipher.c Checks that cleanup clears cipher state.
tests/​api/​test_certman.h Registers CA key-size coverage.
tests/​api/​test_certman.c Tests RSA and ECC minimum CA key sizes.
src/​ssl_sk.c Implements pointer deletion and comparator-based sorting.
src/​ssl_err.c Implements error-library allocation and registration stubs.
src/​pk_ec.c Clears private keys and fixes decoding/allocation handling.
src/​internal.c Updates verification state, cleanup, and CRL calls.
doc/​dox_comments/​header_files/​ssl.h Documents store behavior and memory-function retrieval.
doc/​dox_comments/​header_files/​ecc.h Documents ECDSA signature allocation semantics.
configure.ac Enables ECDSA signature allocation with opensslall.
cmake/​options.h.in Exports the ECDSA signature allocation option.
certs/​intermediate/​trust_anchor/​wrongca-root-cert.pem Adds a root for wrong-issuer tests.
certs/​intermediate/​trust_anchor/​wrongca-old-int-cert.pem Adds an expired wrong-issuer intermediate fixture.
certs/​intermediate/​trust_anchor/​wrongca-int-leaf-cert.pem Adds a leaf for wrong-issuer chain tests.
certs/​intermediate/​trust_anchor/​wrongca-int-cert.pem Adds an intermediate for issuer-selection tests.
certs/​intermediate/​trust_anchor/​wrongca-cert.pem Adds a competing CA fixture.
certs/​intermediate/​trust_anchor/​v1-root-leaf-cert.pem Adds a leaf issued by a version-1 root.
certs/​intermediate/​trust_anchor/​v1-root-cert.pem Adds a version-1 trust-anchor fixture.
certs/​intermediate/​trust_anchor/​self-key.pem Adds a self-signed fixture’s test key.
certs/​intermediate/​trust_anchor/​self-copy-cert.pem Adds a same-key self-signed certificate variant.
certs/​intermediate/​trust_anchor/​self-cert.pem Adds a self-signed trust-anchor fixture.
certs/​intermediate/​trust_anchor/​root-key.pem Adds a trust-anchor test key.
certs/​intermediate/​trust_anchor/​root-cert.pem Adds the base trust-anchor certificate.
certs/​intermediate/​trust_anchor/​renewed-root-leaf-cert.pem Adds a leaf for renewed-root selection tests.
certs/​intermediate/​trust_anchor/​renewed-root-cert.pem Adds a renewed-root fixture.
certs/​intermediate/​trust_anchor/​notca-twin-cert.pem Adds a CA counterpart for non-CA selection tests.
certs/​intermediate/​trust_anchor/​notca-root-leaf-cert.pem Adds a leaf beneath a non-CA root.
certs/​intermediate/​trust_anchor/​notca-root-cert.pem Adds a non-CA trust-anchor fixture.
certs/​intermediate/​trust_anchor/​nokid-root-cert.pem Adds a root without key identifiers.
certs/​intermediate/​trust_anchor/​nobc-root-leaf-cert.pem Adds a leaf for missing-basic-constraints tests.
certs/​intermediate/​trust_anchor/​nobc-root-cert.pem Adds a root without basic constraints.
certs/​intermediate/​trust_anchor/​noaki-root-link-cert.pem Adds a same-name link certificate.
certs/​intermediate/​trust_anchor/​noaki-root-leaf-cert.pem Adds a leaf for authority-key-identifier tests.
certs/​intermediate/​trust_anchor/​noaki-root-cert.pem Adds a root without an authority key identifier.
certs/​intermediate/​trust_anchor/​link-noaki-cert.pem Adds a link without an authority key identifier.
certs/​intermediate/​trust_anchor/​link-key.pem Adds the link certificate’s test key.
certs/​intermediate/​trust_anchor/​link-cert.pem Adds a same-name intermediate link.
certs/​intermediate/​trust_anchor/​leaf-key.pem Adds the base leaf’s test key.
certs/​intermediate/​trust_anchor/​leaf-cert.pem Adds the base chain’s leaf certificate.
certs/​intermediate/​trust_anchor/​int-crl.pem Adds an intermediate-issued CRL fixture.
certs/​intermediate/​trust_anchor/​forge-sub-leaf-cert.pem Adds a leaf for forged-chain rejection tests.
certs/​intermediate/​trust_anchor/​forge-sub-cert.pem Adds a subordinate CA for forged-chain tests.
certs/​intermediate/​trust_anchor/​forge-root-cert.pem Adds the forged-chain test root.
certs/​intermediate/​trust_anchor/​forge-q-cert.pem Adds another CA in the forged-chain fixtures.
certs/​intermediate/​trust_anchor/​forge-int-cert.pem Adds an intermediate with a controlled key identifier.
certs/​intermediate/​trust_anchor/​forge-fake-int-leaf-cert.pem Adds a leaf beneath the fake intermediate.
certs/​intermediate/​trust_anchor/​forge-fake-int-cert.pem Adds a fake intermediate fixture.
certs/​intermediate/​trust_anchor/​forge-bad-sub-cert.pem Adds a subordinate with a mismatched issuer identifier.
certs/​intermediate/​trust_anchor/​expired-root-old-link-cert.pem Adds an expired same-name link.
certs/​intermediate/​trust_anchor/​expired-root-nokid-cert.pem Adds a same-name root without key identifiers.
certs/​intermediate/​trust_anchor/​expired-root-link-leaf-cert.pem Adds a leaf beneath an expired-root link.
certs/​intermediate/​trust_anchor/​expired-root-link-cert.pem Adds a valid link to an expired root.
certs/​intermediate/​trust_anchor/​expired-root-leaf-cert.pem Adds a leaf issued by an expired root.
certs/​intermediate/​trust_anchor/​expired-root-int-leaf-cert.pem Adds a leaf for an expired-root intermediate chain.
certs/​intermediate/​trust_anchor/​expired-root-int-cert.pem Adds an intermediate issued by an expired root.
certs/​intermediate/​trust_anchor/​expired-root-cert.pem Adds an expired trust-anchor fixture.
certs/​intermediate/​trust_anchor/​expired-int-cert.pem Adds an expired intermediate fixture.
certs/​intermediate/​trust_anchor/​ec-name-root-cert.pem Adds a same-name EC root candidate.
certs/​intermediate/​trust_anchor/​dated-root-late-cert.pem Adds a later-validity root variant.
certs/​intermediate/​trust_anchor/​dated-root-empty-crl.pem Adds an empty root-issued CRL.
certs/​intermediate/​trust_anchor/​dated-root-crl.pem Adds a root-issued CRL with a revoked entry.
certs/​intermediate/​trust_anchor/​dated-root-cert.pem Adds a root with fixed validity dates.
certs/​intermediate/​trust_anchor/​dated-int-leaf-cert.pem Adds a leaf with fixed validity dates.
certs/​intermediate/​trust_anchor/​dated-int-cert.pem Adds an intermediate with fixed validity dates.
certs/​intermediate/​trust_anchor/​cross-root-late-cert.pem Adds a later-validity cross-signed root.
certs/​intermediate/​trust_anchor/​cross-root-cert.pem Adds a cross-signed root fixture.
certs/​intermediate/​trust_anchor/​badkid-int-leaf-cert.pem Adds a leaf for key-identifier mismatch tests.
certs/​intermediate/​trust_anchor/​badkid-int-cert.pem Adds an intermediate with a mismatched key identifier.
certs/​intermediate/​trust_anchor/​alt-root-cert.pem Adds an alternate-chain root.
certs/​intermediate/​trust_anchor/​alt-old-int-cert.pem Adds an expired alternate intermediate.
certs/​intermediate/​trust_anchor/​alt-notca-int-cert.pem Adds a non-CA alternate intermediate.
certs/​intermediate/​trust_anchor/​alt-int-leaf-cert.pem Adds a leaf for alternate-chain selection.
certs/​intermediate/​trust_anchor/​alt-int-cert.pem Adds an alternate-chain intermediate.
certs/​intermediate/​trust_anchor/​alt-end-int-cert.pem Adds an intermediate on a dead-end chain.
certs/​intermediate/​trust_anchor/​alt-end-cert.pem Adds a dead-end issuer fixture.
certs/​intermediate/​include.am Includes trust-anchor fixtures in source distributions.
certs/​check_dates.sh Allows intentionally expired test certificates.
certs/​check_cert_chains.sh Adds fixture issuer-pair checks.
.wolfssl_known_macro_extras Removes the now-configured allocation macro exception.
.github/​ci-deps/​packages-ubuntu-24.04-full.txt Adds xmlsec build dependencies.

Review effort: Balanced

Comment thread src/ssl_err.c
static int errLibNext = WOLFSSL_ERR_LIB_FIRST_DYNAMIC;

WOLFSSL_ENTER("wolfSSL_ERR_get_next_error_library");
return errLibNext++;
Comment thread wolfssl/openssl/err.h
Comment on lines +62 to +65
#define ERR_get_next_error_library wolfSSL_ERR_get_next_error_library
#define ERR_load_strings wolfSSL_ERR_load_strings
#define ERR_unload_strings wolfSSL_ERR_unload_strings
#define ERR_STRING_DATA WOLFSSL_ERR_STRING_DATA
@julek-wolfssl

Copy link
Copy Markdown
Member Author

Depends on wolfSSL/osp#368

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants