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

21242225

namespace 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+235538

X509_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() {

240543241544

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

249557250558

// Parse errors from the built-in roots are fatal.

251559

CHECK_NOT_NULL(x509);