/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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/. */ #include "WebTransportCertificateVerifier.h" #include "ScopedNSSTypes.h" #include "nss/mozpkix/pkixutil.h" #include "nss/mozpkix/pkixcheck.h" #include "hasht.h" namespace mozilla::psm { class ServerCertHashesTrustDomain : public mozilla::pkix::TrustDomain { public: ServerCertHashesTrustDomain() = default; virtual mozilla::pkix::Result FindIssuer( mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, mozilla::pkix::Time time) override; virtual mozilla::pkix::Result GetCertTrust( mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertPolicyId& policy, mozilla::pkix::Input candidateCertDER, /*out*/ mozilla::pkix::TrustLevel& trustLevel) override; virtual mozilla::pkix::Result CheckSignatureDigestAlgorithm( mozilla::pkix::DigestAlgorithm digestAlg, mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::Time notBefore) override; virtual mozilla::pkix::Result CheckRSAPublicKeyModulusSizeInBits( mozilla::pkix::EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) override; virtual mozilla::pkix::Result VerifyRSAPKCS1SignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) override; virtual mozilla::pkix::Result VerifyRSAPSSSignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) override; virtual mozilla::pkix::Result CheckECDSACurveIsAcceptable( mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::NamedCurve curve) override; virtual mozilla::pkix::Result VerifyECDSASignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) override; virtual mozilla::pkix::Result DigestBuf( mozilla::pkix::Input item, mozilla::pkix::DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) override; virtual mozilla::pkix::Result CheckValidityIsAcceptable( mozilla::pkix::Time notBefore, mozilla::pkix::Time notAfter, mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::KeyPurposeId keyPurpose) override; virtual mozilla::pkix::Result NetscapeStepUpMatchesServerAuth( mozilla::pkix::Time notBefore, /*out*/ bool& matches) override; virtual mozilla::pkix::Result CheckRevocation( mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, mozilla::pkix::Duration validityDuration, /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse, /*optional*/ const mozilla::pkix::Input* aiaExtension, /*optional*/ const mozilla::pkix::Input* sctExtension) override; virtual mozilla::pkix::Result IsChainValid( const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time, const mozilla::pkix::CertPolicyId& requiredPolicy) override; virtual void NoteAuxiliaryExtension( mozilla::pkix::AuxiliaryExtension extension, mozilla::pkix::Input extensionData) override; }; mozilla::pkix::Result ServerCertHashesTrustDomain::FindIssuer( mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, mozilla::pkix::Time time) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::GetCertTrust( mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertPolicyId& policy, mozilla::pkix::Input candidateCertDER, /*out*/ mozilla::pkix::TrustLevel& trustLevel) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::CheckSignatureDigestAlgorithm( mozilla::pkix::DigestAlgorithm digestAlg, mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::Time notBefore) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::CheckRSAPublicKeyModulusSizeInBits( mozilla::pkix::EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) { return mozilla::pkix::Result:: ERROR_UNSUPPORTED_KEYALG; // RSA is not supported for // serverCertificateHashes, // Chromium does only support it for an intermediate period due to spec // change, we do not support it. } mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyRSAPKCS1SignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyRSAPSSSignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::CheckECDSACurveIsAcceptable( mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::NamedCurve curve) { return mozilla::pkix::Result::Success; } mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyECDSASignedData( mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::DigestBuf( mozilla::pkix::Input item, mozilla::pkix::DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::CheckValidityIsAcceptable( mozilla::pkix::Time notBefore, mozilla::pkix::Time notAfter, mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::KeyPurposeId keyPurpose) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::NetscapeStepUpMatchesServerAuth( mozilla::pkix::Time notBefore, /*out*/ bool& matches) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::CheckRevocation( mozilla::pkix::EndEntityOrCA endEntityOrCA, const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, mozilla::pkix::Duration validityDuration, /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse, /*optional*/ const mozilla::pkix::Input* aiaExtension, /*optional*/ const mozilla::pkix::Input* sctExtension) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } mozilla::pkix::Result ServerCertHashesTrustDomain::IsChainValid( const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time, const mozilla::pkix::CertPolicyId& requiredPolicy) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } void ServerCertHashesTrustDomain::NoteAuxiliaryExtension( mozilla::pkix::AuxiliaryExtension extension, mozilla::pkix::Input extensionData) { MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); } } // namespace mozilla::psm namespace mozilla::net { // Does certificate verificate as required for serverCertificateHashes // This function is currently only used for Quic, but may be used later also for // http/2 mozilla::pkix::Result AuthCertificateWithServerCertificateHashes( nsTArray& peerCert, const nsTArray>& aServerCertHashes) { using namespace mozilla::pkix; Input certDER; mozilla::pkix::Result rv = certDER.Init(peerCert.Elements(), peerCert.Length()); if (rv != Success) { return rv; } BackCert cert(certDER, EndEntityOrCA::MustBeEndEntity, nullptr); rv = cert.Init(); if (rv != Success) { return rv; } Time notBefore(Time::uninitialized); Time notAfter(Time::uninitialized); rv = ParseValidity(cert.GetValidity(), ¬Before, ¬After); if (rv != Success) { return rv; } // now we check that validity is not greater than 14 days Duration certDuration(notBefore, notAfter); if (certDuration > Duration(60 * 60 * 24 * 14)) { return mozilla::pkix::Result::ERROR_VALIDITY_TOO_LONG; } Time now = Now(); // and if the certificate is actually valid? rv = CheckValidity(now, notBefore, notAfter); if (rv != Success) { return rv; } mozilla::psm::ServerCertHashesTrustDomain trustDomain; rv = CheckSubjectPublicKeyInfo( cert.GetSubjectPublicKeyInfo(), trustDomain, EndEntityOrCA::MustBeEndEntity /* should be ignored*/); if (rv != Success) { return rv; } // ok now the final check, calculate the hash and compare it: // https://w3c.github.io/webtransport/#compute-a-certificate-hash nsTArray certHash; if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, peerCert, certHash)) || certHash.Length() != SHA256_LENGTH) { return mozilla::pkix::Result::ERROR_INVALID_ALGORITHM; } // https://w3c.github.io/webtransport/#verify-a-certificate-hash for (const auto& hash : aServerCertHashes) { nsCString algorithm; if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") { continue; } nsTArray value; if (NS_FAILED(hash->GetValue(value))) { continue; } if (certHash == value) { return Success; } } return mozilla::pkix::Result::ERROR_UNTRUSTED_CERT; } } // namespace mozilla::net