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
2124
2225namespace node {
2326
@@ -232,6 +235,306 @@ unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
232235 }
233236}
234237
238+ // 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 () {
236539 static std::vector<X509*> root_certs_vector;
237540 static bool root_certs_vector_loaded = false ;
@@ -240,12 +543,17 @@ X509_STORE* NewRootCertStore() {
240543
241544 if (!root_certs_vector_loaded) {
242545 if (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
249557
250558 // Parse errors from the built-in roots are fatal.
251559 CHECK_NOT_NULL (x509);
0 commit comments