aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-17 03:31:40 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-17 03:31:40 +0100
commit3425cddd75fa105b940c8c0afe4a63065c446515 (patch)
treef1c2840985feeb06a9187d6bd99fe8274daf5412
parent1bbf7e6c2ff571b2e26b643a7e86e35790b91875 (diff)
downloadBobinkCOpcUa-3425cddd75fa105b940c8c0afe4a63065c446515.tar.gz
BobinkCOpcUa-3425cddd75fa105b940c8c0afe4a63065c446515.zip
Make authentication mode and credentials configurable via CLI
Replace hardcoded user/password credentials with a new <auth-mode> parameter that accepts "anonymous" or "user". When "user" is chosen, two additional <username> <password> arguments are required. ServerRegister accepts two independent auth modes: one for its own server-side access control and one for authenticating to the LDS when registering. ClientFindServers passes credentials to readServerTime, which selects UA_Client_connectUsername or UA_Client_connect accordingly. Update CLAUDE.md running examples and add an auth modes table.
-rw-r--r--CLAUDE.md21
-rw-r--r--src/client_find_servers.c52
-rw-r--r--src/server_lds.c64
-rw-r--r--src/server_register.c124
4 files changed, 212 insertions, 49 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index b3ee66f..4a6af57 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -69,10 +69,11 @@ All three programs run from the project root. Start them in order in separate te
build/ServerLDS 4840 "urn:bobink.ServerLDS" \
certs/ServerLDS_cert.der certs/ServerLDS_key.der \
60 \
+ user user password \
certs/ServerRegisterClient_cert.der certs/ClientFindServers_cert.der
```
-Args: `<port> <applicationUri> <cert> <key> <cleanup-timeout-seconds> [trustlist...]`
+Args: `<port> <applicationUri> <cert> <key> <cleanup-timeout-seconds> <auth-mode> [<username> <password>] [trustlist...]`
The trustlist must include the client certs that will connect over encrypted channels: `ServerRegisterClient_cert.der` (used by `ServerRegister`) and `ClientFindServers_cert.der` (used by `ClientFindServers`). Cleanup timeout must be > 10.
@@ -84,12 +85,14 @@ build/ServerRegister 4841 "urn:bobink.ServerRegister" \
certs/ServerRegisterClient_cert.der certs/ServerRegisterClient_key.der \
"opc.tcp://localhost:4840" 10 \
SignAndEncrypt Aes128_Sha256_RsaOaep \
+ user user password \
+ user user password \
certs/ServerLDS_cert.der certs/ClientFindServers_cert.der
```
-Args: `<port> <applicationUri> <server-cert> <server-key> <client-cert> <client-key> <discovery-endpoint> <register-interval-seconds> <security-mode> <security-policy> [trustlist...]`
+Args: `<port> <applicationUri> <server-cert> <server-key> <client-cert> <client-key> <discovery-endpoint> <register-interval-seconds> <security-mode> <security-policy> <server-auth-mode> [<server-user> <server-pass>] <client-auth-mode> [<client-user> <client-pass>] [trustlist...]`
-Uses separate server/client certificate pairs. The client cert+key are for the secure channel to the LDS. Re-registers periodically at the given interval. Trustlist should include the LDS cert and any client certs that will query this server's endpoints (e.g. `ClientFindServers_cert.der`).
+Uses separate server/client certificate pairs. The client cert+key are for the secure channel to the LDS. Re-registers periodically at the given interval. Server auth mode controls what clients connecting to this server need; client auth mode controls how this server authenticates to the LDS. Trustlist should include the LDS cert and any client certs that will query this server's endpoints (e.g. `ClientFindServers_cert.der`).
**3. Find Servers Client**
@@ -98,14 +101,15 @@ build/ClientFindServers "opc.tcp://localhost:4840" \
"urn:bobink.ClientFindServers" \
certs/ClientFindServers_cert.der certs/ClientFindServers_key.der \
SignAndEncrypt Aes128_Sha256_RsaOaep \
+ user user password \
certs/ServerLDS_cert.der certs/ServerRegister_cert.der
```
-Args: `<discovery-server-endpoint> <applicationUri> <cert> <key> <security-mode> <security-policy> [trustlist...]`
+Args: `<discovery-server-endpoint> <applicationUri> <cert> <key> <security-mode> <security-policy> <auth-mode> [<username> <password>] [trustlist...]`
-Queries the LDS and prints all registered servers and their endpoints. The trustlist should include the certs of all servers whose endpoints will be queried.
+Queries the LDS and prints all registered servers and their endpoints. Auth mode controls how the client authenticates when reading server time. The trustlist should include the certs of all servers whose endpoints will be queried.
-**Security options** (for both ServerRegister and ClientFindServers):
+**Security and auth options** (for both ServerRegister and ClientFindServers):
| Security modes | Security policies |
|----------------|-------------------|
@@ -115,6 +119,11 @@ Queries the LDS and prints all registered servers and their endpoints. The trust
| | `Aes128_Sha256_RsaOaep` |
| | `ECC_nistP256` |
+| Auth modes | Description |
+|------------|-------------|
+| `anonymous` | No session credentials |
+| `user` | Username/password (requires two extra args) |
+
## Project Structure
| Path | Purpose |
diff --git a/src/client_find_servers.c b/src/client_find_servers.c
index 4789b38..21d48ca 100644
--- a/src/client_find_servers.c
+++ b/src/client_find_servers.c
@@ -129,11 +129,14 @@ getServersEndpoints (UA_Client *client,
* @param applicationDescriptionArray Array of server descriptions from
* FindServers.
* @param applicationDescriptionArraySize Number of servers in the array.
+ * @param username Username for session auth, or NULL for anonymous.
+ * @param password Password for session auth (ignored when username is NULL).
*/
static void
readServerTime (UA_Client *client,
UA_ApplicationDescription *applicationDescriptionArray,
- size_t applicationDescriptionArraySize)
+ size_t applicationDescriptionArraySize, const char *username,
+ const char *password)
{
for (size_t i = 0; i < applicationDescriptionArraySize; i++)
{
@@ -160,8 +163,11 @@ readServerTime (UA_Client *client,
UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT,
"Connecting to %s to read current time...", url);
- UA_StatusCode retval
- = UA_Client_connectUsername (client, url, "user", "password");
+ UA_StatusCode retval;
+ if (username)
+ retval = UA_Client_connectUsername (client, url, username, password);
+ else
+ retval = UA_Client_connect (client, url);
UA_free (url);
if (retval != UA_STATUSCODE_GOOD)
{
@@ -204,19 +210,21 @@ readServerTime (UA_Client *client,
int
main (int argc, char **argv)
{
- if (argc < 7)
+ if (argc < 8)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Usage: %s <discovery-server-endpoint>\n"
" <applicationUri>\n"
" <certificate.der> <private-key.der>\n"
" <security-mode> <security-policy>\n"
+ " <auth-mode> [<username> <password>]\n"
" [<trustlist1.der>, ...]\n"
"\n"
"Security modes : None, Sign, SignAndEncrypt\n"
"Security policies: None, Basic256Sha256, "
"Aes256_Sha256_RsaPss,\n"
- " Aes128_Sha256_RsaOaep, ECC_nistP256",
+ " Aes128_Sha256_RsaOaep, ECC_nistP256\n"
+ "Auth modes : anonymous, user",
argv[0]);
return EXIT_FAILURE;
}
@@ -242,8 +250,36 @@ main (int argc, char **argv)
return EXIT_FAILURE;
}
- char **trustPaths = argv + 7;
- size_t trustSize = (argc > 7) ? (size_t)argc - 7 : 0;
+ int idx = 7;
+ const char *authMode = argv[idx++];
+ const char *username = NULL, *password = NULL;
+
+ if (strcmp (authMode, "anonymous") == 0)
+ {
+ /* No extra args needed */
+ }
+ else if (strcmp (authMode, "user") == 0)
+ {
+ if (idx + 2 > argc)
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Auth mode 'user' requires <username> <password>");
+ return EXIT_FAILURE;
+ }
+ username = argv[idx++];
+ password = argv[idx++];
+ }
+ else
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Unknown auth mode: %s "
+ "(expected 'anonymous' or 'user')",
+ authMode);
+ return EXIT_FAILURE;
+ }
+
+ char **trustPaths = argv + idx;
+ size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
UA_Client *client = UA_Client_new ();
UA_StatusCode retval = createSecureClientConfig (
@@ -271,7 +307,7 @@ main (int argc, char **argv)
applicationDescriptionArraySize);
readServerTime (client, applicationDescriptionArray,
- applicationDescriptionArraySize);
+ applicationDescriptionArraySize, username, password);
UA_Client_delete (client);
UA_Array_delete (applicationDescriptionArray,
diff --git a/src/server_lds.c b/src/server_lds.c
index 12dfe59..fc51596 100644
--- a/src/server_lds.c
+++ b/src/server_lds.c
@@ -33,14 +33,17 @@ main (int argc, char *argv[])
signal (SIGINT, stopHandler);
signal (SIGTERM, stopHandler);
- if (argc < 6)
+ if (argc < 7)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Usage: %s\n"
" <port> <applicationUri>\n"
" <server-certificate.der> <private-key.der>\n"
" <cleanup-timeout-seconds>\n"
- " [<trustlist1.der>, ...]",
+ " <auth-mode> [<username> <password>]\n"
+ " [<trustlist1.der>, ...]\n"
+ "\n"
+ "Auth modes: anonymous, user",
argv[0]);
return EXIT_FAILURE;
}
@@ -57,27 +60,62 @@ main (int argc, char *argv[])
cleanupTimeout);
return EXIT_FAILURE;
}
- size_t trustSize = (argc > 6) ? (size_t)argc - 6 : 0;
+
+ int idx = 6;
+ const char *authMode = argv[idx++];
+ UA_Boolean allowAnonymous;
+ char *username = NULL, *password = NULL;
+
+ if (strcmp (authMode, "anonymous") == 0)
+ {
+ allowAnonymous = true;
+ }
+ else if (strcmp (authMode, "user") == 0)
+ {
+ if (idx + 2 > argc)
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Auth mode 'user' requires <username> <password>");
+ return EXIT_FAILURE;
+ }
+ allowAnonymous = false;
+ username = argv[idx++];
+ password = argv[idx++];
+ }
+ else
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Unknown auth mode: %s "
+ "(expected 'anonymous' or 'user')",
+ authMode);
+ return EXIT_FAILURE;
+ }
+
+ size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
UA_StatusCode retval;
UA_Server *server = createSecureServer (port, argv[2], argv[3], argv[4],
- argv + 6, trustSize, &retval);
+ argv + idx, trustSize, &retval);
if (!server)
return EXIT_FAILURE;
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
- /* Disallow anonymous sessions.
+ /* Configure access control after server creation because
UA_ServerConfig_setDefaultWithSecurityPolicies (called by
- createSecureServer) resets access control, so this must come after server
- creation. The static credential list is deep-copied. */
- UA_UsernamePasswordLogin logins[]
- = { { UA_STRING_STATIC ("user"), UA_STRING_STATIC ("password") } };
- retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
- if (retval != UA_STATUSCODE_GOOD)
+ createSecureServer) resets the access control plugin. The credential
+ list is deep-copied by UA_AccessControl_default. */
+ if (!allowAnonymous)
{
- UA_Server_delete (server);
- return EXIT_FAILURE;
+ UA_UsernamePasswordLogin logins[1];
+ logins[0].username = UA_STRING (username);
+ logins[0].password = UA_STRING (password);
+ retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
+ if (retval != UA_STATUSCODE_GOOD)
+ {
+ UA_Server_delete (server);
+ return EXIT_FAILURE;
+ }
}
/* Mark this server as a Discovery Server so clients can identify it. */
diff --git a/src/server_register.c b/src/server_register.c
index 8b750fe..c90fc31 100644
--- a/src/server_register.c
+++ b/src/server_register.c
@@ -41,7 +41,7 @@ main (int argc, char **argv)
signal (SIGINT, stopHandler);
signal (SIGTERM, stopHandler);
- if (argc < 11)
+ if (argc < 13)
{
UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Usage: %s\n"
@@ -51,12 +51,15 @@ main (int argc, char **argv)
" <discovery-server-endpoint>\n"
" <register-interval-seconds>\n"
" <security-mode> <security-policy>\n"
+ " <server-auth-mode> [<server-user> <server-pass>]\n"
+ " <client-auth-mode> [<client-user> <client-pass>]\n"
" [<trustlist1.der>, ...]\n"
"\n"
"Security modes : None, Sign, SignAndEncrypt\n"
"Security policies: None, Basic256Sha256, "
"Aes256_Sha256_RsaPss,\n"
- " Aes128_Sha256_RsaOaep, ECC_nistP256",
+ " Aes128_Sha256_RsaOaep, ECC_nistP256\n"
+ "Auth modes : anonymous, user",
argv[0]);
return EXIT_FAILURE;
}
@@ -84,27 +87,101 @@ main (int argc, char **argv)
return EXIT_FAILURE;
}
- size_t trustSize = (argc > 11) ? (size_t)argc - 11 : 0;
+ /* Parse server-side auth mode (what clients connecting to this server
+ need). "anonymous" allows unauthenticated sessions; "user" requires
+ a username/password pair. */
+ int idx = 11;
+ const char *serverAuthMode = argv[idx++];
+ UA_Boolean serverAllowAnonymous;
+ char *serverUsername = NULL, *serverPassword = NULL;
+
+ if (strcmp (serverAuthMode, "anonymous") == 0)
+ {
+ serverAllowAnonymous = true;
+ }
+ else if (strcmp (serverAuthMode, "user") == 0)
+ {
+ if (idx + 2 > argc)
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Server auth mode 'user' requires "
+ "<username> <password>");
+ return EXIT_FAILURE;
+ }
+ serverAllowAnonymous = false;
+ serverUsername = argv[idx++];
+ serverPassword = argv[idx++];
+ }
+ else
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Unknown server auth mode: %s "
+ "(expected 'anonymous' or 'user')",
+ serverAuthMode);
+ return EXIT_FAILURE;
+ }
+
+ /* Parse client-side auth mode (how this server authenticates to the
+ LDS when registering). */
+ if (idx >= argc)
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Missing client auth mode");
+ return EXIT_FAILURE;
+ }
+ const char *clientAuthMode = argv[idx++];
+ char *clientUsername = NULL, *clientPassword = NULL;
+
+ if (strcmp (clientAuthMode, "anonymous") == 0)
+ {
+ /* No extra args needed */
+ }
+ else if (strcmp (clientAuthMode, "user") == 0)
+ {
+ if (idx + 2 > argc)
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Client auth mode 'user' requires "
+ "<username> <password>");
+ return EXIT_FAILURE;
+ }
+ clientUsername = argv[idx++];
+ clientPassword = argv[idx++];
+ }
+ else
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Unknown client auth mode: %s "
+ "(expected 'anonymous' or 'user')",
+ clientAuthMode);
+ return EXIT_FAILURE;
+ }
+
+ size_t trustSize = (idx < argc) ? (size_t)(argc - idx) : 0;
UA_StatusCode retval;
UA_Server *server = createSecureServer (
- port, applicationUri, argv[3], argv[4], argv + 11, trustSize, &retval);
+ port, applicationUri, argv[3], argv[4], argv + idx, trustSize, &retval);
if (!server)
return EXIT_FAILURE;
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
- /* Disallow anonymous sessions.
+ /* Configure access control after server creation because
UA_ServerConfig_setDefaultWithSecurityPolicies (called by
- createSecureServer) resets access control, so this must come after server
- creation. The static credential list is deep-copied. */
- UA_UsernamePasswordLogin logins[]
- = { { UA_STRING_STATIC ("user"), UA_STRING_STATIC ("password") } };
- retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
- if (retval != UA_STATUSCODE_GOOD)
+ createSecureServer) resets the access control plugin. The credential
+ list is deep-copied by UA_AccessControl_default. */
+ if (!serverAllowAnonymous)
{
- UA_Server_delete (server);
- return EXIT_FAILURE;
+ UA_UsernamePasswordLogin logins[1];
+ logins[0].username = UA_STRING (serverUsername);
+ logins[0].password = UA_STRING (serverPassword);
+ retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins);
+ if (retval != UA_STATUSCODE_GOOD)
+ {
+ UA_Server_delete (server);
+ return EXIT_FAILURE;
+ }
}
serverConfig->applicationDescription.applicationType
@@ -117,7 +194,7 @@ main (int argc, char **argv)
UA_ClientConfig clientConfig;
memset (&clientConfig, 0, sizeof (UA_ClientConfig));
retval = createSecureClientConfig (
- &clientConfig, applicationUri, clientCertPath, clientKeyPath, argv + 11,
+ &clientConfig, applicationUri, clientCertPath, clientKeyPath, argv + idx,
trustSize, securityMode, securityPolicyUri);
if (retval != UA_STATUSCODE_GOOD)
{
@@ -125,8 +202,9 @@ main (int argc, char **argv)
UA_Server_delete (server);
return EXIT_FAILURE;
}
- UA_ClientConfig_setAuthenticationUsername (&clientConfig, "user",
- "password");
+ if (clientUsername)
+ UA_ClientConfig_setAuthenticationUsername (&clientConfig, clientUsername,
+ clientPassword);
UA_String discoveryUrl = UA_STRING_ALLOC (discoveryEndpoint);
retval = UA_Server_registerDiscovery (server, &clientConfig, discoveryUrl,
@@ -149,11 +227,12 @@ main (int argc, char **argv)
memset (&clientConfig, 0, sizeof (UA_ClientConfig));
retval = createSecureClientConfig (
&clientConfig, applicationUri, clientCertPath, clientKeyPath,
- argv + 11, trustSize, securityMode, securityPolicyUri);
+ argv + idx, trustSize, securityMode, securityPolicyUri);
if (retval == UA_STATUSCODE_GOOD)
{
- UA_ClientConfig_setAuthenticationUsername (&clientConfig, "user",
- "password");
+ if (clientUsername)
+ UA_ClientConfig_setAuthenticationUsername (
+ &clientConfig, clientUsername, clientPassword);
UA_String reregUrl = UA_STRING_ALLOC (discoveryEndpoint);
retval = UA_Server_registerDiscovery (server, &clientConfig,
reregUrl, UA_STRING_NULL);
@@ -169,12 +248,13 @@ main (int argc, char **argv)
memset (&clientConfig, 0, sizeof (UA_ClientConfig));
retval = createSecureClientConfig (
- &clientConfig, applicationUri, clientCertPath, clientKeyPath, argv + 11,
+ &clientConfig, applicationUri, clientCertPath, clientKeyPath, argv + idx,
trustSize, securityMode, securityPolicyUri);
if (retval == UA_STATUSCODE_GOOD)
{
- UA_ClientConfig_setAuthenticationUsername (&clientConfig, "user",
- "password");
+ if (clientUsername)
+ UA_ClientConfig_setAuthenticationUsername (
+ &clientConfig, clientUsername, clientPassword);
UA_String deregUrl = UA_STRING_ALLOC (discoveryEndpoint);
retval = UA_Server_deregisterDiscovery (server, &clientConfig, deregUrl);
UA_String_clear (&deregUrl);