/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 | 4 | #define SHA1_DIGEST_LENGTH 20 |
23 | | |
24 | | /* Configuration parameters */ |
25 | | |
26 | | #define MEMORYCERTSTORE_PARAMETERSSIZE 2 |
27 | 9.01k | #define MEMORYCERTSTORE_PARAMINDEX_MAXTRUSTLISTSIZE 0 |
28 | 9.01k | #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 | 9.01k | MemoryCertStore_setTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { |
85 | | /* Check parameter */ |
86 | 9.01k | if(certGroup == NULL || trustList == NULL) { |
87 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
88 | 0 | } |
89 | | |
90 | 9.01k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
91 | 9.01k | if(context->maxTrustListSize != 0 && UA_TrustListDataType_getSize(trustList) > context->maxTrustListSize) { |
92 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
93 | 0 | } |
94 | 9.01k | context->reloadRequired = true; |
95 | | /* Remove the section of the trust list that needs to be reset, while keeping the remaining parts intact */ |
96 | 9.01k | return UA_TrustListDataType_set(trustList, &context->trustList); |
97 | 9.01k | } |
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 | 9.01k | MemoryCertStore_clear(UA_CertificateGroup *certGroup) { |
249 | | /* check parameter */ |
250 | 9.01k | if(certGroup == NULL) { |
251 | 0 | return; |
252 | 0 | } |
253 | | |
254 | 9.01k | UA_NodeId_clear(&certGroup->certificateGroupId); |
255 | | |
256 | 9.01k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
257 | 9.01k | if(context) { |
258 | 9.01k | UA_TrustListDataType_clear(&context->trustList); |
259 | | |
260 | 9.01k | UA_Array_delete(context->rejectedCertificates, context->rejectedCertificatesSize, &UA_TYPES[UA_TYPES_BYTESTRING]); |
261 | 9.01k | context->rejectedCertificates = NULL; |
262 | 9.01k | context->rejectedCertificatesSize = 0; |
263 | | |
264 | 9.01k | sk_X509_pop_free(context->trustedCertificates, X509_free); |
265 | 9.01k | sk_X509_pop_free(context->issuerCertificates, X509_free); |
266 | 9.01k | sk_X509_CRL_pop_free(context->crls, X509_CRL_free); |
267 | | |
268 | 9.01k | UA_free(context); |
269 | 9.01k | certGroup->context = NULL; |
270 | 9.01k | } |
271 | 9.01k | } |
272 | | |
273 | | static UA_StatusCode |
274 | 9.01k | reloadCertificates(UA_CertificateGroup *certGroup) { |
275 | | /* Check parameter */ |
276 | 9.01k | if(certGroup == NULL) { |
277 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
278 | 0 | } |
279 | | |
280 | 9.01k | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
281 | | |
282 | 9.01k | sk_X509_pop_free(context->trustedCertificates, X509_free); |
283 | 9.01k | context->trustedCertificates = sk_X509_new_null(); |
284 | 9.01k | if(context->trustedCertificates == NULL) { |
285 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
286 | 0 | } |
287 | 9.01k | 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 | 9.01k | sk_X509_pop_free(context->issuerCertificates, X509_free); |
295 | 9.01k | context->issuerCertificates = sk_X509_new_null(); |
296 | 9.01k | if(context->issuerCertificates == NULL) { |
297 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
298 | 0 | } |
299 | 9.01k | 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 | 9.01k | sk_X509_CRL_pop_free(context->crls, X509_CRL_free); |
307 | 9.01k | context->crls = sk_X509_CRL_new_null(); |
308 | 9.01k | if(context->crls == NULL) { |
309 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
310 | 0 | } |
311 | 9.01k | 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 | 9.01k | 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 | 9.01k | return UA_STATUSCODE_GOOD; |
351 | 9.01k | } |
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 | bstrchr(const unsigned char *s, const unsigned char ch, size_t l) { |
358 | | /* find first occurrence of c in char s[] for length l*/ |
359 | 0 | for(; l > 0; ++s, --l) { |
360 | 0 | if(*s == ch) |
361 | 0 | return s; |
362 | 0 | } |
363 | 0 | return NULL; |
364 | 0 | } |
365 | | |
366 | | static const unsigned char * |
367 | 0 | UA_Bstrstr(const unsigned char *s1, size_t l1, const unsigned char *s2, size_t l2) { |
368 | | /* find first occurrence of s2[] in s1[] for length l1*/ |
369 | 0 | const unsigned char *ss1 = s1; |
370 | 0 | const unsigned char *ss2 = s2; |
371 | | /* handle special case */ |
372 | 0 | if(l1 == 0) |
373 | 0 | return NULL; |
374 | 0 | if(l2 == 0) |
375 | 0 | return s1; |
376 | | |
377 | | /* match prefix */ |
378 | 0 | for(; (s1 = bstrchr(s1, *s2, (uintptr_t)ss1-(uintptr_t)s1+(uintptr_t)l1)) != NULL && |
379 | 0 | (uintptr_t)ss1-(uintptr_t)s1+(uintptr_t)l1 != 0; ++s1) { |
380 | | |
381 | | /* match rest of prefix */ |
382 | 0 | const unsigned char *sc1, *sc2; |
383 | 0 | for(sc1 = s1, sc2 = s2; ;) |
384 | 0 | if(++sc2 >= ss2+l2) |
385 | 0 | return s1; |
386 | 0 | else if (*++sc1 != *sc2) |
387 | 0 | break; |
388 | 0 | } |
389 | 0 | return NULL; |
390 | 0 | } |
391 | | |
392 | | static const unsigned char openssl_PEM_PRE[28] = "-----BEGIN CERTIFICATE-----"; |
393 | | |
394 | | /* Extract the leaf certificate from a bytestring that may contain an entire chain */ |
395 | | static X509 * |
396 | 0 | openSSLLoadLeafCertificate(UA_ByteString cert, size_t *offset) { |
397 | 0 | if(cert.length <= *offset) |
398 | 0 | return NULL; |
399 | 0 | cert.length -= *offset; |
400 | 0 | cert.data += *offset; |
401 | | |
402 | | /* Detect DER encoding. Extract the encoding length and cut. */ |
403 | 0 | if(cert.length >= 4 && cert.data[0] == 0x30 && cert.data[1] == 0x82) { |
404 | | /* The certificate length is encoded after the magic bytes */ |
405 | 0 | size_t certLen = 4; /* Magic numbers + length bytes */ |
406 | 0 | certLen += (size_t)(((uint16_t)cert.data[2]) << 8); |
407 | 0 | certLen += cert.data[3]; |
408 | 0 | if(certLen > cert.length) |
409 | 0 | return NULL; |
410 | 0 | cert.length = certLen; |
411 | 0 | *offset += certLen; |
412 | 0 | const UA_Byte *dataPtr = cert.data; |
413 | 0 | return d2i_X509(NULL, &dataPtr, (long)cert.length); |
414 | 0 | } |
415 | | |
416 | | /* Assume PEM encoding. Detect multiple certificates and cut. */ |
417 | 0 | if(cert.length > 27 * 4) { |
418 | 0 | const unsigned char *match = |
419 | 0 | UA_Bstrstr(openssl_PEM_PRE, 27, &cert.data[27*2], cert.length - (27*2)); |
420 | 0 | if(match) |
421 | 0 | cert.length = (uintptr_t)(match - cert.data); |
422 | 0 | } |
423 | 0 | *offset += cert.length; |
424 | |
|
425 | 0 | BIO *bio = BIO_new_mem_buf((void *) cert.data, (int)cert.length); |
426 | 0 | X509 *result = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
427 | 0 | BIO_free(bio); |
428 | 0 | return result; |
429 | 0 | } |
430 | | |
431 | | /* The bytestring might contain an entire certificate chain. The first |
432 | | * stack-element is the leaf certificate itself. The remaining ones are |
433 | | * potential issuer certificates. */ |
434 | | static STACK_OF(X509) * |
435 | 0 | openSSLLoadCertificateStack(const UA_ByteString cert) { |
436 | 0 | size_t offset = 0; |
437 | 0 | X509 *x509 = NULL; |
438 | 0 | STACK_OF(X509) *result = sk_X509_new_null(); |
439 | 0 | if(!result) |
440 | 0 | return NULL; |
441 | 0 | while((x509 = openSSLLoadLeafCertificate(cert, &offset))) { |
442 | 0 | sk_X509_push(result, x509); |
443 | 0 | } |
444 | 0 | return result; |
445 | 0 | } |
446 | | |
447 | | /* Return the first matching issuer candidate AFTER prev */ |
448 | | static X509 * |
449 | 0 | openSSLFindNextIssuer(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) { |
450 | | /* First check issuers from the stack - provided in the same bytestring as |
451 | | * the certificate. This can also return x509 itself. */ |
452 | 0 | X509_NAME *in = X509_get_issuer_name(x509); |
453 | 0 | do { |
454 | 0 | int size = sk_X509_num(stack); |
455 | 0 | for(int i = 0; i < size; i++) { |
456 | 0 | X509 *candidate = sk_X509_value(stack, i); |
457 | 0 | if(prev) { |
458 | 0 | if(prev == candidate) |
459 | 0 | prev = NULL; /* This was the last issuer we tried to verify */ |
460 | 0 | continue; |
461 | 0 | } |
462 | | /* This checks subject/issuer name and the key usage of the issuer. |
463 | | * It does not verify the validity period and if the issuer key was |
464 | | * used for the signature. We check that afterwards. */ |
465 | 0 | if(X509_NAME_cmp(in, X509_get_subject_name(candidate)) == 0) |
466 | 0 | return candidate; |
467 | 0 | } |
468 | | /* Switch from the stack that came with the cert to the issuer list and |
469 | | * then to the trust list. */ |
470 | 0 | if(stack == ctx->trustedCertificates) |
471 | 0 | stack = NULL; |
472 | 0 | else if(stack == ctx->issuerCertificates) |
473 | 0 | stack = ctx->trustedCertificates; |
474 | 0 | else |
475 | 0 | stack = ctx->issuerCertificates; |
476 | 0 | } while(stack); |
477 | 0 | return NULL; |
478 | 0 | } |
479 | | |
480 | | /* Is the certificate a CA? */ |
481 | | static UA_Boolean |
482 | 0 | openSSLCheckCA(X509 *cert) { |
483 | 0 | uint32_t flags = X509_get_extension_flags(cert); |
484 | | /* The basic constraints must be set with the CA flag true */ |
485 | 0 | if(!(flags & EXFLAG_CA)) |
486 | 0 | return false; |
487 | | |
488 | | /* The Key Usage extension must be set */ |
489 | 0 | if(!(flags & EXFLAG_KUSAGE)) |
490 | 0 | return false; |
491 | | |
492 | | /* The Key Usage must include cert signing and CRL issuing */ |
493 | 0 | uint32_t usage = X509_get_key_usage(cert); |
494 | 0 | if(!(usage & KU_KEY_CERT_SIGN) || !(usage & KU_CRL_SIGN)) |
495 | 0 | return false; |
496 | | |
497 | 0 | return true; |
498 | 0 | } |
499 | | |
500 | | static UA_StatusCode |
501 | 0 | openSSLCheckRevoked(UA_CertificateGroup *cg, MemoryCertStore *ctx, X509 *cert) { |
502 | 0 | const ASN1_INTEGER *sn = X509_get0_serialNumber(cert); |
503 | 0 | const X509_NAME *in = X509_get_issuer_name(cert); |
504 | 0 | int size = sk_X509_CRL_num(ctx->crls); |
505 | |
|
506 | 0 | if(size == 0) { |
507 | 0 | UA_LOG_WARNING(cg->logging, UA_LOGCATEGORY_SECURITYPOLICY, |
508 | 0 | "Zero revocation lists have been loaded. " |
509 | 0 | "This seems intentional - omitting the check."); |
510 | 0 | return UA_STATUSCODE_GOOD; |
511 | 0 | } |
512 | | |
513 | | /* Loop over the crl and match the Issuer Name */ |
514 | 0 | UA_StatusCode res = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; |
515 | 0 | for(int i = 0; i < size; i++) { |
516 | | /* The crl contains a list of serial numbers from the same issuer */ |
517 | 0 | X509_CRL *crl = sk_X509_CRL_value(ctx->crls, i); |
518 | 0 | if(X509_NAME_cmp(in, X509_CRL_get_issuer(crl)) != 0) |
519 | 0 | continue; |
520 | 0 | STACK_OF(X509_REVOKED) *rs = X509_CRL_get_REVOKED(crl); |
521 | 0 | int rsize = sk_X509_REVOKED_num(rs); |
522 | 0 | for(int j = 0; j < rsize; j++) { |
523 | 0 | X509_REVOKED *r = sk_X509_REVOKED_value(rs, j); |
524 | 0 | if(ASN1_INTEGER_cmp(sn, X509_REVOKED_get0_serialNumber(r)) == 0) |
525 | 0 | return UA_STATUSCODE_BADCERTIFICATEREVOKED; |
526 | 0 | } |
527 | 0 | res = UA_STATUSCODE_GOOD; /* There was at least one crl that did not revoke (so far) */ |
528 | 0 | } |
529 | 0 | return res; |
530 | 0 | } |
531 | | |
532 | 0 | #define UA_OPENSSL_MAX_CHAIN_LENGTH 10 |
533 | | |
534 | | static UA_StatusCode |
535 | | openSSL_verifyChain(UA_CertificateGroup *cg, MemoryCertStore *ctx, STACK_OF(X509) *stack, |
536 | 0 | X509 **old_issuers, X509 *cert, int depth) { |
537 | | /* Maxiumum chain length */ |
538 | 0 | if(depth == UA_OPENSSL_MAX_CHAIN_LENGTH) |
539 | 0 | return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
540 | | |
541 | | /* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is |
542 | | * returned only if all possible chains are incomplete. */ |
543 | 0 | X509 *issuer = NULL; |
544 | 0 | UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
545 | 0 | while(ret != UA_STATUSCODE_GOOD) { |
546 | | /* Find the issuer. We jump back here to find a different path if a |
547 | | * subsequent check fails. */ |
548 | 0 | issuer = openSSLFindNextIssuer(ctx, stack, cert, issuer); |
549 | 0 | if(!issuer) |
550 | 0 | break; |
551 | | |
552 | | /* Verification Step: Certificate Usage |
553 | | * Can the issuer act as CA? Omit for self-signed leaf certificates. */ |
554 | 0 | if((depth > 0 || issuer != cert) && !openSSLCheckCA(issuer)) { |
555 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED; |
556 | 0 | continue; |
557 | 0 | } |
558 | | |
559 | | /* Verification Step: Signature */ |
560 | 0 | int opensslRet = X509_verify(cert, X509_get0_pubkey(issuer)); |
561 | 0 | if(opensslRet == -1) { |
562 | 0 | return UA_STATUSCODE_BADCERTIFICATEINVALID; /* Ill-formed signature */ |
563 | 0 | } else if(opensslRet == 0) { |
564 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */ |
565 | 0 | continue; |
566 | 0 | } |
567 | | |
568 | | /* The certificate is self-signed. We have arrived at the top of the |
569 | | * chain. We check whether the certificate is trusted below. This is the |
570 | | * only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED. |
571 | | * This signals that the chain is complete (but can be still |
572 | | * untrusted). |
573 | | * |
574 | | * Break here as we have reached the end of the chain. Omit the |
575 | | * Revocation Check for self-signed certificates. */ |
576 | 0 | if(cert == issuer || X509_cmp(cert, issuer) == 0) { |
577 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; |
578 | 0 | break; |
579 | 0 | } |
580 | | |
581 | | /* Verification Step: Revocation Check */ |
582 | 0 | ret = openSSLCheckRevoked(cg, ctx, cert); |
583 | 0 | if(depth > 0) { |
584 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEREVOKED) |
585 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED; |
586 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN) |
587 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; |
588 | 0 | } |
589 | 0 | if(ret != UA_STATUSCODE_GOOD) |
590 | 0 | continue; |
591 | | |
592 | | /* Detect (endless) loops of issuers. The last one can be skipped by the |
593 | | * check for self-signed just before. */ |
594 | 0 | for(int i = 0; i < depth; i++) { |
595 | 0 | if(old_issuers[i] == issuer) |
596 | 0 | return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; |
597 | 0 | } |
598 | 0 | old_issuers[depth] = issuer; |
599 | | |
600 | | /* We have found the issuer certificate used for the signature. Recurse |
601 | | * to the next certificate in the chain (verify the current issuer). */ |
602 | 0 | ret = openSSL_verifyChain(cg, ctx, stack, old_issuers, issuer, depth + 1); |
603 | 0 | } |
604 | | |
605 | | /* Is the certificate in the trust list? If yes, then we are done. */ |
606 | 0 | if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) { |
607 | 0 | for(int i = 0; i < sk_X509_num(ctx->trustedCertificates); i++) { |
608 | 0 | if(X509_cmp(cert, sk_X509_value(ctx->trustedCertificates, i)) == 0) { |
609 | 0 | ret = UA_STATUSCODE_GOOD; |
610 | 0 | break; |
611 | 0 | } |
612 | 0 | } |
613 | 0 | } |
614 | |
|
615 | 0 | if (ret == UA_STATUSCODE_GOOD) { |
616 | | /* Verification Step: Validity Period */ |
617 | 0 | ASN1_TIME *notBefore = X509_get_notBefore(cert); |
618 | 0 | ASN1_TIME *notAfter = X509_get_notAfter(cert); |
619 | 0 | if(X509_cmp_current_time(notBefore) != -1 || X509_cmp_current_time(notAfter) != 1) |
620 | 0 | return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID : |
621 | 0 | UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID; |
622 | 0 | } |
623 | | |
624 | 0 | return ret; |
625 | 0 | } |
626 | | |
627 | | /* This follows Part 6, 6.1.3 Determining if a Certificate is trusted. |
628 | | * It defines a sequence of steps for certificate verification. */ |
629 | | static UA_StatusCode |
630 | 0 | verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certificate) { |
631 | | /* Check parameter */ |
632 | 0 | if(certGroup == NULL || certGroup->context == NULL) |
633 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
634 | | |
635 | 0 | UA_StatusCode ret = UA_STATUSCODE_GOOD; |
636 | 0 | MemoryCertStore *context = (MemoryCertStore *)certGroup->context; |
637 | 0 | if(context->reloadRequired) { |
638 | 0 | ret = reloadCertificates(certGroup); |
639 | 0 | if(ret != UA_STATUSCODE_GOOD) |
640 | 0 | return ret; |
641 | 0 | context->reloadRequired = false; |
642 | 0 | } |
643 | | |
644 | | /* Verification Step: Certificate Structure */ |
645 | 0 | STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate); |
646 | 0 | if(!stack || sk_X509_num(stack) < 1) { |
647 | 0 | sk_X509_pop_free(stack, X509_free); |
648 | 0 | return UA_STATUSCODE_BADCERTIFICATEINVALID; |
649 | 0 | } |
650 | | |
651 | | /* Verification Step: Certificate Usage |
652 | | * Check whether the certificate is a User certificate or a CA certificate. |
653 | | * Refer the test case CTT/Security/Security Certificate Validation/029.js |
654 | | * for more details. */ |
655 | 0 | X509 *leaf = sk_X509_value(stack, 0); |
656 | 0 | if(openSSLCheckCA(leaf)) { |
657 | 0 | sk_X509_pop_free(stack, X509_free); |
658 | 0 | return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; |
659 | 0 | } |
660 | | |
661 | | /* These steps are performed outside of this method. |
662 | | * Because we need the server or client context. |
663 | | * - Security Policy |
664 | | * - Host Name |
665 | | * - URI */ |
666 | | |
667 | | /* Verification Step: Build Certificate Chain |
668 | | * We perform the checks for each certificate inside. */ |
669 | 0 | X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH]; |
670 | 0 | ret = openSSL_verifyChain(certGroup, context, stack, old_issuers, leaf, 0); |
671 | 0 | sk_X509_pop_free(stack, X509_free); |
672 | 0 | return ret; |
673 | 0 | } |
674 | | |
675 | | static UA_StatusCode |
676 | | MemoryCertStore_verifyCertificate(UA_CertificateGroup *certGroup, |
677 | 0 | const UA_ByteString *certificate) { |
678 | | /* Check parameter */ |
679 | 0 | if(certGroup == NULL || certificate == NULL) { |
680 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
681 | 0 | } |
682 | | |
683 | 0 | UA_StatusCode retval = verifyCertificate(certGroup, certificate); |
684 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
685 | 0 | if(MemoryCertStore_addToRejectedList(certGroup, certificate) != UA_STATUSCODE_GOOD) { |
686 | 0 | UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SECURITYPOLICY, |
687 | 0 | "Could not append certificate to rejected list"); |
688 | 0 | } |
689 | 0 | } |
690 | 0 | return retval; |
691 | 0 | } |
692 | | |
693 | | UA_StatusCode |
694 | | UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup, |
695 | | UA_NodeId *certificateGroupId, |
696 | | const UA_TrustListDataType *trustList, |
697 | | const UA_Logger *logger, |
698 | 9.01k | const UA_KeyValueMap *params) { |
699 | | |
700 | 9.01k | if(certGroup == NULL || certificateGroupId == NULL) { |
701 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
702 | 0 | } |
703 | | |
704 | 9.01k | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
705 | | |
706 | | /* Clear if the plugin is already initialized */ |
707 | 9.01k | if(certGroup->clear) |
708 | 0 | certGroup->clear(certGroup); |
709 | | |
710 | 9.01k | UA_NodeId_copy(certificateGroupId, &certGroup->certificateGroupId); |
711 | 9.01k | certGroup->logging = logger; |
712 | | |
713 | 9.01k | certGroup->getTrustList = MemoryCertStore_getTrustList; |
714 | 9.01k | certGroup->setTrustList = MemoryCertStore_setTrustList; |
715 | 9.01k | certGroup->addToTrustList = MemoryCertStore_addToTrustList; |
716 | 9.01k | certGroup->removeFromTrustList = MemoryCertStore_removeFromTrustList; |
717 | 9.01k | certGroup->getRejectedList = MemoryCertStore_getRejectedList; |
718 | 9.01k | certGroup->getCertificateCrls = MemoryCertStore_getCertificateCrls; |
719 | 9.01k | certGroup->verifyCertificate = MemoryCertStore_verifyCertificate; |
720 | 9.01k | certGroup->clear = MemoryCertStore_clear; |
721 | | |
722 | | /* Set PKI Store context data */ |
723 | 9.01k | MemoryCertStore *context = (MemoryCertStore *)UA_calloc(1, sizeof(MemoryCertStore)); |
724 | 9.01k | if(!context) { |
725 | 0 | retval = UA_STATUSCODE_BADOUTOFMEMORY; |
726 | 0 | goto cleanup; |
727 | 0 | } |
728 | 9.01k | certGroup->context = context; |
729 | | /* Default values */ |
730 | 9.01k | context->maxTrustListSize = 65535; |
731 | 9.01k | context->maxRejectedListSize = 100; |
732 | | |
733 | 9.01k | if(params) { |
734 | 9.01k | const UA_UInt32 *maxTrustListSize = (const UA_UInt32*) |
735 | 9.01k | UA_KeyValueMap_getScalar(params, MemoryCertStoreParameters[MEMORYCERTSTORE_PARAMINDEX_MAXTRUSTLISTSIZE].name, |
736 | 9.01k | &UA_TYPES[UA_TYPES_UINT32]); |
737 | | |
738 | 9.01k | const UA_UInt32 *maxRejectedListSize = (const UA_UInt32*) |
739 | 9.01k | UA_KeyValueMap_getScalar(params, MemoryCertStoreParameters[MEMORYCERTSTORE_PARAMINDEX_MAXREJECTEDLISTSIZE].name, |
740 | 9.01k | &UA_TYPES[UA_TYPES_UINT32]); |
741 | | |
742 | 9.01k | if(maxTrustListSize) { |
743 | 0 | context->maxTrustListSize = *maxTrustListSize; |
744 | 0 | } |
745 | | |
746 | 9.01k | if(maxRejectedListSize) { |
747 | 0 | context->maxRejectedListSize = *maxRejectedListSize; |
748 | 0 | } |
749 | 9.01k | } |
750 | | |
751 | 9.01k | UA_TrustListDataType_add(trustList, &context->trustList); |
752 | 9.01k | reloadCertificates(certGroup); |
753 | | |
754 | 9.01k | return UA_STATUSCODE_GOOD; |
755 | | |
756 | 0 | cleanup: |
757 | 0 | certGroup->clear(certGroup); |
758 | 0 | return retval; |
759 | 9.01k | } |
760 | | |
761 | | UA_StatusCode |
762 | | UA_CertificateUtils_verifyApplicationUri(const UA_ByteString *certificate, |
763 | 59 | const UA_String *applicationURI) { |
764 | 59 | const unsigned char *pData = certificate->data; |
765 | 59 | if(!pData) |
766 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
767 | | |
768 | 59 | X509 *certificateX509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
769 | 59 | if(!certificateX509) |
770 | 59 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
771 | | |
772 | 0 | GENERAL_NAMES *pNames = (GENERAL_NAMES *) |
773 | 0 | X509_get_ext_d2i(certificateX509, NID_subject_alt_name, NULL, NULL); |
774 | 0 | if(!pNames) { |
775 | 0 | X509_free(certificateX509); |
776 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
777 | 0 | } |
778 | | |
779 | 0 | UA_String subjectURI = UA_STRING_NULL; |
780 | 0 | for(int i = 0; i < sk_GENERAL_NAME_num(pNames); i++) { |
781 | 0 | GENERAL_NAME *value = sk_GENERAL_NAME_value(pNames, i); |
782 | 0 | if(value->type == GEN_URI) { |
783 | 0 | subjectURI.length = (size_t) (value->d.ia5->length); |
784 | 0 | subjectURI.data = (UA_Byte*)UA_malloc(subjectURI.length); |
785 | 0 | if(!subjectURI.data) { |
786 | 0 | X509_free(certificateX509); |
787 | 0 | sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); |
788 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
789 | 0 | } |
790 | 0 | (void)memcpy(subjectURI.data, value->d.ia5->data, subjectURI.length); |
791 | 0 | break; |
792 | 0 | } |
793 | 0 | } |
794 | | |
795 | 0 | UA_StatusCode ret = UA_STATUSCODE_GOOD; |
796 | 0 | if(UA_Bstrstr(subjectURI.data, subjectURI.length, |
797 | 0 | applicationURI->data, applicationURI->length) == NULL) |
798 | 0 | ret = UA_STATUSCODE_BADCERTIFICATEURIINVALID; |
799 | |
|
800 | 0 | X509_free(certificateX509); |
801 | 0 | sk_GENERAL_NAME_pop_free(pNames, GENERAL_NAME_free); |
802 | 0 | UA_String_clear(&subjectURI); |
803 | 0 | return ret; |
804 | 0 | } |
805 | | |
806 | | UA_StatusCode |
807 | | UA_CertificateUtils_getExpirationDate(UA_ByteString *certificate, |
808 | 85 | UA_DateTime *expiryDateTime) { |
809 | 85 | X509 *x509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
810 | 85 | if(!x509) |
811 | 85 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
812 | | |
813 | | /* Get the certificate Expiry date */ |
814 | 0 | ASN1_TIME *not_after = X509_get_notAfter(x509); |
815 | |
|
816 | 0 | struct tm dtTime; |
817 | 0 | ASN1_TIME_to_tm(not_after, &dtTime); |
818 | 0 | X509_free(x509); |
819 | |
|
820 | 0 | struct musl_tm dateTime; |
821 | 0 | memset(&dateTime, 0, sizeof(struct musl_tm)); |
822 | 0 | dateTime.tm_year = dtTime.tm_year; |
823 | 0 | dateTime.tm_mon = dtTime.tm_mon; |
824 | 0 | dateTime.tm_mday = dtTime.tm_mday; |
825 | 0 | dateTime.tm_hour = dtTime.tm_hour; |
826 | 0 | dateTime.tm_min = dtTime.tm_min; |
827 | 0 | dateTime.tm_sec = dtTime.tm_sec; |
828 | |
|
829 | 0 | long long sec_epoch = musl_tm_to_secs(&dateTime); |
830 | 0 | *expiryDateTime = UA_DATETIME_UNIX_EPOCH; |
831 | 0 | *expiryDateTime += sec_epoch * UA_DATETIME_SEC; |
832 | 0 | return UA_STATUSCODE_GOOD; |
833 | 85 | } |
834 | | |
835 | | UA_StatusCode |
836 | | UA_CertificateUtils_getSubjectName(UA_ByteString *certificate, |
837 | 196 | UA_String *subjectName) { |
838 | 196 | X509_NAME *sn = NULL; |
839 | 196 | X509 *x509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
840 | 196 | X509_CRL *x509_crl = NULL; |
841 | | |
842 | 196 | if(x509) { |
843 | 0 | sn = X509_get_subject_name(x509); |
844 | 196 | } else { |
845 | 196 | x509_crl = UA_OpenSSL_LoadCrl(certificate); |
846 | 196 | if(!x509_crl) |
847 | 196 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
848 | 0 | sn = X509_CRL_get_issuer(x509_crl); |
849 | 0 | } |
850 | | |
851 | 0 | char buf[1024]; |
852 | 0 | *subjectName = UA_STRING_ALLOC(X509_NAME_oneline(sn, buf, 1024)); |
853 | |
|
854 | 0 | if(x509) |
855 | 0 | X509_free(x509); |
856 | 0 | if(x509_crl) |
857 | 0 | X509_CRL_free(x509_crl); |
858 | 0 | return UA_STATUSCODE_GOOD; |
859 | 196 | } |
860 | | |
861 | | UA_StatusCode |
862 | | UA_CertificateUtils_getThumbprint(UA_ByteString *certificate, |
863 | 4 | UA_String *thumbprint) { |
864 | 4 | if(certificate == NULL || thumbprint->length != (SHA1_DIGEST_LENGTH * 2)) |
865 | 4 | return UA_STATUSCODE_BADINTERNALERROR; |
866 | | |
867 | 0 | unsigned char digest[SHA1_DIGEST_LENGTH]; |
868 | 0 | unsigned int digestLen; |
869 | |
|
870 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
871 | 0 | if(cert) { |
872 | 0 | if(X509_digest(cert, EVP_sha1(), digest, &digestLen) != 1) { |
873 | 0 | X509_free(cert); |
874 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
875 | 0 | } |
876 | 0 | X509_free(cert); |
877 | 0 | } else { |
878 | 0 | X509_CRL *crl = UA_OpenSSL_LoadCrl(certificate); |
879 | 0 | if(!crl) |
880 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
881 | 0 | if(X509_CRL_digest(crl, EVP_sha1(), digest, &digestLen) != 1) { |
882 | 0 | X509_CRL_free(crl); |
883 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
884 | 0 | } |
885 | 0 | X509_CRL_free(crl); |
886 | 0 | } |
887 | | |
888 | 0 | UA_String thumb = UA_STRING_NULL; |
889 | 0 | thumb.length = (SHA1_DIGEST_LENGTH * 2) + 1; |
890 | 0 | thumb.data = (UA_Byte*)malloc(sizeof(UA_Byte) * thumb.length); |
891 | | |
892 | | /* Create a string containing a hex representation */ |
893 | 0 | char *p = (char*)thumb.data; |
894 | 0 | for(size_t i = 0; i < digestLen; i++) { |
895 | 0 | p += sprintf(p, "%.2X", digest[i]); |
896 | 0 | } |
897 | |
|
898 | 0 | memcpy(thumbprint->data, thumb.data, thumbprint->length); |
899 | 0 | free(thumb.data); |
900 | |
|
901 | 0 | return UA_STATUSCODE_GOOD; |
902 | 0 | } |
903 | | |
904 | | UA_StatusCode |
905 | | UA_CertificateUtils_getKeySize(UA_ByteString *certificate, |
906 | 123 | size_t *keySize){ |
907 | 123 | if(certificate == NULL) |
908 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
909 | | |
910 | 123 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
911 | 123 | if(!cert) |
912 | 123 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
913 | | |
914 | 0 | EVP_PKEY *pkey = X509_get_pubkey(cert); |
915 | 0 | if(!pkey) { |
916 | 0 | X509_free(cert); |
917 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
918 | 0 | } |
919 | | |
920 | 0 | if(EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) { |
921 | 0 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
922 | 0 | *keySize = EVP_PKEY_get_size(pkey) * 8; |
923 | | #else |
924 | | *keySize = RSA_size(get_pkey_rsa(pkey)) * 8; |
925 | | #endif |
926 | 0 | } else if(EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { |
927 | 0 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
928 | 0 | *keySize = EVP_PKEY_get_size(pkey) * 8; |
929 | | #else |
930 | | *keySize = ECDSA_size(EVP_PKEY_get0_EC_KEY(pkey)) * 8; |
931 | | #endif |
932 | 0 | } else { |
933 | 0 | EVP_PKEY_free(pkey); |
934 | 0 | X509_free(cert); |
935 | 0 | return UA_STATUSCODE_BADINTERNALERROR; /* Unsupported key type */ |
936 | 0 | } |
937 | | |
938 | 0 | EVP_PKEY_free(pkey); |
939 | 0 | X509_free(cert); |
940 | |
|
941 | 0 | return UA_STATUSCODE_GOOD; |
942 | 0 | } |
943 | | |
944 | | UA_StatusCode |
945 | | UA_CertificateUtils_comparePublicKeys(const UA_ByteString *certificate1, |
946 | 0 | const UA_ByteString *certificate2) { |
947 | 0 | if(certificate1 == NULL || certificate2 == NULL) |
948 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
949 | | |
950 | 0 | BIO *dataBio1 = BIO_new_mem_buf(certificate1->data, certificate1->length); |
951 | 0 | if(!dataBio1) |
952 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
953 | | |
954 | 0 | BIO *dataBio2 = BIO_new_mem_buf(certificate2->data, certificate2->length); |
955 | 0 | if(!dataBio2) { |
956 | 0 | BIO_free(dataBio1); |
957 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
958 | 0 | } |
959 | | |
960 | 0 | X509 *cert1 = NULL; |
961 | 0 | X509 *cert2 = NULL; |
962 | 0 | X509_REQ *csr1 = NULL; |
963 | 0 | X509_REQ *csr2 = NULL; |
964 | | /* Try to read the certificate as PEM first */ |
965 | 0 | cert1 = PEM_read_bio_X509(dataBio1, NULL, 0, NULL); |
966 | 0 | if(!cert1) { |
967 | | /* If PEM read fails, reset BIO and try reading as DER */ |
968 | 0 | (void)BIO_reset(dataBio1); |
969 | 0 | cert1 = d2i_X509_bio(dataBio1, NULL); |
970 | 0 | } |
971 | | /* Try to read as a csr */ |
972 | 0 | if(!cert1) { |
973 | 0 | (void)BIO_reset(dataBio1); |
974 | 0 | csr1 = PEM_read_bio_X509_REQ(dataBio1, NULL, 0, NULL); |
975 | 0 | if(!csr1) { |
976 | 0 | (void)BIO_reset(dataBio1); |
977 | 0 | csr1 = d2i_X509_REQ_bio(dataBio1, NULL); |
978 | 0 | } |
979 | 0 | } |
980 | 0 | if(!cert1 && !csr1) { |
981 | 0 | BIO_free(dataBio1); |
982 | 0 | BIO_free(dataBio2); |
983 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
984 | 0 | } |
985 | | |
986 | 0 | cert2 = PEM_read_bio_X509(dataBio2, NULL, 0, NULL); |
987 | 0 | if(!cert2) { |
988 | | /* If PEM read fails, reset BIO and try reading as DER */ |
989 | 0 | (void)BIO_reset(dataBio2); |
990 | 0 | cert2 = d2i_X509_bio(dataBio2, NULL); |
991 | 0 | } |
992 | | /* Try to load as a csr */ |
993 | 0 | if(!cert2) { |
994 | 0 | (void)BIO_reset(dataBio2); |
995 | 0 | csr2 = PEM_read_bio_X509_REQ(dataBio2, NULL, 0, NULL); |
996 | 0 | if(!csr2) { |
997 | 0 | (void)BIO_reset(dataBio2); |
998 | 0 | csr2 = d2i_X509_REQ_bio(dataBio2, NULL); |
999 | 0 | } |
1000 | 0 | } |
1001 | 0 | if(!cert2 && !csr2) { |
1002 | 0 | X509_free(cert1); |
1003 | 0 | X509_REQ_free(csr1); |
1004 | 0 | BIO_free(dataBio1); |
1005 | 0 | BIO_free(dataBio2); |
1006 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1007 | 0 | } |
1008 | | |
1009 | 0 | BIO_free(dataBio1); |
1010 | 0 | BIO_free(dataBio2); |
1011 | |
|
1012 | 0 | EVP_PKEY *pkey1 = cert1 ? X509_get_pubkey(cert1) : X509_REQ_get_pubkey(csr1); |
1013 | 0 | EVP_PKEY *pkey2 = cert2 ? X509_get_pubkey(cert2) : X509_REQ_get_pubkey(csr2); |
1014 | |
|
1015 | 0 | X509_free(cert1); |
1016 | 0 | X509_free(cert2); |
1017 | 0 | X509_REQ_free(csr1); |
1018 | 0 | X509_REQ_free(csr2); |
1019 | |
|
1020 | 0 | if(!pkey1 || !pkey2) { |
1021 | 0 | EVP_PKEY_free(pkey1); |
1022 | 0 | EVP_PKEY_free(pkey2); |
1023 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1024 | 0 | } |
1025 | | |
1026 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
1027 | 0 | #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
1028 | 0 | int isEqual = EVP_PKEY_eq(pkey1, pkey2); |
1029 | 0 | if(isEqual == 0) |
1030 | 0 | retval = UA_STATUSCODE_BADNOMATCH; |
1031 | 0 | if(isEqual < 0) |
1032 | 0 | retval = UA_STATUSCODE_BADINTERNALERROR; |
1033 | | #else |
1034 | | int isEqual = EVP_PKEY_cmp(pkey1, pkey2); |
1035 | | if(isEqual == 0) |
1036 | | retval = UA_STATUSCODE_BADNOMATCH; |
1037 | | if(isEqual < 0) |
1038 | | retval = UA_STATUSCODE_BADINTERNALERROR; |
1039 | | #endif |
1040 | |
|
1041 | 0 | EVP_PKEY_free(pkey1); |
1042 | 0 | EVP_PKEY_free(pkey2); |
1043 | |
|
1044 | 0 | return retval; |
1045 | 0 | } |
1046 | | |
1047 | | UA_StatusCode |
1048 | | UA_CertificateUtils_checkKeyPair(const UA_ByteString *certificate, |
1049 | 0 | const UA_ByteString *privateKey) { |
1050 | 0 | if(certificate == NULL || privateKey == NULL) |
1051 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1052 | | |
1053 | 0 | X509 *cert = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
1054 | 0 | if(!cert) |
1055 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1056 | | |
1057 | 0 | EVP_PKEY *pkey = UA_OpenSSL_LoadPrivateKey(privateKey); |
1058 | 0 | if(!pkey) { |
1059 | 0 | X509_free(cert); |
1060 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1061 | 0 | } |
1062 | | |
1063 | | /* Verify if the private key matches the public key in the certificate */ |
1064 | 0 | if(X509_check_private_key(cert, pkey) != 1) { |
1065 | 0 | X509_free(cert); |
1066 | 0 | EVP_PKEY_free(pkey); |
1067 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1068 | 0 | } |
1069 | | |
1070 | 0 | X509_free(cert); |
1071 | 0 | EVP_PKEY_free(pkey); |
1072 | |
|
1073 | 0 | return UA_STATUSCODE_GOOD; |
1074 | 0 | } |
1075 | | |
1076 | | UA_StatusCode |
1077 | 546 | UA_CertificateUtils_checkCA(const UA_ByteString *certificate) { |
1078 | 546 | if(certificate == NULL) |
1079 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1080 | | |
1081 | 546 | X509 *certificateX509 = UA_OpenSSL_LoadCertificate(certificate, EVP_PKEY_NONE); |
1082 | 546 | if(!certificateX509) |
1083 | 546 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1084 | | |
1085 | 0 | UA_StatusCode retval = openSSLCheckCA(certificateX509) ? |
1086 | 0 | UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOMATCH; |
1087 | 0 | X509_free(certificateX509); |
1088 | 0 | return retval; |
1089 | 546 | } |
1090 | | |
1091 | | static int |
1092 | 0 | privateKeyPasswordCallback(char *buf, int size, int rwflag, void *userdata) { |
1093 | 0 | (void) rwflag; |
1094 | 0 | UA_ByteString *pw = (UA_ByteString*)userdata; |
1095 | 0 | if(pw->length <= (size_t)size) |
1096 | 0 | memcpy(buf, pw->data, pw->length); |
1097 | 0 | return (int)pw->length; |
1098 | 0 | } |
1099 | | |
1100 | | UA_StatusCode |
1101 | | UA_CertificateUtils_decryptPrivateKey(const UA_ByteString privateKey, |
1102 | | const UA_ByteString password, |
1103 | 0 | UA_ByteString *outDerKey) { |
1104 | 0 | if(!outDerKey) |
1105 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1106 | | |
1107 | 0 | if(privateKey.length == 0) { |
1108 | 0 | *outDerKey = UA_BYTESTRING_NULL; |
1109 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
1110 | 0 | } |
1111 | | |
1112 | 0 | EVP_PKEY *pkey = NULL; |
1113 | 0 | const unsigned char * in = privateKey.data; |
1114 | | |
1115 | | /* Check if input is already in DER format */ |
1116 | 0 | pkey = d2i_AutoPrivateKey(NULL, &in, privateKey.length); |
1117 | 0 | if(pkey != NULL) { |
1118 | 0 | EVP_PKEY_free(pkey); |
1119 | 0 | return UA_ByteString_copy(&privateKey, outDerKey); |
1120 | 0 | } |
1121 | | |
1122 | | /* Decrypt */ |
1123 | 0 | BIO *bio = BIO_new_mem_buf((void*)privateKey.data, (int)privateKey.length); |
1124 | 0 | pkey = PEM_read_bio_PrivateKey(bio, NULL, |
1125 | 0 | privateKeyPasswordCallback, |
1126 | 0 | (void*)(uintptr_t)&password); |
1127 | 0 | BIO_free(bio); |
1128 | 0 | if(!pkey) |
1129 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1130 | | |
1131 | | /* Write DER encoded, allocates the new memory */ |
1132 | 0 | unsigned char *data = NULL; |
1133 | 0 | const int numBytes = i2d_PrivateKey(pkey, &data); |
1134 | 0 | EVP_PKEY_free(pkey); |
1135 | 0 | if(!data) |
1136 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1137 | | |
1138 | | /* Copy to the data to outDerKey |
1139 | | * Passing the data pointer directly causes a heap corruption on Windows |
1140 | | * when outDerKey is cleared. |
1141 | | */ |
1142 | 0 | UA_ByteString temp = UA_BYTESTRING_NULL; |
1143 | 0 | temp.data = data; |
1144 | 0 | temp.length = (size_t)numBytes; |
1145 | 0 | const UA_StatusCode success = UA_ByteString_copy(&temp, outDerKey); |
1146 | | /* OPENSSL_clear_free() is not supported by the LibreSSL version in the CI */ |
1147 | 0 | OPENSSL_cleanse(data, numBytes); |
1148 | | OPENSSL_free(data); |
1149 | 0 | return success; |
1150 | 0 | } |
1151 | | |
1152 | | #endif |