"Fossies" - the Fresh Open Source Software Archive 
Member "netxms-3.8.166/src/libnetxms/cert.cpp" (23 Feb 2021, 15039 Bytes) of package /linux/misc/netxms-3.8.166.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "cert.cpp" see the
Fossies "Dox" file reference documentation.
1 /*
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2020 Victor Kirhenshtein
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published
8 ** by the Free Software Foundation; either version 3 of the License, or
9 ** (at your option) any later version.
10 **
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
15 **
16 ** You should have received a copy of the GNU Lesser General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: cert.cpp
21 **
22 **/
23
24 #include "libnetxms.h"
25 #include <nxcrypto.h>
26 #include <nxstat.h>
27 #include <openssl/asn1t.h>
28 #include <openssl/x509v3.h>
29
30 #define DEBUG_TAG _T("crypto.cert")
31
32 #ifdef _WITH_ENCRYPTION
33
34 #if OPENSSL_VERSION_NUMBER < 0x10100000L
35 static inline const ASN1_TIME *X509_get0_notAfter(const X509 *x)
36 {
37 return X509_get_notAfter(x);
38 }
39 static inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *s)
40 {
41 return s->data;
42 }
43 #endif
44
45 /**
46 * Get field from X.509 name
47 */
48 static bool GetX509NameField(X509_NAME *name, int nid, TCHAR *buffer, size_t size)
49 {
50 int idx = X509_NAME_get_index_by_NID(name, nid, -1);
51 if (idx == -1)
52 return false;
53
54 X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, idx);
55 if (entry == nullptr)
56 return false;
57
58 ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry);
59 if (data == nullptr)
60 return false;
61
62 unsigned char *text;
63 ASN1_STRING_to_UTF8(&text, data);
64 #ifdef UNICODE
65 MultiByteToWideChar(CP_UTF8, 0, (char *)text, -1, buffer, (int)size);
66 #else
67 utf8_to_mb((char *)text, -1, buffer, (int)size);
68 #endif
69 buffer[size - 1] = 0;
70 OPENSSL_free(text);
71 return true;
72 }
73
74 /**
75 * Get single field from certificate subject
76 */
77 bool LIBNETXMS_EXPORTABLE GetCertificateSubjectField(const X509 *cert, int nid, TCHAR *buffer, size_t size)
78 {
79 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
80 X509_NAME *subject = X509_get_subject_name(cert);
81 #else
82 X509_NAME *subject = X509_get_subject_name(const_cast<X509*>(cert));
83 #endif
84 return (subject != nullptr) ? GetX509NameField(subject, nid, buffer, size) : false;
85 }
86
87 /**
88 * Get single field from certificate issuer
89 */
90 bool LIBNETXMS_EXPORTABLE GetCertificateIssuerField(const X509 *cert, int nid, TCHAR *buffer, size_t size)
91 {
92 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
93 X509_NAME *issuer = X509_get_issuer_name(cert);
94 #else
95 X509_NAME *issuer = X509_get_issuer_name(const_cast<X509*>(cert));
96 #endif
97 return (issuer != nullptr) ? GetX509NameField(issuer, nid, buffer, size) : false;
98 }
99
100 /**
101 * Get CN from certificate
102 */
103 bool LIBNETXMS_EXPORTABLE GetCertificateCN(const X509 *cert, TCHAR *buffer, size_t size)
104 {
105 return GetCertificateSubjectField(cert, NID_commonName, buffer, size);
106 }
107
108 /**
109 * Get OU from certificate
110 */
111 bool LIBNETXMS_EXPORTABLE GetCertificateOU(const X509 *cert, TCHAR *buffer, size_t size)
112 {
113 return GetCertificateSubjectField(cert, NID_organizationalUnitName, buffer, size);
114 }
115
116 /**
117 * Get subject/issuer/... string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
118 */
119 template<bool (*GetField)(const X509*, int, TCHAR*, size_t)> static inline String GetCertificateNameString(const X509 *cert)
120 {
121 StringBuffer text;
122 TCHAR buffer[256];
123 if (GetField(cert, NID_countryName, buffer, 256))
124 {
125 text.append(_T("C="));
126 text.append(buffer);
127 }
128 if (GetField(cert, NID_stateOrProvinceName, buffer, 256))
129 {
130 if (!text.isEmpty())
131 text.append(_T(','));
132 text.append(_T("ST="));
133 text.append(buffer);
134 }
135 if (GetField(cert, NID_localityName, buffer, 256))
136 {
137 if (!text.isEmpty())
138 text.append(_T(','));
139 text.append(_T("L="));
140 text.append(buffer);
141 }
142 if (GetField(cert, NID_organizationName, buffer, 256))
143 {
144 if (!text.isEmpty())
145 text.append(_T(','));
146 text.append(_T("O="));
147 text.append(buffer);
148 }
149 if (GetField(cert, NID_organizationalUnitName, buffer, 256))
150 {
151 if (!text.isEmpty())
152 text.append(_T(','));
153 text.append(_T("OU="));
154 text.append(buffer);
155 }
156 if (GetField(cert, NID_commonName, buffer, 256))
157 {
158 if (!text.isEmpty())
159 text.append(_T(','));
160 text.append(_T("CN="));
161 text.append(buffer);
162 }
163 return text;
164 }
165
166 /**
167 * Get subject string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
168 */
169 String LIBNETXMS_EXPORTABLE GetCertificateSubjectString(const X509 *cert)
170 {
171 return GetCertificateNameString<GetCertificateSubjectField>(cert);
172 }
173
174 /**
175 * Get issuer string (C=XX,ST=state,L=locality,O=org,OU=unit,CN=cn) from certificate
176 */
177 String LIBNETXMS_EXPORTABLE GetCertificateIssuerString(const X509 *cert)
178 {
179 return GetCertificateNameString<GetCertificateIssuerField>(cert);
180 }
181
182 /**
183 * Get certificate expiration time
184 */
185 time_t LIBNETXMS_EXPORTABLE GetCertificateExpirationTime(const X509 *cert)
186 {
187 #if OPENSSL_VERSION_NUMBER >= 0x10101000L
188 struct tm expTime;
189 ASN1_TIME_to_tm(X509_get0_notAfter(cert), &expTime);
190 return timegm(&expTime);
191 #elif OPENSSL_VERSION_NUMBER >= 0x10100000L
192 ASN1_TIME *epoch = ASN1_TIME_set(nullptr, static_cast<time_t>(0));
193 int days, seconds;
194 ASN1_TIME_diff(&days, &seconds, epoch, X509_get0_notAfter(cert));
195 ASN1_TIME_free(epoch);
196 return static_cast<time_t>(days) * 86400 + seconds;
197 #else
198 struct tm expTime;
199 memset(&expTime, 0, sizeof(expTime));
200
201 const ASN1_TIME *atime = X509_get0_notAfter(cert);
202
203 size_t i = 0;
204 const char *s = reinterpret_cast<const char*>(atime->data);
205 if (atime->type == V_ASN1_UTCTIME) /* two digit year */
206 {
207 if (atime->length < 12)
208 return 0; // Invalid format
209 expTime.tm_year = (s[i] - '0') * 10 + (s[i + 1] - '0');
210 if (expTime.tm_year < 70)
211 expTime.tm_year += 100;
212 i += 2;
213 }
214 else if (atime->type == V_ASN1_GENERALIZEDTIME) /* four digit year */
215 {
216 if (atime->length < 14)
217 return 0; // Invalid format
218 expTime.tm_year = (s[i] - '0') * 1000 + (s[i + 1] - '0') * 100 + (s[i + 2] - '0') * 10 + (s[i + 3] - '0');
219 expTime.tm_year -= 1900;
220 i += 4;
221 }
222 else
223 {
224 return 0; // Unknown type
225 }
226 expTime.tm_mon = (s[i] - '0') * 10 + (s[i + 1] - '0') - 1; // -1 since January is 0 not 1.
227 i += 2;
228 expTime.tm_mday = (s[i] - '0') * 10 + (s[i + 1] - '0');
229 i += 2;
230 expTime.tm_hour = (s[i] - '0') * 10 + (s[i + 1] - '0');
231 i += 2;
232 expTime.tm_min = (s[i] - '0') * 10 + (s[i + 1] - '0');
233 i += 2;
234 expTime.tm_sec = (s[i] - '0') * 10 + (s[i + 1] - '0');
235 return timegm(&expTime);
236 #endif
237 }
238
239 /**
240 * Certificate template extension structure as described in Microsoft documentation
241 * https://docs.microsoft.com/en-us/windows/win32/api/certenroll/nn-certenroll-ix509extensiontemplate
242 *
243 * ----------------------------------------------------------------------
244 * -- CertificateTemplate
245 * -- XCN_OID_CERTIFICATE_TEMPLATE (1.3.6.1.4.1.311.21.7)
246 * ----------------------------------------------------------------------
247 *
248 * CertificateTemplate ::= SEQUENCE
249 * {
250 * templateID EncodedObjectID,
251 * templateMajorVersion TemplateVersion,
252 * templateMinorVersion TemplateVersion OPTIONAL
253 * }
254 */
255 struct CERTIFICATE_TEMPLATE
256 {
257 ASN1_OBJECT *templateId;
258 ASN1_INTEGER *templateMajorVersion;
259 ASN1_INTEGER *templateMinorVersion;
260 };
261
262 /**
263 * Define ASN.1 sequence for CERTIFICATE_TEMPLATE
264 */
265 ASN1_NDEF_SEQUENCE(CERTIFICATE_TEMPLATE) =
266 {
267 ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateId, ASN1_OBJECT),
268 ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateMajorVersion, ASN1_INTEGER),
269 ASN1_SIMPLE(CERTIFICATE_TEMPLATE, templateMinorVersion, ASN1_INTEGER)
270 } ASN1_NDEF_SEQUENCE_END(CERTIFICATE_TEMPLATE)
271
272 /**
273 * Function implementations for CERTIFICATE_TEMPLATE structure
274 */
275 IMPLEMENT_ASN1_FUNCTIONS(CERTIFICATE_TEMPLATE)
276
277 /**
278 * Get certificate's template ID (extension 1.3.6.1.4.1.311.21.7)
279 */
280 String LIBNETXMS_EXPORTABLE GetCertificateTemplateId(const X509 *cert)
281 {
282 ASN1_OBJECT *oid = OBJ_txt2obj("1.3.6.1.4.1.311.21.7", 1);
283 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
284 int index = X509_get_ext_by_OBJ(cert, oid, -1);
285 #else
286 int index = X509_get_ext_by_OBJ(const_cast<X509*>(cert), oid, -1);
287 #endif
288 ASN1_OBJECT_free(oid);
289 if (index == -1)
290 return String();
291 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
292 X509_EXTENSION *ext = X509_get_ext(cert, index);
293 #else
294 X509_EXTENSION *ext = X509_get_ext(const_cast<X509*>(cert), index);
295 #endif
296 if (ext == nullptr)
297 return String();
298 ASN1_STRING *value = X509_EXTENSION_get_data(ext);
299 if (value == nullptr)
300 return String();
301 const unsigned char *bytes = ASN1_STRING_get0_data(value);
302 CERTIFICATE_TEMPLATE *t = d2i_CERTIFICATE_TEMPLATE(nullptr, &bytes, ASN1_STRING_length(value));
303 if (t == nullptr)
304 return String();
305 char oidA[256];
306 OBJ_obj2txt(oidA, 256, t->templateId, 1);
307 CERTIFICATE_TEMPLATE_free(t);
308 #ifdef UNICODE
309 WCHAR oidW[256];
310 mb_to_wchar(oidA, -1, oidW, 256);
311 return String(oidW);
312 #else
313 return String(oidA);
314 #endif
315 }
316
317 /**
318 * Get HTTP/HTTPS CRL distribution point from certificate
319 */
320 String LIBNETXMS_EXPORTABLE GetCertificateCRLDistributionPoint(const X509 *cert)
321 {
322 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
323 STACK_OF(DIST_POINT) *dps = static_cast<STACK_OF(DIST_POINT)*>(X509_get_ext_d2i(cert, NID_crl_distribution_points, nullptr, nullptr));
324 #else
325 STACK_OF(DIST_POINT) *dps = static_cast<STACK_OF(DIST_POINT)*>(X509_get_ext_d2i(const_cast<X509*>(cert), NID_crl_distribution_points, nullptr, nullptr));
326 #endif
327 if (dps == nullptr)
328 return String();
329
330 StringBuffer result;
331 for (int i = 0; i < sk_DIST_POINT_num(dps); i++)
332 {
333 DIST_POINT *dp = sk_DIST_POINT_value(dps, i);
334 if (dp->distpoint->type == 0)
335 {
336 GENERAL_NAMES *names = dp->distpoint->name.fullname;
337 for (int j = 0; j < sk_GENERAL_NAME_num(names); j++)
338 {
339 GENERAL_NAME *n = sk_GENERAL_NAME_value(names, j);
340 if (n->type == GEN_URI)
341 {
342 ASN1_IA5STRING *uri = n->d.uniformResourceIdentifier;
343 int l = ASN1_STRING_length(uri);
344 if (l > 7)
345 {
346 const unsigned char *data = ASN1_STRING_get0_data(uri);
347 if (!memcmp(data, "http:", 5) || !memcmp(data, "https:", 6))
348 {
349 result.appendMBString(reinterpret_cast<const char*>(data), l, CP_UTF8);
350 break;
351 }
352 }
353 }
354 }
355 }
356 }
357 sk_DIST_POINT_free(dps);
358 return result;
359 }
360
361 /**
362 * Create default certificate store with trusted certificates
363 */
364 X509_STORE LIBNETXMS_EXPORTABLE *CreateTrustedCertificatesStore(const StringSet& trustedCertificates, bool useSystemStore)
365 {
366 X509_STORE *store = X509_STORE_new();
367 if (store == nullptr)
368 {
369 nxlog_debug_tag(DEBUG_TAG, 3, _T("CreateTrustedCertificatesStore: cannot create certificate store"));
370 return nullptr;
371 }
372
373 X509_LOOKUP *dirLookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
374 X509_LOOKUP *fileLookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
375
376 if (!trustedCertificates.isEmpty())
377 {
378 auto it = trustedCertificates.constIterator();
379 while(it->hasNext())
380 {
381 const TCHAR *trustedRoot = it->next();
382 NX_STAT_STRUCT st;
383 if (CALL_STAT(trustedRoot, &st) != 0)
384 continue;
385
386 #ifdef UNICODE
387 char mbTrustedRoot[MAX_PATH];
388 WideCharToMultiByteSysLocale(trustedRoot, mbTrustedRoot, MAX_PATH);
389 #else
390 const char *mbTrustedRoot = trustedRoot;
391 #endif
392 int added = S_ISDIR(st.st_mode) ?
393 X509_LOOKUP_add_dir(dirLookup, mbTrustedRoot, X509_FILETYPE_PEM) :
394 X509_LOOKUP_load_file(fileLookup, mbTrustedRoot, X509_FILETYPE_PEM);
395 if (added)
396 nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: trusted %s \"%s\" added"), S_ISDIR(st.st_mode) ? _T("certificate directory") : _T("certificate"), trustedRoot);
397 }
398 delete it;
399 }
400
401 if (useSystemStore)
402 {
403 #ifdef _WIN32
404 HCERTSTORE hStore = CertOpenSystemStore(0, L"ROOT");
405 if (hStore != nullptr)
406 {
407 PCCERT_CONTEXT context = nullptr;
408 while ((context = CertEnumCertificatesInStore(hStore, context)) != nullptr)
409 {
410 TCHAR certName[1024];
411 CertGetNameString(context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, certName, 1024);
412
413 const unsigned char *in = context->pbCertEncoded;
414 X509 *cert = d2i_X509(nullptr, &in, context->cbCertEncoded);
415 if (cert != nullptr)
416 {
417 if (X509_STORE_add_cert(store, cert))
418 nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: added trusted root certificate \"%s\" from system store"), certName);
419 else
420 nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: cannot add trusted root certificate \"%s\" from system store"), certName);
421 X509_free(cert);
422 }
423 else
424 {
425 nxlog_debug_tag(DEBUG_TAG, 4, _T("CreateTrustedCertificatesStore: cannot convert certificate \"%s\""), certName);
426 }
427 }
428 CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
429 }
430 else
431 {
432 TCHAR buffer[1024];
433 nxlog_debug_tag(DEBUG_TAG, 4, _T("CreateTrustedCertificatesStore: cannot open certificate store \"ROOT\" (%s)"), GetSystemErrorText(GetLastError(), buffer, 1024));
434 }
435 #else /* _WIN32 */
436 static const char *defaultStoreLocations[] = {
437 "/etc/ssl/certs", // Ubuntu, Debian, and many other Linux distros
438 "/usr/local/share/certs", // FreeBSD
439 "/etc/pki/tls/certs", // Fedora/RHEL
440 "/etc/openssl/certs", // NetBSD
441 "/var/ssl/certs", // AIX
442 nullptr
443 };
444 for(int i = 0; defaultStoreLocations[i] != nullptr; i++)
445 {
446 NX_STAT_STRUCT st;
447 if (NX_STAT(defaultStoreLocations[i], &st) == 0)
448 {
449 int added = S_ISDIR(st.st_mode) ?
450 X509_LOOKUP_add_dir(dirLookup, defaultStoreLocations[i], X509_FILETYPE_PEM) :
451 X509_LOOKUP_load_file(fileLookup, defaultStoreLocations[i], X509_FILETYPE_PEM);
452 if (added)
453 {
454 nxlog_debug_tag(DEBUG_TAG, 6, _T("CreateTrustedCertificatesStore: added system certificate store at \"%hs\""), defaultStoreLocations[i]);
455 break;
456 }
457 }
458 }
459 #endif /* _WIN32 */
460 }
461
462 return store;
463 }
464
465 #endif /* _WITH_ENCRYPTION */