feat(plugin): allow custom password validation

Username password validation in access control plugin allows only
passwords stored in plaintext.  Add a callback to implement custom
validation.  This allows salted password hashes and timing safe
comparison in the server.  Furthermore remote validation could be
implemented.  The new function UA_AccessControl_defaultWithLoginCallback()
calls existing UA_AccessControl_default() and after that installs
the given UA_UsernamePasswordLoginCallback with context.
This commit is contained in:
Alexander Bluhm 2023-08-07 20:32:06 +02:00 committed by Julius Pfrommer
parent 1186cfd559
commit f4b481276a
2 changed files with 49 additions and 4 deletions

View File

@ -18,6 +18,11 @@ typedef struct {
UA_String password;
} UA_UsernamePasswordLogin;
typedef UA_StatusCode (*UA_UsernamePasswordLoginCallback)
(const UA_String *userName, const UA_ByteString *password,
size_t usernamePasswordLoginSize, const UA_UsernamePasswordLogin
*usernamePasswordLogin, void **sessionContext, void *loginContext);
/* Default access control. The log-in can be anonymous or username-password. A
* logged-in user has all access rights.
*
@ -31,6 +36,16 @@ UA_AccessControl_default(UA_ServerConfig *config,
size_t usernamePasswordLoginSize,
const UA_UsernamePasswordLogin *usernamePasswordLogin);
UA_EXPORT UA_StatusCode
UA_AccessControl_defaultWithLoginCallback(UA_ServerConfig *config,
UA_Boolean allowAnonymous,
UA_CertificateVerification *verifyX509,
const UA_ByteString *userTokenPolicyUri,
size_t usernamePasswordLoginSize,
const UA_UsernamePasswordLogin *usernamePasswordLogin,
UA_UsernamePasswordLoginCallback loginCallback,
void *loginContext);
_UA_END_DECLS
#endif /* UA_ACCESSCONTROL_DEFAULT_H_ */

View File

@ -21,6 +21,8 @@ typedef struct {
UA_Boolean allowAnonymous;
size_t usernamePasswordLoginSize;
UA_UsernamePasswordLogin *usernamePasswordLogin;
UA_UsernamePasswordLoginCallback loginCallback;
void *loginContext;
UA_CertificateVerification verifyX509;
} AccessControlContext;
@ -95,11 +97,18 @@ activateSession_default(UA_Server *server, UA_AccessControl *ac,
/* Try to match username/pw */
UA_Boolean match = false;
for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) {
if(UA_String_equal(&userToken->userName, &context->usernamePasswordLogin[i].username) &&
UA_String_equal(&userToken->password, &context->usernamePasswordLogin[i].password)) {
if(context->loginCallback) {
if(context->loginCallback(&userToken->userName, &userToken->password,
context->usernamePasswordLoginSize, context->usernamePasswordLogin,
sessionContext, context->loginContext) == UA_STATUSCODE_GOOD)
match = true;
break;
} else {
for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) {
if(UA_String_equal(&userToken->userName, &context->usernamePasswordLogin[i].username) &&
UA_String_equal(&userToken->password, &context->usernamePasswordLogin[i].password)) {
match = true;
break;
}
}
}
if(!match)
@ -399,3 +408,24 @@ UA_AccessControl_default(UA_ServerConfig *config,
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_AccessControl_defaultWithLoginCallback(UA_ServerConfig *config,
UA_Boolean allowAnonymous, UA_CertificateVerification *verifyX509,
const UA_ByteString *userTokenPolicyUri, size_t usernamePasswordLoginSize,
const UA_UsernamePasswordLogin *usernamePasswordLogin,
UA_UsernamePasswordLoginCallback loginCallback, void *loginContext)
{
AccessControlContext *context;
UA_StatusCode sc;
sc = UA_AccessControl_default(config, allowAnonymous, verifyX509,
userTokenPolicyUri, usernamePasswordLoginSize, usernamePasswordLogin);
if (sc != UA_STATUSCODE_GOOD)
return sc;
context = (AccessControlContext *)config->accessControl.context;
context->loginCallback = loginCallback;
context->loginContext = loginContext;
return UA_STATUSCODE_GOOD;
}