crypto: added support for reading certificates from macOS system store · nodejs/node@39a474f
@@ -18,6 +18,9 @@
1818#ifndef OPENSSL_NO_ENGINE
1919#include <openssl/engine.h>
2020#endif // !OPENSSL_NO_ENGINE
21+#ifdef __APPLE__
22+#include <Security/Security.h>
23+#endif
21242225namespace node {
2326@@ -232,6 +235,306 @@ unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
232235 }
233236}
234237238+// Indicates the trust status of a certificate.
239+enum class TrustStatus {
240+// Trust status is unknown / uninitialized.
241+ UNKNOWN,
242+// Certificate inherits trust value from its issuer. If the certificate is the
243+// root of the chain, this implies distrust.
244+ UNSPECIFIED,
245+// Certificate is a trust anchor.
246+ TRUSTED,
247+// Certificate is blocked / explicitly distrusted.
248+ DISTRUSTED
249+};
250+251+bool isSelfIssued(X509* cert) {
252+auto subject = X509_get_subject_name(cert);
253+auto issuer = X509_get_issuer_name(cert);
254+255+return X509_NAME_cmp(subject, issuer) == 0;
256+}
257+258+#ifdef __APPLE__
259+// This code is loosely based on
260+// https://github.com/chromium/chromium/blob/54bd8e3/net/cert/internal/trust_store_mac.cc
261+// Copyright 2015 The Chromium Authors
262+// Licensed under a BSD-style license
263+// See https://chromium.googlesource.com/chromium/src/+/HEAD/LICENSE for
264+// details.
265+TrustStatus IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict,
266+bool is_self_issued) {
267+// Trust settings may be scoped to a single application
268+// skip as this is not supported
269+if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) {
270+return TrustStatus::UNSPECIFIED;
271+ }
272+273+// Trust settings may be scoped using policy-specific constraints. For
274+// example, SSL trust settings might be scoped to a single hostname, or EAP
275+// settings specific to a particular WiFi network.
276+// As this is not presently supported, skip any policy-specific trust
277+// settings.
278+if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) {
279+return TrustStatus::UNSPECIFIED;
280+ }
281+282+// If the trust settings are scoped to a specific policy (via
283+// kSecTrustSettingsPolicy), ensure that the policy is the same policy as
284+// |kSecPolicyAppleSSL|. If there is no kSecTrustSettingsPolicy key, it's
285+// considered a match for all policies.
286+if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicy)) {
287+ SecPolicyRef policy_ref = reinterpret_cast<SecPolicyRef>(const_cast<void*>(
288+CFDictionaryGetValue(trust_dict, kSecTrustSettingsPolicy)));
289+290+if (!policy_ref) {
291+return TrustStatus::UNSPECIFIED;
292+ }
293+294+ CFDictionaryRef policy_dict(SecPolicyCopyProperties(policy_ref));
295+296+// kSecPolicyOid is guaranteed to be present in the policy dictionary.
297+ CFStringRef policy_oid = reinterpret_cast<CFStringRef>(
298+const_cast<void*>(CFDictionaryGetValue(policy_dict, kSecPolicyOid)));
299+300+if (!CFEqual(policy_oid, kSecPolicyAppleSSL)) {
301+return TrustStatus::UNSPECIFIED;
302+ }
303+ }
304+305+int trust_settings_result = kSecTrustSettingsResultTrustRoot;
306+if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsResult)) {
307+ CFNumberRef trust_settings_result_ref =
308+reinterpret_cast<CFNumberRef>(const_cast<void*>(
309+CFDictionaryGetValue(trust_dict, kSecTrustSettingsResult)));
310+311+if (!trust_settings_result_ref ||
312+ !CFNumberGetValue(trust_settings_result_ref,
313+kCFNumberIntType,
314+&trust_settings_result)) {
315+return TrustStatus::UNSPECIFIED;
316+ }
317+318+if (trust_settings_result == kSecTrustSettingsResultDeny) {
319+return TrustStatus::DISTRUSTED;
320+ }
321+322+// This is a bit of a hack: if the cert is self-issued allow either
323+// kSecTrustSettingsResultTrustRoot or kSecTrustSettingsResultTrustAsRoot on
324+// the basis that SecTrustSetTrustSettings should not allow creating an
325+// invalid trust record in the first place. (The spec is that
326+// kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed)
327+// certs and kSecTrustSettingsResultTrustAsRoot is used for other certs.)
328+// This hack avoids having to check the signature on the cert which is slow
329+// if using the platform APIs, and may require supporting MD5 signature
330+// algorithms on some older OSX versions or locally added roots, which is
331+// undesirable in the built-in signature verifier.
332+if (is_self_issued) {
333+return trust_settings_result == kSecTrustSettingsResultTrustRoot ||
334+ trust_settings_result == kSecTrustSettingsResultTrustAsRoot
335+ ? TrustStatus::TRUSTED
336+ : TrustStatus::UNSPECIFIED;
337+ }
338+339+// kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs.
340+return (trust_settings_result == kSecTrustSettingsResultTrustAsRoot)
341+ ? TrustStatus::TRUSTED
342+ : TrustStatus::UNSPECIFIED;
343+ }
344+345+return TrustStatus::UNSPECIFIED;
346+}
347+348+TrustStatus IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings,
349+bool is_self_issued) {
350+// The trust_settings parameter can return a valid but empty CFArrayRef.
351+// This empty trust-settings array means “always trust this certificate”
352+// with an overall trust setting for the certificate of
353+// kSecTrustSettingsResultTrustRoot
354+if (CFArrayGetCount(trust_settings) == 0) {
355+return is_self_issued ? TrustStatus::TRUSTED : TrustStatus::UNSPECIFIED;
356+ }
357+358+for (CFIndex i = 0; i < CFArrayGetCount(trust_settings); ++i) {
359+ CFDictionaryRef trust_dict = reinterpret_cast<CFDictionaryRef>(
360+const_cast<void*>(CFArrayGetValueAtIndex(trust_settings, i)));
361+362+ TrustStatus trust =
363+IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_issued);
364+365+if (trust == TrustStatus::DISTRUSTED || trust == TrustStatus::TRUSTED) {
366+return trust;
367+ }
368+ }
369+return TrustStatus::UNSPECIFIED;
370+}
371+372+bool IsCertificateTrustValid(SecCertificateRef ref) {
373+ SecTrustRef sec_trust = nullptr;
374+ CFMutableArrayRef subj_certs =
375+CFArrayCreateMutable(nullptr, 1, &kCFTypeArrayCallBacks);
376+CFArraySetValueAtIndex(subj_certs, 0, ref);
377+378+ SecPolicyRef policy = SecPolicyCreateSSL(false, nullptr);
379+ OSStatus ortn =
380+SecTrustCreateWithCertificates(subj_certs, policy, &sec_trust);
381+bool result = false;
382+if (ortn) {
383+/* should never happen */
384+ } else {
385+ result = SecTrustEvaluateWithError(sec_trust, nullptr);
386+ }
387+388+if (policy) {
389+CFRelease(policy);
390+ }
391+if (sec_trust) {
392+CFRelease(sec_trust);
393+ }
394+if (subj_certs) {
395+CFRelease(subj_certs);
396+ }
397+return result;
398+}
399+400+bool IsCertificateTrustedForPolicy(X509* cert, SecCertificateRef ref) {
401+ OSStatus err;
402+403+bool trust_evaluated = false;
404+bool is_self_issued = isSelfIssued(cert);
405+406+// Evaluate user trust domain, then admin. User settings can override
407+// admin (and both override the system domain, but we don't check that).
408+for (const auto& trust_domain :
409+ {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin}) {
410+ CFArrayRef trust_settings = nullptr;
411+ err = SecTrustSettingsCopyTrustSettings(ref, trust_domain, &trust_settings);
412+413+if (err != errSecSuccess && err != errSecItemNotFound) {
414+fprintf(stderr,
415+"ERROR: failed to copy trust settings of system certificate%d\n",
416+ err);
417+continue;
418+ }
419+420+if (err == errSecSuccess && trust_settings != nullptr) {
421+ TrustStatus result =
422+IsTrustSettingsTrustedForPolicy(trust_settings, is_self_issued);
423+if (result != TrustStatus::UNSPECIFIED) {
424+CFRelease(trust_settings);
425+return result == TrustStatus::TRUSTED;
426+ }
427+ }
428+429+// An empty trust settings array isn’t the same as no trust settings,
430+// where the trust_settings parameter returns NULL.
431+// No trust-settings array means
432+// “this certificate must be verifiable using a known trusted certificate”.
433+if (trust_settings == nullptr && !trust_evaluated) {
434+bool result = IsCertificateTrustValid(ref);
435+if (result) {
436+return true;
437+ }
438+// no point re-evaluating this in the admin domain
439+ trust_evaluated = true;
440+ } else if (trust_settings) {
441+CFRelease(trust_settings);
442+ }
443+ }
444+return false;
445+}
446+447+void ReadMacOSKeychainCertificates(
448+ std::vector<std::string>* system_root_certificates) {
449+ CFTypeRef search_keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
450+ CFTypeRef search_values[] = {
451+kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue};
452+ CFDictionaryRef search = CFDictionaryCreate(kCFAllocatorDefault,
453+ search_keys,
454+ search_values,
455+3,
456+&kCFTypeDictionaryKeyCallBacks,
457+&kCFTypeDictionaryValueCallBacks);
458+459+ CFArrayRef curr_anchors = nullptr;
460+ OSStatus ortn =
461+SecItemCopyMatching(search, reinterpret_cast<CFTypeRef*>(&curr_anchors));
462+CFRelease(search);
463+464+if (ortn) {
465+fprintf(stderr, "ERROR: SecItemCopyMatching failed %d\n", ortn);
466+ }
467+468+ CFIndex count = CFArrayGetCount(curr_anchors);
469+470+ std::vector<X509*> system_root_certificates_X509;
471+for (int i = 0; i < count; ++i) {
472+ SecCertificateRef cert_ref = reinterpret_cast<SecCertificateRef>(
473+const_cast<void*>(CFArrayGetValueAtIndex(curr_anchors, i)));
474+475+ CFDataRef der_data = SecCertificateCopyData(cert_ref);
476+if (!der_data) {
477+fprintf(stderr, "ERROR: SecCertificateCopyData failed\n");
478+continue;
479+ }
480+auto data_buffer_pointer = CFDataGetBytePtr(der_data);
481+482+ X509* cert =
483+d2i_X509(nullptr, &data_buffer_pointer, CFDataGetLength(der_data));
484+CFRelease(der_data);
485+bool is_valid = IsCertificateTrustedForPolicy(cert, cert_ref);
486+if (is_valid) {
487+ system_root_certificates_X509.emplace_back(cert);
488+ }
489+ }
490+CFRelease(curr_anchors);
491+492+for (size_t i = 0; i < system_root_certificates_X509.size(); i++) {
493+ ncrypto::X509View x509_view(system_root_certificates_X509[i]);
494+495+auto pem_bio = x509_view.toPEM();
496+if (!pem_bio) {
497+fprintf(stderr,
498+"Warning: converting system certificate to PEM format failed\n");
499+continue;
500+ }
501+502+char* pem_data = nullptr;
503+auto pem_size = BIO_get_mem_data(pem_bio.get(), &pem_data);
504+if (pem_size <= 0 || !pem_data) {
505+fprintf(
506+ stderr,
507+"Warning: cannot read PEM-encoded data from system certificate\n");
508+continue;
509+ }
510+ std::string certificate_string_pem(pem_data, pem_size);
511+512+ system_root_certificates->emplace_back(certificate_string_pem);
513+ }
514+}
515+#endif // __APPLE__
516+517+void ReadSystemStoreCertificates(
518+ std::vector<std::string>* system_root_certificates) {
519+#ifdef __APPLE__
520+ReadMacOSKeychainCertificates(system_root_certificates);
521+#endif
522+}
523+524+std::vector<std::string> getCombinedRootCertificates() {
525+ std::vector<std::string> combined_root_certs;
526+527+for (size_t i = 0; i < arraysize(root_certs); i++) {
528+ combined_root_certs.emplace_back(root_certs[i]);
529+ }
530+531+if (per_process::cli_options->use_system_ca) {
532+ReadSystemStoreCertificates(&combined_root_certs);
533+ }
534+535+return combined_root_certs;
536+}
537+235538X509_STORE* NewRootCertStore() {
236539static std::vector<X509*> root_certs_vector;
237540static bool root_certs_vector_loaded = false;
@@ -240,12 +543,17 @@ X509_STORE* NewRootCertStore() {
240543241544if (!root_certs_vector_loaded) {
242545if (per_process::cli_options->ssl_openssl_cert_store == false) {
243-for (size_t i = 0; i < arraysize(root_certs); i++) {
244- X509* x509 = PEM_read_bio_X509(
245-NodeBIO::NewFixed(root_certs[i], strlen(root_certs[i])).get(),
246-nullptr, // no re-use of X509 structure
247- NoPasswordCallback,
248-nullptr); // no callback data
546+ std::vector<std::string> combined_root_certs =
547+getCombinedRootCertificates();
548+549+for (size_t i = 0; i < combined_root_certs.size(); i++) {
550+ X509* x509 =
551+PEM_read_bio_X509(NodeBIO::NewFixed(combined_root_certs[i].data(),
552+ combined_root_certs[i].length())
553+ .get(),
554+nullptr, // no re-use of X509 structure
555+ NoPasswordCallback,
556+nullptr); // no callback data
249557250558// Parse errors from the built-in roots are fatal.
251559CHECK_NOT_NULL(x509);