diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index b7e719fc9d99..dae453c4c85a 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -845,9 +845,10 @@ Result NSSCertDBTrustDomain::CheckRevocationByOCSP( Result stapledOCSPResponseResult = Success; if (stapledOCSPResponse) { bool expired; + uint32_t ageInHours; stapledOCSPResponseResult = VerifyAndMaybeCacheEncodedOCSPResponse( certID, time, maxOCSPLifetimeInDays, *stapledOCSPResponse, - ResponseWasStapled, expired); + ResponseWasStapled, expired, ageInHours); Telemetry::AccumulateCategorical( Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::StapledOCSP); if (stapledOCSPResponseResult == Success) { @@ -1067,9 +1068,10 @@ Result NSSCertDBTrustDomain::SynchronousCheckRevocationWithServer( // or unknown certificate, PR_GetError() will return the appropriate error. // We actually ignore expired here. bool expired; - rv = VerifyAndMaybeCacheEncodedOCSPResponse(certID, time, - maxOCSPLifetimeInDays, response, - ResponseIsFromNetwork, expired); + uint32_t ageInHours; + rv = VerifyAndMaybeCacheEncodedOCSPResponse( + certID, time, maxOCSPLifetimeInDays, response, ResponseIsFromNetwork, + expired, ageInHours); // If the CRLite filter covers the certificate, compare the CRLite result // with the OCSP fetching result. OCSP may have succeeded, said the @@ -1088,6 +1090,11 @@ Result NSSCertDBTrustDomain::SynchronousCheckRevocationWithServer( // CRLite says the certificate is revoked, but OCSP says it is OK. Telemetry::AccumulateCategorical( Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPOk); + + if (mCRLiteMode == CRLiteMode::ConfirmRevocations) { + Telemetry::Accumulate(Telemetry::OCSP_AGE_AT_CRLITE_OVERRIDE, + ageInHours); + } } } else if (rv == Result::ERROR_REVOKED_CERTIFICATE) { if (crliteResult == Success) { @@ -1183,7 +1190,8 @@ Result NSSCertDBTrustDomain::HandleOCSPFailure( Result NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( const CertID& certID, Time time, uint16_t maxLifetimeInDays, Input encodedResponse, EncodedResponseSource responseSource, - /*out*/ bool& expired) { + /*out*/ bool& expired, + /*out*/ uint32_t& ageInHours) { Time thisUpdate(Time::uninitialized); Time validThrough(Time::uninitialized); @@ -1207,6 +1215,30 @@ Result NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow } } + // The `thisUpdate` field holds the latest time at which the server knew the + // response was correct. The age of the response is the time that has elapsed + // since. We only use this for the telemetry defined in Bug 1794479. + uint64_t timeInSeconds; + uint64_t thisUpdateInSeconds; + uint64_t ageInSeconds; + SecondsSinceEpochFromTime(time, &timeInSeconds); + SecondsSinceEpochFromTime(thisUpdate, &thisUpdateInSeconds); + if (timeInSeconds >= thisUpdateInSeconds) { + ageInSeconds = timeInSeconds - thisUpdateInSeconds; + // ageInHours is 32 bits because of the telemetry api. + if (ageInSeconds > UINT32_MAX) { + // We could divide by 3600 before checking the UINT32_MAX bound, but if + // ageInSeconds is more than UINT32_MAX then there's been some sort of + // error. + ageInHours = UINT32_MAX; + } else { + // We start at 1 and divide with truncation to reserve ageInHours=0 for + // the case where `thisUpdate` is in the future. + ageInHours = 1 + ageInSeconds / (60 * 60); + } + } else { + ageInHours = 0; + } if (responseSource == ResponseIsFromNetwork || rv == Success || rv == Result::ERROR_REVOKED_CERTIFICATE || rv == Result::ERROR_OCSP_UNKNOWN_CERT) { diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index 1afd065f7fca..438e9bec10d9 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -264,7 +264,8 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain { Result VerifyAndMaybeCacheEncodedOCSPResponse( const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, uint16_t maxLifetimeInDays, mozilla::pkix::Input encodedResponse, - EncodedResponseSource responseSource, /*out*/ bool& expired); + EncodedResponseSource responseSource, /*out*/ bool& expired, + /*out*/ uint32_t& ageInHours); TimeDuration GetOCSPTimeout() const; Result CheckRevocationByCRLite(const mozilla::pkix::CertID& certID, diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index bed5f9f47aa4..5c237d7ba193 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -3415,6 +3415,19 @@ "n_values": 16, "description": "SSL Handshake Key Exchange Algorithm for resumed handshake (null=0, rsa=1, dh=2, fortezza=3, ecdh=4)" }, + "OCSP_AGE_AT_CRLITE_OVERRIDE": { + "record_in_processes": ["main", "socket"], + "products": ["firefox", "fennec"], + "alert_emails": ["seceng-telemetry@mozilla.com", "jschanck@mozilla.com"], + "bug_numbers": [1794479], + "expires_in_version": "113", + "kind": "linear", + "releaseChannelCollection": "opt-out", + "low": 1, + "high": 240, + "n_buckets": 20, + "description": "When OCSP and CRLite differ, how old is the OCSP response (in hours)?" + }, "CRLITE_VS_OCSP_RESULT": { "record_in_processes": ["main", "socket"], "products": ["firefox", "fennec"],