Files
open62541/tests/server/check_server_password.c
Julius Pfrommer 335cead676 test(mt): synchronize remaining threaded fixture state
Use pointer-sized atomic run flags in threaded fixtures, including dynamically
allocated discovery and browse flags. Pointer sizing supports the MSVC atomic
implementation as well as C11 and GCC atomics.

Serialize the client error-injection test with the server network callbacks
before accessing the internal session and shared send buffer.
2026-09-21 16:23:09 +02:00

259 lines
8.4 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/. */
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/plugin/certificategroup.h>
#include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/accesscontrol_default.h>
#include "test_helpers.h"
#include "thread_wrapper.h"
#if defined(__OpenBSD__) || defined(__linux__)
#include <pwd.h>
#include <unistd.h>
#endif
#include <stdlib.h>
#include <check.h>
UA_Server *server;
UA_atomic(uintptr_t) running;
THREAD_HANDLE server_thread;
#if defined(__OpenBSD__)
static UA_StatusCode
loginCallback(const UA_String *userName, const UA_ByteString *password,
size_t loginSize, const UA_UsernamePasswordLogin *loginList,
void **sessionContext, void *loginContext) {
char *pass;
size_t i;
int userok = 0, passok = 0;
/* UA_ByteString has no terminating NUL byte */
pass = UA_malloc(password->length + 1);
if (pass == NULL)
return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(pass, password->data, password->length);
pass[password->length] = '\0';
/* Always run through full loop to avoid timing attack. */
for (i = 0; i < loginSize; i++, loginList++) {
char hash[_PASSWORD_LEN + 1];
size_t hashlen;
if (userName->length == loginList->username.length &&
timingsafe_bcmp(userName->data, loginList->username.data,
userName->length) == 0)
userok = 1;
else
continue;
/* UA_String has no terminating NUL byte */
hashlen = loginList->password.length < _PASSWORD_LEN ?
loginList->password.length : _PASSWORD_LEN;
memcpy(hash, loginList->password.data, hashlen);
hash[hashlen] = '\0';
if (crypt_checkpass(pass, hash) == 0)
passok = 1;
}
/* Do some work if user does not match to avoid user guessing. */
if (!userok)
crypt_checkpass(pass, NULL);
UA_free(pass);
return passok ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADUSERACCESSDENIED;
}
#endif
#if defined(__linux__)
static UA_StatusCode
loginCallback(const UA_String *userName, const UA_ByteString *password,
size_t loginSize, const UA_UsernamePasswordLogin *loginList,
void **sessionContext, void *loginContext)
{
const char *id = (char *)loginContext;
char *pass;
size_t i;
int userok = 0, passok = 0;
/* UA_ByteString has no terminating NUL byte */
pass = (char *)UA_malloc(password->length + 1);
if (pass == NULL)
return UA_STATUSCODE_BADOUTOFMEMORY;
memcpy(pass, password->data, password->length);
pass[password->length] = '\0';
/* Always run through full loop to avoid timing attack. */
for (i = 0; i < loginSize; i++, loginList++) {
const char *hash;
char salt[1000];
int dollar = 0;
if (userName->length == loginList->username.length &&
memcmp(userName->data, loginList->username.data,
userName->length) == 0)
userok = 1;
else
continue;
/* Check if algorithm has required strength. */
if (memcmp(id, loginList->password.data, strlen(id)) != 0)
continue;
for (i = 0; i < sizeof(salt) - 1 && i < loginList->password.length; i++) {
if (dollar == 3)
break;
salt[i] = (char)loginList->password.data[i];
if (salt[i] == '$')
dollar++;
}
salt[i] = '\0';
hash = crypt(pass, salt);
ck_assert_msg(hash, "crypt");
if (memcmp(hash, loginList->password.data,
loginList->password.length) == 0)
passok = 1;
}
/* Do some work if user does not match to avoid user guessing. */
if (!userok)
crypt(pass, id);
UA_free(pass);
return passok ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADUSERACCESSDENIED;
}
#endif
THREAD_CALLBACK(serverloop) {
while(UA_atomic_load(&running))
UA_Server_run_iterate(server, true);
return 0;
}
static void setup(void) {
UA_atomic_store(&running, true);
server = UA_Server_newForUnitTest();
ck_assert_msg(server, "UA_Server_new");
UA_ServerConfig *config = UA_Server_getConfig(server);
config->allowNonePolicyPassword = true;
UA_String policy = UA_STRING_STATIC("http://opcfoundation.org/UA/SecurityPolicy#None");
UA_UsernamePasswordLogin login[] = {
{ UA_STRING_STATIC("user"),
#if defined(__OpenBSD__)
UA_STRING_STATIC("$2b$08$nz828OX4t7a6Sg8JO/0GnO/bcfY0UyBmlAwkvIGE9ZaBq.0n2tkoS"),
#elif defined(__linux__)
UA_STRING_STATIC("$6$uogBj0wZGItfBChT$jp2zOMGXvC0Jr2GxbYcAcuw2eBpuveezwuOB5EjX/QAXurprMGoG8W7/WKiic0utw0xnJ16tjtFS2UtDmeoYj0"),
#else
UA_STRING_STATIC("pass"),
#endif
},
};
#if defined(__OpenBSD__) || defined(__linux__)
UA_AccessControl_defaultWithLoginCallback(config, false, &policy,
sizeof(login) / sizeof(login[0]), login, loginCallback, "$6$");
#else
UA_AccessControl_default(config, false, &policy,
sizeof(login) / sizeof(login[0]), login);
#endif
UA_Server_run_startup(server);
THREAD_CREATE(server_thread, serverloop);
}
static void teardown(void) {
UA_atomic_store(&running, false);
THREAD_JOIN(server_thread);
UA_Server_run_shutdown(server);
UA_Server_delete(server);
}
static void
ClientConfig_setUsernamePassword(UA_ClientConfig *config,
const UA_String *userName, const UA_String *password)
{
UA_UserNameIdentityToken *identityToken;
UA_ExtensionObject_clear(&config->userIdentityToken);
UA_UserTokenPolicy_clear(&config->userTokenPolicy);
UA_EndpointDescription_clear(&config->endpoint);
identityToken = UA_UserNameIdentityToken_new();
ck_assert_msg(identityToken, "UA_UserNameIdentityToken_new");
config->userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
config->userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN];
config->userIdentityToken.content.decoded.data = identityToken;
UA_String_copy(userName, &identityToken->userName);
UA_String_copy(password, &identityToken->password);
}
START_TEST(Password_good) {
UA_Client *client = UA_Client_newForUnitTest();
ck_assert_msg(client, "UA_Client_new");
UA_ClientConfig *config = UA_Client_getConfig(client);
UA_String user = UA_STRING_STATIC("user");
UA_String pass = UA_STRING_STATIC("pass");
ClientConfig_setUsernamePassword(config, &user, &pass);
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
UA_Client_disconnect(client);
UA_Client_delete(client);
} END_TEST
START_TEST(Password_bad) {
UA_Client *client = UA_Client_newForUnitTest();
ck_assert_msg(client, "UA_Client_new");
UA_ClientConfig *config = UA_Client_getConfig(client);
UA_String user = UA_STRING_STATIC("user");
UA_String pass = UA_STRING_STATIC("bad");
ClientConfig_setUsernamePassword(config, &user, &pass);
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_BADUSERACCESSDENIED);
UA_Client_disconnect(client);
UA_Client_delete(client);
} END_TEST
START_TEST(Password_none) {
UA_Client *client = UA_Client_newForUnitTest();
ck_assert_msg(client, "UA_Client_new");
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
ck_assert_uint_eq(retval, UA_STATUSCODE_BADIDENTITYTOKENREJECTED);
UA_Client_disconnect(client);
UA_Client_delete(client);
} END_TEST
static Suite* testSuite_Password(void) {
Suite *s = suite_create("Password");
TCase *tc = tcase_create("Core");
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, Password_good);
tcase_add_test(tc, Password_bad);
tcase_add_test(tc, Password_none);
suite_add_tcase(s, tc);
return s;
}
int main(void) {
int number_failed = 0;
Suite *s;
SRunner *sr;
s = testSuite_Password();
sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr,CK_NORMAL);
number_failed += srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}