Port keyless certificate validation into the GDS receiver driver instead
of restoring the removed server implementation. Preserve master's timer
batching, PubSub lifecycle and client HTTP configuration handling.
Adapt Publish cancellation to response tokens and the HEL/fuzz harnesses
to the explicit TCP driver. Keep SKS policy history and custom datatype
ownership intact, including failed replacement configuration cleanup.
Retain TPM error cleanup while enforcing key-handle and nonce lengths.
Validation:
- Full mbedTLS unit-test build with full namespace, GDS, SKS and Query.
- 21 affected suites passed with ASan/UBSan, plus 9 isolated SKS ownership
and lifecycle checks.
- OpenSSL certificate update, GDS information model and client encryption
suites passed with sanitizers.
- OPN, MSG and binary-message fuzzers each completed 100 sanitizer runs.
- Both TPM PubSub policy sources passed compilation checks.
The full SKS client suite timed out after multicast UDP sends reported
"No route to host" on this machine; its affected ownership and lifecycle
cases passed separately. No live TPM hardware validation was performed.
Fix misspellings found with codespell across headers, sources, examples,
tests and the documentation, including "propably" in the nodeset
compiler docs. Identifiers, companion spec names and generated code are
left untouched.
OpenSSL 4 makes ASN1_STRING opaque, returns const pointers from the X509_NAME, X509_CRL and X509_EXTENSION getters, and deprecates the X509_cmp_*time family and X509_NAME_get_text_by_NID. The plugin no longer built: the ASN1_IA5STRING access is a hard error, and -Werror turns the rest into errors too.
Read ASN1 strings through ASN1_STRING_length and ASN1_STRING_get0_data, keep the getter results const, compare the validity period with ASN1_TIME_cmp_time_t and read the common name through its name entry. The self-signed certificate gets its subject from a fresh X509_NAME instead of editing the one X509_get_subject_name returns, and the CSR duplicates the subjectAltName extension instead of pushing one it does not own. All of this exists since OpenSSL 1.1.1, which the plugin already requires.
Add a CI job that builds OpenSSL 4.0.2 from source like the LibreSSL job does. The runner ships OpenSSL 3, so the job sets OPENSSL_ROOT_DIR to make sure it cannot quietly fall back to the system library.
Refs #8493
The TPM AES-CTR policies copied sizeof(pointer) bytes regardless of the key length,
and setKeys read 8 bytes from the 4-byte key nonce, past the end of the SKS key
buffer. Check the lengths and copy the size of the destination.
newContext of the TPM AES-CTR policies leaked the group context on errors, and
the AES128 variant also returned with initLock128_g still held, deadlocking the
next call. Route all error paths through one cleanup.
On a C_EncryptUpdate/Final error the TPM AES-CTR policies returned GOOD with the
buffer unchanged, so the NetworkMessage was sent in plaintext. Return the error,
also for decryption, and check the allocation before the operation is initialized.
certificategroup.h declares it in every configuration, but only the crypto backends defined
it, so a caller failed to link without encryption. Return BadNotSupported like the others.
The `securityPolicy` context is still in use, so it should not be
cleared. This commit stores changes in intermediate keys and
certificates and on failure clears these instead.
Re-initializing the MAC context with the same digest for every message
is unnecessary work. The digest is set once at key import and later
initializations pass NULL.
Mirrors the mbedTLS policy (Tables 155-157, HMAC-SHA256 over the whole
NetworkMessage). A group context caches the AES and the MAC context. The
MAC is re-keyed per message as OpenSSL 3.0 cannot restart it otherwise.
The P_SHA256/P_SHA1 key derivation left the derived key stream, the
secret, the seed and the A(n) chain in freed heap memory. The SKS
derives every group key this way. Buffer and context are now cleansed.
Search the certificate input for the next PEM header with the haystack and needle in the correct order. Use a length-bounded helper so partial headers at the end cannot read past the input.
The exact-comparison change in f4c2dc2aa made an absent URI compare equal to an empty ApplicationUri. Require a non-empty URI SAN before accepting the certificate.
The mbedTLS RSA-PSS verifier consumes one complete RSA modulus but its low-level API does not take an input length. Reject malformed ByteStrings before invoking it so a short application signature cannot cause an out-of-bounds read.
Reject malformed raw ECDSA signatures before converting their r and s coordinates. Short signatures could otherwise cause out-of-bounds reads in the OpenSSL backend and an underflowing bounds check in the mbedTLS backend; oversized mbedTLS signatures could overflow the fixed DER buffer.
Require serialized PKCS#11 handles and the specified four-byte key nonce before copying key material into the TPM channel context. This rejects incompatible raw SKS keys and prevents out-of-bounds reads from undersized inputs.
encrypt_pubsub_aes128ctr_tpm/decrypt_pubsub_aes128ctr_tpm and their
AES-256-CTR counterparts computed the block-aligned processing size
(sizeToEncrypt/sizeToDecrypt) by rounding data->length up to the next
16-byte boundary, but stored the result in a CK_BYTE. Two distinct bugs
followed from this:
- For a data->length whose rounded-up value exceeds 255, the 8-bit value
wraps around, so far fewer bytes than intended are processed while the
function still returns UA_STATUSCODE_GOOD.
- For any data->length that is not itself a multiple of 16, the rounded-up
size is larger than the caller's actual buffer. The PKCS#11 update calls
then read block-sized chunks directly from data->data past its end, and
the final loop that copies the result back writes that same rounded-up
size into data->data, overflowing the caller's allocation.
AES-CTR is a stream cipher and never needs block-aligned input. Fix this
by processing exactly data->length bytes throughout, using size_t/CK_ULONG
for all lengths instead of CK_BYTE: each PKCS#11 update call is chunked at
MAX_ENCRYPTION_SIZE bytes with a shorter final chunk when data->length is
not itself a multiple of it, operating on the caller's buffer directly
with no rounding, no padding, and no risk of the output exceeding the
caller's allocation.
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
Internal Vulnerability Advisory: open62541-SA-2026-0033
The OpenSSL HMAC-SHA1/SHA256 verify helpers compared the recomputed
MAC against the peer-supplied signature with UA_ByteString_equal(),
which reduces to a plain memcmp(). memcmp() carries no constant-time
guarantee and, on a byte-at-a-time implementation (the norm on the
small-footprint libcs open62541 is routinely built against), returns
as soon as it finds a differing byte -- a timing side channel a
remote peer can use to recover a valid MAC byte-by-byte.
Switch to UA_constantTimeEqual(), already used correctly by every
other symmetric SecurityPolicy (mbedTLS Basic128Rsa15, Basic256,
Basic256Sha256, Aes128Sha256RsaOaep). Since it takes a single length
and reads that many bytes from both buffers, first validate
signature->length against the expected MAC length, mirroring the
pattern used by the project's mbedTLS RSA policy Verify functions.
The digest length is fixed by the algorithm and policy, not attacker
data, so this early-return introduces no new timing side channel.
Internal Vulnerability Advisory: open62541-SA-2026-0038
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
encrypt_sp_pubsub_aes128ctr_tpm/decrypt_sp_pubsub_aes128ctr_tpm and their
AES-256-CTR counterparts computed the block-aligned processing size
(sizeToEncrypt/sizeToDecrypt) by rounding data->length up to the next
16-byte boundary, but stored the result in a CK_BYTE. Two distinct bugs
followed from this:
- For a data->length whose rounded-up value exceeds 255, the 8-bit value
wraps around, so far fewer bytes than intended are processed while the
function still returns UA_STATUSCODE_GOOD.
- For any data->length that is not itself a multiple of 16, the rounded-up
size is larger than the caller's actual buffer. The PKCS#11 update calls
then read block-sized chunks directly from data->data past its end, and
the final loop that copies the result back writes that same rounded-up
size into data->data, overflowing the caller's allocation.
AES-CTR is a stream cipher and never needs block-aligned input. Fix this
by processing exactly data->length bytes throughout, using size_t/CK_ULONG
for all lengths instead of CK_BYTE: each PKCS#11 update call is chunked at
MAX_ENCRYPTION_SIZE bytes with a shorter final chunk when data->length is
not itself a multiple of it, operating on the caller's buffer directly
with no rounding, no padding, and no risk of the output exceeding the
caller's allocation.
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
Internal Vulnerability Advisory: open62541-SA-2026-0033
gmtime and inet_ntoa return a pointer to a static buffer shared by all threads. The mbedTLS certificate creation now derives its validity dates from UA_DateTime_now/UA_DateTime_toStruct, which also keeps the expiry date in UTC (mktime interpreted the gmtime result as local time). Zephyr uses gmtime_r and lwIP ip4addr_ntoa_r.
Fixes#7997
Use the common name when available and fall back to the subject for CRLs and
certificates without a common name.
Check path formatting, propagate storage errors, and clean up trust-list data
on every exit path.
According to the OPC UA specification part 12 appendix F.1 all
certificate files should have a '.der' extension and all CRLs
should have a '.crl' extension in the file name.
UA_CertificateUtils_verifyApplicationUri() checked the certificate's Subject
Alternative Name URI field using a substring search: the function returned
success whenever the expected applicationURI appeared anywhere inside the
certificate's URI string.
An attacker who can present a certificate trusted by the peer (e.g. a
self-signed certificate added to the trust list, or any certificate issued by
a trusted CA) can craft a URI SAN of the form
"urn:attacker.example:padding:<legitimateURI>:suffix". The substring search
accepts this certificate as if it belonged to the legitimate application,
allowing impersonation of any OPC UA endpoint whose ApplicationUri is known.
OpenSSL backend: replace the UA_Bstrstr() call with a length-and-memcmp exact
match against the first URI SAN entry extracted by OpenSSL.
mbedTLS backend (< 3.4.0): replace the raw-blob UA_Bstrstr() call with a
minimal DER walker (verifySanUri) that locates the Subject Alternative Name
extension by OID (2.5.29.17), iterates the GeneralNames SEQUENCE, and
performs an exact length+memcmp comparison against each URI ([6] IMPLICIT
IA5String) GeneralName entry, using the existing named mbedTLS constants
for the tag class and SAN type instead of raw literals. mbedTLS >= 3.4.0
already used UA_String_equal() and is unaffected.
Internal Vulnerability Advisory: open62541-SA-2026-0019
Co-authored-by: Joerg Fischer <joerg.fischer@sick.de>
Thanks-to: Chuan Wei [SCU] for the responsible disclosure.
The filestore security policy derives the filename for a certificate
written by ServerConfiguration.UpdateCertificate from the certificate's
Subject Common Name. On the mbedTLS backend, the subject-name renderer
preserves path separator characters found in the CN field, so a
certificate with a CN such as "../../trusted/certs/x" or containing
backslashes causes the resulting .der/.key files to be written outside
the intended own/certs and own/private directories once ApplyChanges is
called, including into other parts of the certificate store.
createCertName() now sanitizes the subject-name substring used to build
the filename: '/', '\', and control characters are replaced with '_'
before the string is combined with the certificate thumbprint. Since the
sanitized value can no longer contain a path separator, it cannot be used
to address any file outside the fixed own/certs and own/private
directories, independent of how a given crypto backend renders the
subject string.
Internal Vulnerability Advisory: open62541-SA-2026-0016
Thanks-to: Chuan Wei [SCU] for the responsible disclosure.
Pass the certificate and output subject in the documented order. The reversed arguments caused the mbedTLS implementation to clear the decoded certificate buffer before request cleanup, leaking the original allocation.
Add a backend-neutral API for reading certificate extended key usage.
Jörg Fischer of SICK AG reported the EKU validation gap and supplied the
negative-test PKI stores that motivated this API.
Co-authored-by: Jörg Fischer <joerg.fischer@sick.de>
getCertFileName() extracts the certificate subject's CN field via
strstr() and inserts it directly into the rejected-certificate file
path without sanitization or truncation at the trailing RDN comma. A
remote peer presenting a certificate with a subject such as
CN=../../tmp/evil can make the FileCertStore plugin write the
rejected certificate DER outside the configured rejected-certificate
directory, into any existing directory reachable via the traversal
sequence. The untruncated CN also lets subsequent RDN components leak
into the generated filename.
Truncate subName at the first ',' to drop trailing RDN components,
and replace '/' and '\' in the extracted CN with '_' before it is
used to build the file path.
Internal Vulnerability Advisory: open62541-SA-2026-0011
Co-authored-by: Tomer Dricker <tomerdricker@microsoft.com>