aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-17 23:11:29 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-17 23:27:11 +0100
commitb2002d96f495dcb3bd2f5a738ec1615034ca876f (patch)
treeaee665fb83cedebaa8ad093d0a2896a1a83881eb
parent229a536a87f6b2075000e659219e0567b45345c5 (diff)
downloadBobinkCOpcUa-b2002d96f495dcb3bd2f5a738ec1615034ca876f.tar.gz
BobinkCOpcUa-b2002d96f495dcb3bd2f5a738ec1615034ca876f.zip
Make LDS security config optional, add nosec_anon test
ServerLDS and ServerRegister can now run without encryption when certificate, privateKey, and trustStore are all omitted from the server config file. When any of the three is present, all three are still required. The unsecured server uses UA_ServerConfig_setMinimal with SecurityPolicy#None only. Add nosec_anon integration test covering the LDS unsecured path. Update readme: use symlinks instead of copies for trust stores, note that ServerLDS and ServerRegister support running without certs.
-rw-r--r--CMakeLists.txt4
-rw-r--r--readme.md22
-rw-r--r--src/server_lds.c86
-rw-r--r--src/server_register.c88
-rw-r--r--tests/nosec_anon/client.conf9
-rw-r--r--tests/nosec_anon/server_lds.conf8
-rw-r--r--tests/nosec_anon/server_register.conf12
-rw-r--r--tests/nosec_anon/server_register_client.conf13
8 files changed, 173 insertions, 69 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab6f4fd..5da5a4c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,9 +67,9 @@ enable_testing()
set(_test_script "${CMAKE_SOURCE_DIR}/tests/run_test.sh")
-set(_test_names none_anon none_user basic256sha256_anon aes128_user)
+set(_test_names nosec_anon none_anon none_user basic256sha256_anon aes128_user)
-set(_test_policies None None Basic256Sha256 Aes128_Sha256_RsaOaep)
+set(_test_policies None None None Basic256Sha256 Aes128_Sha256_RsaOaep)
foreach(_name _policy IN ZIP_LISTS _test_names _test_policies)
add_test(NAME "${_name}" COMMAND bash "${_test_script}" "tests/${_name}"
diff --git a/readme.md b/readme.md
index 4156e51..bb99256 100644
--- a/readme.md
+++ b/readme.md
@@ -25,8 +25,11 @@ cd opcua_c
### Generate certificates
-The programs use TLS certificates for mutual authentication. Four identities
-are needed — run these from the project root:
+The programs use TLS certificates for mutual authentication. ServerLDS and
+ServerRegister can also run without certificates (SecurityPolicy#None only) by
+omitting the `certificate`, `privateKey`, and `trustStore` keys from their
+config files. For encrypted operation, four identities are needed — run these
+from the project root:
```sh
tools/generate_certificate.sh certs ServerLDS
@@ -37,22 +40,22 @@ tools/generate_certificate.sh certs ClientFindServers
### Populate the trust stores
-Each program trusts a specific set of peers. Copy the certificates into the
-trust store directories so they can find each other:
+Each program trusts a specific set of peers. Create symlinks to the
+certificates in the trust store directories so they can find each other:
```sh
mkdir -p certs/trust/{server_lds,server_register,server_register_client,client}
-cp certs/ServerRegisterClient_cert.der certs/ClientFindServers_cert.der \
+ln -s ../../ServerRegisterClient_cert.der ../../ClientFindServers_cert.der \
certs/trust/server_lds/
-cp certs/ServerLDS_cert.der certs/ClientFindServers_cert.der \
+ln -s ../../ServerLDS_cert.der ../../ClientFindServers_cert.der \
certs/trust/server_register/
-cp certs/ServerLDS_cert.der \
+ln -s ../../ServerLDS_cert.der \
certs/trust/server_register_client/
-cp certs/ServerLDS_cert.der certs/ServerRegister_cert.der \
+ln -s ../../ServerLDS_cert.der ../../ServerRegister_cert.der \
certs/trust/client/
```
@@ -93,10 +96,11 @@ All three programs accept an optional log level as the last argument
## Tests
-Integration tests exercise four combinations of security and authentication:
+Integration tests exercise five combinations of security and authentication:
| Test | Security | Auth |
|------|----------|------|
+| `nosec_anon` | LDS unsecured / None | anonymous |
| `none_anon` | None | anonymous |
| `none_user` | None | user/password |
| `basic256sha256_anon` | SignAndEncrypt / Basic256Sha256 | anonymous |
diff --git a/src/server_lds.c b/src/server_lds.c
index a9a68bc..e3407d5 100644
--- a/src/server_lds.c
+++ b/src/server_lds.c
@@ -2,8 +2,10 @@
* @file server_lds.c
* @brief Local Discovery Server implementation.
*
- * This program runs an OPC UA Local Discovery Server (LDS) configured with
- * encryption and a configurable cleanup timeout. Other OPC UA servers register
+ * This program runs an OPC UA Local Discovery Server (LDS) with a configurable
+ * cleanup timeout. Encryption is optional: when certificate, privateKey, and
+ * trustStore are provided, the server offers all security policies; otherwise
+ * it runs with SecurityPolicy#None only. Other OPC UA servers register
* with this LDS using the RegisterServer2 service. Clients can query this LDS
* using the FindServers service to discover registered servers.
*/
@@ -60,18 +62,33 @@ main (int argc, char *argv[])
int port = configRequireInt (&cfg, "port", "ServerLDS");
const char *applicationUri
= configRequire (&cfg, "applicationUri", "ServerLDS");
- const char *certPath = configRequire (&cfg, "certificate", "ServerLDS");
- const char *keyPath = configRequire (&cfg, "privateKey", "ServerLDS");
int cleanupTimeout = configRequireInt (&cfg, "cleanupTimeout", "ServerLDS");
const char *authMode = configRequire (&cfg, "authMode", "ServerLDS");
- if (!applicationUri || !certPath || !keyPath || !authMode || port < 0
- || cleanupTimeout < 0)
+ if (!applicationUri || !authMode || port < 0 || cleanupTimeout < 0)
{
configFree (&cfg);
return EXIT_FAILURE;
}
+ /* Security configuration (optional). When certificate, privateKey, and
+ trustStore are all omitted the server runs with SecurityPolicy#None
+ only. When any of the three is present, all three are required. */
+ const char *certPath = configGet (&cfg, "certificate");
+ const char *keyPath = configGet (&cfg, "privateKey");
+ const char *trustStore = configGet (&cfg, "trustStore");
+ UA_Boolean secure
+ = (certPath != NULL || keyPath != NULL || trustStore != NULL);
+
+ if (secure && (!certPath || !keyPath || !trustStore))
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Incomplete security config: certificate, privateKey, and "
+ "trustStore must all be set, or all omitted");
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
+
/* The OPC UA specification requires the cleanup timeout to exceed the
register-server interval. open62541 enforces a floor of 10 seconds. */
if (cleanupTimeout <= 10)
@@ -111,30 +128,41 @@ main (int argc, char *argv[])
return EXIT_FAILURE;
}
- const char *trustStore = configRequire (&cfg, "trustStore", "ServerLDS");
- if (!trustStore)
- {
- configFree (&cfg);
- return EXIT_FAILURE;
- }
-
char **trustPaths = NULL;
size_t trustSize = 0;
- if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0)
+ UA_StatusCode retval;
+ UA_Server *server;
+
+ if (secure)
{
- configFree (&cfg);
- return EXIT_FAILURE;
+ if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0)
+ {
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
+ server = createSecureServer ((UA_UInt16)port, applicationUri, certPath,
+ keyPath, trustPaths, trustSize, &retval);
+ if (!server)
+ {
+ freeTrustStore (trustPaths, trustSize);
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
}
-
- UA_StatusCode retval;
- UA_Server *server
- = createSecureServer ((UA_UInt16)port, applicationUri, certPath, keyPath,
- trustPaths, trustSize, &retval);
- if (!server)
+ else
{
- freeTrustStore (trustPaths, trustSize);
- configFree (&cfg);
- return EXIT_FAILURE;
+ server = UA_Server_new ();
+ UA_ServerConfig *config = UA_Server_getConfig (server);
+ retval = UA_ServerConfig_setMinimal (config, (UA_UInt16)port, NULL);
+ if (retval != UA_STATUSCODE_GOOD)
+ {
+ UA_Server_delete (server);
+ configFree (&cfg);
+ return EXIT_FAILURE;
+ }
+ UA_String_clear (&config->applicationDescription.applicationUri);
+ config->applicationDescription.applicationUri
+ = UA_String_fromChars (applicationUri);
}
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
@@ -145,10 +173,10 @@ main (int argc, char *argv[])
Downgrade to a warning so third-party servers can still register. */
serverConfig->verifyRequestTimestamp = UA_RULEHANDLING_WARN;
- /* Configure access control after server creation because
- UA_ServerConfig_setDefaultWithSecurityPolicies (called by
- createSecureServer) resets the access control plugin. The credential
- list is deep-copied by UA_AccessControl_default. */
+ /* Configure access control after server creation because both
+ UA_ServerConfig_setDefaultWithSecurityPolicies and
+ UA_ServerConfig_setMinimal reset the access control plugin. The
+ credential list is deep-copied by UA_AccessControl_default. */
if (!allowAnonymous)
{
UA_UsernamePasswordLogin logins[1];
diff --git a/src/server_register.c b/src/server_register.c
index 6e1eb6d..cea7124 100644
--- a/src/server_register.c
+++ b/src/server_register.c
@@ -73,22 +73,36 @@ main (int argc, char **argv)
int port = configRequireInt (&serverCfg, "port", "ServerRegister");
const char *applicationUri
= configRequire (&serverCfg, "applicationUri", "ServerRegister");
- const char *serverCertPath
- = configRequire (&serverCfg, "certificate", "ServerRegister");
- const char *serverKeyPath
- = configRequire (&serverCfg, "privateKey", "ServerRegister");
int registerInterval
= configRequireInt (&serverCfg, "registerInterval", "ServerRegister");
const char *serverAuthMode
= configRequire (&serverCfg, "authMode", "ServerRegister");
- if (!applicationUri || !serverCertPath || !serverKeyPath || !serverAuthMode
- || port < 0 || registerInterval < 0)
+ if (!applicationUri || !serverAuthMode || port < 0 || registerInterval < 0)
{
configFree (&serverCfg);
return EXIT_FAILURE;
}
+ /* Security configuration (optional). When certificate, privateKey, and
+ trustStore are all omitted the server runs with SecurityPolicy#None
+ only. When any of the three is present, all three are required. */
+ const char *serverCertPath = configGet (&serverCfg, "certificate");
+ const char *serverKeyPath = configGet (&serverCfg, "privateKey");
+ const char *serverTrustStore = configGet (&serverCfg, "trustStore");
+ UA_Boolean serverSecure = (serverCertPath != NULL || serverKeyPath != NULL
+ || serverTrustStore != NULL);
+
+ if (serverSecure && (!serverCertPath || !serverKeyPath || !serverTrustStore))
+ {
+ UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+ "Incomplete server security config: certificate, "
+ "privateKey, and trustStore must all be set, or all "
+ "omitted");
+ configFree (&serverCfg);
+ return EXIT_FAILURE;
+ }
+
/* Parse server-side auth mode (what clients connecting to this server
need). "anonymous" allows unauthenticated sessions; "user" requires
a username/password pair. */
@@ -122,18 +136,11 @@ main (int argc, char **argv)
return EXIT_FAILURE;
}
- const char *serverTrustStore
- = configRequire (&serverCfg, "trustStore", "ServerRegister");
- if (!serverTrustStore)
- {
- configFree (&serverCfg);
- return EXIT_FAILURE;
- }
-
char **serverTrustPaths = NULL;
size_t serverTrustSize = 0;
- if (loadTrustStore (serverTrustStore, &serverTrustPaths, &serverTrustSize)
- != 0)
+ if (serverSecure
+ && loadTrustStore (serverTrustStore, &serverTrustPaths, &serverTrustSize)
+ != 0)
{
configFree (&serverCfg);
return EXIT_FAILURE;
@@ -251,25 +258,48 @@ main (int argc, char **argv)
/* ── Create and configure server ────────────────────────────── */
UA_StatusCode retval;
- UA_Server *server = createSecureServer (
- (UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath,
- serverTrustPaths, serverTrustSize, &retval);
- if (!server)
+ UA_Server *server;
+
+ if (serverSecure)
{
- freeTrustStore (clientTrustPaths, clientTrustSize);
- freeTrustStore (serverTrustPaths, serverTrustSize);
- configFree (&clientCfg);
- configFree (&serverCfg);
- return EXIT_FAILURE;
+ server = createSecureServer ((UA_UInt16)port, applicationUri,
+ serverCertPath, serverKeyPath,
+ serverTrustPaths, serverTrustSize, &retval);
+ if (!server)
+ {
+ freeTrustStore (clientTrustPaths, clientTrustSize);
+ freeTrustStore (serverTrustPaths, serverTrustSize);
+ configFree (&clientCfg);
+ configFree (&serverCfg);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ server = UA_Server_new ();
+ UA_ServerConfig *config = UA_Server_getConfig (server);
+ retval = UA_ServerConfig_setMinimal (config, (UA_UInt16)port, NULL);
+ if (retval != UA_STATUSCODE_GOOD)
+ {
+ UA_Server_delete (server);
+ freeTrustStore (clientTrustPaths, clientTrustSize);
+ freeTrustStore (serverTrustPaths, serverTrustSize);
+ configFree (&clientCfg);
+ configFree (&serverCfg);
+ return EXIT_FAILURE;
+ }
+ UA_String_clear (&config->applicationDescription.applicationUri);
+ config->applicationDescription.applicationUri
+ = UA_String_fromChars (applicationUri);
}
UA_ServerConfig *serverConfig = UA_Server_getConfig (server);
serverConfig->logging->context = (void *)(uintptr_t)logLevel;
- /* Configure access control after server creation because
- UA_ServerConfig_setDefaultWithSecurityPolicies (called by
- createSecureServer) resets the access control plugin. The credential
- list is deep-copied by UA_AccessControl_default. */
+ /* Configure access control after server creation because both
+ UA_ServerConfig_setDefaultWithSecurityPolicies and
+ UA_ServerConfig_setMinimal reset the access control plugin. The
+ credential list is deep-copied by UA_AccessControl_default. */
if (!serverAllowAnonymous)
{
UA_UsernamePasswordLogin logins[1];
diff --git a/tests/nosec_anon/client.conf b/tests/nosec_anon/client.conf
new file mode 100644
index 0000000..72679c9
--- /dev/null
+++ b/tests/nosec_anon/client.conf
@@ -0,0 +1,9 @@
+# Client — test: nosec_anon
+
+applicationUri = urn:localhost:bobink:ClientFindServers
+certificate = certs/ClientFindServers_cert.der
+privateKey = certs/ClientFindServers_key.der
+securityMode = None
+securityPolicy = None
+authMode = anonymous
+trustStore = certs/trust/client
diff --git a/tests/nosec_anon/server_lds.conf b/tests/nosec_anon/server_lds.conf
new file mode 100644
index 0000000..aa4b6ec
--- /dev/null
+++ b/tests/nosec_anon/server_lds.conf
@@ -0,0 +1,8 @@
+# ServerLDS — test: nosec_anon
+# No certificate/privateKey/trustStore: runs with SecurityPolicy#None only.
+
+port = 14840
+applicationUri = urn:localhost:bobink:ServerLDS
+cleanupTimeout = 60
+
+authMode = anonymous
diff --git a/tests/nosec_anon/server_register.conf b/tests/nosec_anon/server_register.conf
new file mode 100644
index 0000000..0e8ed59
--- /dev/null
+++ b/tests/nosec_anon/server_register.conf
@@ -0,0 +1,12 @@
+# ServerRegister server config — test: nosec_anon
+
+port = 14841
+applicationUri = urn:localhost:bobink:ServerRegister
+certificate = certs/ServerRegister_cert.der
+privateKey = certs/ServerRegister_key.der
+
+registerInterval = 10
+
+authMode = anonymous
+
+trustStore = certs/trust/server_register
diff --git a/tests/nosec_anon/server_register_client.conf b/tests/nosec_anon/server_register_client.conf
new file mode 100644
index 0000000..8bdc05e
--- /dev/null
+++ b/tests/nosec_anon/server_register_client.conf
@@ -0,0 +1,13 @@
+# ServerRegister client config — test: nosec_anon
+# Connects to an unsecured LDS, so no trust store for the LDS cert is needed.
+
+applicationUri = urn:localhost:bobink:ServerRegister
+certificate = certs/ServerRegisterClient_cert.der
+privateKey = certs/ServerRegisterClient_key.der
+
+securityMode = None
+securityPolicy = None
+
+authMode = anonymous
+
+trustStore = certs/trust/server_register_client