Files
open62541/tests/server/check_server_password.c

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_Boolean 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] = 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(running)
UA_Server_run_iterate(server, true);
return 0;
}
static void setup(void) {
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) {
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;
}