1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// During certificate authentication, we call CertVerifier::VerifySSLServerCert.
// This function may make zero or more HTTP requests (e.g. to gather revocation
// information). Our fetching logic for these requests processes them on the
// socket transport service thread.
//
// Because the connection for which we are verifying the certificate is
// happening on the socket transport thread, if our cert auth hook were to call
// VerifySSLServerCert directly, there would be a deadlock: VerifySSLServerCert
// would cause an event to be asynchronously posted to the socket transport
// thread, and then it would block the socket transport thread waiting to be
// notified of the HTTP response. However, the HTTP request would never actually
// be processed because the socket transport thread would be blocked and so it
// wouldn't be able process HTTP requests.
//
// Consequently, when we are asked to verify a certificate, we must always call
// VerifySSLServerCert on another thread. To accomplish this, our auth cert hook
// dispatches a SSLServerCertVerificationJob to a pool of background threads,
// and then immediately returns SECWouldBlock to libssl. These jobs are where
// VerifySSLServerCert is actually called.
//
// When our auth cert hook returns SECWouldBlock, libssl will carry on the
// handshake while we validate the certificate. This will free up the socket
// transport thread so that HTTP requests--including the OCSP requests needed
// for cert verification as mentioned above--can be processed.
//
// Once VerifySSLServerCert returns, the cert verification job dispatches a
// SSLServerCertVerificationResult to the socket transport thread; the
// SSLServerCertVerificationResult will notify libssl that the certificate
// authentication is complete. Once libssl is notified that the authentication
// is complete, it will continue the TLS handshake (if it hasn't already
// finished) and it will begin allowing us to send/receive data on the
// connection.
//
// Timeline of events (for connections managed by the socket transport service):
//
// * libssl calls SSLServerCertVerificationJob::Dispatch on the socket
// transport thread.
// * SSLServerCertVerificationJob::Dispatch queues a job
// (instance of SSLServerCertVerificationJob) to its background thread
// pool and returns.
// * One of the background threads calls CertVerifier::VerifySSLServerCert,
// which may enqueue some HTTP request(s) onto the socket transport thread,
// and then blocks that background thread waiting for the responses and/or
// timeouts or errors for those requests.
// * Once those HTTP responses have all come back or failed, the
// CertVerifier::VerifySSLServerCert function returns a result indicating
// that the validation succeeded or failed.
// * If the validation succeeded, then a SSLServerCertVerificationResult
// event is posted to the socket transport thread, and the cert
// verification thread becomes free to verify other certificates.
// * Otherwise, we do cert override processing to see if the validation
// error can be convered by override rules. The result of this processing
// is similarly dispatched in a SSLServerCertVerificationResult.
// * The SSLServerCertVerificationResult event will either wake up the
// socket (using SSL_AuthCertificateComplete) if validation succeeded or
// there was an error override, or it will set an error flag so that the
// next I/O operation on the socket will fail, causing the socket transport
// thread to close the connection.
//
// SSLServerCertVerificationResult must be dispatched to the socket transport
// thread because we must only call SSL_* functions on the socket transport
// thread since they may do I/O, because many parts of NSSSocketControl and the
// PSM NSS I/O layer are not thread-safe, and because we need the event to
// interrupt the PR_Poll that may waiting for I/O on the socket for which we
// are validating the cert.
//
// When socket process is enabled, libssl is running on socket process. To
// perform certificate authentication with CertVerifier, we have to send all
// needed information to parent process and send the result back to socket
// process via IPC. The workflow is described below.
// 1. In AuthCertificateHookInternal(), we call RemoteProcessCertVerification()
// instead of SSLServerCertVerificationJob::Dispatch when we are on socket
// process.
// 2. In RemoteProcessCertVerification(), PVerifySSLServerCert actors will be
// created on IPDL background thread for carrying needed information via IPC.
// 3. On parent process, VerifySSLServerCertParent is created and it calls
// SSLServerCertVerificationJob::Dispatch for doing certificate verification
// on one of CertVerificationThreads.
// 4. When validation is done, OnVerifiedSSLServerCertSuccess IPC message is
// sent through the IPDL background thread when
// CertVerifier::VerifySSLServerCert returns Success. Otherwise,
// OnVerifiedSSLServerCertFailure is sent.
// 5. After setp 4, PVerifySSLServerCert actors will be released. The
// verification result will be dispatched via
// SSLServerCertVerificationResult.
#include "SSLServerCertVerification.h"
#include <cstring>
#include "CertVerifier.h"
#include "CryptoTask.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSSocketControl.h"
#include "PSMRunnable.h"
#include "RootCertificateTelemetryUtils.h"
#include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "VerifySSLServerCertChild.h"
#include "cert.h"
#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/glean/SecurityManagerSslMetrics.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsICertOverrideService.h"
#include "nsIPublicKeyPinningService.h"
#include "nsISiteSecurityService.h"
#include "nsISocketProvider.h"
#include "nsThreadPool.h"
#include "nsNetUtil.h"
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "nsNSSIOLayer.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsURLHelper.h"
#include "nsXPCOMCIDInternal.h"
#include "mozpkix/pkix.h"
#include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixutil.h"
#include "secerr.h"
#include "secport.h"
#include "ssl.h"
#include "sslerr.h"
#include "sslexp.h"
extern mozilla::LazyLogModule gPIPNSSLog;
using namespace mozilla::pkix;
namespace mozilla {
namespace psm {
// do not use a nsCOMPtr to avoid static initializer/destructor
nsIThreadPool* gCertVerificationThreadPool = nullptr;
// Called when the socket transport thread starts, to initialize the SSL cert
// verification thread pool. By tying the thread pool startup/shutdown directly
// to the STS thread's lifetime, we ensure that they are *always* available for
// SSL connections and that there are no races during startup and especially
// shutdown. (Previously, we have had multiple problems with races in PSM
// background threads, and the race-prevention/shutdown logic used there is
// brittle. Since this service is critical to things like downloading updates,
// we take no chances.) Also, by doing things this way, we avoid the need for
// locks, since gCertVerificationThreadPool is only ever accessed on the socket
// transport thread.
void InitializeSSLServerCertVerificationThreads() {
// TODO: tuning, make parameters preferences
gCertVerificationThreadPool = new nsThreadPool();
NS_ADDREF(gCertVerificationThreadPool);
(void)gCertVerificationThreadPool->SetThreadLimit(5);
(void)gCertVerificationThreadPool->SetIdleThreadLimit(1);
(void)gCertVerificationThreadPool->SetIdleThreadMaximumTimeout(30 * 1000);
(void)gCertVerificationThreadPool->SetIdleThreadGraceTimeout(500);
(void)gCertVerificationThreadPool->SetName("SSL Cert"_ns);
}
// Called when the socket transport thread finishes, to destroy the thread
// pool. Since the socket transport service has stopped processing events, it
// will not attempt any more SSL I/O operations, so it is clearly safe to shut
// down the SSL cert verification infrastructure. Also, the STS will not
// dispatch many SSL verification result events at this point, so any pending
// cert verifications will (correctly) fail at the point they are dispatched.
//
// The other shutdown race condition that is possible is a race condition with
// shutdown of the nsNSSComponent service. We use the
// nsNSSShutdownPreventionLock where needed (not here) to prevent that.
void StopSSLServerCertVerificationThreads() {
if (gCertVerificationThreadPool) {
gCertVerificationThreadPool->Shutdown();
NS_RELEASE(gCertVerificationThreadPool);
}
}
// A probe value of 1 means "no error".
uint32_t MapOverridableErrorToProbeValue(PRErrorCode errorCode) {
switch (errorCode) {
case SEC_ERROR_UNKNOWN_ISSUER:
return 2;
case SEC_ERROR_CA_CERT_INVALID:
return 3;
case SEC_ERROR_UNTRUSTED_ISSUER:
return 4;
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
return 5;
case SEC_ERROR_UNTRUSTED_CERT:
return 6;
case SEC_ERROR_INADEQUATE_KEY_USAGE:
return 7;
case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
return 8;
case SSL_ERROR_BAD_CERT_DOMAIN:
return 9;
case SEC_ERROR_EXPIRED_CERTIFICATE:
return 10;
case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
return 11;
case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
return 12;
case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
return 13;
case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
return 14;
case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
return 15;
case SEC_ERROR_INVALID_TIME:
return 16;
case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
return 17;
// mozilla::pkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED was
// 18
case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
return 19;
case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
return 20;
case mozilla::pkix::
MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY:
return 21;
}
NS_WARNING(
"Unknown certificate error code. Does MapOverridableErrorToProbeValue "
"handle everything in CategorizeCertificateError?");
return 0;
}
static uint32_t MapCertErrorToProbeValue(PRErrorCode errorCode) {
uint32_t probeValue;
switch (errorCode) {
// see security/pkix/include/pkix/Result.h
#define MOZILLA_PKIX_MAP(name, value, nss_name) \
case nss_name: \
probeValue = value; \
break;
MOZILLA_PKIX_MAP_LIST
#undef MOZILLA_PKIX_MAP
default:
return 0;
}
// Since FATAL_ERROR_FLAG is 0x800, fatal error values are much larger than
// non-fatal error values. To conserve space, we remap these so they start at
// (decimal) 90 instead of 0x800. Currently there are ~50 non-fatal errors
// mozilla::pkix might return, so saving space for 90 should be sufficient
// (similarly, there are 4 fatal errors, so saving space for 10 should also
// be sufficient).
static_assert(
FATAL_ERROR_FLAG == 0x800,
"mozilla::pkix::FATAL_ERROR_FLAG is not what we were expecting");
if (probeValue & FATAL_ERROR_FLAG) {
probeValue ^= FATAL_ERROR_FLAG;
probeValue += 90;
}
return probeValue;
}
// If the given PRErrorCode is an overridable certificate error, return which
// category (trust, time, domain mismatch) it falls in. If it is not
// overridable, return Nothing.
Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
CategorizeCertificateError(PRErrorCode certificateError) {
switch (certificateError) {
case SEC_ERROR_CA_CERT_INVALID:
case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
case SEC_ERROR_UNKNOWN_ISSUER:
case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
case mozilla::pkix::
MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY:
case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
return Some(
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST);
case SSL_ERROR_BAD_CERT_DOMAIN:
return Some(
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN);
case SEC_ERROR_EXPIRED_CERTIFICATE:
case SEC_ERROR_INVALID_TIME:
case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
return Some(
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME);
default:
break;
}
return Nothing();
}
// Helper function to determine if overrides are allowed for this host.
// Overrides are not allowed for known HSTS hosts or hosts with pinning
// information. However, IP addresses can never be HSTS hosts and don't have
// pinning information.
static nsresult OverrideAllowedForHost(
uint64_t aPtrForLog, const nsACString& aHostname,
const OriginAttributes& aOriginAttributes, /*out*/ bool& aOverrideAllowed) {
aOverrideAllowed = false;
// If this is an IP address, overrides are allowed, because an IP address is
// never an HSTS host. nsISiteSecurityService takes this into account
// already, but the real problem here is that calling NS_NewURI with an IPv6
// address fails. We do this to avoid that. A more comprehensive fix would be
// to have Necko provide an nsIURI to PSM and to use that here (and
// everywhere). However, that would be a wide-spanning change.
if (net_IsValidIPv6Addr(aHostname)) {
aOverrideAllowed = true;
return NS_OK;
}
// If this is an HTTP Strict Transport Security host or a pinned host and the
// certificate is bad, don't allow overrides (RFC 6797 section 12.1).
bool strictTransportSecurityEnabled = false;
bool isStaticallyPinned = false;
nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
if (!sss) {
MOZ_LOG(
gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] Couldn't get nsISiteSecurityService to check HSTS",
aPtrForLog));
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + aHostname);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] Creating new URI failed", aPtrForLog));
return rv;
}
rv =
sss->IsSecureURI(uri, aOriginAttributes, &strictTransportSecurityEnabled);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] checking for HSTS failed", aPtrForLog));
return rv;
}
nsCOMPtr<nsIPublicKeyPinningService> pkps =
do_GetService(NS_PKPSERVICE_CONTRACTID, &rv);
if (!pkps) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64
"] Couldn't get nsIPublicKeyPinningService to check pinning",
aPtrForLog));
return NS_ERROR_FAILURE;
}
rv = pkps->HostHasPins(uri, &isStaticallyPinned);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] checking for static pin failed", aPtrForLog));
return rv;
}
aOverrideAllowed = !strictTransportSecurityEnabled && !isStaticallyPinned;
return NS_OK;
}
// This function assumes that we will only use the SPDY connection coalescing
// feature on connections where we have negotiated SPDY using NPN. If we ever
// talk SPDY without having negotiated it with SPDY, this code will give wrong
// and perhaps unsafe results.
//
// Returns SECSuccess on the initial handshake of all connections, on
// renegotiations for any connections where we did not negotiate SPDY, or on any
// SPDY connection where the server's certificate did not change.
//
// Prohibit changing the server cert only if we negotiated SPDY,
// in order to support SPDY's cross-origin connection pooling.
static SECStatus BlockServerCertChangeForSpdy(
NSSSocketControl* socketControl, const UniqueCERTCertificate& serverCert) {
if (!socketControl->IsHandshakeCompleted()) {
// first handshake on this connection, not a
// renegotiation.
return SECSuccess;
}
// Filter out sockets that did not neogtiate SPDY via NPN
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
nsresult rv = socketControl->GetSecurityInfo(getter_AddRefs(securityInfo));
MOZ_ASSERT(NS_SUCCEEDED(rv), "GetSecurityInfo() failed during renegotiation");
if (NS_FAILED(rv) || !securityInfo) {
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
return SECFailure;
}
nsAutoCString negotiatedNPN;
rv = securityInfo->GetNegotiatedNPN(negotiatedNPN);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"GetNegotiatedNPN() failed during renegotiation");
if (NS_SUCCEEDED(rv) && !StringBeginsWith(negotiatedNPN, "spdy/"_ns)) {
return SECSuccess;
}
// If GetNegotiatedNPN() failed we will assume spdy for safety's safe
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("BlockServerCertChangeForSpdy failed GetNegotiatedNPN() call."
" Assuming spdy."));
}
// Check to see if the cert has actually changed
nsCOMPtr<nsIX509Cert> cert(socketControl->GetServerCert());
if (!cert) {
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
return SECFailure;
}
nsTArray<uint8_t> certDER;
if (NS_FAILED(cert->GetRawDER(certDER))) {
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
return SECFailure;
}
if (certDER.Length() == serverCert->derCert.len &&
memcmp(certDER.Elements(), serverCert->derCert.data, certDER.Length()) ==
0) {
return SECSuccess;
}
// Report an error - changed cert is confirmed
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("SPDY refused to allow new cert during renegotiation"));
PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0);
return SECFailure;
}
void GatherTelemetryForSingleSCT(const ct::VerifiedSCT& verifiedSct) {
// See scts_verification_status in metrics.yaml.
uint32_t verificationStatus = 0;
switch (verifiedSct.logState) {
case ct::CTLogState::Admissible:
verificationStatus = 1;
break;
case ct::CTLogState::Retired:
verificationStatus = 5;
break;
}
glean::ssl::scts_verification_status.AccumulateSingleSample(
verificationStatus);
}
void GatherCertificateTransparencyTelemetry(
const nsTArray<uint8_t>& rootCert,
const CertificateTransparencyInfo& info) {
if (!info.enabled) {
// No telemetry is gathered when CT is disabled.
return;
}
for (const ct::VerifiedSCT& sct : info.verifyResult.verifiedScts) {
GatherTelemetryForSingleSCT(sct);
}
// See scts_verification_status in metrics.yaml.
for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
glean::ssl::scts_verification_status.AccumulateSingleSample(0);
}
for (size_t i = 0; i < info.verifyResult.sctsFromUnknownLogs; ++i) {
glean::ssl::scts_verification_status.AccumulateSingleSample(2);
}
for (size_t i = 0; i < info.verifyResult.sctsWithInvalidSignatures; ++i) {
glean::ssl::scts_verification_status.AccumulateSingleSample(3);
}
for (size_t i = 0; i < info.verifyResult.sctsWithInvalidTimestamps; ++i) {
glean::ssl::scts_verification_status.AccumulateSingleSample(4);
}
for (size_t i = 0; i < info.verifyResult.sctsWithDistrustedTimestamps; ++i) {
glean::ssl::scts_verification_status.AccumulateSingleSample(6);
}
// See scts_origin in metrics.yaml.
for (size_t i = 0; i < info.verifyResult.embeddedSCTs; ++i) {
glean::ssl::scts_origin.AccumulateSingleSample(1);
}
for (size_t i = 0; i < info.verifyResult.sctsFromTLSHandshake; ++i) {
glean::ssl::scts_origin.AccumulateSingleSample(2);
}
for (size_t i = 0; i < info.verifyResult.sctsFromOCSP; ++i) {
glean::ssl::scts_origin.AccumulateSingleSample(3);
}
// Handle the histogram of SCTs counts.
uint32_t sctsCount =
static_cast<uint32_t>(info.verifyResult.verifiedScts.size());
// Note that sctsCount can also be 0 in case we've received SCT binary data,
// but it failed to parse (e.g. due to unsupported CT protocol version).
glean::ssl::scts_per_connection.AccumulateSingleSample(sctsCount);
uint32_t sctsFromTiledLogs = 0;
for (auto verifiedSCT : info.verifyResult.verifiedScts) {
if (verifiedSCT.logFormat == ct::CTLogFormat::Tiled) {
sctsFromTiledLogs++;
}
}
glean::ssl::scts_from_tiled_logs_per_connection.AccumulateSingleSample(
sctsFromTiledLogs);
// Report CT Policy compliance by CA.
if (info.policyCompliance.isSome() &&
*info.policyCompliance != ct::CTPolicyCompliance::Compliant) {
int32_t binId = RootCABinNumber(rootCert);
if (binId != ROOT_CERTIFICATE_HASH_FAILURE) {
glean::ssl::ct_policy_non_compliant_connections_by_ca_2
.AccumulateSingleSample(binId);
}
}
}
// This function collects telemetry about certs. It will be called on one of
// CertVerificationThread. When the socket process is used this will be called
// on the parent process.
static void CollectCertTelemetry(
mozilla::pkix::Result aCertVerificationResult, EVStatus aEVStatus,
CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
KeySizeStatus aKeySizeStatus,
const PinningTelemetryInfo& aPinningTelemetryInfo,
const nsTArray<nsTArray<uint8_t>>& aBuiltCertChain,
const CertificateTransparencyInfo& aCertificateTransparencyInfo,
const IssuerSources& issuerSources) {
uint32_t evStatus = (aCertVerificationResult != Success) ? 0 // 0 = Failure
: (aEVStatus != EVStatus::EV) ? 1 // 1 = DV
: 2; // 2 = EV
glean::cert::ev_status.AccumulateSingleSample(evStatus);
if (aOcspStaplingStatus != CertVerifier::OCSP_STAPLING_NEVER_CHECKED) {
glean::ssl::ocsp_stapling.AccumulateSingleSample(aOcspStaplingStatus);
}
if (aKeySizeStatus != KeySizeStatus::NeverChecked) {
glean::cert::chain_key_size_status.AccumulateSingleSample(
static_cast<uint32_t>(aKeySizeStatus));
}
if (aPinningTelemetryInfo.accumulateForRoot) {
glean::cert_pinning::failures_by_ca_2.AccumulateSingleSample(
aPinningTelemetryInfo.rootBucket);
}
if (aPinningTelemetryInfo.accumulateResult) {
if (aPinningTelemetryInfo.isMoz) {
if (aPinningTelemetryInfo.testMode) {
glean::cert_pinning::moz_test_results_by_host.AccumulateSingleSample(
aPinningTelemetryInfo.certPinningResultBucket);
} else {
glean::cert_pinning::moz_results_by_host.AccumulateSingleSample(
aPinningTelemetryInfo.certPinningResultBucket);
}
} else {
if (aPinningTelemetryInfo.testMode) {
glean::cert_pinning::test_results
.EnumGet(static_cast<glean::cert_pinning::TestResultsLabel>(
aPinningTelemetryInfo.certPinningResultBucket))
.Add();
} else {
glean::cert_pinning::results
.EnumGet(static_cast<glean::cert_pinning::ResultsLabel>(
aPinningTelemetryInfo.certPinningResultBucket))
.Add();
}
}
}
if (aCertVerificationResult == Success && aBuiltCertChain.Length() > 0) {
const nsTArray<uint8_t>& rootCert = aBuiltCertChain.LastElement();
int32_t binId = RootCABinNumber(rootCert);
if (binId != ROOT_CERTIFICATE_HASH_FAILURE) {
glean::cert::validation_success_by_ca_2.AccumulateSingleSample(binId);
}
mozilla::glean::tls::certificate_verifications.Add(1);
if (issuerSources.contains(IssuerSource::TLSHandshake)) {
mozilla::glean::verification_used_cert_from::tls_handshake.AddToNumerator(
1);
}
if (issuerSources.contains(IssuerSource::PreloadedIntermediates)) {
mozilla::glean::verification_used_cert_from::preloaded_intermediates
.AddToNumerator(1);
}
if (issuerSources.contains(IssuerSource::ThirdPartyCertificates)) {
mozilla::glean::verification_used_cert_from::third_party_certificates
.AddToNumerator(1);
}
if (issuerSources.contains(IssuerSource::NSSCertDB)) {
mozilla::glean::verification_used_cert_from::nss_cert_db.AddToNumerator(
1);
}
if (issuerSources.contains(IssuerSource::BuiltInRootsModule)) {
mozilla::glean::verification_used_cert_from::built_in_roots_module
.AddToNumerator(1);
}
}
if ((aCertVerificationResult == Success ||
aCertVerificationResult ==
Result::ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY) &&
aBuiltCertChain.Length() > 0) {
const nsTArray<uint8_t>& rootCert = aBuiltCertChain.LastElement();
GatherCertificateTransparencyTelemetry(rootCert,
aCertificateTransparencyInfo);
}
}
// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
Result AuthCertificate(
CertVerifier& certVerifier, void* aPinArg,
const nsTArray<uint8_t>& certBytes,
const nsTArray<nsTArray<uint8_t>>& peerCertChain,
const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
const Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
const Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
Time time, uint32_t certVerifierFlags,
/*out*/ nsTArray<nsTArray<uint8_t>>& builtCertChain,
/*out*/ EVStatus& evStatus,
/*out*/ CertificateTransparencyInfo& certificateTransparencyInfo,
/*out*/ bool& aIsBuiltCertChainRootBuiltInRoot,
/*out*/ bool& aMadeOCSPRequests) {
CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
PinningTelemetryInfo pinningTelemetryInfo;
nsTArray<nsTArray<uint8_t>> peerCertsBytes;
// Don't include the end-entity certificate.
if (!peerCertChain.IsEmpty()) {
std::transform(
peerCertChain.cbegin() + 1, peerCertChain.cend(),
MakeBackInserter(peerCertsBytes),
[](const auto& elementArray) { return elementArray.Clone(); });
}
IssuerSources issuerSources;
Result rv = certVerifier.VerifySSLServerCert(
certBytes, time, aPinArg, aHostName, builtCertChain, certVerifierFlags,
Some(std::move(peerCertsBytes)), stapledOCSPResponse,
sctsFromTLSExtension, dcInfo, aOriginAttributes, &evStatus,
&ocspStaplingStatus, &keySizeStatus, &pinningTelemetryInfo,
&certificateTransparencyInfo, &aIsBuiltCertChainRootBuiltInRoot,
&aMadeOCSPRequests, &issuerSources);
CollectCertTelemetry(rv, evStatus, ocspStaplingStatus, keySizeStatus,
pinningTelemetryInfo, builtCertChain,
certificateTransparencyInfo, issuerSources);
return rv;
}
PRErrorCode AuthCertificateParseResults(
uint64_t aPtrForLog, const nsACString& aHostName, int32_t aPort,
const OriginAttributes& aOriginAttributes,
const nsCOMPtr<nsIX509Cert>& aCert, mozilla::pkix::Time aTime,
PRErrorCode aCertVerificationError,
/* out */
nsITransportSecurityInfo::OverridableErrorCategory&
aOverridableErrorCategory) {
uint32_t probeValue = MapCertErrorToProbeValue(aCertVerificationError);
glean::ssl::cert_verification_errors.AccumulateSingleSample(probeValue);
Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
maybeOverridableErrorCategory =
CategorizeCertificateError(aCertVerificationError);
// If this isn't an overridable error, return it now. This will stop the
// connection and report the given error.
if (!maybeOverridableErrorCategory.isSome()) {
return aCertVerificationError;
}
aOverridableErrorCategory = *maybeOverridableErrorCategory;
bool overrideAllowed = false;
nsresult rv = OverrideAllowedForHost(aPtrForLog, aHostName, aOriginAttributes,
overrideAllowed);
if (NS_FAILED(rv)) {
return aCertVerificationError;
}
if (!overrideAllowed) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] HSTS or pinned host - no overrides allowed",
aPtrForLog));
return aCertVerificationError;
}
nsCOMPtr<nsICertOverrideService> overrideService =
do_GetService(NS_CERTOVERRIDE_CONTRACTID);
if (!overrideService) {
return aCertVerificationError;
}
bool haveOverride;
bool isTemporaryOverride;
rv = overrideService->HasMatchingOverride(aHostName, aPort, aOriginAttributes,
aCert, &isTemporaryOverride,
&haveOverride);
if (NS_FAILED(rv)) {
return aCertVerificationError;
}
(void)isTemporaryOverride;
if (haveOverride) {
uint32_t probeValue =
MapOverridableErrorToProbeValue(aCertVerificationError);
glean::ssl::cert_error_overrides.AccumulateSingleSample(probeValue);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[0x%" PRIx64 "] certificate error overridden", aPtrForLog));
return 0;
}
return aCertVerificationError;
}
static nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
const UniqueSECItemArray& aCertChain) {
nsTArray<nsTArray<uint8_t>> certsBytes;
for (size_t i = 0; i < aCertChain->len; i++) {
nsTArray<uint8_t> certBytes;
certBytes.AppendElements(aCertChain->items[i].data,
aCertChain->items[i].len);
certsBytes.AppendElement(std::move(certBytes));
}
return certsBytes;
}
/*static*/
SECStatus SSLServerCertVerificationJob::Dispatch(
uint64_t addrForLogging, void* aPinArg,
nsTArray<nsTArray<uint8_t>>&& peerCertChain, const nsACString& aHostName,
int32_t aPort, const OriginAttributes& aOriginAttributes,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags, Time time,
uint32_t certVerifierFlags,
BaseSSLServerCertVerificationResult* aResultTask) {
// Runs on the socket transport thread
if (!aResultTask || peerCertChain.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE(
"must have result task and non-empty peer cert chain");
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
return SECFailure;
}
if (!gCertVerificationThreadPool) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
RefPtr<SSLServerCertVerificationJob> job(new SSLServerCertVerificationJob(
addrForLogging, aPinArg, std::move(peerCertChain), aHostName, aPort,
aOriginAttributes, stapledOCSPResponse, sctsFromTLSExtension, dcInfo,
providerFlags, time, certVerifierFlags, aResultTask));
nsresult nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
if (NS_FAILED(nrv)) {
// We can't call SetCertVerificationResult here to change
// mCertVerificationState because SetCertVerificationResult will call
// libssl functions that acquire SSL locks that are already being held at
// this point. However, we can set an error with PR_SetError and return
// SECFailure, and the correct thing will happen (the error will be
// propagated and this connection will be terminated).
PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY ? PR_OUT_OF_MEMORY_ERROR
: PR_INVALID_STATE_ERROR;
PR_SetError(error, 0);
return SECFailure;
}
PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
return SECWouldBlock;
}
NS_IMETHODIMP
SSLServerCertVerificationJob::Run() {
// Runs on a cert verification thread and only on parent process.
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%" PRIx64 "] SSLServerCertVerificationJob::Run", mAddrForLogging));
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
if (!certVerifier) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
mResultTask.forget().leak();
return NS_ERROR_FAILURE;
}
TimeStamp jobStartTime = TimeStamp::Now();
EVStatus evStatus;
CertificateTransparencyInfo certificateTransparencyInfo;
bool isCertChainRootBuiltInRoot = false;
bool madeOCSPRequests = false;
nsTArray<nsTArray<uint8_t>> builtChainBytesArray;
nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
Result result = AuthCertificate(
*certVerifier, mPinArg, certBytes, mPeerCertChain, mHostName,
mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
mProviderFlags, mTime, mCertVerifierFlags, builtChainBytesArray, evStatus,
certificateTransparencyInfo, isCertChainRootBuiltInRoot,
madeOCSPRequests);
TimeDuration elapsed = TimeStamp::Now() - jobStartTime;
if (result == Success) {
mozilla::glean::cert_verification_time::success.AccumulateRawDuration(
elapsed);
glean::ssl::cert_error_overrides.AccumulateSingleSample(1);
nsresult rv = mResultTask->Dispatch(
std::move(builtChainBytesArray), std::move(mPeerCertChain),
TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
certificateTransparencyInfo),
evStatus, true, 0,
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET,
isCertChainRootBuiltInRoot, mProviderFlags, madeOCSPRequests);
if (NS_FAILED(rv)) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
mResultTask.forget().leak();
}
return rv;
}
mozilla::glean::cert_verification_time::failure.AccumulateRawDuration(
elapsed);
PRErrorCode error = MapResultToPRErrorCode(result);
nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory =
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
PRErrorCode finalError = AuthCertificateParseResults(
mAddrForLogging, mHostName, mPort, mOriginAttributes, cert, mTime, error,
overridableErrorCategory);
// NB: finalError may be 0 here, in which the connection will continue.
nsresult rv = mResultTask->Dispatch(
std::move(builtChainBytesArray), std::move(mPeerCertChain),
TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
certificateTransparencyInfo),
EVStatus::NotEV, false, finalError, overridableErrorCategory,
// If the certificate verifier returned Result::ERROR_BAD_CERT_DOMAIN,
// a chain was built, so isCertChainRootBuiltInRoot is valid and
// potentially useful. Otherwise, assume no chain was built.
result == Result::ERROR_BAD_CERT_DOMAIN ? isCertChainRootBuiltInRoot
: false,
mProviderFlags, madeOCSPRequests);
if (NS_FAILED(rv)) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
mResultTask.forget().leak();
}
return rv;
}
// Takes information needed for cert verification, does some consistency
// checks and calls SSLServerCertVerificationJob::Dispatch.
SECStatus AuthCertificateHookInternal(
CommonSocketControl* socketControl, const void* aPtrForLogging,
const nsACString& hostName, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
uint32_t certVerifierFlags) {
// Runs on the socket transport thread
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] starting AuthCertificateHookInternal\n", aPtrForLogging));
if (!socketControl || peerCertChain.IsEmpty()) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
bool onSTSThread;
nsresult nrv;
nsCOMPtr<nsIEventTarget> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
if (NS_SUCCEEDED(nrv)) {
nrv = sts->IsOnCurrentThread(&onSTSThread);
}
if (NS_FAILED(nrv)) {
NS_ERROR("Could not get STS service or IsOnCurrentThread failed");
PR_SetError(PR_UNKNOWN_ERROR, 0);
return SECFailure;
}
MOZ_ASSERT(onSTSThread);
if (!onSTSThread) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
uint64_t addr = reinterpret_cast<uintptr_t>(aPtrForLogging);
RefPtr<SSLServerCertVerificationResult> resultTask =
new SSLServerCertVerificationResult(socketControl);
if (XRE_IsSocketProcess()) {
return RemoteProcessCertVerification(
std::move(peerCertChain), hostName, socketControl->GetPort(),
socketControl->GetOriginAttributes(), stapledOCSPResponse,
sctsFromTLSExtension, dcInfo, providerFlags, certVerifierFlags,
resultTask);
}
// We *must* do certificate verification on a background thread because
// we need the socket transport thread to be free for our OCSP requests,
// and we *want* to do certificate verification on a background thread
// because of the performance benefits of doing so.
return SSLServerCertVerificationJob::Dispatch(
addr, socketControl, std::move(peerCertChain), hostName,
socketControl->GetPort(), socketControl->GetOriginAttributes(),
stapledOCSPResponse, sctsFromTLSExtension, dcInfo, providerFlags, Now(),
certVerifierFlags, resultTask);
}
// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to AuthCertificateHookInternal. AuthCertificateHookInternal will call
// SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob
// should never do anything with fd except logging.
SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
PRBool isServer) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] starting AuthCertificateHook\n", fd));
// Modern libssl always passes PR_TRUE for checkSig, and we have no means of
// doing verification without checking signatures.
MOZ_ASSERT(checkSig, "AuthCertificateHook: checkSig unexpectedly false");
// PSM never causes libssl to call this function with PR_TRUE for isServer,
// and many things in PSM assume that we are a client.
MOZ_ASSERT(!isServer, "AuthCertificateHook: isServer unexpectedly true");
NSSSocketControl* socketInfo = static_cast<NSSSocketControl*>(arg);
UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));
if (!checkSig || isServer || !socketInfo || !serverCert) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
socketInfo->SetFullHandshake();
if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess) {
return SECFailure;
}
UniqueSECItemArray peerCertChain;
SECStatus rv =
SSL_PeerCertificateChainDER(fd, TempPtrToSetter(&peerCertChain));
if (rv != SECSuccess) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
MOZ_ASSERT(peerCertChain,
"AuthCertificateHook: peerCertChain unexpectedly null");
nsTArray<nsTArray<uint8_t>> peerCertsBytes =
CreateCertBytesArray(peerCertChain);
// SSL_PeerStapledOCSPResponses will never return a non-empty response if
// OCSP stapling wasn't enabled because libssl wouldn't have let the server
// return a stapled OCSP response.
// We don't own these pointers.
const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
// we currently only support single stapled responses
if (csa && csa->len == 1) {
stapledOCSPResponse.emplace();
stapledOCSPResponse->SetCapacity(csa->items[0].len);
stapledOCSPResponse->AppendElements(csa->items[0].data, csa->items[0].len);
}
Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
const SECItem* sctsFromTLSExtensionSECItem = SSL_PeerSignedCertTimestamps(fd);
if (sctsFromTLSExtensionSECItem) {
sctsFromTLSExtension.emplace();
sctsFromTLSExtension->SetCapacity(sctsFromTLSExtensionSECItem->len);
sctsFromTLSExtension->AppendElements(sctsFromTLSExtensionSECItem->data,
sctsFromTLSExtensionSECItem->len);
}
uint32_t providerFlags = 0;
socketInfo->GetProviderFlags(&providerFlags);
uint32_t certVerifierFlags = 0;
if (!StaticPrefs::security_ssl_enable_ocsp_stapling() ||
!StaticPrefs::security_ssl_enable_ocsp_must_staple()) {
certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
// Get DC information
Maybe<DelegatedCredentialInfo> dcInfo;
SSLPreliminaryChannelInfo channelPreInfo;
rv = SSL_GetPreliminaryChannelInfo(fd, &channelPreInfo,
sizeof(channelPreInfo));
if (rv != SECSuccess) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
if (channelPreInfo.peerDelegCred) {
dcInfo.emplace(DelegatedCredentialInfo(channelPreInfo.signatureScheme,
channelPreInfo.authKeyBits));
}
// If we configured an ECHConfig and NSS returned the public name
// for verification, ECH was rejected. Proceed, verifying to the
// public name. The result determines how NSS will fail (i.e. with
// any provided retry_configs if successful). See draft-ietf-tls-esni-08.
nsCString echConfig;
nsresult nsrv = socketInfo->GetEchConfig(echConfig);
bool verifyToEchPublicName =
NS_SUCCEEDED(nsrv) && echConfig.Length() && channelPreInfo.echPublicName;
const nsCString echPublicName(channelPreInfo.echPublicName);
const nsACString& hostname =
verifyToEchPublicName ? echPublicName : socketInfo->GetHostName();
socketInfo->SetCertVerificationWaiting();
rv = AuthCertificateHookInternal(socketInfo, static_cast<const void*>(fd),
hostname, std::move(peerCertsBytes),
stapledOCSPResponse, sctsFromTLSExtension,
dcInfo, providerFlags, certVerifierFlags);
return rv;
}
// Takes information needed for cert verification, does some consistency
// checks and calls SSLServerCertVerificationJob::Dispatch.
// This function is used for Quic.
SECStatus AuthCertificateHookWithInfo(
CommonSocketControl* socketControl, const nsACString& aHostName,
const void* aPtrForLogging, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
Maybe<nsTArray<nsTArray<uint8_t>>>& stapledOCSPResponses,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags) {
if (peerCertChain.IsEmpty()) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
// we currently only support single stapled responses
Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
if (stapledOCSPResponses && (stapledOCSPResponses->Length() == 1)) {
stapledOCSPResponse.emplace(stapledOCSPResponses->ElementAt(0).Clone());
}
uint32_t certVerifierFlags = 0;
if (!StaticPrefs::security_ssl_enable_ocsp_stapling() ||
!StaticPrefs::security_ssl_enable_ocsp_must_staple()) {
certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
// Need to update Quic stack to reflect the PreliminaryInfo fields
// for Delegated Credentials.
Maybe<DelegatedCredentialInfo> dcInfo;
return AuthCertificateHookInternal(socketControl, aPtrForLogging, aHostName,
std::move(peerCertChain),
stapledOCSPResponse, sctsFromTLSExtension,
dcInfo, providerFlags, certVerifierFlags);
}
NS_IMPL_ISUPPORTS_INHERITED0(SSLServerCertVerificationResult, Runnable)
SSLServerCertVerificationResult::SSLServerCertVerificationResult(
CommonSocketControl* socketControl)
: Runnable("psm::SSLServerCertVerificationResult"),
mSocketControl(socketControl),
mCertificateTransparencyStatus(0),
mEVStatus(EVStatus::NotEV),
mSucceeded(false),
mFinalError(0),
mOverridableErrorCategory(
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET),
mProviderFlags(0) {}
nsresult SSLServerCertVerificationResult::Dispatch(
nsTArray<nsTArray<uint8_t>>&& aBuiltChain,
nsTArray<nsTArray<uint8_t>>&& aPeerCertChain,
uint16_t aCertificateTransparencyStatus, EVStatus aEVStatus,
bool aSucceeded, PRErrorCode aFinalError,
nsITransportSecurityInfo::OverridableErrorCategory
aOverridableErrorCategory,
bool aIsBuiltCertChainRootBuiltInRoot, uint32_t aProviderFlags,
bool aMadeOCSPRequests) {
mBuiltChain = std::move(aBuiltChain);
mPeerCertChain = std::move(aPeerCertChain);
mCertificateTransparencyStatus = aCertificateTransparencyStatus;
mEVStatus = aEVStatus;
mSucceeded = aSucceeded;
mFinalError = aFinalError;
mOverridableErrorCategory = aOverridableErrorCategory;
mIsBuiltCertChainRootBuiltInRoot = aIsBuiltCertChainRootBuiltInRoot;
mProviderFlags = aProviderFlags;
mMadeOCSPRequests = aMadeOCSPRequests;
if (mSucceeded &&
(mBuiltChain.IsEmpty() || mFinalError != 0 ||
mOverridableErrorCategory !=
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET)) {
MOZ_ASSERT_UNREACHABLE(
"if certificate verification succeeded without overridden errors, the "
"built chain shouldn't be empty and any error bits should be unset");
mSucceeded = false;
mFinalError = SEC_ERROR_LIBRARY_FAILURE;
}
// Note that mSucceeded can be false while mFinalError is 0, in which case
// the connection will proceed.
if (!mSucceeded && mPeerCertChain.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE(
"if certificate verification failed, the peer chain shouldn't be "
"empty");
mFinalError = SEC_ERROR_LIBRARY_FAILURE;
}
nsresult rv;
nsCOMPtr<nsIEventTarget> stsTarget =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
MOZ_ASSERT(stsTarget, "Failed to get socket transport service event target");
if (!stsTarget) {
// This has to be released on STS; just leak it
mSocketControl.forget().leak();
return NS_ERROR_FAILURE;
}
rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Failed to dispatch SSLServerCertVerificationResult");
return rv;
}
NS_IMETHODIMP
SSLServerCertVerificationResult::Run() {
#ifdef DEBUG
bool onSTSThread = false;
nsresult nrv;
nsCOMPtr<nsIEventTarget> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
if (NS_SUCCEEDED(nrv)) {
nrv = sts->IsOnCurrentThread(&onSTSThread);
}
MOZ_ASSERT(onSTSThread);
#endif
mSocketControl->SetMadeOCSPRequests(mMadeOCSPRequests);
mSocketControl->SetIsBuiltCertChainRootBuiltInRoot(
mIsBuiltCertChainRootBuiltInRoot);
mSocketControl->SetCertificateTransparencyStatus(
mCertificateTransparencyStatus);
if (mSucceeded) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("SSLServerCertVerificationResult::Run setting NEW cert"));
nsTArray<uint8_t> certBytes(mBuiltChain.ElementAt(0).Clone());
nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
mSocketControl->SetServerCert(cert, mEVStatus);
mSocketControl->SetSucceededCertChain(std::move(mBuiltChain));
} else {
nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
mSocketControl->SetServerCert(cert, EVStatus::NotEV);
if (mOverridableErrorCategory !=
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
mSocketControl->SetStatusErrorBits(mOverridableErrorCategory);
}
}
mSocketControl->SetHandshakeCertificates(std::move(mPeerCertChain));
mSocketControl->SetCertVerificationResult(mFinalError);
// Release this reference to the socket control so that it will be freed on
// the socket thread.
mSocketControl = nullptr;
return NS_OK;
}
} // namespace psm
} // namespace mozilla
|