fix(server): offer user tokens on #None only if they can be secured

Without encryption support, the #None endpoint offered x509 and password
tokens that ActivateSession always rejects. The endpoint check now runs in
every build. It removes x509 tokens without the password warning.
This commit is contained in:
Andreas Ebner
2026-10-01 23:35:03 +02:00
committed by Julius Pfrommer
parent 8572ffcd16
commit bf8d24e0cf
3 changed files with 130 additions and 8 deletions
+10 -8
View File
@@ -404,11 +404,11 @@ updateEndpointUserIdentityToken(UA_Server *server,
utp = &ed->userIdentityTokens[ed->userIdentityTokensSize - 1];
UA_String_clear(&utp->securityPolicyUri);
#ifdef UA_ENABLE_ENCRYPTION
/* Anonymous tokens don't need encryption. All other tokens require
* encryption with the exception of Username/Password if also the
* allowNonePolicyPassword option has been set. The same logic is used
* in selectEndpointAndTokenPolicy (ua_services_session.c). */
* in selectEndpointAndTokenPolicy (ua_services_session.c), which
* rejects the tokens removed here. */
if(utp->tokenType != UA_USERTOKENTYPE_ANONYMOUS &&
UA_String_equal(&ed->securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) &&
(!sc->allowNonePolicyPassword || utp->tokenType != UA_USERTOKENTYPE_USERNAME)) {
@@ -420,11 +420,14 @@ updateEndpointUserIdentityToken(UA_Server *server,
else
encSP = getSecurityPolicyByUri(server, &ed->securityPolicyUri);
if(!encSP) {
/* No encrypted SecurityPolicy available */
UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT,
"Removing a UserTokenPolicy that would allow the "
"password to be transmitted without encryption "
"(Can be enabled via config->allowNonePolicyPassword)");
/* No encrypted SecurityPolicy available. An x509 token
* carries no secret and is offered by default with a session
* PKI. So it is removed without a warning. */
if(utp->tokenType != UA_USERTOKENTYPE_CERTIFICATE)
UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT,
"Removing a UserTokenPolicy that would allow the "
"password to be transmitted without encryption "
"(Can be enabled via config->allowNonePolicyPassword)");
UA_StatusCode res2 =
UA_Array_resize((void **)&ed->userIdentityTokens,
&ed->userIdentityTokensSize,
@@ -435,7 +438,6 @@ updateEndpointUserIdentityToken(UA_Server *server,
}
res |= UA_String_copy(&encSP->policyUri, &utp->securityPolicyUri);
}
#endif
/* Append the SecurityMode and SecurityPolicy postfix to the PolicyId to
* make it unique */
+57
View File
@@ -10,6 +10,7 @@
#include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/transport_generated.h>
#include <open62541/plugin/accesscontrol_default.h>
#include "server/ua_server_internal.h"
#include "server/ua_services.h"
@@ -139,6 +140,61 @@ START_TEST(checkGetLifecycleState) {
ck_assert_int_eq(state, UA_LIFECYCLESTATE_STOPPED);
} END_TEST
static size_t
countNoneEndpointTokens(UA_UserTokenType tokenType) {
UA_GetEndpointsRequest request;
UA_GetEndpointsRequest_init(&request);
request.endpointUrl = UA_STRING("opc.tcp://localhost:4840");
UA_GetEndpointsResponse response;
UA_GetEndpointsResponse_init(&response);
lockServer(server);
Service_GetEndpoints(server, &server->adminSession, &request, &response);
unlockServer(server);
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
size_t count = 0;
for(size_t i = 0; i < response.endpointsSize; i++) {
const UA_EndpointDescription *ep = &response.endpoints[i];
if(!UA_String_equal(&ep->securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI))
continue;
for(size_t j = 0; j < ep->userIdentityTokensSize; j++) {
if(ep->userIdentityTokens[j].tokenType == tokenType)
count++;
}
}
UA_GetEndpointsResponse_clear(&response);
return count;
}
/* Without an encrypting SecurityPolicy, ActivateSession rejects x509 and
* password tokens on #None. So the #None endpoint does not offer them. */
START_TEST(checkNoneEndpointUserTokens) {
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_UsernamePasswordLogin login = {UA_STRING_STATIC("user"),
UA_STRING_STATIC("password")};
ck_assert_uint_eq(UA_AccessControl_default(config, true, NULL, 1, &login),
UA_STATUSCODE_GOOD);
/* The AccessControl offers x509, as the session PKI accepts all */
UA_Boolean haveX509 = false;
for(size_t i = 0; i < config->accessControl.userTokenPoliciesSize; i++) {
if(config->accessControl.userTokenPolicies[i].tokenType ==
UA_USERTOKENTYPE_CERTIFICATE)
haveX509 = true;
}
ck_assert(haveX509);
config->allowNonePolicyPassword = false;
ck_assert_uint_gt(countNoneEndpointTokens(UA_USERTOKENTYPE_ANONYMOUS), 0);
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_USERNAME), 0);
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_CERTIFICATE), 0);
/* Passwords can be allowed without encryption, x509 tokens cannot */
config->allowNonePolicyPassword = true;
ck_assert_uint_gt(countNoneEndpointTokens(UA_USERTOKENTYPE_USERNAME), 0);
ck_assert_uint_eq(countNoneEndpointTokens(UA_USERTOKENTYPE_CERTIFICATE), 0);
} END_TEST
/* ---- Additional coverage tests ---- */
START_TEST(checkGetDataTypes) {
@@ -767,6 +823,7 @@ int main(void) {
tcase_add_test(tc_call, checkServer_run);
tcase_add_test(tc_call, helloEndpointUrlLimit);
tcase_add_test(tc_call, helloTrailingData);
tcase_add_test(tc_call, checkNoneEndpointUserTokens);
suite_add_tcase(s, tc_call);
TCase *tc_ext = tcase_create("server - extended");
+63
View File
@@ -6,6 +6,8 @@
#include <open62541/plugin/accesscontrol_default.h>
#include <open62541/plugin/securitypolicy_default.h>
#include "server/ua_server_internal.h"
#include "server/ua_services.h"
#include "thread_wrapper.h"
#include "test_helpers.h"
#include "../encryption/certificates.h"
@@ -205,6 +207,63 @@ START_TEST(testServerCertificateInGetEndpoints) {
}
END_TEST
/* Each x509 token must have a SecurityPolicy other than #None, which its
* signature needs. */
static size_t
countCertificateTokens(UA_Server *s, const UA_String *securityPolicyUri) {
UA_GetEndpointsRequest request;
UA_GetEndpointsRequest_init(&request);
request.endpointUrl = UA_STRING("opc.tcp://localhost:4840");
UA_GetEndpointsResponse response;
UA_GetEndpointsResponse_init(&response);
lockServer(s);
Service_GetEndpoints(s, &s->adminSession, &request, &response);
unlockServer(s);
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
const UA_String noneUri = UA_STRING_STATIC(NONE_URI);
size_t count = 0;
for(size_t i = 0; i < response.endpointsSize; i++) {
UA_EndpointDescription *ep = &response.endpoints[i];
if(!UA_String_equal(&ep->securityPolicyUri, securityPolicyUri))
continue;
for(size_t j = 0; j < ep->userIdentityTokensSize; j++) {
UA_UserTokenPolicy *utp = &ep->userIdentityTokens[j];
if(utp->tokenType != UA_USERTOKENTYPE_CERTIFICATE)
continue;
const UA_String *tokenUri = (utp->securityPolicyUri.length > 0) ?
&utp->securityPolicyUri : &ep->securityPolicyUri;
ck_assert(!UA_String_equal(tokenUri, &noneUri));
count++;
}
}
UA_GetEndpointsResponse_clear(&response);
return count;
}
/* The endpoints choose the SecurityPolicy of the user tokens. So x509 is
* offered for a SecurityPolicy added after the AccessControl was set up. */
START_TEST(certificateTokenWithLaterSecurityPolicy) {
UA_Server *s = UA_Server_newForUnitTest();
ck_assert_ptr_ne(s, NULL);
UA_ServerConfig *config = UA_Server_getConfig(s);
UA_ByteString certificate = {CERT_DER_LENGTH, CERT_DER_DATA};
UA_ByteString privateKey = {KEY_DER_LENGTH, KEY_DER_DATA};
UA_StatusCode retval =
UA_ServerConfig_addSecurityPolicyBasic256Sha256(config, &certificate,
&privateKey);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_String secureUri = UA_STRING(BASIC256SHA256_URI);
retval = UA_ServerConfig_addEndpoint(config, secureUri,
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT);
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_String noneUri = UA_STRING(NONE_URI);
ck_assert_uint_gt(countCertificateTokens(s, &secureUri), 0);
ck_assert_uint_gt(countCertificateTokens(s, &noneUri), 0);
UA_Server_delete(s);
} END_TEST
int main(void) {
Suite *s = suite_create("Server GetEndpoints");
TCase *tc = tcase_create("ServerCertificate present in Endpoint");
@@ -212,6 +271,10 @@ int main(void) {
tcase_add_test(tc, testServerCertificateInGetEndpoints);
suite_add_tcase(s, tc);
TCase *tc_tokens = tcase_create("Certificate token policies");
tcase_add_test(tc_tokens, certificateTokenWithLaterSecurityPolicy);
suite_add_tcase(s, tc_tokens);
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);