diff options
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | readme.md | 22 | ||||
| -rw-r--r-- | src/server_lds.c | 86 | ||||
| -rw-r--r-- | src/server_register.c | 88 | ||||
| -rw-r--r-- | tests/nosec_anon/client.conf | 9 | ||||
| -rw-r--r-- | tests/nosec_anon/server_lds.conf | 8 | ||||
| -rw-r--r-- | tests/nosec_anon/server_register.conf | 12 | ||||
| -rw-r--r-- | tests/nosec_anon/server_register_client.conf | 13 |
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}" @@ -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 |
