High Performance OPC UA Server SDK  1.3.0.231
Lesson 3: Adding User Authentication

This lesson will extend the previous example client with user authentication.

Client Configuration

In this example we introduce two more configuration defines to configure the user authentication.

  • CONFIG_USERNAME The username to use for authentication.
  • CONFIG_PASSWORD: The password to use for authentication.

Of course, you should never hard code usernames and passwords in real applications like in this example.

/* Simple client configuration: this is normally read out from a configuration file.
* This configures the security to use when connecting to the server.
*/
#if 1
# define CONFIG_SECURITY_POLICY UA_SECURITY_POLICY_BASIC256SHA256_STRING
# define CONFIG_MESSAGE_MODE UA_MESSAGESECURITYMODE_SIGNANDENCRYPT
#else
# define CONFIG_SECURITY_POLICY UA_SECURITY_POLICY_BASIC256SHA256_STRING
# define CONFIG_MESSAGE_MODE UA_MESSAGESECURITYMODE_SIGN
#endif
/* This is just an example. NEVER hardcode passwords in real applications!!!
* This configures the UsernamePassword Token to use for user authentication.
*/
#define CONFIG_USERNAME "john"
#define CONFIG_PASSWORD "master"

When connecting using a secure connection, it is not problem to transmit clear text passwords. But it is when connecting using security policy None or with message mode Sign. This example also demonstrates how to securely authenticate with encrypted passwords if the connection itself is not encrypted, but only signed.

You can verify this in Wireshark if you capture the traffic and change #if 1 to #if 0 to disable the secure channel encryption.

Security Note

It is even possible to use encrypted passwords with security policy None. The passwords are still encrypted, but without a trusted secure channel you cannot verify that the server is who it claims to be. It could be an attacker which performs a man in the middle (MITM) attack to intercept user credentials.

For this reason it is recommended to always use a secure channel with a policy other than None and enable at least message mode Sign.

Exteding GetEndpoints Callback Logic

The GetEndpoints service callback is now extended to also select a matching user token. This is done by calling the new helper function find_user_token.

Setting the authentication info is done by creating a ua_auth_credentials structure and calling ua_client_set_credentials. Most of the information could be filled without even knowing the user token. But one of the required information is the policy_id, which is a server defined string, which can only be retrieved from the server's endpoint descriptions. The other is the security policy uri to use for encrypting the user identity token. Usually this is the same as for the secure channel (except for security policy none), but the returned security_policy_uri in struct ua_usertokenpolicy can also be NULL, which means you have to use the security_policy_uri of the secure channel (See section 7.37 UserTokenPolicy, Part 4 OPC UA Specification 1.04).

static int sample_discovery_endpoint_cb(
int result,
struct ua_responseheader *res_header,
void *cb_data)
{
struct sample_client *ctx = cb_data;
const struct ua_endpointdescription *ep;
const struct ua_usertokenpolicy *ut;
const struct ua_string *sec_policy;
struct ua_auth_credentials credentials;
unsigned char *cert_id;
char scertid[PKI_STORE_CERT_ID_SIZE*2+1];
int ret;
if (result != UA_EGOOD) {
TRACE_ERROR(TRACE_FAC_APPLICATION, "GetEndpoints failed with error 0x%08x\n", result);
ctx->result = result;
ctx->cur_state = SAMPLE_CLIENT_STATE_FINISHED;
return 0;
}
if (res_header->service_result != 0) {
TRACE_ERROR(TRACE_FAC_APPLICATION, "GetEndpoints return status code 0x%08x\n", res_header->service_result);
ctx->result = res_header->service_result;
ctx->cur_state = SAMPLE_CLIENT_STATE_FINISHED;
return 0;
}
/* print all available endpoints */
dump_endpoints(res->endpoints, res->num_endpoints);
/* search for matchin endpoint */
ep = find_endpoint_description(res->endpoints, res->num_endpoints, CONFIG_SECURITY_POLICY, CONFIG_MESSAGE_MODE);
/* configure Client SDK to use the selected endpoint configuration */
if (ep) {
cert_id = uaapplication_get_certificate_id(ctx->app, g_appconfig.client.certificate);
pki_store_sha1_to_string(cert_id, scertid);
printf("Configuring client security:\n");
printf(" Client PKI store = %i\n", g_appconfig.client.store);
printf(" Client certificate ID = %s\n", scertid);
&ctx->client, /* the client context */
g_appconfig.client.store, /* the configured client PKI store */
cert_id, /* the configured client certificate id */
0, /* number of CA certificates for cert_id */
NULL, /* array of CA certificates (DER encoded) */
ep); /* selected endpoint configuration */
if (ret == 0) {
ctx->cur_state = SAMPLE_CLIENT_STATE_DISCOVERED;
} else {
TRACE_ERROR(TRACE_FAC_USERAPPLICATION, "%s: ua_client_set_security_from_endpointdescription failed with error %i\n", __func__, ret);
ctx->result = ret;
ctx->cur_state = SAMPLE_CLIENT_STATE_FINISHED;
}
/* choose best user token type from selected endpoint */
if (ut) {
/* fill the policyid that was returned by getendpoints */
ua_string_copy(&credentials.policy_id, &ut->policy_id); /* set server's policyid */
if (ua_string_is_null(&ut->security_policy_uri)) /* check if token has a valid security_policy_uri */
sec_policy = &ep->security_policy_uri; /* use endpoint security_policy_uri as fallback */
else
sec_policy = &ut->security_policy_uri; /* or use the defined token security_policy_uri */
/* convert policy uri to enum */
ret = seconv_get_policy_id(ua_string_const_data(sec_policy), (int32_t)ua_string_length(sec_policy), &credentials.security_policy_id);
/* create username/password credentials */
credentials.cred_type = UA_AUTH_USERNAMEPASSWORD;
ua_string_attach_const(&credentials.cred.pwd.username, CONFIG_USERNAME);
ua_string_attach_const(&credentials.cred.pwd.password, CONFIG_PASSWORD);
ua_client_set_credentials(&ctx->client, &credentials);
} else {
TRACE_ERROR(TRACE_FAC_USERAPPLICATION, "%s: The server does not support the configured user token type.\n", __func__);
ctx->result = UA_EBADINVALIDSTATE;
ctx->cur_state = SAMPLE_CLIENT_STATE_FINISHED;
}
} else {
TRACE_ERROR(TRACE_FAC_USERAPPLICATION, "%s: The server does not support the configured security policy.\n", __func__);
ctx->result = UA_EBADINVALIDSTATE;
ctx->cur_state = SAMPLE_CLIENT_STATE_FINISHED;
}
return 0;
}

Helper function find_user_token:

static const struct ua_usertokenpolicy *find_user_token(
const struct ua_usertokenpolicy *user_tokens,
int num_tokens,
{
const struct ua_usertokenpolicy *ut;
int i;
for (i = 0; i < num_tokens; ++i) {
ut = &user_tokens[i];
if (ut->token_type == token_type &&
/* avoid plain text passwords */
return ut;
}
}
return NULL;
}