|
18 | 18 | #ifndef OPENSSL_NO_ENGINE |
19 | 19 | #include <openssl/engine.h> |
20 | 20 | #endif // !OPENSSL_NO_ENGINE |
| 21 | +#ifdef __APPLE__ |
| 22 | +#include <Security/Security.h> |
| 23 | +#endif |
21 | 24 |
|
22 | 25 | namespace node { |
23 | 26 |
|
@@ -232,6 +235,306 @@ unsigned long LoadCertsFromFile( // NOLINT(runtime/int) |
232 | 235 | } |
233 | 236 | } |
234 | 237 |
|
| 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 | + |
235 | 538 | X509_STORE* NewRootCertStore() { |
236 | 539 | static std::vector<X509*> root_certs_vector; |
237 | 540 | static bool root_certs_vector_loaded = false; |
@@ -240,12 +543,17 @@ X509_STORE* NewRootCertStore() { |
240 | 543 |
|
241 | 544 | if (!root_certs_vector_loaded) { |
242 | 545 | 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 |
249 | 557 |
|
250 | 558 | // Parse errors from the built-in roots are fatal. |
251 | 559 | CHECK_NOT_NULL(x509); |
|