diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-18 22:17:30 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-18 22:17:30 +0100 |
| commit | 77e70beff33d89f30082f3e5d513cd657fa529ea (patch) | |
| tree | 2943ddf1eb2709c8dc4414f93e4e8461d889cea5 /src/client.c | |
| parent | 95f40458a9dd927fba35624564b64b5f973dd9fe (diff) | |
| download | BobinkCOpcUa-77e70beff33d89f30082f3e5d513cd657fa529ea.tar.gz BobinkCOpcUa-77e70beff33d89f30082f3e5d513cd657fa529ea.zip | |
Add download-cert client operation with integration test
Retrieves the server's DER certificate via GetEndpoints and
writes it to a local file. The test starts a secure ServerLDS,
downloads its certificate, and verifies it matches the original.
Diffstat (limited to 'src/client.c')
| -rw-r--r-- | src/client.c | 125 |
1 files changed, 118 insertions, 7 deletions
diff --git a/src/client.c b/src/client.c index ed8b12a..011792e 100644 --- a/src/client.c +++ b/src/client.c @@ -2,10 +2,11 @@ * @file client.c * @brief Unified OPC UA client for discovery and server interaction. * - * Supports three operations selected via CLI: + * Supports four operations selected via CLI: * find-servers — queries a server's FindServers service * get-endpoints — queries a server's GetEndpoints service * read-time — connects to a server and reads the current time + * download-cert — downloads a server's certificate to a local file * * Encryption is optional: when certificate, privateKey, and trustStore are * provided, the client uses the configured security policy; otherwise it @@ -19,6 +20,7 @@ #include <open62541/client_highlevel.h> #include <open62541/plugin/log_stdout.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> @@ -31,6 +33,7 @@ typedef enum OP_FIND_SERVERS, OP_GET_ENDPOINTS, OP_READ_TIME, + OP_DOWNLOAD_CERT, OP_INVALID } Operation; @@ -43,6 +46,8 @@ parseOperation (const char *name) return OP_GET_ENDPOINTS; if (strcmp (name, "read-time") == 0) return OP_READ_TIME; + if (strcmp (name, "download-cert") == 0) + return OP_DOWNLOAD_CERT; return OP_INVALID; } @@ -154,6 +159,80 @@ opReadTime (UA_Client *client, const char *url) return rc; } +/** + * Downloads the server's certificate via GetEndpoints and writes it to a file. + * + * Picks the first endpoint that carries a non-empty serverCertificate. + * + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opDownloadCert (UA_Client *client, const char *url, const char *outputPath) +{ + size_t arraySize = 0; + UA_EndpointDescription *array = NULL; + + UA_StatusCode retval + = UA_Client_getEndpoints (client, url, &arraySize, &array); + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "GetEndpoints failed: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + UA_ByteString *cert = NULL; + for (size_t i = 0; i < arraySize; i++) + { + if (array[i].serverCertificate.length > 0) + { + cert = &array[i].serverCertificate; + break; + } + } + + if (!cert) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "No endpoint returned a server certificate"); + UA_Array_delete (array, arraySize, + &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + return EXIT_FAILURE; + } + + FILE *fp = fopen (outputPath, "wb"); + if (!fp) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Could not open output file: %s", outputPath); + UA_Array_delete (array, arraySize, + &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + return EXIT_FAILURE; + } + + size_t written = fwrite (cert->data, 1, cert->length, fp); + fclose (fp); + + int rc; + if (written != cert->length) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Failed to write certificate (%zu of %zu bytes)", written, + cert->length); + rc = EXIT_FAILURE; + } + else + { + UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Certificate saved to %s (%zu bytes)", outputPath, + cert->length); + rc = EXIT_SUCCESS; + } + + UA_Array_delete (array, arraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + return rc; +} + /* ======================================================================== * Main * ======================================================================== */ @@ -161,13 +240,16 @@ opReadTime (UA_Client *client, const char *url) int main (int argc, char **argv) { - if (argc < 4 || argc > 5) + if (argc < 4) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Usage: %s <config-file> <operation> <endpoint-url> " "[log-level]\n" - "Operations: find-servers, get-endpoints, read-time", - argv[0]); + " %s <config-file> download-cert <endpoint-url> " + "<output-file> [log-level]\n" + "Operations: find-servers, get-endpoints, read-time, " + "download-cert", + argv[0], argv[0]); return EXIT_FAILURE; } @@ -175,15 +257,41 @@ main (int argc, char **argv) if (op == OP_INVALID) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown operation: %s " - "(expected find-servers, get-endpoints, read-time)", + "Unknown operation: %s (expected find-servers, " + "get-endpoints, read-time, download-cert)", argv[2]); return EXIT_FAILURE; } const char *endpointUrl = argv[3]; + const char *outputPath = NULL; + const char *logLevelStr = "info"; - const char *logLevelStr = (argc == 5) ? argv[4] : "info"; + if (op == OP_DOWNLOAD_CERT) + { + if (argc < 5 || argc > 6) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Usage: %s <config-file> download-cert " + "<endpoint-url> <output-file> [log-level]", + argv[0]); + return EXIT_FAILURE; + } + outputPath = argv[4]; + logLevelStr = (argc == 6) ? argv[5] : "info"; + } + else + { + if (argc > 5) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Usage: %s <config-file> <operation> <endpoint-url> " + "[log-level]", + argv[0]); + return EXIT_FAILURE; + } + logLevelStr = (argc == 5) ? argv[4] : "info"; + } int logLevel = parseLogLevel (logLevelStr); if (logLevel < 0) { @@ -262,6 +370,9 @@ main (int argc, char **argv) case OP_READ_TIME: rc = opReadTime (client, endpointUrl); break; + case OP_DOWNLOAD_CERT: + rc = opDownloadCert (client, endpointUrl, outputPath); + break; default: rc = EXIT_FAILURE; break; |
