/src/open62541/plugins/crypto/ua_certificategroup_filestore.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2022 (c) Mark Giraud, Fraunhofer IOSB |
6 | | * Copyright 2023 (c) Fraunhofer IOSB (Author: Kai Huebl) |
7 | | * Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf) |
8 | | */ |
9 | | |
10 | | #include <open62541/util.h> |
11 | | #include <open62541/plugin/certificategroup_default.h> |
12 | | |
13 | | #include "ua_filestore_common.h" |
14 | | #include "mp_printf.h" |
15 | | |
16 | | #ifdef UA_ENABLE_ENCRYPTION |
17 | | |
18 | | #if defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) || defined(__APPLE__) || defined(__OpenBSD__) |
19 | | |
20 | | #ifdef __linux__ |
21 | 0 | #define EVENT_SIZE (sizeof(struct inotify_event)) |
22 | 0 | #define BUF_LEN (1024 * ( EVENT_SIZE + 16 )) |
23 | | #endif /* __linux__ */ |
24 | | |
25 | | static size_t |
26 | 16.0k | UA_strnlen(const char *s, size_t maxlen) { |
27 | 16.0k | size_t len = 0; |
28 | 1.79M | while(len < maxlen && s[len] != '\0') |
29 | 1.77M | len++; |
30 | 16.0k | return len; |
31 | 16.0k | } |
32 | | |
33 | | typedef struct { |
34 | | /* Memory cert store as a base */ |
35 | | UA_CertificateGroup *store; |
36 | | |
37 | | #ifdef __linux__ |
38 | | int inotifyFd; |
39 | | #endif /* __linux__ */ |
40 | | |
41 | | UA_String trustedCertFolder; |
42 | | UA_String trustedCrlFolder; |
43 | | UA_String issuerCertFolder; |
44 | | UA_String issuerCrlFolder; |
45 | | UA_String rejectedCertFolder; |
46 | | UA_String ownCertFolder; |
47 | | UA_String ownKeyFolder; |
48 | | UA_String rootFolder; |
49 | | } FileCertStore; |
50 | | |
51 | | static int |
52 | 55.9k | mkpath(char *dir, UA_MODE mode) { |
53 | 55.9k | if(dir == NULL) |
54 | 0 | return 1; |
55 | | |
56 | 55.9k | size_t dirLen = strlen(dir); |
57 | 55.9k | char *path = (char*)UA_malloc(dirLen + 1); |
58 | 55.9k | if(!path) |
59 | 0 | return 1; |
60 | 55.9k | memcpy(path, dir, dirLen + 1); |
61 | | |
62 | 55.9k | char *pos = path; |
63 | 55.9k | if(pos[0] == '/') |
64 | 28 | pos++; |
65 | | |
66 | 6.90M | for(; *pos; pos++) { |
67 | 6.84M | if(*pos != '/') |
68 | 6.63M | continue; |
69 | | |
70 | 214k | *pos = '\0'; |
71 | 214k | if(path[0] != '\0' && UA_mkdir(path, mode) != 0 && errno != EEXIST) { |
72 | 480 | UA_free(path); |
73 | 480 | return 1; |
74 | 480 | } |
75 | 213k | *pos = '/'; |
76 | 213k | } |
77 | | |
78 | 55.5k | if(UA_mkdir(path, mode) != 0 && errno != EEXIST) { |
79 | 0 | UA_free(path); |
80 | 0 | return 1; |
81 | 0 | } |
82 | | |
83 | 55.5k | UA_free(path); |
84 | 55.5k | return 0; |
85 | 55.5k | } |
86 | | |
87 | | static UA_StatusCode |
88 | 0 | removeAllFilesFromDir(const char *const path, bool removeSubDirs) { |
89 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
90 | | |
91 | | /* Check parameter */ |
92 | 0 | if(path == NULL) |
93 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
94 | | |
95 | | /* remove all regular files from directory */ |
96 | 0 | UA_DIR *dir = UA_opendir(path); |
97 | 0 | if(!dir) |
98 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
99 | | |
100 | 0 | struct UA_DIRENT *dirent; |
101 | 0 | while((dirent = UA_readdir(dir)) != NULL) { |
102 | 0 | if(dirent->d_type == UA_DT_REG) { |
103 | 0 | char file_name[UA_FILENAME_MAX]; |
104 | 0 | mp_snprintf(file_name, UA_FILENAME_MAX, "%s/%s", path, |
105 | 0 | (char *)dirent->d_name); |
106 | 0 | UA_remove(file_name); |
107 | 0 | } |
108 | 0 | if(dirent->d_type == UA_DT_DIR && removeSubDirs == true) { |
109 | 0 | char *directory = (char*)dirent->d_name; |
110 | |
|
111 | 0 | char dir_name[UA_FILENAME_MAX]; |
112 | 0 | mp_snprintf(dir_name, UA_FILENAME_MAX, "%s/%s", path, (char *)dirent->d_name); |
113 | |
|
114 | 0 | if(strlen(directory) == 1 && directory[0] == '.') |
115 | 0 | continue; |
116 | 0 | if(strlen(directory) == 2 && directory[0] == '.' && directory[1] == '.') |
117 | 0 | continue; |
118 | | |
119 | 0 | retval = removeAllFilesFromDir(dir_name, removeSubDirs); |
120 | 0 | } |
121 | 0 | } |
122 | 0 | UA_closedir(dir); |
123 | |
|
124 | 0 | return retval; |
125 | 0 | } |
126 | | |
127 | | static UA_StatusCode |
128 | | getCertFileName(const char *path, const UA_ByteString *certificate, |
129 | 0 | char *fileNameBuf, size_t fileNameLen) { |
130 | | /* Check parameter */ |
131 | 0 | if(path == NULL || certificate == NULL || fileNameBuf == NULL) |
132 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
133 | | |
134 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
135 | |
|
136 | 0 | UA_String thumbprint = UA_STRING_NULL; |
137 | 0 | thumbprint.length = 40; |
138 | 0 | thumbprint.data = (UA_Byte*)UA_calloc(thumbprint.length, sizeof(UA_Byte)); |
139 | |
|
140 | 0 | UA_String commonName = UA_STRING_NULL; |
141 | |
|
142 | 0 | UA_CertificateUtils_getThumbprint((UA_ByteString*)(uintptr_t)certificate, &thumbprint); |
143 | 0 | UA_CertificateUtils_getCertCommonName((UA_ByteString*)(uintptr_t)certificate, &commonName); |
144 | |
|
145 | 0 | if((thumbprint.length + commonName.length + 2) > fileNameLen) { |
146 | 0 | UA_String_clear(&thumbprint); |
147 | 0 | UA_String_clear(&commonName); |
148 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
149 | 0 | } |
150 | | |
151 | 0 | char *thumbprintBuffer = (char*)UA_malloc(thumbprint.length + 1); |
152 | 0 | char *commonNameBuffer = (char*)UA_malloc(commonName.length + 1); |
153 | |
|
154 | 0 | memcpy(thumbprintBuffer, thumbprint.data, thumbprint.length); |
155 | 0 | thumbprintBuffer[thumbprint.length] = '\0'; |
156 | 0 | memcpy(commonNameBuffer, commonName.data, commonName.length); |
157 | 0 | commonNameBuffer[commonName.length] = '\0'; |
158 | |
|
159 | 0 | for(char *c = commonNameBuffer; *c; c++) { |
160 | 0 | if(*c == '/' || *c == '\\') |
161 | 0 | *c = '_'; |
162 | 0 | } |
163 | |
|
164 | 0 | if(mp_snprintf(fileNameBuf, fileNameLen, "%s/%s[%s]", path, commonNameBuffer, |
165 | 0 | thumbprintBuffer) < 0) |
166 | 0 | retval = UA_STATUSCODE_BADINTERNALERROR; |
167 | |
|
168 | 0 | UA_String_clear(&thumbprint); |
169 | 0 | UA_String_clear(&commonName); |
170 | 0 | UA_free(thumbprintBuffer); |
171 | 0 | UA_free(commonNameBuffer); |
172 | |
|
173 | 0 | return retval; |
174 | 0 | } |
175 | | |
176 | | static bool |
177 | 126k | isRegularFile(const char *path, const struct UA_DIRENT *dirent) { |
178 | 126k | if(dirent->d_type == UA_DT_DIR) |
179 | 126k | return false; |
180 | 0 | if(dirent->d_type == UA_DT_REG) |
181 | 0 | return true; |
182 | | |
183 | 0 | #ifndef UA_ARCHITECTURE_WIN32 |
184 | 0 | char filename[UA_PATH_MAX] = {0}; |
185 | 0 | int len = mp_snprintf(filename, UA_PATH_MAX, "%s/%s", path, dirent->d_name); |
186 | 0 | if(len < 0 || len >= UA_PATH_MAX) |
187 | 0 | return false; |
188 | | |
189 | 0 | struct UA_STAT statBuf; |
190 | 0 | return (UA_stat(filename, &statBuf) == 0 && S_ISREG(statBuf.st_mode)); |
191 | | #else |
192 | | return false; |
193 | | #endif |
194 | 0 | } |
195 | | |
196 | | static UA_StatusCode |
197 | 31.7k | readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) { |
198 | 31.7k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
199 | | |
200 | 31.7k | char listPath[UA_PATH_MAX] = {0}; |
201 | 31.7k | int pathLen = mp_snprintf(listPath, UA_PATH_MAX, "%.*s", |
202 | 31.7k | (int)path.length, (char*)path.data); |
203 | 31.7k | if(pathLen < 0 || pathLen >= UA_PATH_MAX) |
204 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
205 | | |
206 | | /* Determine number of certificates */ |
207 | 31.7k | size_t numCerts = 0; |
208 | 31.7k | UA_DIR *dir = UA_opendir(listPath); |
209 | 31.7k | if(!dir) { |
210 | 0 | if(mkpath(listPath, 0777) != 0) |
211 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
212 | 0 | dir = UA_opendir(listPath); |
213 | 0 | if(!dir) |
214 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
215 | 0 | } |
216 | | |
217 | 31.7k | struct UA_DIRENT *dirent; |
218 | 95.1k | while((dirent = UA_readdir(dir)) != NULL) { |
219 | 63.4k | if(!isRegularFile(listPath, dirent)) |
220 | 63.4k | continue; |
221 | 0 | numCerts++; |
222 | 0 | } |
223 | | |
224 | 31.7k | retval = UA_Array_resize((void **)list, listSize, numCerts, &UA_TYPES[UA_TYPES_BYTESTRING]); |
225 | 31.7k | if(retval != UA_STATUSCODE_GOOD) { |
226 | 0 | UA_closedir(dir); |
227 | 0 | return retval; |
228 | 0 | } |
229 | | |
230 | | /* Read files from directory */ |
231 | 31.7k | size_t numActCerts = 0; |
232 | 31.7k | UA_rewinddir(dir); |
233 | | |
234 | 95.1k | while((dirent = UA_readdir(dir)) != NULL) { |
235 | 63.4k | if(!isRegularFile(listPath, dirent)) |
236 | 63.4k | continue; |
237 | 0 | if(numActCerts < numCerts) { |
238 | | /* Create filename to load */ |
239 | 0 | char filename[UA_PATH_MAX] = {0}; |
240 | 0 | if(mp_snprintf(filename, UA_PATH_MAX, "%s/%s", listPath, dirent->d_name) < 0) { |
241 | 0 | UA_closedir(dir); |
242 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
243 | 0 | } |
244 | | |
245 | | /* Load data from file */ |
246 | 0 | retval = readFileToByteString(filename, &((*list)[numActCerts])); |
247 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
248 | 0 | UA_closedir(dir); |
249 | 0 | return retval; |
250 | 0 | } |
251 | 0 | } |
252 | 0 | numActCerts++; |
253 | 0 | } |
254 | 31.7k | UA_closedir(dir); |
255 | | |
256 | 31.7k | return retval; |
257 | 31.7k | } |
258 | | |
259 | | static UA_StatusCode |
260 | 7.92k | readTrustStore(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) { |
261 | 7.92k | if(certGroup == NULL) |
262 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
263 | | |
264 | 7.92k | FileCertStore *context = (FileCertStore *)certGroup->context; |
265 | | |
266 | 7.92k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
267 | 7.92k | retval |= readCertificates(&trustList->trustedCertificates, &trustList->trustedCertificatesSize, |
268 | 7.92k | context->trustedCertFolder); |
269 | 7.92k | retval |= readCertificates(&trustList->trustedCrls, &trustList->trustedCrlsSize, |
270 | 7.92k | context->trustedCrlFolder); |
271 | 7.92k | retval |= readCertificates(&trustList->issuerCertificates, &trustList->issuerCertificatesSize, |
272 | 7.92k | context->issuerCertFolder); |
273 | 7.92k | retval |= readCertificates(&trustList->issuerCrls, &trustList->issuerCrlsSize, |
274 | 7.92k | context->issuerCrlFolder); |
275 | | |
276 | 7.92k | return retval; |
277 | 7.92k | } |
278 | | |
279 | | static UA_StatusCode |
280 | 7.92k | reloadAndWriteTrustStore(UA_CertificateGroup *certGroup) { |
281 | 7.92k | FileCertStore *context = (FileCertStore *)certGroup->context; |
282 | | |
283 | 7.92k | UA_TrustListDataType trustList; |
284 | 7.92k | UA_TrustListDataType_init(&trustList); |
285 | 7.92k | trustList.specifiedLists = UA_TRUSTLISTMASKS_ALL; |
286 | | |
287 | 7.92k | UA_StatusCode retval = readTrustStore(certGroup, &trustList); |
288 | 7.92k | if(retval != UA_STATUSCODE_GOOD) { |
289 | 0 | UA_TrustListDataType_clear(&trustList); |
290 | 0 | return retval; |
291 | 0 | } |
292 | | |
293 | 7.92k | retval = context->store->setTrustList(context->store, &trustList); |
294 | 7.92k | UA_TrustListDataType_clear(&trustList); |
295 | | |
296 | 7.92k | return retval; |
297 | 7.92k | } |
298 | | |
299 | | static UA_StatusCode |
300 | 0 | reloadTrustStore(UA_CertificateGroup *certGroup) { |
301 | 0 | if(certGroup == NULL) |
302 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
303 | | |
304 | 0 | #ifdef __linux__ |
305 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
306 | |
|
307 | 0 | char buffer[BUF_LEN]; |
308 | 0 | const UA_Int64 length = (UA_Int64)read(context->inotifyFd, buffer, BUF_LEN); |
309 | 0 | if(length == -1 && errno != EAGAIN) |
310 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
311 | | #else |
312 | | /* TODO: Implement a way to check for changes in the pki folder */ |
313 | | const UA_Int64 length = 0; |
314 | | #endif /* __linux__ */ |
315 | | |
316 | | /* No events, which means no changes to the pki folder */ |
317 | | /* If the nonblocking read() found no events to read, then |
318 | | * it returns -1 with errno set to EAGAIN. In that case, |
319 | | * we exit the loop. */ |
320 | 0 | if(length <= 0) |
321 | 0 | return UA_STATUSCODE_GOOD; |
322 | | |
323 | 0 | return reloadAndWriteTrustStore(certGroup); |
324 | 0 | } |
325 | | |
326 | | static UA_StatusCode |
327 | | writeCertificates(UA_CertificateGroup *certGroup, const UA_ByteString *list, |
328 | 0 | size_t listSize, const char *listPath) { |
329 | | /* Check parameter */ |
330 | 0 | if(listPath == NULL) |
331 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
332 | 0 | if(listSize > 0 && list == NULL) |
333 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
334 | | |
335 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
336 | 0 | for(size_t i = 0; i < listSize; i++) { |
337 | | /* Create filename to load */ |
338 | 0 | char filename[UA_PATH_MAX] = {0}; |
339 | 0 | retval = getCertFileName(listPath, &list[i], filename, UA_PATH_MAX); |
340 | 0 | if(retval != UA_STATUSCODE_GOOD) |
341 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
342 | | |
343 | | /* Store data in file */ |
344 | 0 | retval = writeByteStringToFile(filename, &list[i]); |
345 | 0 | if(retval != UA_STATUSCODE_GOOD) |
346 | 0 | return retval; |
347 | 0 | } |
348 | | |
349 | 0 | return retval; |
350 | 0 | } |
351 | | |
352 | | static UA_StatusCode |
353 | | writeTrustList(UA_CertificateGroup *certGroup, const UA_ByteString *list, |
354 | 0 | size_t listSize, const UA_String path) { |
355 | | /* Check parameter */ |
356 | 0 | if(path.length == 0) |
357 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
358 | 0 | if(listSize > 0 && list == NULL) |
359 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
360 | | |
361 | 0 | char listPath[UA_PATH_MAX] = {0}; |
362 | 0 | mp_snprintf(listPath, UA_PATH_MAX, "%.*s", (int)path.length, (char *)path.data); |
363 | | /* remove existing files in directory */ |
364 | 0 | UA_StatusCode retval = removeAllFilesFromDir(listPath, false); |
365 | 0 | if(retval != UA_STATUSCODE_GOOD) |
366 | 0 | return retval; |
367 | | |
368 | 0 | return writeCertificates(certGroup, list, listSize, listPath); |
369 | 0 | } |
370 | | |
371 | | static UA_StatusCode |
372 | 0 | writeTrustStore(UA_CertificateGroup *certGroup, const UA_UInt32 trustListMask) { |
373 | | /* Check parameter */ |
374 | 0 | if(certGroup == NULL) |
375 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
376 | | |
377 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
378 | |
|
379 | 0 | UA_TrustListDataType trustList; |
380 | 0 | UA_TrustListDataType_init(&trustList); |
381 | 0 | trustList.specifiedLists = trustListMask; |
382 | |
|
383 | 0 | context->store->getTrustList(context->store, &trustList); |
384 | |
|
385 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
386 | 0 | if(trustList.specifiedLists & UA_TRUSTLISTMASKS_TRUSTEDCERTIFICATES) { |
387 | 0 | retval = writeTrustList(certGroup, trustList.trustedCertificates, |
388 | 0 | trustList.trustedCertificatesSize, context->trustedCertFolder); |
389 | 0 | if(retval != UA_STATUSCODE_GOOD) |
390 | 0 | return retval; |
391 | 0 | } |
392 | 0 | if(trustList.specifiedLists & UA_TRUSTLISTMASKS_TRUSTEDCRLS) { |
393 | 0 | retval = writeTrustList(certGroup, trustList.trustedCrls, |
394 | 0 | trustList.trustedCrlsSize, context->trustedCrlFolder); |
395 | 0 | if(retval != UA_STATUSCODE_GOOD) |
396 | 0 | return retval; |
397 | 0 | } |
398 | 0 | if(trustList.specifiedLists & UA_TRUSTLISTMASKS_ISSUERCERTIFICATES) { |
399 | 0 | retval = writeTrustList(certGroup, trustList.issuerCertificates, |
400 | 0 | trustList.issuerCertificatesSize, context->issuerCertFolder); |
401 | 0 | if(retval != UA_STATUSCODE_GOOD) |
402 | 0 | return retval; |
403 | 0 | } |
404 | 0 | if(trustList.specifiedLists & UA_TRUSTLISTMASKS_ISSUERCRLS) { |
405 | 0 | retval = writeTrustList(certGroup, trustList.issuerCrls, |
406 | 0 | trustList.issuerCrlsSize, context->issuerCrlFolder); |
407 | 0 | if(retval != UA_STATUSCODE_GOOD) |
408 | 0 | return retval; |
409 | 0 | } |
410 | 0 | UA_TrustListDataType_clear(&trustList); |
411 | |
|
412 | 0 | return retval; |
413 | 0 | } |
414 | | |
415 | | static UA_StatusCode |
416 | | FileCertStore_setupStorePath(char *directory, char *rootDirectory, |
417 | | size_t rootDirectorySize, UA_String *out, |
418 | 56.0k | const UA_Logger *logger) { |
419 | 56.0k | char path[UA_PATH_MAX] = {0}; |
420 | 56.0k | int pathLen = mp_snprintf(path, UA_PATH_MAX, "%.*s%s", |
421 | 56.0k | (int)rootDirectorySize, rootDirectory, directory); |
422 | 56.0k | if(pathLen < 0 || pathLen >= UA_PATH_MAX) |
423 | 62 | return UA_STATUSCODE_BADINTERNALERROR; |
424 | | |
425 | 55.9k | *out = UA_STRING_ALLOC(path); |
426 | 55.9k | if(out->data == NULL) |
427 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
428 | | |
429 | 55.9k | if(mkpath(path, 0777) != 0) { |
430 | 480 | UA_LOG_ERROR(logger, UA_LOGCATEGORY_APPLICATION, |
431 | 480 | "Could not create PKI directory %s (errno %i)", path, errno); |
432 | 480 | return UA_STATUSCODE_BADINTERNALERROR; |
433 | 480 | } |
434 | 55.5k | return UA_STATUSCODE_GOOD; |
435 | 55.9k | } |
436 | | |
437 | | static UA_StatusCode |
438 | 8.02k | FileCertStore_createPkiDirectory(UA_CertificateGroup *certGroup, const UA_String directory) { |
439 | 8.02k | if(certGroup == NULL) |
440 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
441 | | |
442 | 8.02k | FileCertStore *context = (FileCertStore *)certGroup->context; |
443 | 8.02k | if(!context) |
444 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
445 | | |
446 | 8.02k | char rootDirectory[UA_PATH_MAX] = {0}; |
447 | 8.02k | size_t rootDirectorySize = 0; |
448 | | |
449 | 8.02k | if(directory.length <= 0 || directory.length >= UA_PATH_MAX) |
450 | 18 | return UA_STATUSCODE_BADINTERNALERROR; |
451 | | |
452 | 8.00k | memcpy(rootDirectory, directory.data, directory.length); |
453 | 8.00k | rootDirectorySize = UA_strnlen(rootDirectory, UA_PATH_MAX); |
454 | | |
455 | | /* Add Certificate Group Id */ |
456 | 8.00k | UA_NodeId applCertGroup = |
457 | 8.00k | UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP); |
458 | 8.00k | UA_NodeId httpCertGroup = |
459 | 8.00k | UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTHTTPSGROUP); |
460 | 8.00k | UA_NodeId userTokenCertGroup = |
461 | 8.00k | UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTUSERTOKENGROUP); |
462 | | |
463 | 8.00k | if(UA_NodeId_equal(&certGroup->certificateGroupId, &applCertGroup)) { |
464 | 4.04k | strncpy(&rootDirectory[rootDirectorySize], "/ApplCerts", UA_PATH_MAX - rootDirectorySize); |
465 | 4.04k | } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &httpCertGroup)) { |
466 | 0 | strncpy(&rootDirectory[rootDirectorySize], "/HttpCerts", UA_PATH_MAX - rootDirectorySize); |
467 | 3.96k | } else if(UA_NodeId_equal(&certGroup->certificateGroupId, &userTokenCertGroup)) { |
468 | 3.96k | strncpy(&rootDirectory[rootDirectorySize], "/UserTokenCerts", UA_PATH_MAX - rootDirectorySize); |
469 | 3.96k | } else { |
470 | 0 | UA_String nodeIdStr; |
471 | 0 | UA_String_init(&nodeIdStr); |
472 | 0 | UA_NodeId_print(&certGroup->certificateGroupId, &nodeIdStr); |
473 | 0 | strncpy(&rootDirectory[rootDirectorySize], (char *)nodeIdStr.data, UA_PATH_MAX - rootDirectorySize); |
474 | 0 | UA_String_clear(&nodeIdStr); |
475 | 0 | } |
476 | 8.00k | rootDirectorySize = UA_strnlen(rootDirectory, UA_PATH_MAX); |
477 | | |
478 | 8.00k | context->rootFolder = UA_STRING_ALLOC(rootDirectory); |
479 | | |
480 | 8.00k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
481 | 8.00k | retval |= FileCertStore_setupStorePath("/trusted/certs", rootDirectory, |
482 | 8.00k | rootDirectorySize, &context->trustedCertFolder, |
483 | 8.00k | certGroup->logging); |
484 | 8.00k | retval |= FileCertStore_setupStorePath("/trusted/crl", rootDirectory, |
485 | 8.00k | rootDirectorySize, &context->trustedCrlFolder, |
486 | 8.00k | certGroup->logging); |
487 | 8.00k | retval |= FileCertStore_setupStorePath("/issuer/certs", rootDirectory, |
488 | 8.00k | rootDirectorySize, &context->issuerCertFolder, |
489 | 8.00k | certGroup->logging); |
490 | 8.00k | retval |= FileCertStore_setupStorePath("/issuer/crl", rootDirectory, |
491 | 8.00k | rootDirectorySize, &context->issuerCrlFolder, |
492 | 8.00k | certGroup->logging); |
493 | 8.00k | retval |= FileCertStore_setupStorePath("/rejected/certs", rootDirectory, |
494 | 8.00k | rootDirectorySize, &context->rejectedCertFolder, |
495 | 8.00k | certGroup->logging); |
496 | 8.00k | retval |= FileCertStore_setupStorePath("/own/certs", rootDirectory, |
497 | 8.00k | rootDirectorySize, &context->ownCertFolder, |
498 | 8.00k | certGroup->logging); |
499 | 8.00k | retval |= FileCertStore_setupStorePath("/own/private", rootDirectory, |
500 | 8.00k | rootDirectorySize, &context->ownKeyFolder, |
501 | 8.00k | certGroup->logging); |
502 | | |
503 | 8.00k | return retval; |
504 | 8.02k | } |
505 | | |
506 | | #ifdef __linux__ |
507 | | |
508 | | static UA_StatusCode |
509 | 7.92k | FileCertStore_createInotifyEvent(UA_CertificateGroup *certGroup) { |
510 | 7.92k | if(certGroup == NULL) |
511 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
512 | | |
513 | 7.92k | FileCertStore *context = (FileCertStore *)certGroup->context; |
514 | | |
515 | 7.92k | context->inotifyFd = inotify_init1(IN_NONBLOCK); |
516 | 7.92k | if(context->inotifyFd == -1) |
517 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
518 | | |
519 | 7.92k | char folder[UA_PATH_MAX] = {0}; |
520 | 7.92k | mp_snprintf(folder, UA_PATH_MAX, "%.*s", |
521 | 7.92k | (int)context->rootFolder.length, (char*)context->rootFolder.data); |
522 | 7.92k | int wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); |
523 | 7.92k | if(wd == -1) { |
524 | 0 | close(context->inotifyFd); |
525 | 0 | context->inotifyFd = -1; |
526 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
527 | 0 | } |
528 | | |
529 | 7.92k | mp_snprintf(folder, UA_PATH_MAX, "%.*s", |
530 | 7.92k | (int)context->trustedCertFolder.length, (char*)context->trustedCertFolder.data); |
531 | 7.92k | wd = inotify_add_watch(context->inotifyFd, folder, IN_ALL_EVENTS); |
532 | 7.92k | if(wd == -1) { |
533 | 0 | close(context->inotifyFd); |
534 | 0 | context->inotifyFd = -1; |
535 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
536 | 0 | } |
537 | | |
538 | 7.92k | return UA_STATUSCODE_GOOD; |
539 | 7.92k | } |
540 | | |
541 | | #endif /* __linux__ */ |
542 | | |
543 | | static UA_StatusCode |
544 | 0 | FileCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) { |
545 | | /* Check parameter */ |
546 | 0 | if(certGroup == NULL || trustList == NULL) |
547 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
548 | | |
549 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
550 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
551 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
552 | 0 | if(retval != UA_STATUSCODE_GOOD) |
553 | 0 | return retval; |
554 | | |
555 | 0 | return context->store->getTrustList(context->store, trustList); |
556 | 0 | } |
557 | | |
558 | | static UA_StatusCode |
559 | 0 | FileCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
560 | | /* Check parameter */ |
561 | 0 | if(certGroup == NULL || trustList == NULL) |
562 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
563 | | |
564 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
565 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
566 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
567 | 0 | if(retval != UA_STATUSCODE_GOOD) |
568 | 0 | return retval; |
569 | | |
570 | 0 | retval = context->store->setTrustList(context->store, trustList); |
571 | 0 | if(retval != UA_STATUSCODE_GOOD) |
572 | 0 | return retval; |
573 | | |
574 | 0 | return writeTrustStore(certGroup, trustList->specifiedLists); |
575 | 0 | } |
576 | | |
577 | | static UA_StatusCode |
578 | 0 | FileCertStore_addToTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
579 | | /* Check parameter */ |
580 | 0 | if(certGroup == NULL || trustList == NULL) |
581 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
582 | | |
583 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
584 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
585 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
586 | 0 | if(retval != UA_STATUSCODE_GOOD) |
587 | 0 | return retval; |
588 | | |
589 | 0 | retval = context->store->addToTrustList(context->store, trustList); |
590 | 0 | if(retval != UA_STATUSCODE_GOOD) |
591 | 0 | return retval; |
592 | | |
593 | 0 | return writeTrustStore(certGroup, trustList->specifiedLists); |
594 | 0 | } |
595 | | |
596 | | static UA_StatusCode |
597 | 0 | FileCertStore_removeFromTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
598 | | /* Check parameter */ |
599 | 0 | if(certGroup == NULL || trustList == NULL) |
600 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
601 | | |
602 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
603 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
604 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
605 | 0 | if(retval != UA_STATUSCODE_GOOD) |
606 | 0 | return retval; |
607 | | |
608 | 0 | retval = context->store->removeFromTrustList(context->store, trustList); |
609 | 0 | if(retval != UA_STATUSCODE_GOOD) |
610 | 0 | return retval; |
611 | | |
612 | 0 | return writeTrustStore(certGroup, trustList->specifiedLists); |
613 | 0 | } |
614 | | |
615 | | static UA_StatusCode |
616 | 0 | FileCertStore_getRejectedList(UA_CertificateGroup *certGroup, UA_ByteString **rejectedList, size_t *rejectedListSize) { |
617 | | /* Check parameter */ |
618 | 0 | if(certGroup == NULL || rejectedList == NULL || rejectedListSize == NULL) |
619 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
620 | | |
621 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
622 | 0 | return context->store->getRejectedList(context->store, rejectedList, rejectedListSize); |
623 | 0 | } |
624 | | |
625 | | static UA_StatusCode |
626 | | FileCertStore_getCertificateCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate, |
627 | | const UA_Boolean isTrusted, UA_ByteString **crls, |
628 | 0 | size_t *crlsSize) { |
629 | | /* Check parameter */ |
630 | 0 | if(certGroup == NULL || certificate == NULL || crls == NULL) { |
631 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
632 | 0 | } |
633 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
634 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
635 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
636 | 0 | if(retval != UA_STATUSCODE_GOOD) |
637 | 0 | return retval; |
638 | | |
639 | 0 | return context->store->getCertificateCrls(context->store, certificate, isTrusted, crls, crlsSize); |
640 | 0 | } |
641 | | |
642 | | static UA_StatusCode |
643 | 0 | FileCertStore_verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certificate) { |
644 | | /* Check parameter */ |
645 | 0 | if(certGroup == NULL || certificate == NULL) |
646 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
647 | | |
648 | 0 | FileCertStore *context = (FileCertStore *)certGroup->context; |
649 | | /* It will only re-read the Cert store on the file system if there have been changes to files. */ |
650 | 0 | UA_StatusCode retval = reloadTrustStore(certGroup); |
651 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
652 | 0 | return retval; |
653 | 0 | } |
654 | | |
655 | 0 | retval = context->store->verifyCertificate(context->store, certificate); |
656 | 0 | if(retval == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED || |
657 | 0 | retval == UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED || |
658 | 0 | retval == UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN || |
659 | 0 | retval == UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN) { |
660 | | /* write rejectedList to filestore */ |
661 | 0 | UA_ByteString *rejectedList = NULL; |
662 | 0 | size_t rejectedListSize = 0; |
663 | 0 | context->store->getRejectedList(context->store, &rejectedList, &rejectedListSize); |
664 | 0 | writeTrustList(certGroup, rejectedList, rejectedListSize, context->rejectedCertFolder); |
665 | 0 | UA_Array_delete(rejectedList, rejectedListSize, &UA_TYPES[UA_TYPES_BYTESTRING]); |
666 | 0 | } |
667 | |
|
668 | 0 | return retval; |
669 | 0 | } |
670 | | |
671 | | static void |
672 | 8.12k | FileCertStore_clear(UA_CertificateGroup *certGroup) { |
673 | | /* check parameter */ |
674 | 8.12k | if(!certGroup || !certGroup->context) |
675 | 98 | return; |
676 | | |
677 | 8.02k | UA_NodeId_clear(&certGroup->certificateGroupId); |
678 | | |
679 | 8.02k | FileCertStore *context = (FileCertStore *)certGroup->context; |
680 | | |
681 | 8.02k | if(context->store) { |
682 | 7.92k | context->store->clear(context->store); |
683 | 7.92k | UA_free(context->store); |
684 | 7.92k | } |
685 | 8.02k | UA_String_clear(&context->trustedCertFolder); |
686 | 8.02k | UA_String_clear(&context->trustedCrlFolder); |
687 | 8.02k | UA_String_clear(&context->issuerCertFolder); |
688 | 8.02k | UA_String_clear(&context->issuerCrlFolder); |
689 | 8.02k | UA_String_clear(&context->rejectedCertFolder); |
690 | 8.02k | UA_String_clear(&context->ownCertFolder); |
691 | 8.02k | UA_String_clear(&context->ownKeyFolder); |
692 | 8.02k | UA_String_clear(&context->rootFolder); |
693 | | |
694 | 8.02k | #ifdef __linux__ |
695 | 8.02k | if(context->inotifyFd > 0) |
696 | 7.92k | close(context->inotifyFd); |
697 | 8.02k | #endif /* __linux__ */ |
698 | | |
699 | 8.02k | UA_free(context); |
700 | 8.02k | certGroup->context = NULL; |
701 | 8.02k | } |
702 | | |
703 | | UA_StatusCode |
704 | | UA_CertificateGroup_Filestore(UA_CertificateGroup *certGroup, |
705 | | UA_NodeId *certificateGroupId, |
706 | | const UA_String storePath, |
707 | | const UA_Logger *logger, |
708 | 8.02k | const UA_KeyValueMap *params) { |
709 | 8.02k | if(certGroup == NULL || certificateGroupId == NULL) { |
710 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
711 | 0 | } |
712 | | |
713 | 8.02k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
714 | | |
715 | | /* Clear if the plugin is already initialized */ |
716 | 8.02k | if(certGroup->clear) |
717 | 8.02k | certGroup->clear(certGroup); |
718 | | |
719 | 8.02k | UA_NodeId_copy(certificateGroupId, &certGroup->certificateGroupId); |
720 | 8.02k | certGroup->logging = logger; |
721 | | |
722 | 8.02k | certGroup->getTrustList = FileCertStore_getTrustList; |
723 | 8.02k | certGroup->setTrustList = FileCertStore_setTrustList; |
724 | 8.02k | certGroup->addToTrustList = FileCertStore_addToTrustList; |
725 | 8.02k | certGroup->removeFromTrustList = FileCertStore_removeFromTrustList; |
726 | 8.02k | certGroup->getRejectedList = FileCertStore_getRejectedList; |
727 | 8.02k | certGroup->getCertificateCrls = FileCertStore_getCertificateCrls; |
728 | 8.02k | certGroup->verifyCertificate = FileCertStore_verifyCertificate; |
729 | 8.02k | certGroup->clear = FileCertStore_clear; |
730 | | |
731 | | /* Set PKI Store context data */ |
732 | 8.02k | FileCertStore *context = (FileCertStore *)UA_calloc(1, sizeof(FileCertStore)); |
733 | 8.02k | if(!context) { |
734 | 0 | retval = UA_STATUSCODE_BADOUTOFMEMORY; |
735 | 0 | goto cleanup; |
736 | 0 | } |
737 | 8.02k | certGroup->context = context; |
738 | | |
739 | 8.02k | retval = FileCertStore_createPkiDirectory(certGroup, storePath); |
740 | 8.02k | if(retval != UA_STATUSCODE_GOOD) { |
741 | 98 | UA_LOG_ERROR(logger, UA_LOGCATEGORY_APPLICATION, |
742 | 98 | "Could not create the PKI directory structure"); |
743 | 98 | goto cleanup; |
744 | 98 | } |
745 | | |
746 | 7.92k | context->store = (UA_CertificateGroup*)UA_calloc(1, sizeof(UA_CertificateGroup)); |
747 | 7.92k | retval = UA_CertificateGroup_Memorystore(context->store, certificateGroupId, NULL, logger, params); |
748 | 7.92k | if(retval != UA_STATUSCODE_GOOD) { |
749 | 0 | UA_LOG_ERROR(logger, UA_LOGCATEGORY_APPLICATION, |
750 | 0 | "Could not initialize the PKI memory store"); |
751 | 0 | goto cleanup; |
752 | 0 | } |
753 | | |
754 | 7.92k | #ifdef __linux__ |
755 | 7.92k | retval = FileCertStore_createInotifyEvent(certGroup); |
756 | 7.92k | if(retval != UA_STATUSCODE_GOOD) { |
757 | 0 | goto cleanup; |
758 | 0 | } |
759 | 7.92k | #endif /* __linux__ */ |
760 | | |
761 | 7.92k | retval = reloadAndWriteTrustStore(certGroup); |
762 | 7.92k | if(retval != UA_STATUSCODE_GOOD) { |
763 | 0 | UA_LOG_ERROR(logger, UA_LOGCATEGORY_APPLICATION, |
764 | 0 | "Could not load the PKI trust store from the filestore"); |
765 | 0 | goto cleanup; |
766 | 0 | } |
767 | | |
768 | 7.92k | return UA_STATUSCODE_GOOD; |
769 | | |
770 | 98 | cleanup: |
771 | 98 | certGroup->clear(certGroup); |
772 | 98 | return retval; |
773 | 7.92k | } |
774 | | |
775 | | #endif /* defined(__linux__) || defined(UA_ARCHITECTURE_WIN32) */ |
776 | | |
777 | | #endif /* UA_ENABLE_ENCRYPTION */ |