High Performance OPC UA Server SDK  1.7.1.383
Authentication

Overview

This document describes how the identity of clients is checked, this is independent how access rights of nodes are checked which is handled in authorization.

The authentication is implemented in the authentication backend, which can be selected through cmake by the option UA_AUTHENTICATION_BACKEND. It defaults to internal which is the implementation included in the SDK and will be described further in the following sections.

Another included option is null which will disable the backend and disable authentication, so only clients with an AnonymousIdentityToken will be allowed access to the server. As there is no access control to this backend is generally not recommended and should only be used where public access to the server is impossible and the complete network infrastructure and devices are under full control of the administrator.

It is also possible for customers to completely implement their own backend, that requires to add it to src/uaserver/CMakeLists.txt and implement the interface given by src/uaserver/session/authentication.h described at authentication. However this would be some rather extensive work, so if possible the callbacks provided by the internal backend as described further below in this document should be used.

Authentication with Usernames and Passwords

The internal backend stores usernames and passwords and verifies the credentials provided by clients against these. Passwords can either be stored as cleartext or as hash with additional salt. It is recommended to generate these hashes with the UA Password Manager. For customers with their own tooling it is also possible to generate these in their own applications by generating a salt and concatenating the salt, username and password and generating the hash in the following way:

<hashed password> = to_hex(sha256(<salt><username><password>))

Loading from Filesystem

At the start of the server users and passwords can be imported from file, in the examples this is the passwd file. Again it is recommended to use UA Password Manager for managing that file, the format of the file can have the following forms, where one line represents one user:

Cleartext:

<username>:cleartext:<password>
john:cleartext:password1

Hashed:

<username>:<hash>:<salt>:<hashed password>
john:sha256:wZa9AHGxXAZ6vjkL:8650866a3e667b247321ea479183e5cada8257112183b6fdd09c70e6da4b5c58

The username, salt and cleartext password are limited to printable characters except ':' and '#'. The hashed password is stored as hexadecimal string, so the password itself may contain any characters.

Loading from Code

If using a filesytem is no appropiate solution, users can be added directly within the code at server startup or during runtime. As cleartext passwords can be extracted from the built executable, it is recommended to use hashed also in the code. The salt and hash can be generated with the -n option of the uapasswd tool and given to ua_authentication_add_user.

#include <uaserver/session/authentication/internal/internal_authentication.h>

int ret;

// add user with cleartext password
ret = ua_authentication_add_user(UA_AUTH_HASH_NONE, "john", NULL, "password1");
if (ret != 0) goto error;

// add user with hashed password (note: the same user cannot be added twice)
ret = ua_authentication_add_user(UA_AUTH_HASH_SHA256, "john", "wZa9AHGxXAZ6vjkL", "8650866a3e667b247321ea479183e5cada8257112183b6fdd09c70e6da4b5c58");
if (ret != 0) goto error;

Authentication with Certificates

The SDK allows users to authenticate with a X.509 Certificate if the respective user token policy is configured and the certificate verifies against the PKI store configured for user authentication by the setting session.authentication_store. The further mapping of the client to a user/role is done by the authorization module.

Implementing Callbacks

An easy way to influence the authentication process is implementing the callbacks offered by the internal backend, this is possible for the UserNameIdentityToken and the IssuedIdentityToken. Both callbacks can be either finished asynchronous or synchronous by calling the finish function directly, more details regarding the functions can be found at the internal backend description.

Username/Password

For customers who might want to check users against their own database or some authentication service, the username/password check can be replaced by their own implementation. This is a simple example how to use the callback with a single hardcoded user and password. As mentioned above, the password could be extracted from the executable, so this is for demonstration of the callback only.

#include <uaserver/session/authentication/internal/callbacks.h>
#include <uaserver/session/authentication/internal/authentication_username.h>

static int example_check_password(struct ua_authentication_username_pw_info *in,
                                  struct uasession_user_info *out,
                                  void *auth_ctx)
{
    if (ua_string_compare_const(&in->username, "john") == 0) {

        if (ua_bytestring_compare_data(&in->password, "password123", 11) == 0) {
            // username and password verification succeeded
            out->result = 0;
        } else {
            TRACE_ERROR(TRACE_FAC_APPLICATION, "%s: wrong password\n", __func__);
            out->result = UA_SCBADUSERACCESSDENIED;
        }
    } else {
        TRACE_ERROR(TRACE_FAC_APPLICATION, "%s: unkown user\n", __func__);
        out->result = UA_SCBADUSERACCESSDENIED;
    }

    ua_authentication_finish_username_pw_check(auth_ctx);
    return 0;
}

// e.g. during provider init set the callback
ua_authentication_set_username_pw_callback(example_check_password);

IssuedToken

The issued token allows different tokens in different formats, currently the SDK does not have builtin support for any specific token, so if an issued token is to be used this callback must be implemented. This includes decrypting the token and verifing against the configured token, which is specific for each token type, so this example only shows how to set the result of the token.

#include <uaserver/session/authentication/internal/callbacks.h>
#include <uaserver/session/authentication/internal/authentication_issuedtoken.h>

static int example_check_issuedtoken(struct ua_authentication_issuedtoken_info *in,
                                     struct uasession_user_info *out,
                                     void *auth_ctx)
{
    struct ua_string *roles = NULL;
    int ret;

    // a real implementation would need to decrypt, parse and verify the provided token
    UA_UNUSED(in);

    // set roles extracted from token
    roles = ipc_calloc(sizeof(struct ua_string), 2);
    if (roles == NULL) goto out;
    ret = ua_string_set(&roles[0], "some_role");
    if (ret != 0) goto out;
    ret = ua_string_set(&roles[1], "some_other_role");
    if (ret != 0) goto out;
    out->token_data.issuedtoken.roles = roles;
    out->token_data.issuedtoken.num_roles = 2;
    roles = NULL;

    // for the issued token the client_user_id "shall be a string that represents the owner of the token"
    ret = ua_string_set(&out->client_user_id, "some_user_identity");
    if (ret != 0) goto out;

    out->result = 0;

out:
    if (roles != NULL) {
        ua_string_clear(&roles[0]);
        ua_string_clear(&roles[1]);
        ipc_free(roles);
    }

    ua_authentication_finish_issuedtoken_check(auth_ctx);
    return 0;
}

// e.g. during provider init set the callback
ua_authentication_set_issuedtoken_callback(example_check_issuedtoken);