Files
Niels Beier 5b7f5d1a53 test(crypto): add regression test for EccEncryptedSecret length check
Add encryption_malformed_ecc_usertoken_length, which sends an
ActivateSession request with a UserNameIdentityToken carrying an
EccEncryptedSecret whose Length field makes endOfSecret smaller than
the ECC_nistP384 signature size. Asserts the server responds with
BadIdentityTokenInvalid instead of the process being killed while
handling the request.

Internal Vulnerability Advisory: open62541-SA-2026-0043
Co-Authored-By: X1AOxiang <X1AOxiang@users.noreply.github.com>
2026-10-18 10:03:15 +02:00

837 lines
31 KiB
C

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
*/
/* Consolidated ECC encryption integration tests.
* Tests all ECC security profiles added by o6 Automation:
* ECC_nistP384, ECC_brainpoolP256r1, ECC_brainpoolP384r1,
* ECC_curve25519, ECC_curve448
*
* Each curve is tested with DER and PEM certificate formats. */
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/securitypolicy.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/accesscontrol_default.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include "client/ua_client_internal.h"
#include "ua_server_internal.h"
#include "ua_securechannel.h"
#include <stdio.h>
#include <stdlib.h>
#include "test_helpers.h"
#include "certificates.h"
#include "check.h"
#include "testing_clock.h"
#include "testing_networklayers.h"
#include "thread_wrapper.h"
/* --- Curve parameter table --- */
/* Constructor for the deprecated (non-AEAD) ECC SecurityPolicies. They are no
* longer part of the default policy set (OPC UA Part 7), so the test adds them
* explicitly. NULL for the AEAD curves (curve25519/curve448) that remain in the
* default set. */
typedef UA_StatusCode (*EccPolicyCtor)(UA_SecurityPolicy *,
const UA_ApplicationType,
const UA_ByteString,
const UA_ByteString,
const UA_Logger *);
typedef struct {
const char *name;
const char *policyUri;
UA_Byte *certDer; size_t certDerLen;
UA_Byte *keyDer; size_t keyDerLen;
UA_Byte *certPem; size_t certPemLen;
UA_Byte *keyPem; size_t keyPemLen;
EccPolicyCtor deprecatedCtor; /* non-NULL for the deprecated curves */
} EccCurveTestData;
static EccCurveTestData eccCurves[] = {
{"ECC_nistP384",
"http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384",
CERT_P384_DER_DATA, CERT_P384_DER_LENGTH,
KEY_P384_DER_DATA, KEY_P384_DER_LENGTH,
CERT_P384_PEM_DATA, CERT_P384_PEM_LENGTH,
KEY_P384_PEM_DATA, KEY_P384_PEM_LENGTH,
UA_SecurityPolicy_EccNistP384},
{"ECC_brainpoolP256r1",
"http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1",
CERT_BP256R1_DER_DATA, CERT_BP256R1_DER_LENGTH,
KEY_BP256R1_DER_DATA, KEY_BP256R1_DER_LENGTH,
CERT_BP256R1_PEM_DATA, CERT_BP256R1_PEM_LENGTH,
KEY_BP256R1_PEM_DATA, KEY_BP256R1_PEM_LENGTH,
UA_SecurityPolicy_EccBrainpoolP256r1},
{"ECC_brainpoolP384r1",
"http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1",
CERT_BP384R1_DER_DATA, CERT_BP384R1_DER_LENGTH,
KEY_BP384R1_DER_DATA, KEY_BP384R1_DER_LENGTH,
CERT_BP384R1_PEM_DATA, CERT_BP384R1_PEM_LENGTH,
KEY_BP384R1_PEM_DATA, KEY_BP384R1_PEM_LENGTH,
UA_SecurityPolicy_EccBrainpoolP384r1},
#ifdef UA_ENABLE_ENCRYPTION_OPENSSL
{"ECC_curve25519",
"http://opcfoundation.org/UA/SecurityPolicy#ECC_curve25519",
CERT_ED25519_DER_DATA, CERT_ED25519_DER_LENGTH,
KEY_ED25519_DER_DATA, KEY_ED25519_DER_LENGTH,
CERT_ED25519_PEM_DATA, CERT_ED25519_PEM_LENGTH,
KEY_ED25519_PEM_DATA, KEY_ED25519_PEM_LENGTH,
NULL},
{"ECC_curve448",
"http://opcfoundation.org/UA/SecurityPolicy#ECC_curve448",
CERT_ED448_DER_DATA, CERT_ED448_DER_LENGTH,
KEY_ED448_DER_DATA, KEY_ED448_DER_LENGTH,
CERT_ED448_PEM_DATA, CERT_ED448_PEM_LENGTH,
KEY_ED448_PEM_DATA, KEY_ED448_PEM_LENGTH,
NULL}
#endif
};
/* Add the active curve's deprecated SecurityPolicy to a server config (no-op
* for the AEAD curves that are part of the default set). */
static void
addDeprecatedServerPolicy(UA_ServerConfig *config, EccCurveTestData *c,
UA_ByteString cert, UA_ByteString key) {
if(!c->deprecatedCtor)
return;
config->securityPolicies = (UA_SecurityPolicy *)
UA_realloc(config->securityPolicies,
sizeof(UA_SecurityPolicy) * (config->securityPoliciesSize + 1));
c->deprecatedCtor(&config->securityPolicies[config->securityPoliciesSize],
UA_APPLICATIONTYPE_SERVER, cert, key, config->logging);
config->securityPoliciesSize++;
UA_ServerConfig_addAllEndpoints(config);
}
/* Add the active curve's deprecated SecurityPolicy to a client config. */
static void
addDeprecatedClientPolicy(UA_ClientConfig *cc, EccCurveTestData *c,
UA_ByteString cert, UA_ByteString key) {
if(!c->deprecatedCtor)
return;
cc->securityPolicies = (UA_SecurityPolicy *)
UA_realloc(cc->securityPolicies,
sizeof(UA_SecurityPolicy) * (cc->securityPoliciesSize + 1));
c->deprecatedCtor(&cc->securityPolicies[cc->securityPoliciesSize],
UA_APPLICATIONTYPE_CLIENT, cert, key, cc->logging);
cc->securityPoliciesSize++;
}
#define NUM_ECC_CURVES (sizeof(eccCurves) / sizeof(eccCurves[0]))
/* --- Shared state --- */
static UA_Server *server;
static UA_Boolean running;
static THREAD_HANDLE server_thread;
static size_t activeCurve;
THREAD_CALLBACK(serverloop) {
while(running)
UA_Server_run_iterate(server, true);
return 0;
}
static void setup_common(void) {
running = true;
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate;
certificate.length = c->certDerLen;
certificate.data = c->certDer;
UA_ByteString privateKey;
privateKey.length = c->keyDerLen;
privateKey.data = c->keyDer;
server = UA_Server_newForUnitTestWithSecurityPolicies(
4840, &certificate, &privateKey, NULL, 0, NULL, 0, NULL, 0);
ck_assert(server != NULL);
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_CertificateGroup_AcceptAll(&config->secureChannelPKI);
UA_CertificateGroup_AcceptAll(&config->sessionPKI);
UA_String_clear(&config->applicationDescription.applicationUri);
config->applicationDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
addDeprecatedServerPolicy(config, c, certificate, privateKey);
/* Add a writable test variable */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 initValue = 42;
UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_INT32]);
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_StatusCode addRes =
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 50000),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "TestVar"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, NULL);
ck_assert_uint_eq(addRes, UA_STATUSCODE_GOOD);
UA_Server_run_startup(server);
THREAD_CREATE(server_thread, serverloop);
}
/* Per-curve setup functions (set activeCurve before calling common setup) */
#define DEFINE_CURVE_SETUP(N) \
static void setup_curve_##N(void) { activeCurve = N; setup_common(); }
DEFINE_CURVE_SETUP(0)
DEFINE_CURVE_SETUP(1)
DEFINE_CURVE_SETUP(2)
#ifdef UA_ENABLE_ENCRYPTION_OPENSSL
DEFINE_CURVE_SETUP(3)
DEFINE_CURVE_SETUP(4)
#endif
static void (*curveSetups[])(void) = {
setup_curve_0, setup_curve_1, setup_curve_2,
#ifdef UA_ENABLE_ENCRYPTION_OPENSSL
setup_curve_3, setup_curve_4
#endif
};
static void teardown(void) {
running = false;
THREAD_JOIN(server_thread);
UA_Server_run_shutdown(server);
UA_Server_delete(server);
}
static void pauseServer(void) {
running = false;
THREAD_JOIN(server_thread);
}
static void runServer(void) {
running = true;
THREAD_CREATE(server_thread, serverloop);
}
/* Helper: create an encrypted client for the active curve using DER certs */
static UA_Client *
createEncryptedClient(void) {
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate;
certificate.length = c->certDerLen;
certificate.data = c->certDer;
UA_ByteString privateKey;
privateKey.length = c->keyDerLen;
privateKey.data = c->keyDer;
UA_Client *client = UA_Client_newForUnitTest();
UA_ClientConfig *cc = UA_Client_getConfig(client);
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
NULL, 0, NULL, 0);
cc->certificateVerification.clear(&cc->certificateVerification);
UA_CertificateGroup_AcceptAll(&cc->certificateVerification);
addDeprecatedClientPolicy(cc, c, certificate, privateKey);
cc->securityPolicyUri = UA_STRING_ALLOC(c->policyUri);
UA_String_clear(&cc->clientDescription.applicationUri);
cc->clientDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
return client;
}
/* --- Test: connect with DER certificate --- */
START_TEST(encryption_connect) {
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate;
certificate.length = c->certDerLen;
certificate.data = c->certDer;
UA_ByteString privateKey;
privateKey.length = c->keyDerLen;
privateKey.data = c->keyDer;
/* Discover endpoints */
UA_Client *client = UA_Client_newForUnitTest();
ck_assert(client != NULL);
UA_EndpointDescription *endpointArray = NULL;
size_t endpointArraySize = 0;
UA_StatusCode retval =
UA_Client_getEndpoints(client, "opc.tcp://localhost:4840",
&endpointArraySize, &endpointArray);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(endpointArraySize > 0);
UA_Array_delete(endpointArray, endpointArraySize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
UA_Client_delete(client);
/* Encrypted connect */
client = UA_Client_newForUnitTest();
UA_ClientConfig *cc = UA_Client_getConfig(client);
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
NULL, 0, NULL, 0);
cc->certificateVerification.clear(&cc->certificateVerification);
UA_CertificateGroup_AcceptAll(&cc->certificateVerification);
addDeprecatedClientPolicy(cc, c, certificate, privateKey);
cc->securityPolicyUri = UA_STRING_ALLOC(c->policyUri);
UA_String_clear(&cc->clientDescription.applicationUri);
cc->clientDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant val;
UA_Variant_init(&val);
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: connect with PEM certificate --- */
START_TEST(encryption_connect_pem) {
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate;
certificate.length = c->certPemLen;
certificate.data = c->certPem;
UA_ByteString privateKey;
privateKey.length = c->keyPemLen;
privateKey.data = c->keyPem;
/* Discover endpoints */
UA_Client *client = UA_Client_newForUnitTest();
ck_assert(client != NULL);
UA_EndpointDescription *endpointArray = NULL;
size_t endpointArraySize = 0;
UA_StatusCode retval =
UA_Client_getEndpoints(client, "opc.tcp://localhost:4840",
&endpointArraySize, &endpointArray);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(endpointArraySize > 0);
UA_Array_delete(endpointArray, endpointArraySize,
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
UA_Client_delete(client);
/* Encrypted connect */
client = UA_Client_newForUnitTest();
UA_ClientConfig *cc = UA_Client_getConfig(client);
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
NULL, 0, NULL, 0);
cc->certificateVerification.clear(&cc->certificateVerification);
UA_CertificateGroup_AcceptAll(&cc->certificateVerification);
UA_String_clear(&cc->clientDescription.applicationUri);
cc->clientDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
addDeprecatedClientPolicy(cc, c, certificate, privateKey);
cc->securityPolicyUri = UA_STRING_ALLOC(c->policyUri);
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant val;
UA_Variant_init(&val);
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: write and read back over encrypted channel --- */
START_TEST(encryption_connect_write) {
UA_Client *client = createEncryptedClient();
ck_assert(client != NULL);
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Write a value to the test variable */
UA_NodeId testVarId = UA_NODEID_NUMERIC(1, 50000);
UA_Int32 writeValue = 1234;
UA_Variant val;
UA_Variant_setScalar(&val, &writeValue, &UA_TYPES[UA_TYPES_INT32]);
retval = UA_Client_writeValueAttribute(client, testVarId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read back and verify */
UA_Variant readVal;
UA_Variant_init(&readVal);
retval = UA_Client_readValueAttribute(client, testVarId, &readVal);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(readVal.type == &UA_TYPES[UA_TYPES_INT32]);
ck_assert_int_eq(*(UA_Int32 *)readVal.data, 1234);
UA_Variant_clear(&readVal);
/* Write a different value */
writeValue = 5678;
UA_Variant_setScalar(&val, &writeValue, &UA_TYPES[UA_TYPES_INT32]);
retval = UA_Client_writeValueAttribute(client, testVarId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read back again */
UA_Variant_init(&readVal);
retval = UA_Client_readValueAttribute(client, testVarId, &readVal);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert_int_eq(*(UA_Int32 *)readVal.data, 5678);
UA_Variant_clear(&readVal);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: reconnect session over new SecureChannel --- */
START_TEST(encryption_reconnect) {
UA_Client *client = createEncryptedClient();
ck_assert(client != NULL);
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Initial read */
UA_Variant val;
UA_Variant_init(&val);
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
/* Remember the session token */
UA_NodeId oldAuthToken = client->authenticationToken;
/* Close the SecureChannel without closing the session */
UA_Client_disconnectSecureChannel(client);
/* Reconnect — reuses the previous session */
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read again — reactivates the session */
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
/* Session token must be the same */
ck_assert(UA_NodeId_equal(&oldAuthToken, &client->authenticationToken));
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: SecureChannel token renewal under encryption --- */
START_TEST(encryption_renew) {
UA_Client *client = createEncryptedClient();
ck_assert(client != NULL);
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_UInt32 channelId = client->channel.securityToken.channelId;
/* Pause the server thread so we can manually step client/server */
pauseServer();
/* Advance time to 80% of token lifetime to trigger renewal */
UA_fakeSleep((UA_UInt32)(client->channel.securityToken.revisedLifetime * 0.8));
UA_Client_run_iterate(client, 1);
UA_Server_run_iterate(server, false);
UA_Client_run_iterate(client, 1);
UA_Server_run_iterate(server, false);
/* Advance again to trigger a second renewal */
UA_fakeSleep((UA_UInt32)(client->channel.securityToken.revisedLifetime * 0.8));
UA_Client_run_iterate(client, 1);
UA_Server_run_iterate(server, false);
UA_Client_run_iterate(client, 1);
UA_Server_run_iterate(server, false);
/* Still the same channel */
ck_assert_uint_eq(channelId, client->channel.securityToken.channelId);
/* Resume the server thread */
runServer();
/* Read a value to verify the channel still works after renewal */
UA_Variant val;
UA_Variant_init(&val);
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: connect with Sign-only mode (exercises AEAD sign path
* for curve25519/448 and HMAC sign path for NIST/Brainpool) --- */
START_TEST(encryption_connect_sign_only) {
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate;
certificate.length = c->certDerLen;
certificate.data = c->certDer;
UA_ByteString privateKey;
privateKey.length = c->keyDerLen;
privateKey.data = c->keyDer;
UA_Client *client = UA_Client_newForUnitTest();
UA_ClientConfig *cc = UA_Client_getConfig(client);
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
NULL, 0, NULL, 0);
cc->certificateVerification.clear(&cc->certificateVerification);
UA_CertificateGroup_AcceptAll(&cc->certificateVerification);
addDeprecatedClientPolicy(cc, c, certificate, privateKey);
cc->securityPolicyUri = UA_STRING_ALLOC(c->policyUri);
cc->securityMode = UA_MESSAGESECURITYMODE_SIGN;
UA_String_clear(&cc->clientDescription.applicationUri);
cc->clientDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* Read a value to exercise the signed channel */
UA_Variant val;
UA_Variant_init(&val);
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE);
retval = UA_Client_readValueAttribute(client, nodeId, &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
/* Write to exercise sign path for larger payloads */
UA_NodeId testVarId = UA_NODEID_NUMERIC(1, 50000);
UA_Int32 writeValue = 9999;
UA_Variant wval;
UA_Variant_setScalar(&wval, &writeValue, &UA_TYPES[UA_TYPES_INT32]);
retval = UA_Client_writeValueAttribute(client, testVarId, &wval);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: CSR generation for ECC policies --- */
START_TEST(encryption_csr_generation) {
UA_ServerConfig *config = UA_Server_getConfig(server);
/* Find the active curve's security policy on the server */
EccCurveTestData *c = &eccCurves[activeCurve];
UA_SecurityPolicy *sp = NULL;
for(size_t i = 0; i < config->securityPoliciesSize; i++) {
UA_String policyUri = UA_STRING((char *)(uintptr_t)c->policyUri);
if(UA_String_equal(&config->securityPolicies[i].policyUri, &policyUri)) {
sp = &config->securityPolicies[i];
break;
}
}
ck_assert_ptr_ne(sp, NULL);
ck_assert(sp->createSigningRequest != NULL);
/* Generate a CSR without regenerating the private key */
UA_ByteString csr = UA_BYTESTRING_NULL;
UA_String subjectName = UA_STRING("CN=TestECC O=open62541");
UA_StatusCode retval = sp->createSigningRequest(sp, &subjectName,
NULL, NULL,
&csr, NULL);
/* Curve25519/448 may not support standard CSR generation */
if(retval != UA_STATUSCODE_GOOD) {
UA_ByteString_clear(&csr);
return;
}
ck_assert(csr.length > 0);
UA_ByteString_clear(&csr);
/* Generate a CSR with a new private key */
UA_ByteString newPrivKey = UA_BYTESTRING_NULL;
retval = sp->createSigningRequest(sp, &subjectName,
NULL, NULL,
&csr, &newPrivKey);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
ck_assert(csr.length > 0);
ck_assert(newPrivKey.length > 0);
UA_ByteString_clear(&csr);
UA_ByteString_clear(&newPrivKey);
}
END_TEST
/* --- Test: update certificate and private key on active ECC policy --- */
START_TEST(encryption_update_certificate) {
UA_ServerConfig *config = UA_Server_getConfig(server);
EccCurveTestData *c = &eccCurves[activeCurve];
UA_SecurityPolicy *sp = NULL;
for(size_t i = 0; i < config->securityPoliciesSize; i++) {
UA_String policyUri = UA_STRING((char *)(uintptr_t)c->policyUri);
if(UA_String_equal(&config->securityPolicies[i].policyUri, &policyUri)) {
sp = &config->securityPolicies[i];
break;
}
}
ck_assert_ptr_ne(sp, NULL);
ck_assert(sp->updateCertificate != NULL);
/* Update with the same cert and key (should succeed) */
UA_ByteString certificate;
certificate.length = c->certDerLen;
certificate.data = c->certDer;
UA_ByteString privateKey;
privateKey.length = c->keyDerLen;
privateKey.data = c->keyDer;
UA_StatusCode retval = sp->updateCertificate(sp, certificate, privateKey);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
/* The policy should still be functional after update — connect and read */
UA_Client *client = createEncryptedClient();
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant val;
UA_Variant_init(&val);
retval = UA_Client_readValueAttribute(client,
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE), &val);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Variant_clear(&val);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test: malformed EccEncryptedSecret user-token length (P-384) --- */
/* Same as setup_curve_0, but also registers a UserName login so the ECC
* endpoint advertises a UserName token policy - the malformed-length code
* path in decryptUserTokenEcc is only reached via that token type. */
static void setup_p384_usertoken(void) {
running = true;
activeCurve = 0;
EccCurveTestData *c = &eccCurves[activeCurve];
UA_ByteString certificate = {c->certDerLen, c->certDer};
UA_ByteString privateKey = {c->keyDerLen, c->keyDer};
server = UA_Server_newForUnitTestWithSecurityPolicies(
4840, &certificate, &privateKey, NULL, 0, NULL, 0, NULL, 0);
ck_assert(server != NULL);
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_CertificateGroup_AcceptAll(&config->secureChannelPKI);
UA_CertificateGroup_AcceptAll(&config->sessionPKI);
UA_String_clear(&config->applicationDescription.applicationUri);
config->applicationDescription.applicationUri =
UA_STRING_ALLOC("urn:unconfigured:application");
addDeprecatedServerPolicy(config, c, certificate, privateKey);
UA_UsernamePasswordLogin login = {UA_STRING_STATIC("user"), UA_STRING_STATIC("pass")};
UA_AccessControl_default(config, true, NULL, 1, &login);
UA_Server_run_startup(server);
THREAD_CREATE(server_thread, serverloop);
}
/* Replicates the client's ActivateSession signature computation so the
* server accepts the (otherwise malformed) request up to where it parses
* the token. */
static UA_StatusCode
computeClientSignatureForTest(UA_Client *client, UA_SignatureData *sd) {
UA_SecureChannel *channel = &client->channel;
const UA_SecurityPolicy *sp = channel->securityPolicy;
void *cc = channel->channelContext;
const UA_SecurityPolicySignatureAlgorithm *signAlg = &sp->asymSignatureAlgorithm;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(!UA_SecurityPolicy_isEnhancedSecurity(sp))
retval = UA_String_copy(&signAlg->uri, &sd->algorithm);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = UA_ByteString_allocBuffer(&sd->signature, signAlg->getLocalSignatureSize(sp, cc));
if(retval != UA_STATUSCODE_GOOD)
return retval;
if(UA_SecurityPolicy_isEnhancedSecurity(sp)) {
UA_ByteString signData = UA_BYTESTRING_NULL;
retval = UA_SecureChannel_buildActivateSessionSignatureData(
channel, &client->serverSessionNonce, &client->clientSessionNonce,
&channel->remoteCertificate, &channel->remoteCertificate,
&sp->localCertificate, &signData);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = signAlg->sign(sp, cc, &signData, &sd->signature);
UA_ByteString_clear(&signData);
return retval;
}
UA_ByteString leaf = getLeafCertificate(channel->remoteCertificate);
UA_ByteString signData;
signData.length = leaf.length + client->serverSessionNonce.length;
signData.data = (UA_Byte *)UA_malloc(signData.length);
if(!signData.data)
return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(signData.data, leaf.data, leaf.length);
memcpy(signData.data + leaf.length, client->serverSessionNonce.data,
client->serverSessionNonce.length);
retval = signAlg->sign(sp, cc, &signData, &sd->signature);
UA_ByteString_clear(&signData);
return retval;
}
/* Builds a structurally-consistent EccEncryptedSecret whose Length field (80)
* makes endOfSecret = 89, less than the P-384 signature size (96). Before the
* fix, decryptUserTokenEcc computes endOfSecret - sigLen for the
* signature-verify length, underflowing to ~2^64 and reading off the end of
* the buffer. After the fix, the length is rejected before that subtraction. */
static void
buildMalformedEccSecret(UA_ByteString *out) {
static const unsigned char typeId[] = {0x01, 0x00, 0x8A, 0x44}; /* i=17546 */
const char *policyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384";
size_t uriLen = strlen(policyUri);
size_t off = 0;
size_t total = 89;
UA_ByteString_allocBuffer(out, total);
memcpy(out->data + off, typeId, 4); off += 4;
out->data[off++] = 0x01; /* encodingMask */
out->data[off++] = 80; out->data[off++] = 0; out->data[off++] = 0; out->data[off++] = 0;
out->data[off++] = (UA_Byte)(uriLen & 0xFF); out->data[off++] = (UA_Byte)((uriLen >> 8) & 0xFF);
out->data[off++] = 0; out->data[off++] = 0;
memcpy(out->data + off, policyUri, uriLen); off += uriLen;
out->data[off++] = 0xFF; out->data[off++] = 0xFF; out->data[off++] = 0xFF; out->data[off++] = 0xFF;
memset(out->data + off, 0, 8); off += 8;
out->data[off++] = 0; out->data[off++] = 0;
memset(out->data + off, 0, total - off);
}
START_TEST(encryption_malformed_ecc_usertoken_length) {
UA_Client *client = createEncryptedClient();
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_ActivateSessionRequest req;
UA_ActivateSessionRequest_init(&req);
UA_NodeId_copy(&client->authenticationToken, &req.requestHeader.authenticationToken);
retval = computeClientSignatureForTest(client, &req.clientSignature);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_UserNameIdentityToken *unit = UA_UserNameIdentityToken_new();
unit->policyId =
UA_STRING_ALLOC("open62541-username-policy-sign+encrypt#ECC_nistP384");
unit->userName = UA_STRING_ALLOC("attacker");
buildMalformedEccSecret(&unit->password);
req.userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
req.userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN];
req.userIdentityToken.content.decoded.data = unit;
UA_ActivateSessionResponse resp;
UA_ActivateSessionResponse_init(&resp);
__UA_Client_Service(client, &req, &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST],
&resp, &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE]);
/* The malformed length must be rejected with a defined StatusCode. Prior
* to the fix, the server crashes while processing this request instead
* of returning a response. */
ck_assert_uint_eq(resp.responseHeader.serviceResult, UA_STATUSCODE_BADIDENTITYTOKENINVALID);
UA_ActivateSessionRequest_clear(&req);
UA_ActivateSessionResponse_clear(&resp);
UA_Client_disconnect(client);
UA_Client_delete(client);
}
END_TEST
/* --- Test suite: one TCase per curve --- */
static Suite* testSuite_encryption(void) {
Suite *s = suite_create("Encryption ECC");
for(size_t i = 0; i < NUM_ECC_CURVES; i++) {
TCase *tc = tcase_create(eccCurves[i].name);
tcase_add_checked_fixture(tc, curveSetups[i], teardown);
#ifdef UA_ENABLE_ENCRYPTION
tcase_add_test(tc, encryption_connect);
tcase_add_test(tc, encryption_connect_pem);
tcase_add_test(tc, encryption_connect_sign_only);
tcase_add_test(tc, encryption_csr_generation);
/* These exercise generic SecureChannel/Session behavior and certificate
* update handling. Running them for every ECC curve adds many expensive
* full handshakes without increasing curve-specific coverage. */
if(i == 0) {
tcase_add_test(tc, encryption_connect_write);
tcase_add_test(tc, encryption_reconnect);
tcase_add_test(tc, encryption_renew);
tcase_add_test(tc, encryption_update_certificate);
}
#endif /* UA_ENABLE_ENCRYPTION */
suite_add_tcase(s, tc);
}
#ifdef UA_ENABLE_ENCRYPTION
/* Separate TCase: needs a UserName login registered on the server, unlike
* the plain per-curve fixture used above. */
TCase *tcUserToken = tcase_create("ECC_nistP384 malformed user token");
tcase_add_checked_fixture(tcUserToken, setup_p384_usertoken, teardown);
tcase_add_test(tcUserToken, encryption_malformed_ecc_usertoken_length);
suite_add_tcase(s, tcUserToken);
#endif /* UA_ENABLE_ENCRYPTION */
return s;
}
int main(void) {
Suite *s = testSuite_encryption();
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
int number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}