/src/open62541_15/plugins/crypto/openssl/certificategroup.c
Line | Count | Source |
1 | | /* This work is licensed under a Creative Commons CCZero 1.0 Universal License. |
2 | | * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. |
3 | | * |
4 | | * Copyright 2020 (c) Wind River Systems, Inc. |
5 | | * Copyright 2020 (c) basysKom GmbH |
6 | | * Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf) |
7 | | * Copyright 2024 (c) Siemens AG (Authors: Tin Raic, Thomas Zeschg) |
8 | | */ |
9 | | |
10 | | #include <open62541/util.h> |
11 | | #include <open62541/plugin/certificategroup_default.h> |
12 | | |
13 | | #if defined(UA_ENABLE_ENCRYPTION_OPENSSL) || defined(UA_ENABLE_ENCRYPTION_LIBRESSL) |
14 | | #include <openssl/x509.h> |
15 | | #include <openssl/x509_vfy.h> |
16 | | #include <openssl/x509v3.h> |
17 | | #include <openssl/pem.h> |
18 | | |
19 | | #include "libc_time.h" |
20 | | #include "securitypolicy_common.h" |
21 | | |
22 | 7 | #define SHA1_DIGEST_LENGTH 20 |
23 | | |
24 | | /* Configuration parameters */ |
25 | | |
26 | | #define MEMORYCERTSTORE_PARAMETERSSIZE 2 |
27 | 7.09k | #define MEMORYCERTSTORE_PARAMINDEX_MAXTRUSTLISTSIZE 0 |
28 | 7.09k | #define MEMORYCERTSTORE_PARAMINDEX_MAXREJECTEDLISTSIZE 1 |
29 | | |
30 | | static const struct { |
31 | | UA_QualifiedName name; |
32 | | const UA_DataType *type; |
33 | | UA_Boolean required; |
34 | | } MemoryCertStoreParameters[MEMORYCERTSTORE_PARAMETERSSIZE] = { |
35 | | {{0, UA_STRING_STATIC("maxTrustListSize")}, &UA_TYPES[UA_TYPES_UINT16], false}, |
36 | | {{0, UA_STRING_STATIC("maxRejectedListSize")}, &UA_TYPES[UA_TYPES_STRING], false} |
37 | | }; |
38 | | |
39 | | struct MemoryCertStore; |
40 | | typedef struct MemoryCertStore MemoryCertStore; |
41 | | |
42 | | struct MemoryCertStore { |
43 | | UA_TrustListDataType trustList; |
44 | | size_t rejectedCertificatesSize; |
45 | | UA_ByteString *rejectedCertificates; |
46 | | |
47 | | UA_UInt32 maxTrustListSize; |
48 | | UA_UInt32 maxRejectedListSize; |
49 | | |
50 | | UA_Boolean reloadRequired; |
51 | | |
52 | | STACK_OF(X509) *trustedCertificates; |
53 | | STACK_OF(X509) *issuerCertificates; |
54 | | STACK_OF(X509_CRL) *crls; |
55 | | }; |
56 | | |
57 | | static UA_Boolean |
58 | | openSSLCheckCA(X509 *cert); |
59 | | |
60 | | static UA_StatusCode |
61 | 0 | MemoryCertStore_removeFromTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
62 | | /* Check parameter */ |
63 | 0 | if(certGroup == NULL || trustList == NULL) { |
64 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
65 | 0 | } |
66 | | |
67 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
68 | 0 | context->reloadRequired = true; |
69 | 0 | return UA_TrustListDataType_remove(trustList, &context->trustList); |
70 | 0 | } |
71 | | |
72 | | static UA_StatusCode |
73 | 0 | MemoryCertStore_getTrustList(UA_CertificateGroup *certGroup, UA_TrustListDataType *trustList) { |
74 | | /* Check parameter */ |
75 | 0 | if(certGroup == NULL || trustList == NULL) { |
76 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
77 | 0 | } |
78 | | |
79 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
80 | 0 | return UA_TrustListDataType_copy(&context->trustList, trustList); |
81 | 0 | } |
82 | | |
83 | | static UA_StatusCode |
84 | 7.09k | MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
85 | | /* Check parameter */ |
86 | 7.09k | if(certGroup == NULL || trustList == NULL) { |
87 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
88 | 0 | } |
89 | | |
90 | 7.09k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
91 | 7.09k | if(context->maxTrustListSize != 0 && UA_TrustListDataType_getSize(trustList) > context->maxTrustListSize) { |
92 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
93 | 0 | } |
94 | 7.09k | context->reloadRequired = true; |
95 | | /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */ |
96 | 7.09k | return UA_TrustListDataType_set(trustList, &context->trustList); |
97 | 7.09k | } |
98 | | |
99 | | static UA_StatusCode |
100 | 0 | MemoryCertStore_addToTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
101 | | /* Check parameter */ |
102 | 0 | if(certGroup == NULL || trustList == NULL) { |
103 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
104 | 0 | } |
105 | | |
106 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
107 | 0 | if(context->maxTrustListSize != 0 && UA_TrustListDataType_getSize(&context->trustList) + UA_TrustListDataType_getSize(trustList) > context->maxTrustListSize) { |
108 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
109 | 0 | } |
110 | 0 | context->reloadRequired = true; |
111 | 0 | return UA_TrustListDataType_add(trustList, &context->trustList); |
112 | 0 | } |
113 | | |
114 | | static UA_StatusCode |
115 | 0 | MemoryCertStore_getRejectedList(UA_CertificateGroup *certGroup, UA_ByteString **rejectedList, size_t *rejectedListSize) { |
116 | | /* Check parameter */ |
117 | 0 | if(certGroup == NULL || rejectedList == NULL || rejectedListSize == NULL) { |
118 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
119 | 0 | } |
120 | | |
121 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
122 | 0 | UA_StatusCode retval = UA_Array_copy(context->rejectedCertificates, context->rejectedCertificatesSize, |
123 | 0 | (void**)rejectedList, &UA_TYPES[UA_TYPES_BYTESTRING]); |
124 | |
|
125 | 0 | if(retval == UA_STATUSCODE_GOOD) |
126 | 0 | *rejectedListSize = context->rejectedCertificatesSize; |
127 | |
|
128 | 0 | return retval; |
129 | 0 | } |
130 | | |
131 | | static UA_StatusCode |
132 | 0 | openSSLCheckCrlMatch(X509 *cert, X509_CRL *crl) { |
133 | 0 | X509_NAME *certSubject = X509_get_subject_name(cert); |
134 | 0 | if(!certSubject) |
135 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
136 | | |
137 | 0 | X509_NAME *crlIssuer = X509_CRL_get_issuer(crl); |
138 | 0 | if(!crlIssuer) { |
139 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
140 | 0 | } |
141 | | |
142 | 0 | if(X509_NAME_cmp(certSubject, crlIssuer) == 0) |
143 | 0 | return UA_STATUSCODE_GOOD; |
144 | | |
145 | 0 | return UA_STATUSCODE_BADNOMATCH; |
146 | 0 | } |
147 | | |
148 | | static UA_StatusCode |
149 | | openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate, |
150 | | const UA_ByteString *crlList, const size_t crlListSize, |
151 | 0 | UA_ByteString **crls, size_t *crlsSize) { |
152 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
153 | |
|
154 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
155 | 0 | if(!cert) |
156 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
157 | | |
158 | | /* Check if the certificate is a CA certificate. |
159 | | * Only a CA certificate can have a CRL. */ |
160 | 0 | if(!openSSLCheckCA(cert)) { |
161 | 0 | UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, |
162 | 0 | "The certificate is not a CA certificate and therefore does not have a CRL."); |
163 | 0 | X509_free(cert); |
164 | 0 | return UA_STATUSCODE_GOOD; |
165 | 0 | } |
166 | | |
167 | 0 | UA_Boolean foundMatch = false; |
168 | 0 | for(size_t i = 0; i < crlListSize; i++) { |
169 | 0 | X509_CRL *crl = UA_OpenSSL_LoadCrl(&crlList[i]); |
170 | 0 | if(!crl) { |
171 | 0 | X509_free(cert); |
172 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
173 | 0 | } |
174 | | |
175 | 0 | retval = openSSLCheckCrlMatch(cert, crl); |
176 | 0 | X509_CRL_free(crl); |
177 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
178 | 0 | continue; |
179 | 0 | } |
180 | | |
181 | | /* Continue the search, as a certificate may be associated with multiple CRLs. */ |
182 | 0 | foundMatch = true; |
183 | 0 | retval = UA_Array_appendCopy((void **)crls, crlsSize, &crlList[i], |
184 | 0 | &UA_TYPES[UA_TYPES_BYTESTRING]); |
185 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
186 | 0 | X509_free(cert); |
187 | 0 | return retval; |
188 | 0 | } |
189 | 0 | } |
190 | 0 | X509_free(cert); |
191 | 0 | if(!foundMatch) |
192 | 0 | return UA_STATUSCODE_BADNOMATCH; |
193 | | |
194 | 0 | return UA_STATUSCODE_GOOD; |
195 | 0 | } |
196 | | |
197 | | static UA_StatusCode |
198 | | MemoryCertStore_getCertificateCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate, |
199 | | const UA_Boolean isTrusted, UA_ByteString **crls, |
200 | 0 | size_t *crlsSize) { |
201 | | /* Check parameter */ |
202 | 0 | if(certGroup == NULL || certificate == NULL || crls == NULL) { |
203 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
204 | 0 | } |
205 | | |
206 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
207 | |
|
208 | 0 | if(isTrusted) { |
209 | 0 | return openSSLFindCrls(certGroup, certificate, |
210 | 0 | context->trustList.trustedCrls, |
211 | 0 | context->trustList.trustedCrlsSize, crls, |
212 | 0 | crlsSize); |
213 | 0 | } |
214 | 0 | return openSSLFindCrls(certGroup, certificate, |
215 | 0 | context->trustList.issuerCrls, |
216 | 0 | context->trustList.issuerCrlsSize, crls, |
217 | 0 | crlsSize); |
218 | 0 | } |
219 | | |
220 | | static UA_StatusCode |
221 | 0 | MemoryCertStore_addToRejectedList(UA_CertificateGroup *certGroup, const UA_ByteString *certificate) { |
222 | | /* Check parameter */ |
223 | 0 | if(certGroup == NULL || certificate == NULL) { |
224 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
225 | 0 | } |
226 | | |
227 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
228 | | |
229 | | /* check duplicate certificate */ |
230 | 0 | for(size_t i = 0; i < context->rejectedCertificatesSize; i++) { |
231 | 0 | if(UA_ByteString_equal(certificate, &context->rejectedCertificates[i])) |
232 | 0 | return UA_STATUSCODE_GOOD; /* certificate already exist */ |
233 | 0 | } |
234 | | |
235 | | /* Store rejected certificate */ |
236 | 0 | if(context->maxRejectedListSize == 0 || context->rejectedCertificatesSize < context->maxRejectedListSize) { |
237 | 0 | return UA_Array_appendCopy((void**)&context->rejectedCertificates, &context->rejectedCertificatesSize, |
238 | 0 | certificate, &UA_TYPES[UA_TYPES_BYTESTRING]); |
239 | 0 | } |
240 | 0 | UA_Array_delete(context->rejectedCertificates, context->rejectedCertificatesSize, &UA_TYPES[UA_TYPES_BYTESTRING]); |
241 | 0 | context->rejectedCertificates = NULL; |
242 | 0 | context->rejectedCertificatesSize = 0; |
243 | 0 | return UA_Array_appendCopy((void**)&context->rejectedCertificates, &context->rejectedCertificatesSize, |
244 | 0 | certificate, &UA_TYPES[UA_TYPES_BYTESTRING]); |
245 | 0 | } |
246 | | |
247 | | static void |
248 | 7.09k | MemoryCertStore_clear(UA_CertificateGroup *certGroup) { |
249 | | /* check parameter */ |
250 | 7.09k | if(certGroup == NULL) { |
251 | 0 | return; |
252 | 0 | } |
253 | | |
254 | 7.09k | UA_NodeId_clear(&certGroup->certificateGroupId); |
255 | | |
256 | 7.09k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
257 | 7.09k | if(context) { |
258 | 7.09k | UA_TrustListDataType_clear(&context->trustList); |
259 | | |
260 | 7.09k | UA_Array_delete(context->rejectedCertificates, context->rejectedCertificatesSize, &UA_TYPES[UA_TYPES_BYTESTRING]); |
261 | 7.09k | context->rejectedCertificates = NULL; |
262 | 7.09k | context->rejectedCertificatesSize = 0; |
263 | | |
264 | 7.09k | sk_X509_pop_free(context->trustedCertificates, X509_free); |
265 | 7.09k | sk_X509_pop_free(context->issuerCertificates, X509_free); |
266 | 7.09k | sk_X509_CRL_pop_free(context->crls, X509_CRL_free); |
267 | | |
268 | 7.09k | UA_free(context); |
269 | 7.09k | certGroup->context = NULL; |
270 | 7.09k | } |
271 | 7.09k | } |
272 | | |
273 | | static UA_StatusCode |
274 | 7.09k | reloadCertificates(UA_CertificateGroup *certGroup) { |
275 | | /* Check parameter */ |
276 | 7.09k | if(certGroup == NULL) { |
277 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
278 | 0 | } |
279 | | |
280 | 7.09k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
281 | | |
282 | 7.09k | sk_X509_pop_free(context->trustedCertificates, X509_free); |
283 | 7.09k | context->trustedCertificates = sk_X509_new_null(); |
284 | 7.09k | if(context->trustedCertificates == NULL) { |
285 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
286 | 0 | } |
287 | 7.09k | for(size_t i = 0; i < context->trustList.trustedCertificatesSize; i++) { |
288 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(&context->trustList.trustedCertificates[i], EVP_PKEY_NONE); |
289 | 0 | if(cert == NULL) |
290 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
291 | 0 | sk_X509_push(context->trustedCertificates, cert); |
292 | 0 | } |
293 | | |
294 | 7.09k | sk_X509_pop_free(context->issuerCertificates, X509_free); |
295 | 7.09k | context->issuerCertificates = sk_X509_new_null(); |
296 | 7.09k | if(context->issuerCertificates == NULL) { |
297 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
298 | 0 | } |
299 | 7.09k | for(size_t i = 0; i < context->trustList.issuerCertificatesSize; i++) { |
300 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(&context->trustList.issuerCertificates[i], EVP_PKEY_NONE); |
301 | 0 | if(cert == NULL) |
302 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
303 | 0 | sk_X509_push(context->issuerCertificates, cert); |
304 | 0 | } |
305 | | |
306 | 7.09k | sk_X509_CRL_pop_free(context->crls, X509_CRL_free); |
307 | 7.09k | context->crls = sk_X509_CRL_new_null(); |
308 | 7.09k | if(context->crls == NULL) { |
309 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
310 | 0 | } |
311 | 7.09k | for(size_t i = 0; i < context->trustList.trustedCrlsSize; i++) { |
312 | 0 | X509_CRL * crl = NULL; |
313 | 0 | BIO * bio = NULL; |
314 | |
|
315 | 0 | bio = BIO_new_mem_buf((void *)context->trustList.trustedCrls[i].data, |
316 | 0 | (int)context->trustList.trustedCrls[i].length); |
317 | | /* Try to load DER encoded CRL */ |
318 | 0 | crl = d2i_X509_CRL_bio(bio, NULL); |
319 | 0 | if(crl == NULL) { |
320 | | /* Try to load PEM encoded CRL */ |
321 | 0 | (void)BIO_reset(bio); |
322 | 0 | crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); |
323 | 0 | } |
324 | 0 | BIO_free(bio); |
325 | |
|
326 | 0 | if(crl == NULL) |
327 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
328 | 0 | sk_X509_CRL_push(context->crls, crl); |
329 | 0 | } |
330 | 7.09k | for(size_t i = 0; i < context->trustList.issuerCrlsSize; i++) { |
331 | 0 | X509_CRL * crl = NULL; |
332 | 0 | BIO * bio = NULL; |
333 | |
|
334 | 0 | bio = BIO_new_mem_buf((void *)context->trustList.issuerCrls[i].data, |
335 | 0 | (int)context->trustList.issuerCrls[i].length); |
336 | | /* Try to load DER encoded Issuer CRL */ |
337 | 0 | crl = d2i_X509_CRL_bio(bio, NULL); |
338 | 0 | if(crl == NULL) { |
339 | | /* Try to load PEM encoded Issuer CRL */ |
340 | 0 | (void)BIO_reset(bio); |
341 | 0 | crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); |
342 | 0 | } |
343 | 0 | BIO_free(bio); |
344 | |
|
345 | 0 | if(crl == NULL) |
346 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
347 | 0 | sk_X509_CRL_push(context->crls, crl); |
348 | 0 | } |
349 | | |
350 | 7.09k | return UA_STATUSCODE_GOOD; |
351 | 7.09k | } |
352 | | |
353 | | /* Find binary substring. Taken and adjusted from |
354 | | * http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */ |
355 | | |
356 | | static const unsigned char * |
357 | 0 | UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l2) { |
358 | 0 | if(l2 == 0) |
359 | 0 | return s1; |
360 | 0 | if(l2 > l1) |
361 | 0 | return NULL; |
362 | 0 | size_t limit = l1 - l2 + 1; |
363 | 0 | for(size_t i = 0; i < limit; i++) { |
364 | 0 | if(s1[i] == s2[0] && memcmp(&s1[i], s2, l2) == 0) |
365 | 0 | return &s1[i]; |
366 | 0 | } |
367 | 0 | return NULL; |
368 | 0 | } |
369 | | |
370 | | static const unsigned char openssl_PEM_PRE[28] = "-----BEGIN CERTIFICATE-----"; |
371 | | |
372 | | /* Extract the leaf certificate from a bytestring that may contain an entire chain */ |
373 | | static X509 * |
374 | 0 | openSSLLoadLeafCertificate(UA_ByteString cert, size_t *offset) { |
375 | 0 | if(cert.length <= *offset) |
376 | 0 | return NULL; |
377 | 0 | cert.length -= *offset; |
378 | 0 | cert.data += *offset; |
379 | | |
380 | | /* Detect DER encoding. Extract the encoding length and cut. */ |
381 | 0 | if(cert.length >= 4 && cert.data[0] == 0x30 && cert.data[1] == 0x82) { |
382 | | /* The certificate length is encoded after the magic bytes */ |
383 | 0 | size_t certLen = 4; /* Magic numbers + length bytes */ |
384 | 0 | certLen += (size_t)(((uint16_t)cert.data[2]) << 8); |
385 | 0 | certLen += cert.data[3]; |
386 | 0 | if(certLen > cert.length) |
387 | 0 | return NULL; |
388 | 0 | cert.length = certLen; |
389 | 0 | *offset += certLen; |
390 | 0 | const UA_Byte *dataPtr = cert.data; |
391 | 0 | return d2i_X509(NULL, &dataPtr, (long)cert.length); |
392 | 0 | } |
393 | | |
394 | | /* Assume PEM encoding. Detect multiple certificates and cut. */ |
395 | 0 | if(cert.length > 27 * 4) { |
396 | 0 | const unsigned char *match = |
397 | 0 | UA_Bstrstr(&cert.data[27*2], cert.length - (27*2), |
398 | 0 | openssl_PEM_PRE, 27); |
399 | 0 | if(match) |
400 | 0 | cert.length = (uintptr_t)(match - cert.data); |
401 | 0 | } |
402 | 0 | *offset += cert.length; |
403 | |
|
404 | 0 | BIO *bio = BIO_new_mem_buf((void *) cert.data, (int)cert.length); |
405 | 0 | X509 *result = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
406 | 0 | BIO_free(bio); |
407 | 0 | return result; |
408 | 0 | } |
409 | | |
410 | | /* The bytestring might contain an entire certificate chain. The first |
411 | | * stack-element is the leaf certificate itself. The remaining ones are |
412 | | * potential issuer certificates. */ |
413 | | static STACK_OF(X509) * |
414 | 0 | openSSLLoadCertificateStack(const UA_ByteString cert) { |
415 | 0 | size_t offset = 0; |
416 | 0 | X509 *x509 = NULL; |
417 | 0 | STACK_OF(X509) *result = sk_X509_new_null(); |
418 | 0 | if(!result) |
419 | 0 | return NULL; |
420 | 0 | while((x509 = openSSLLoadLeafCertificate(cert, &offset))) { |
421 | 0 | sk_X509_push(result, x509); |
422 | 0 | } |
423 | 0 | return result; |
424 | 0 | } |
425 | | |
426 | | /* Return the first matching issuer candidate AFTER prev */ |
427 | | static X509 * |
428 | 0 | openSSLFindNextIssuer(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) { |
429 | | /* First check issuers from the stack - provided in the same bytestring as |
430 | | * the certificate. This can also return x509 itself. */ |
431 | 0 | X509_NAME *in = X509_get_issuer_name(x509); |
432 | 0 | do { |
433 | 0 | int size = sk_X509_num(stack); |
434 | 0 | for(int i = 0; i < size; i++) { |
435 | 0 | X509 *candidate = sk_X509_value(stack, i); |
436 | 0 | if(prev) { |
437 | 0 | if(prev == candidate) |
438 | 0 | prev = NULL; /* This was the last issuer we tried to verify */ |
439 | 0 | continue; |
440 | 0 | } |
441 | | /* This checks subject/issuer name and the key usage of the issuer. |
442 | | * It does not verify the validity period and if the issuer key was |
443 | | * used for the signature. We check that afterwards. */ |
444 | 0 | if(X509_NAME_cmp(in, X509_get_subject_name(candidate)) == 0) |
445 | 0 | return candidate; |
446 | 0 | } |
447 | | /* Switch from the stack that came with the cert to the issuer list and |
448 | | * then to the trust list. */ |
449 | 0 | if(stack == ctx->trustedCertificates) |
450 | 0 | stack = NULL; |
451 | 0 | else if(stack == ctx->issuerCertificates) |
452 | 0 | stack = ctx->trustedCertificates; |
453 | 0 | else |
454 | 0 | stack = ctx->issuerCertificates; |
455 | 0 | } while(stack); |
456 | 0 | return NULL; |
457 | 0 | } |
458 | | |
459 | | /* Is the certificate a CA? */ |
460 | | static UA_Boolean |
461 | 114 | openSSLCheckCA(X509 *cert) { |
462 | 114 | uint32_t flags = X509_get_extension_flags(cert); |
463 | | /* The basic constraints must be set with the CA flag true */ |
464 | 114 | if(!(flags & EXFLAG_CA)) |
465 | 34 | return false; |
466 | | |
467 | | /* The Key Usage extension must be set */ |
468 | 80 | if(!(flags & EXFLAG_KUSAGE)) |
469 | 66 | return false; |
470 | | |
471 | | /* The Key Usage must include cert signing and CRL issuing */ |
472 | 14 | uint32_t usage = X509_get_key_usage(cert); |
473 | 14 | if(!(usage & KU_KEY_CERT_SIGN) || !(usage & KU_CRL_SIGN)) |
474 | 9 | return false; |
475 | | |
476 | 5 | return true; |
477 | 14 | } |
478 | | |
479 | | static UA_StatusCode |
480 | 0 | openSSLCheckRevoked(UA_CertificateGroup *cg, MemoryCertStore *ctx, X509 *cert) { |
481 | 0 | const ASN1_INTEGER *sn = X509_get0_serialNumber(cert); |
482 | 0 | const X509_NAME *in = X509_get_issuer_name(cert); |
483 | 0 | int size = sk_X509_CRL_num(ctx->crls); |
484 | |
|
485 | 0 | if(size == 0) { |
486 | 0 | UA_LOG_WARNING(cg->logging, UA_LOGCATEGORY_SECURITYPOLICY, |
487 | 0 | "Zero revocation lists have been loaded. " |
488 | 0 | "This seems intentional - omitting the check."); |
489 | 0 | return UA_STATUSCODE_GOOD; |
490 | 0 | } |
491 | | |
492 | | /* Loop over the crl and match the Issuer Name */ |
493 | 0 | UA_StatusCode res = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; |
494 | 0 | for(int i = 0; i < size; i++) { |
495 | | /* The crl contains a list of serial numbers from the same issuer */ |
496 | 0 | X509_CRL *crl = sk_X509_CRL_value(ctx->crls, i); |
497 | 0 | if(X509_NAME_cmp(in, X509_CRL_get_issuer(crl)) != 0) |
498 | 0 | continue; |
499 | 0 | STACK_OF(X509_REVOKED) *rs = X509_CRL_get_REVOKED(crl); |
500 | 0 | int rsize = sk_X509_REVOKED_num(rs); |
501 | 0 | for(int j = 0; j < rsize; j++) { |
502 | 0 | X509_REVOKED *r = sk_X509_REVOKED_value(rs, j); |
503 | 0 | if(ASN1_INTEGER_cmp(sn, X509_REVOKED_get0_serialNumber(r)) == 0) |
504 | 0 | return UA_STATUSCODE_BADCERTIFICATEREVOKED; |
505 | 0 | } |
506 | 0 | res = UA_STATUSCODE_GOOD; /* There was at least one crl that did not revoke (so far) */ |
507 | 0 | } |
508 | 0 | return res; |
509 | 0 | } |
510 | | |
511 | 0 | #define UA_OPENSSL_MAX_CHAIN_LENGTH 10 |
512 | | |
513 | | static UA_StatusCode |
514 | | openSSL_verifyChain(UA_CertificateGroup *cg, MemoryCertStore *ctx, STACK_OF(X509) *stack, |
515 | 0 | X509 **old_issuers, X509 *cert, int depth) { |
516 | | /* Maxiumum chain length */ |
517 | 0 | if(depth == UA_OPENSSL_MAX_CHAIN_LENGTH) |
518 | 0 | return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
519 | | |
520 | | /* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is |
521 | | * returned only if all possible chains are incomplete. */ |
522 | 0 | X509 *issuer = NULL; |
523 | 0 | UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
524 | 0 | while(ret != UA_STATUSCODE_GOOD) { |
525 | | /* Find the issuer. We jump back here to find a different path if a |
526 | | * subsequent check fails. */ |
527 | 0 | issuer = openSSLFindNextIssuer(ctx, stack, cert, issuer); |
528 | 0 | if(!issuer) |
529 | 0 | break; |
530 | | |
531 | | /* Verification Step: Certificate Usage |
532 | | * Can the issuer act as CA? Omit for self-signed leaf certificates. */ |
533 | 0 | if((depth > 0 || issuer != cert) && !openSSLCheckCA(issuer)) { |
534 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED; |
535 | 0 | continue; |
536 | 0 | } |
537 | | |
538 | | /* Verification Step: Signature */ |
539 | 0 | int opensslRet = X509_verify(cert, X509_get0_pubkey(issuer)); |
540 | 0 | if(opensslRet == -1) { |
541 | 0 | return UA_STATUSCODE_BADCERTIFICATEINVALID; /* Ill-formed signature */ |
542 | 0 | } else if(opensslRet == 0) { |
543 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */ |
544 | 0 | continue; |
545 | 0 | } |
546 | | |
547 | | /* The certificate is self-signed. We have arrived at the top of the |
548 | | * chain. We check whether the certificate is trusted below. This is the |
549 | | * only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED. |
550 | | * This signals that the chain is complete (but can be still |
551 | | * untrusted). |
552 | | * |
553 | | * Break here as we have reached the end of the chain. Omit the |
554 | | * Revocation Check for self-signed certificates. */ |
555 | 0 | if(cert == issuer || X509_cmp(cert, issuer) == 0) { |
556 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; |
557 | 0 | break; |
558 | 0 | } |
559 | | |
560 | | /* Verification Step: Revocation Check */ |
561 | 0 | ret = openSSLCheckRevoked(cg, ctx, cert); |
562 | 0 | if(depth > 0) { |
563 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEREVOKED) |
564 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED; |
565 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN) |
566 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; |
567 | 0 | } |
568 | 0 | if(ret != UA_STATUSCODE_GOOD) |
569 | 0 | continue; |
570 | | |
571 | | /* Detect (endless) loops of issuers. The last one can be skipped by the |
572 | | * check for self-signed just before. */ |
573 | 0 | for(int i = 0; i < depth; i++) { |
574 | 0 | if(old_issuers[i] == issuer) |
575 | 0 | return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
576 | 0 | } |
577 | 0 | old_issuers[depth] = issuer; |
578 | | |
579 | | /* We have found the issuer certificate used for the signature. Recurse |
580 | | * to the next certificate in the chain (verify the current issuer). */ |
581 | 0 | ret = openSSL_verifyChain(cg, ctx, stack, old_issuers, issuer, depth + 1); |
582 | 0 | } |
583 | | |
584 | | /* Is the certificate in the trust list? If yes, then we are done. */ |
585 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) { |
586 | 0 | for(int i = 0; i < sk_X509_num(ctx->trustedCertificates); i++) { |
587 | 0 | if(X509_cmp(cert, sk_X509_value(ctx->trustedCertificates, i)) == 0) { |
588 | 0 | ret = UA_STATUSCODE_GOOD; |
589 | 0 | break; |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | |
|
594 | 0 | if (ret == UA_STATUSCODE_GOOD) { |
595 | | /* Verification Step: Validity Period */ |
596 | 0 | ASN1_TIME *notBefore = X509_get_notBefore(cert); |
597 | 0 | ASN1_TIME *notAfter = X509_get_notAfter(cert); |
598 | 0 | if(X509_cmp_current_time(notBefore) != -1 || X509_cmp_current_time(notAfter) != 1) |
599 | 0 | return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID : |
600 | 0 | UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID; |
601 | 0 | } |
602 | | |
603 | 0 | return ret; |
604 | 0 | } |
605 | | |
606 | | /* This follows Part 6, 6.1.3 Determining if a Certificate is trusted. |
607 | | * It defines a sequence of steps for certificate verification. */ |
608 | | static UA_StatusCode |
609 | 0 | verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certificate) { |
610 | | /* Check parameter */ |
611 | 0 | if(certGroup == NULL || certGroup->context == NULL) |
612 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
613 | | |
614 | 0 | UA_StatusCode ret = UA_STATUSCODE_GOOD; |
615 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
616 | 0 | if(context->reloadRequired) { |
617 | 0 | ret = reloadCertificates(certGroup); |
618 | 0 | if(ret != UA_STATUSCODE_GOOD) |
619 | 0 | return ret; |
620 | 0 | context->reloadRequired = false; |
621 | 0 | } |
622 | | |
623 | | /* Verification Step: Certificate Structure */ |
624 | 0 | STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate); |
625 | 0 | if(!stack || sk_X509_num(stack) < 1) { |
626 | 0 | sk_X509_pop_free(stack, X509_free); |
627 | 0 | return UA_STATUSCODE_BADCERTIFICATEINVALID; |
628 | 0 | } |
629 | | |
630 | | /* Verification Step: Certificate Usage |
631 | | * Check whether the certificate is a User certificate or a CA certificate. |
632 | | * Refer the test case CTT/Security/Security Certificate Validation/029.js |
633 | | * for more details. */ |
634 | 0 | X509 *leaf = sk_X509_value(stack, 0); |
635 | 0 | if(openSSLCheckCA(leaf)) { |
636 | 0 | sk_X509_pop_free(stack, X509_free); |
637 | 0 | return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; |
638 | 0 | } |
639 | | |
640 | | /* These steps are performed outside of this method. |
641 | | * Because we need the server or client context. |
642 | | * - Security Policy |
643 | | * - Host Name |
644 | | * - URI */ |
645 | | |
646 | | /* Verification Step: Build Certificate Chain |
647 | | * We perform the checks for each certificate inside. */ |
648 | 0 | X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH]; |
649 | 0 | ret = openSSL_verifyChain(certGroup, context, stack, old_issuers, leaf, 0); |
650 | 0 | sk_X509_pop_free(stack, X509_free); |
651 | 0 | return ret; |
652 | 0 | } |
653 | | |
654 | | static UA_StatusCode |
655 | | MemoryCertStore_verifyCertificate(UA_CertificateGroup *certGroup, |
656 | 0 | const UA_ByteString *certificate) { |
657 | | /* Check parameter */ |
658 | 0 | if(certGroup == NULL || certificate == NULL) { |
659 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
660 | 0 | } |
661 | | |
662 | 0 | UA_StatusCode retval = verifyCertificate(certGroup, certificate); |
663 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
664 | 0 | if(MemoryCertStore_addToRejectedList(certGroup, certificate) != UA_STATUSCODE_GOOD) { |
665 | 0 | UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, |
666 | 0 | "Could not append certificate to rejected list"); |
667 | 0 | } |
668 | 0 | } |
669 | 0 | return retval; |
670 | 0 | } |
671 | | |
672 | | UA_StatusCode |
673 | | UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup, |
674 | | UA_NodeId *certificateGroupId, |
675 | | const UA_TrustListDataType *trustList, |
676 | | const UA_Logger *logger, |
677 | 7.09k | const UA_KeyValueMap *params) { |
678 | | |
679 | 7.09k | if(certGroup == NULL || certificateGroupId == NULL) { |
680 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
681 | 0 | } |
682 | | |
683 | 7.09k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
684 | | |
685 | | /* Clear if the plugin is already initialized */ |
686 | 7.09k | if(certGroup->clear) |
687 | 0 | certGroup->clear(certGroup); |
688 | | |
689 | 7.09k | UA_NodeId_copy(certificateGroupId, &certGroup->certificateGroupId); |
690 | 7.09k | certGroup->logging = logger; |
691 | | |
692 | 7.09k | certGroup->getTrustList = MemoryCertStore_getTrustList; |
693 | 7.09k | certGroup->setTrustList = MemoryCertStore_setTrustList; |
694 | 7.09k | certGroup->addToTrustList = MemoryCertStore_addToTrustList; |
695 | 7.09k | certGroup->removeFromTrustList = MemoryCertStore_removeFromTrustList; |
696 | 7.09k | certGroup->getRejectedList = MemoryCertStore_getRejectedList; |
697 | 7.09k | certGroup->getCertificateCrls = MemoryCertStore_getCertificateCrls; |
698 | 7.09k | certGroup->verifyCertificate = MemoryCertStore_verifyCertificate; |
699 | 7.09k | certGroup->clear = MemoryCertStore_clear; |
700 | | |
701 | | /* Set PKI Store context data */ |
702 | 7.09k | MemoryCertStore *context = (MemoryCertStore *)UA_calloc(1, sizeof(MemoryCertStore)); |
703 | 7.09k | if(!context) { |
704 | 0 | retval = UA_STATUSCODE_BADOUTOFMEMORY; |
705 | 0 | goto cleanup; |
706 | 0 | } |
707 | 7.09k | certGroup->context = context; |
708 | | /* Default values */ |
709 | 7.09k | context->maxTrustListSize = 65535; |
710 | 7.09k | context->maxRejectedListSize = 100; |
711 | | |
712 | 7.09k | if(params) { |
713 | 7.09k | const UA_UInt32 *maxTrustListSize = (const UA_UInt32*) |
714 | 7.09k | UA_KeyValueMap_getScalar(params, MemoryCertStoreParameters[MEMORYCERTSTORE_PARAMINDEX_MAXTRUSTLISTSIZE].name, |
715 | 7.09k | &UA_TYPES[UA_TYPES_UINT32]); |
716 | | |
717 | 7.09k | const UA_UInt32 *maxRejectedListSize = (const UA_UInt32*) |
718 | 7.09k | UA_KeyValueMap_getScalar(params, MemoryCertStoreParameters[MEMORYCERTSTORE_PARAMINDEX_MAXREJECTEDLISTSIZE].name, |
719 | 7.09k | &UA_TYPES[UA_TYPES_UINT32]); |
720 | | |
721 | 7.09k | if(maxTrustListSize) { |
722 | 0 | context->maxTrustListSize = *maxTrustListSize; |
723 | 0 | } |
724 | | |
725 | 7.09k | if(maxRejectedListSize) { |
726 | 0 | context->maxRejectedListSize = *maxRejectedListSize; |
727 | 0 | } |
728 | 7.09k | } |
729 | | |
730 | 7.09k | UA_TrustListDataType_add(trustList, &context->trustList); |
731 | 7.09k | reloadCertificates(certGroup); |
732 | | |
733 | 7.09k | return UA_STATUSCODE_GOOD; |
734 | | |
735 | 0 | cleanup: |
736 | 0 | certGroup->clear(certGroup); |
737 | 0 | return retval; |
738 | 7.09k | } |
739 | | |
740 | | UA_StatusCode |
741 | | UA_CertificateUtils_verifyApplicationUri(const UA_ByteString *certificate, |
742 | 108 | const UA_String *applicationURI) { |
743 | 108 | const unsigned char *pData = certificate->data; |
744 | 108 | if(!pData) |
745 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
746 | | |
747 | 108 | X509 *certificateX509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
748 | 108 | if(!certificateX509) |
749 | 104 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
750 | | |
751 | 4 | GENERAL_NAMES *pNames = (GENERAL_NAMES *) |
752 | 4 | X509_get_ext_d2i(certificateX509, NID_subject_alt_name, NULL, NULL); |
753 | 4 | if(!pNames) { |
754 | 4 | X509_free(certificateX509); |
755 | 4 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
756 | 4 | } |
757 | | |
758 | 0 | UA_String subjectURI = UA_STRING_NULL; |
759 | 0 | for(int i = 0; i < sk_GENERAL_NAME_num(pNames); i++) { |
760 | 0 | GENERAL_NAME *value = sk_GENERAL_NAME_value(pNames, i); |
761 | 0 | if(value->type == GEN_URI) { |
762 | 0 | subjectURI.length = (size_t) (value->d.ia5->length); |
763 | 0 | subjectURI.data = (UA_Byte*)UA_malloc(subjectURI.length); |
764 | 0 | if(!subjectURI.data) { |
765 | 0 | X509_free(certificateX509); |
766 | 0 | sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); |
767 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
768 | 0 | } |
769 | 0 | (void)memcpy(subjectURI.data, value->d.ia5->data, subjectURI.length); |
770 | 0 | break; |
771 | 0 | } |
772 | 0 | } |
773 | | |
774 | 0 | UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID; |
775 | 0 | if(subjectURI.length > 0 && |
776 | 0 | subjectURI.length == applicationURI->length && |
777 | 0 | memcmp(subjectURI.data, applicationURI->data, subjectURI.length) == 0) |
778 | 0 | ret = UA_STATUSCODE_GOOD; |
779 | |
|
780 | 0 | X509_free(certificateX509); |
781 | 0 | sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); |
782 | 0 | UA_String_clear(&subjectURI); |
783 | 0 | return ret; |
784 | 0 | } |
785 | | |
786 | | UA_StatusCode |
787 | | UA_CertificateUtils_getExpirationDate(UA_ByteString *certificate, |
788 | 252 | UA_DateTime *expiryDateTime) { |
789 | 252 | X509 *x509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
790 | 252 | if(!x509) |
791 | 138 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
792 | | |
793 | | /* Get the certificate Expiry date */ |
794 | 114 | ASN1_TIME *not_after = X509_get_notAfter(x509); |
795 | | |
796 | 114 | struct tm dtTime; |
797 | 114 | ASN1_TIME_to_tm(not_after, &dtTime); |
798 | 114 | X509_free(x509); |
799 | | |
800 | 114 | struct musl_tm dateTime; |
801 | 114 | memset(&dateTime, 0, sizeof(struct musl_tm)); |
802 | 114 | dateTime.tm_year = dtTime.tm_year; |
803 | 114 | dateTime.tm_mon = dtTime.tm_mon; |
804 | 114 | dateTime.tm_mday = dtTime.tm_mday; |
805 | 114 | dateTime.tm_hour = dtTime.tm_hour; |
806 | 114 | dateTime.tm_min = dtTime.tm_min; |
807 | 114 | dateTime.tm_sec = dtTime.tm_sec; |
808 | | |
809 | 114 | long long sec_epoch = musl_tm_to_secs(&dateTime); |
810 | 114 | *expiryDateTime = UA_DATETIME_UNIX_EPOCH; |
811 | 114 | *expiryDateTime += sec_epoch * UA_DATETIME_SEC; |
812 | 114 | return UA_STATUSCODE_GOOD; |
813 | 252 | } |
814 | | |
815 | | UA_StatusCode |
816 | | UA_CertificateUtils_getSubjectName(UA_ByteString *certificate, |
817 | 843 | UA_String *subjectName) { |
818 | 843 | X509_NAME *sn = NULL; |
819 | 843 | X509 *x509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
820 | 843 | X509_CRL *x509_crl = NULL; |
821 | | |
822 | 843 | if(x509) { |
823 | 36 | sn = X509_get_subject_name(x509); |
824 | 807 | } else { |
825 | 807 | x509_crl = UA_OpenSSL_LoadCrl(certificate); |
826 | 807 | if(!x509_crl) |
827 | 807 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
828 | 0 | sn = X509_CRL_get_issuer(x509_crl); |
829 | 0 | } |
830 | | |
831 | 36 | char buf[1024]; |
832 | 36 | *subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024)); |
833 | | |
834 | 36 | if(x509) |
835 | 36 | X509_free(x509); |
836 | 36 | if(x509_crl) |
837 | 0 | X509_CRL_free(x509_crl); |
838 | 36 | return UA_STATUSCODE_GOOD; |
839 | 843 | } |
840 | | |
841 | | UA_StatusCode |
842 | | UA_CertificateUtils_getThumbprint(UA_ByteString *certificate, |
843 | 7 | UA_String *thumbprint) { |
844 | 7 | if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2)) |
845 | 7 | return UA_STATUSCODE_BADINTERNALERROR; |
846 | | |
847 | 0 | unsigned char digest[SHA1_DIGEST_LENGTH]; |
848 | 0 | unsigned int digestLen; |
849 | |
|
850 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
851 | 0 | if(cert) { |
852 | 0 | if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) { |
853 | 0 | X509_free(cert); |
854 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
855 | 0 | } |
856 | 0 | X509_free(cert); |
857 | 0 | } else { |
858 | 0 | X509_CRL *crl = UA_OpenSSL_LoadCrl(certificate); |
859 | 0 | if(!crl) |
860 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
861 | 0 | if(X509_CRL_digest(crl, EVP_sha1(), digest, &digestLen) != 1) { |
862 | 0 | X509_CRL_free(crl); |
863 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
864 | 0 | } |
865 | 0 | X509_CRL_free(crl); |
866 | 0 | } |
867 | | |
868 | 0 | UA_String thumb = UA_STRING_NULL; |
869 | 0 | thumb.length = (SHA1_DIGEST_LENGTH * 2) + 1; |
870 | 0 | thumb.data = (UA_Byte*)malloc(sizeof(UA_Byte) * thumb.length); |
871 | | |
872 | | /* Create a string containing a hex representation */ |
873 | 0 | char *p = (char*)thumb.data; |
874 | 0 | for(size_t i = 0; i < digestLen; i++) { |
875 | 0 | p += sprintf(p, "%.2X", digest[i]); |
876 | 0 | } |
877 | |
|
878 | 0 | memcpy(thumbprint->data, thumb.data, thumbprint->length); |
879 | 0 | free(thumb.data); |
880 | |
|
881 | 0 | return UA_STATUSCODE_GOOD; |
882 | 0 | } |
883 | | |
884 | | UA_StatusCode |
885 | | UA_CertificateUtils_getKeySize(UA_ByteString *certificate, |
886 | 153 | size_t *keySize){ |
887 | 153 | if(certificate == NULL) |
888 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
889 | | |
890 | 153 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
891 | 153 | if(!cert) |
892 | 148 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
893 | | |
894 | 5 | EVP_PKEY *pkey = X509_get_pubkey(cert); |
895 | 5 | if(!pkey) { |
896 | 2 | X509_free(cert); |
897 | 2 | return UA_STATUSCODE_BADINTERNALERROR; |
898 | 2 | } |
899 | | |
900 | 3 | if(EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) { |
901 | 1 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
902 | 1 | *keySize = EVP_PKEY_get_size(pkey) * 8; |
903 | | #else |
904 | | *keySize = RSA_size(get_pkey_rsa(pkey)) * 8; |
905 | | #endif |
906 | 2 | } else if(EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { |
907 | 2 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
908 | 2 | *keySize = EVP_PKEY_get_size(pkey) * 8; |
909 | | #else |
910 | | *keySize = ECDSA_size(EVP_PKEY_get0_EC_KEY(pkey)) * 8; |
911 | | #endif |
912 | 2 | } else { |
913 | 0 | EVP_PKEY_free(pkey); |
914 | 0 | X509_free(cert); |
915 | 0 | return UA_STATUSCODE_BADINTERNALERROR; /* Unsupported key type */ |
916 | 0 | } |
917 | | |
918 | 3 | EVP_PKEY_free(pkey); |
919 | 3 | X509_free(cert); |
920 | | |
921 | 3 | return UA_STATUSCODE_GOOD; |
922 | 3 | } |
923 | | |
924 | | UA_StatusCode |
925 | | UA_CertificateUtils_comparePublicKeys(const UA_ByteString *certificate1, |
926 | 0 | const UA_ByteString *certificate2) { |
927 | 0 | if(certificate1 == NULL || certificate2 == NULL) |
928 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
929 | | |
930 | 0 | BIO *dataBio1 = BIO_new_mem_buf(certificate1->data, certificate1->length); |
931 | 0 | if(!dataBio1) |
932 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
933 | | |
934 | 0 | BIO *dataBio2 = BIO_new_mem_buf(certificate2->data, certificate2->length); |
935 | 0 | if(!dataBio2) { |
936 | 0 | BIO_free(dataBio1); |
937 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
938 | 0 | } |
939 | | |
940 | 0 | X509 *cert1 = NULL; |
941 | 0 | X509 *cert2 = NULL; |
942 | 0 | X509_REQ *csr1 = NULL; |
943 | 0 | X509_REQ *csr2 = NULL; |
944 | | /* Try to read the certificate as PEM first */ |
945 | 0 | cert1 = PEM_read_bio_X509(dataBio1, NULL, 0, NULL); |
946 | 0 | if(!cert1) { |
947 | | /* If PEM read fails, reset BIO and try reading as DER */ |
948 | 0 | (void)BIO_reset(dataBio1); |
949 | 0 | cert1 = d2i_X509_bio(dataBio1, NULL); |
950 | 0 | } |
951 | | /* Try to read as a csr */ |
952 | 0 | if(!cert1) { |
953 | 0 | (void)BIO_reset(dataBio1); |
954 | 0 | csr1 = PEM_read_bio_X509_REQ(dataBio1, NULL, 0, NULL); |
955 | 0 | if(!csr1) { |
956 | 0 | (void)BIO_reset(dataBio1); |
957 | 0 | csr1 = d2i_X509_REQ_bio(dataBio1, NULL); |
958 | 0 | } |
959 | 0 | } |
960 | 0 | if(!cert1 && !csr1) { |
961 | 0 | BIO_free(dataBio1); |
962 | 0 | BIO_free(dataBio2); |
963 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
964 | 0 | } |
965 | | |
966 | 0 | cert2 = PEM_read_bio_X509(dataBio2, NULL, 0, NULL); |
967 | 0 | if(!cert2) { |
968 | | /* If PEM read fails, reset BIO and try reading as DER */ |
969 | 0 | (void)BIO_reset(dataBio2); |
970 | 0 | cert2 = d2i_X509_bio(dataBio2, NULL); |
971 | 0 | } |
972 | | /* Try to load as a csr */ |
973 | 0 | if(!cert2) { |
974 | 0 | (void)BIO_reset(dataBio2); |
975 | 0 | csr2 = PEM_read_bio_X509_REQ(dataBio2, NULL, 0, NULL); |
976 | 0 | if(!csr2) { |
977 | 0 | (void)BIO_reset(dataBio2); |
978 | 0 | csr2 = d2i_X509_REQ_bio(dataBio2, NULL); |
979 | 0 | } |
980 | 0 | } |
981 | 0 | if(!cert2 && !csr2) { |
982 | 0 | X509_free(cert1); |
983 | 0 | X509_REQ_free(csr1); |
984 | 0 | BIO_free(dataBio1); |
985 | 0 | BIO_free(dataBio2); |
986 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
987 | 0 | } |
988 | | |
989 | 0 | BIO_free(dataBio1); |
990 | 0 | BIO_free(dataBio2); |
991 | |
|
992 | 0 | EVP_PKEY *pkey1 = cert1 ? X509_get_pubkey(cert1) : X509_REQ_get_pubkey(csr1); |
993 | 0 | EVP_PKEY *pkey2 = cert2 ? X509_get_pubkey(cert2) : X509_REQ_get_pubkey(csr2); |
994 | |
|
995 | 0 | X509_free(cert1); |
996 | 0 | X509_free(cert2); |
997 | 0 | X509_REQ_free(csr1); |
998 | 0 | X509_REQ_free(csr2); |
999 | |
|
1000 | 0 | if(!pkey1 || !pkey2) { |
1001 | 0 | EVP_PKEY_free(pkey1); |
1002 | 0 | EVP_PKEY_free(pkey2); |
1003 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1004 | 0 | } |
1005 | | |
1006 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
1007 | 0 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
1008 | 0 | int isEqual = EVP_PKEY_eq(pkey1, pkey2); |
1009 | 0 | if(isEqual == 0) |
1010 | 0 | retval = UA_STATUSCODE_BADNOMATCH; |
1011 | 0 | if(isEqual < 0) |
1012 | 0 | retval = UA_STATUSCODE_BADINTERNALERROR; |
1013 | | #else |
1014 | | int isEqual = EVP_PKEY_cmp(pkey1, pkey2); |
1015 | | if(isEqual == 0) |
1016 | | retval = UA_STATUSCODE_BADNOMATCH; |
1017 | | if(isEqual < 0) |
1018 | | retval = UA_STATUSCODE_BADINTERNALERROR; |
1019 | | #endif |
1020 | |
|
1021 | 0 | EVP_PKEY_free(pkey1); |
1022 | 0 | EVP_PKEY_free(pkey2); |
1023 | |
|
1024 | 0 | return retval; |
1025 | 0 | } |
1026 | | |
1027 | | UA_StatusCode |
1028 | | UA_CertificateUtils_checkKeyPair(const UA_ByteString *certificate, |
1029 | 0 | const UA_ByteString *privateKey) { |
1030 | 0 | if(certificate == NULL || privateKey == NULL) |
1031 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1032 | | |
1033 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
1034 | 0 | if(!cert) |
1035 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1036 | | |
1037 | 0 | EVP_PKEY *pkey = UA_OpenSSL_LoadPrivateKey(privateKey); |
1038 | 0 | if(!pkey) { |
1039 | 0 | X509_free(cert); |
1040 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1041 | 0 | } |
1042 | | |
1043 | | /* Verify if the private key matches the public key in the certificate */ |
1044 | 0 | if(X509_check_private_key(cert, pkey) != 1) { |
1045 | 0 | X509_free(cert); |
1046 | 0 | EVP_PKEY_free(pkey); |
1047 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1048 | 0 | } |
1049 | | |
1050 | 0 | X509_free(cert); |
1051 | 0 | EVP_PKEY_free(pkey); |
1052 | |
|
1053 | 0 | return UA_STATUSCODE_GOOD; |
1054 | 0 | } |
1055 | | |
1056 | | UA_StatusCode |
1057 | 1.02k | UA_CertificateUtils_checkCA(const UA_ByteString *certificate) { |
1058 | 1.02k | if(certificate == NULL) |
1059 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1060 | | |
1061 | 1.02k | X509 *certificateX509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
1062 | 1.02k | if(!certificateX509) |
1063 | 915 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1064 | | |
1065 | 114 | UA_StatusCode retval = openSSLCheckCA(certificateX509) ? |
1066 | 109 | UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOMATCH; |
1067 | 114 | X509_free(certificateX509); |
1068 | 114 | return retval; |
1069 | 1.02k | } |
1070 | | |
1071 | | static int |
1072 | 0 | privateKeyPasswordCallback(char *buf, int size, int rwflag, void *userdata) { |
1073 | 0 | (void) rwflag; |
1074 | 0 | UA_ByteString *pw = (UA_ByteString*)userdata; |
1075 | 0 | if(pw->length <= (size_t)size) |
1076 | 0 | memcpy(buf, pw->data, pw->length); |
1077 | 0 | return (int)pw->length; |
1078 | 0 | } |
1079 | | |
1080 | | UA_StatusCode |
1081 | | UA_CertificateUtils_decryptPrivateKey(const UA_ByteString privateKey, |
1082 | | const UA_ByteString password, |
1083 | 0 | UA_ByteString *outDerKey) { |
1084 | 0 | if(!outDerKey) |
1085 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1086 | | |
1087 | 0 | if(privateKey.length == 0) { |
1088 | 0 | *outDerKey = UA_BYTESTRING_NULL; |
1089 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
1090 | 0 | } |
1091 | | |
1092 | 0 | EVP_PKEY *pkey = NULL; |
1093 | 0 | const unsigned char * in = privateKey.data; |
1094 | | |
1095 | | /* Check if input is already in DER format */ |
1096 | 0 | pkey = d2i_AutoPrivateKey(NULL, &in, privateKey.length); |
1097 | 0 | if(pkey != NULL) { |
1098 | 0 | EVP_PKEY_free(pkey); |
1099 | 0 | return UA_ByteString_copy(&privateKey, outDerKey); |
1100 | 0 | } |
1101 | | |
1102 | | /* Decrypt */ |
1103 | 0 | BIO *bio = BIO_new_mem_buf((void*)privateKey.data, (int)privateKey.length); |
1104 | 0 | pkey = PEM_read_bio_PrivateKey(bio, NULL, |
1105 | 0 | privateKeyPasswordCallback, |
1106 | 0 | (void*)(uintptr_t)&password); |
1107 | 0 | BIO_free(bio); |
1108 | 0 | if(!pkey) |
1109 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1110 | | |
1111 | | /* Write DER encoded, allocates the new memory */ |
1112 | 0 | unsigned char *data = NULL; |
1113 | 0 | const int numBytes = i2d_PrivateKey(pkey, &data); |
1114 | 0 | EVP_PKEY_free(pkey); |
1115 | 0 | if(!data) |
1116 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1117 | | |
1118 | | /* Copy to the data to outDerKey |
1119 | | * Passing the data pointer directly causes a heap corruption on Windows |
1120 | | * when outDerKey is cleared. |
1121 | | */ |
1122 | 0 | UA_ByteString temp = UA_BYTESTRING_NULL; |
1123 | 0 | temp.data = data; |
1124 | 0 | temp.length = (size_t)numBytes; |
1125 | 0 | const UA_StatusCode success = UA_ByteString_copy(&temp, outDerKey); |
1126 | | /* OPENSSL_clear_free() is not supported by the LibreSSL version in the CI */ |
1127 | 0 | OPENSSL_cleanse(data, numBytes); |
1128 | | OPENSSL_free(data); |
1129 | 0 | return success; |
1130 | 0 | } |
1131 | | |
1132 | | #endif |