tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e61decc637d578f55313111a23b4a48e94228317
parent e999124172a9c0bcf9e5c0f30b664b800d63100a
Author: Anna <anna.weine@mozilla.com>
Date:   Fri, 24 Oct 2025 14:30:29 +0000

Bug 1971811 - PDF.js verification: Parsing + signature verification parts r=keeler

Differential Revision: https://phabricator.services.mozilla.com/D259251

Diffstat:
Mjs/xpconnect/src/xpc.msg | 6++++++
Msecurity/manager/ssl/AppSignatureVerification.cpp | 304++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Asecurity/manager/ssl/AppSignatureVerification.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asecurity/manager/ssl/PDFSignatureVerification.cpp | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/manager/ssl/moz.build | 1+
Msecurity/manager/ssl/nsIX509CertDB.idl | 46++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/manager/ssl/tests/unit/moz.build | 2+-
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_correct.p7s | 27+++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_correct.pkcs7spec | 4++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.p7s | 26++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.pkcs7spec | 4++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.p7s | 27+++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.pkcs7spec | 4++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.p7s | 28++++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.pkcs7spec | 4++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/data_correct.bin | 2++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.p7s | 26++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.pkcs7spec | 4++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/moz.build | 37+++++++++++++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/no_certificate.p7s | 12++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/no_certificate.pkcs7spec | 5+++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.p7s | 18++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.pkcs7spec | 5+++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.p7s | 35+++++++++++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.pkcs7spec | 5+++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.p7s | 35+++++++++++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.pkcs7spec | 6++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/poppler_data.bin | 0
Asecurity/manager/ssl/tests/unit/test_pdf_verification/poppler_signature.p7s | 3+++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.p7s | 35+++++++++++++++++++++++++++++++++++
Asecurity/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.pkcs7spec | 5+++++
Asecurity/manager/ssl/tests/unit/test_signed_pdfs.js | 428+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msecurity/manager/ssl/tests/unit/xpcshell.toml | 3+++
Msecurity/manager/tools/pycms.py | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msecurity/nss.symbols | 1+
35 files changed, 1417 insertions(+), 156 deletions(-)

diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg @@ -217,6 +217,12 @@ XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or sig XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO , "The PKCS#7 signature is malformed or invalid.") XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_SIGNED , "The PKCS#7 information is not signed.") +/* Codes related to pdf signature verification */ +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_ERROR_PROCESSING , "The PKCS#7 parsing has failed.") +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED , "The PKCS#7 certificate verification was cancelled.") +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_BAD_SIGNATURE , "Invalid signature.") +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOCERT , "No certificate was found.") + /* Codes related to signed manifests */ XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.") diff --git a/security/manager/ssl/AppSignatureVerification.cpp b/security/manager/ssl/AppSignatureVerification.cpp @@ -6,6 +6,7 @@ #include "nsNSSCertificateDB.h" +#include "AppSignatureVerification.h" #include "AppTrustDomain.h" #include "CryptoTask.h" #include "NSSCertDBTrustDomain.h" @@ -45,6 +46,143 @@ using namespace mozilla::psm; extern mozilla::LazyLogModule gPIPNSSLog; +Span<const uint8_t> GetPKCS7SignerCert( + NSSCMSSignerInfo* signerInfo, + nsTArray<Span<const uint8_t>>& collectedCerts) { + if (!signerInfo) { + return {}; + } + // The NSS APIs use the term "CMS", but since these are all signed by Mozilla + // infrastructure, we know they are actually PKCS7. This means that this only + // needs to handle issuer/serial number signer identifiers. + if (signerInfo->signerIdentifier.identifierType != NSSCMSSignerID_IssuerSN) { + return {}; + } + CERTIssuerAndSN* issuerAndSN = signerInfo->signerIdentifier.id.issuerAndSN; + if (!issuerAndSN) { + return {}; + } + Input issuer; + mozilla::pkix::Result result = + issuer.Init(issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len); + if (result != Success) { + return {}; + } + Input serialNumber; + result = serialNumber.Init(issuerAndSN->serialNumber.data, + issuerAndSN->serialNumber.len); + if (result != Success) { + return {}; + } + for (const auto& certDER : collectedCerts) { + Input certInput; + result = certInput.Init(certDER.Elements(), certDER.Length()); + if (result != Success) { + continue; // probably too big + } + // Since this only decodes the certificate and doesn't attempt to build a + // verified chain with it, the EndEntityOrCA parameter doesn't matter. + BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); + result = cert.Init(); + if (result != Success) { + continue; + } + if (InputsAreEqual(issuer, cert.GetIssuer()) && + InputsAreEqual(serialNumber, cert.GetSerialNumber())) { + return certDER; + } + } + return {}; +} + +NSSCMSSignedData* GetSignedDataContent(NSSCMSMessage* cmsg) { + NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsg, 0); + if (!cinfo) { + return nullptr; + } + + if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) != + SEC_OID_PKCS7_SIGNED_DATA) { + return nullptr; + } + + return static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); +} + +void CollectCertificates(NSSCMSSignedData* signedData, + nsTArray<Span<const uint8_t>>& collectedCerts) { + if (signedData->rawCerts) { + for (size_t i = 0; signedData->rawCerts[i]; ++i) { + Span<const uint8_t> cert(signedData->rawCerts[i]->data, + signedData->rawCerts[i]->len); + collectedCerts.AppendElement(std::move(cert)); + } + } +} + +nsresult VerifySignatureFromCertificate(Span<const uint8_t> signerCertSpan, + NSSCMSSignerInfo* signerInfo, + SECItem* detachedDigest) { + // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType. + const char* pkcs7DataOidString = "1.2.840.113549.1.7.1"; + ScopedAutoSECItem pkcs7DataOid; + if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0) != + SECSuccess) { + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; + } + + // NSS_CMSSignerInfo_Verify relies on NSS_CMSSignerInfo_GetSigningCertificate + // having been called already. This relies on the signing certificate being + // decoded as a CERTCertificate. + // This assertion should never fail, as this certificate has been + // successfully verified, which means it fits in the size of an unsigned int. + SECItem signingCertificateItem = { + siBuffer, const_cast<unsigned char*>(signerCertSpan.Elements()), + AssertedCast<unsigned int>(signerCertSpan.Length())}; + UniqueCERTCertificate signingCertificateHandle(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &signingCertificateItem, nullptr, false, true)); + if (!signingCertificateHandle) { + return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + } + // NB: This function does not return an owning reference, unlike with many + // other NSS APIs. + if (!NSS_CMSSignerInfo_GetSigningCertificate(signerInfo, + CERT_GetDefaultCertDB())) { + return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + } + return MapSECStatus(NSS_CMSSignerInfo_Verify( + signerInfo, const_cast<SECItem*>(detachedDigest), &pkcs7DataOid)); +} + +void GetAllSignerInfosForSupportedDigestAlgorithms( + NSSCMSSignedData* signedData, + /* out */ nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>>& signerInfos) { + static constexpr SECOidTag kSupportedDigestAlgorithms[] = {SEC_OID_SHA256, + SEC_OID_SHA1}; + + int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); + if (numSigners < 1) { + return; + } + + for (const auto& digestAlgorithm : kSupportedDigestAlgorithms) { + for (int i = 0; i < numSigners; i++) { + NSSCMSSignerInfo* signerInfo = + NSS_CMSSignedData_GetSignerInfo(signedData, i); + // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS. + SECOidData* digestAlgOID = + SECOID_FindOID(&signerInfo->digestAlg.algorithm); + if (!digestAlgOID) { + continue; + } + + if (digestAlgOID->offset == digestAlgorithm) { + signerInfos.AppendElement(std::make_tuple(signerInfo, digestAlgorithm)); + } + } + } +} + namespace { // A convenient way to pair the bytes of a digest with the algorithm that @@ -663,89 +801,6 @@ nsresult VerifyCertificate(Span<const uint8_t> signerCert, return NS_OK; } -// Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or -// SEC_OID_SHA256), returns the first signerInfo in the given signedData that -// purports to have been created using that digest algorithm, or nullptr if -// there is none. -// The returned signerInfo is owned by signedData, so the caller must ensure -// that the lifetime of the signerInfo is contained by the lifetime of the -// signedData. -NSSCMSSignerInfo* GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData, - SECOidTag digestAlgorithm) { - MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 || - digestAlgorithm == SEC_OID_SHA256); - if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) { - return nullptr; - } - - int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); - if (numSigners < 1) { - return nullptr; - } - for (int i = 0; i < numSigners; i++) { - NSSCMSSignerInfo* signerInfo = - NSS_CMSSignedData_GetSignerInfo(signedData, i); - // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS. - SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm); - if (!digestAlgOID) { - continue; - } - if (digestAlgorithm == digestAlgOID->offset) { - return signerInfo; - } - } - return nullptr; -} - -Span<const uint8_t> GetPKCS7SignerCert( - NSSCMSSignerInfo* signerInfo, - nsTArray<Span<const uint8_t>>& collectedCerts) { - if (!signerInfo) { - return {}; - } - // The NSS APIs use the term "CMS", but since these are all signed by Mozilla - // infrastructure, we know they are actually PKCS7. This means that this only - // needs to handle issuer/serial number signer identifiers. - if (signerInfo->signerIdentifier.identifierType != NSSCMSSignerID_IssuerSN) { - return {}; - } - CERTIssuerAndSN* issuerAndSN = signerInfo->signerIdentifier.id.issuerAndSN; - if (!issuerAndSN) { - return {}; - } - Input issuer; - mozilla::pkix::Result result = - issuer.Init(issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len); - if (result != Success) { - return {}; - } - Input serialNumber; - result = serialNumber.Init(issuerAndSN->serialNumber.data, - issuerAndSN->serialNumber.len); - if (result != Success) { - return {}; - } - for (const auto& certDER : collectedCerts) { - Input certInput; - result = certInput.Init(certDER.Elements(), certDER.Length()); - if (result != Success) { - continue; // probably too big - } - // Since this only decodes the certificate and doesn't attempt to build a - // verified chain with it, the EndEntityOrCA parameter doesn't matter. - BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); - result = cert.Init(); - if (result != Success) { - continue; - } - if (InputsAreEqual(issuer, cert.GetIssuer()) && - InputsAreEqual(serialNumber, cert.GetSerialNumber())) { - return certDER; - } - } - return {}; -} - nsresult VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, nsTArray<uint8_t>& detachedSHA1Digest, nsTArray<uint8_t>& detachedSHA256Digest, @@ -768,47 +823,39 @@ nsresult VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, return NS_ERROR_CMS_VERIFY_NOT_SIGNED; } - NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0); - if (!cinfo) { + NSSCMSSignedData* signedData = GetSignedDataContent(cmsMsg.get()); + if (!signedData) { return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; } - // We're expecting this to be a PKCS#7 signedData content info. - if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) != - SEC_OID_PKCS7_SIGNED_DATA) { - return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + nsTArray<Span<const uint8_t>> collectedCerts; + CollectCertificates(signedData, collectedCerts); + if (collectedCerts.Length() == 0) { + return NS_ERROR_CMS_VERIFY_NOCERT; } - // signedData is non-owning - NSSCMSSignedData* signedData = - static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); - if (!signedData) { - return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; - } + nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>> signerInfos; - nsTArray<Span<const uint8_t>> collectedCerts; - if (signedData->rawCerts) { - for (size_t i = 0; signedData->rawCerts[i]; ++i) { - Span<const uint8_t> cert(signedData->rawCerts[i]->data, - signedData->rawCerts[i]->len); - collectedCerts.AppendElement(std::move(cert)); - } + GetAllSignerInfosForSupportedDigestAlgorithms(signedData, signerInfos); + + if (signerInfos.Length() == 0) { + return NS_ERROR_CMS_VERIFY_NOT_SIGNED; } - NSSCMSSignerInfo* signerInfo = - GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256); - nsTArray<uint8_t>* tmpDetachedDigest = &detachedSHA256Digest; - digestAlgorithm = SEC_OID_SHA256; - if (!signerInfo) { - signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1); - if (!signerInfo) { - return NS_ERROR_CMS_VERIFY_NOT_SIGNED; - } + // The signerInfo with the hash function with the highest priority is used + NSSCMSSignerInfo* signerInfo = std::get<0>(signerInfos[0]); + digestAlgorithm = std::get<1>(signerInfos[0]); + + nsTArray<uint8_t>* tmpDetachedDigest; + if (digestAlgorithm == SEC_OID_SHA256) { + tmpDetachedDigest = &detachedSHA256Digest; + } else if (digestAlgorithm == SEC_OID_SHA1) { tmpDetachedDigest = &detachedSHA1Digest; - digestAlgorithm = SEC_OID_SHA1; + } else { + return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } - const SECItem detachedDigest = { + SECItem detachedDigest = { siBuffer, tmpDetachedDigest->Elements(), static_cast<unsigned int>(tmpDetachedDigest->Length())}; @@ -827,35 +874,8 @@ nsresult VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, signerCert.Clear(); signerCert.AppendElements(signerCertSpan); - // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType. - const char* pkcs7DataOidString = "1.2.840.113549.1.7.1"; - ScopedAutoSECItem pkcs7DataOid; - if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0) != - SECSuccess) { - return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; - } - - // NSS_CMSSignerInfo_Verify relies on NSS_CMSSignerInfo_GetSigningCertificate - // having been called already. This relies on the signing certificate being - // decoded as a CERTCertificate. - // This assertion should never fail, as this certificate has been - // successfully verified, which means it fits in the size of an unsigned int. - SECItem signingCertificateItem = { - siBuffer, const_cast<unsigned char*>(signerCertSpan.Elements()), - AssertedCast<unsigned int>(signerCertSpan.Length())}; - UniqueCERTCertificate signingCertificateHandle(CERT_NewTempCertificate( - CERT_GetDefaultCertDB(), &signingCertificateItem, nullptr, false, true)); - if (!signingCertificateHandle) { - return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); - } - // NB: This function does not return an owning reference, unlike with many - // other NSS APIs. - if (!NSS_CMSSignerInfo_GetSigningCertificate(signerInfo, - CERT_GetDefaultCertDB())) { - return mozilla::psm::GetXPCOMFromNSSError(SEC_ERROR_PKCS7_BAD_SIGNATURE); - } - return MapSECStatus(NSS_CMSSignerInfo_Verify( - signerInfo, const_cast<SECItem*>(&detachedDigest), &pkcs7DataOid)); + return VerifySignatureFromCertificate(signerCertSpan, signerInfo, + &detachedDigest); } class CoseVerificationContext { diff --git a/security/manager/ssl/AppSignatureVerification.h b/security/manager/ssl/AppSignatureVerification.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef AppSignatureVerification_h +#define AppSignatureVerification_h + +#include "mozpkix/pkix.h" +#include "mozpkix/pkixnss.h" +#include "mozpkix/pkixutil.h" + +// From the list of collectedCerts it gets the SignerCertificate based on +// issuerAndSN. +mozilla::Span<const uint8_t> GetPKCS7SignerCert( + NSSCMSSignerInfo* signerInfo, + nsTArray<mozilla::Span<const uint8_t>>& collectedCerts); + +// Checks that the ContentType is PKCS7 and returns a pointer to inner content. +NSSCMSSignedData* GetSignedDataContent(NSSCMSMessage* cmsg); + +// Gets a list of certificates from the CMS message +void CollectCertificates( + NSSCMSSignedData* signedData, + /* out */ nsTArray<mozilla::Span<const uint8_t>>& collectedCerts); + +nsresult VerifySignatureFromCertificate( + mozilla::Span<const uint8_t> signerCertSpan, NSSCMSSignerInfo* signerInfo, + SECItem* detachedDigest); + +// The function returns prioritized list of (signerInfo, digestAlgorithm +// [used to compute the signature digest of the message in signedInfo]). +// The returned signerInfo is owned by signedData, so the caller must ensure +// that the lifetime of the signerInfo is contained by the lifetime of the +// signedData. +// supportedDigestAlgorithms 1st algorithm has the highest priority, i.e. +// the function will first check if there is any SignerInfo +// with the highest priority digest algorithm. +void GetAllSignerInfosForSupportedDigestAlgorithms( + NSSCMSSignedData* signedData, + /* out */ nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>>& signerInfos); +#endif // AppSignatureVerification_h diff --git a/security/manager/ssl/PDFSignatureVerification.cpp b/security/manager/ssl/PDFSignatureVerification.cpp @@ -0,0 +1,290 @@ +/* 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 "cms.h" +#include "nsNSSCertificateDB.h" +#include "nsNSSCertificate.h" +#include "AppSignatureVerification.h" +#include "CryptoTask.h" + +#include "mozpkix/pkix.h" +#include "mozpkix/pkixnss.h" +#include "mozpkix/pkixtypes.h" +#include "mozpkix/pkixutil.h" + +#include "mozilla/dom/Promise.h" + +using namespace mozilla; +using namespace mozilla::pkix; +using namespace mozilla::psm; + +using dom::Promise; + +nsresult ComputeDigest(SECOidTag digestAlgorithm, + const nsTArray<nsTArray<uint8_t>>& ins, + /* out */ nsTArray<uint8_t>& calculatedDigest) { + Digest digest; + nsresult rv = digest.Begin(digestAlgorithm); + if (NS_FAILED(rv)) { + return rv; + } + + for (auto& in : ins) { + rv = digest.Update(in.Elements(), in.Length()); + if (NS_FAILED(rv)) { + return rv; + } + } + return digest.End(calculatedDigest); +} + +struct VerifySignatureResult { + nsresult signatureVerificationResult; + nsTArray<uint8_t> signerCert; + Time time; + + VerifySignatureResult(nsresult result, Span<const uint8_t> certSpan, Time t) + : signatureVerificationResult(result), time(t) { + signerCert.AppendElements(certSpan.data(), certSpan.Length()); + } +}; + +void VerifySignature( + NSSCMSSignedData* signedData, const nsTArray<nsTArray<uint8_t>>& data, + /* out */ nsTArray<VerifySignatureResult>& signatureVerificationResults) { + nsTArray<std::tuple<NSSCMSSignerInfo*, SECOidTag>> signerInfos; + // Returns a prioritized list of signerInfos. + GetAllSignerInfosForSupportedDigestAlgorithms(signedData, signerInfos); + + Span<const uint8_t> signerCertSpan; + if (signerInfos.Length() == 0) { + signatureVerificationResults.AppendElement( + VerifySignatureResult(NS_ERROR_CMS_VERIFY_NOT_SIGNED, + /* no certificate */ signerCertSpan, + /* default time */ Time(Time::uninitialized))); + return; + } + + nsTArray<Span<const uint8_t>> collectedCerts; + CollectCertificates(signedData, collectedCerts); + if (collectedCerts.Length() == 0) { + signatureVerificationResults.AppendElement( + VerifySignatureResult(NS_ERROR_CMS_VERIFY_NOCERT, + /* no certificate */ signerCertSpan, + /* default time */ Time(Time::uninitialized))); + return; + } + + for (const auto& pair : signerInfos) { + Time defaultTime(Time::uninitialized); + + SECOidTag digestAlgorithm = std::get<1>(pair); + signerCertSpan = Span<const uint8_t>(); + + nsTArray<uint8_t> calculatedDigest; + nsresult rv = ComputeDigest(digestAlgorithm, data, calculatedDigest); + if (NS_FAILED(rv)) { + signatureVerificationResults.AppendElement(VerifySignatureResult( + rv, + /* no certificate */ signerCertSpan, defaultTime)); + continue; + } + + NSSCMSSignerInfo* signerInfo = std::get<0>(pair); + signerCertSpan = GetPKCS7SignerCert(signerInfo, collectedCerts); + + if (signerCertSpan.IsEmpty()) { + signatureVerificationResults.AppendElement(VerifySignatureResult( + NS_ERROR_CMS_VERIFY_NOCERT, + /* no certificate */ signerCertSpan, defaultTime)); + continue; + } + + SECItem detachedDigest = { + siBuffer, calculatedDigest.Elements(), + static_cast<unsigned int>(calculatedDigest.Length())}; + + rv = VerifySignatureFromCertificate(signerCertSpan, signerInfo, + &detachedDigest); + + PRTime signingTime; + if (NSS_CMSSignerInfo_GetSigningTime(signerInfo, &signingTime) == + SECSuccess) { + signatureVerificationResults.AppendElement(VerifySignatureResult( + rv, signerCertSpan, + TimeFromEpochInSeconds((uint64_t)(signingTime / 1000000)))); + } else { + signatureVerificationResults.AppendElement( + VerifySignatureResult(rv, signerCertSpan, defaultTime)); + } + } +} + +nsresult VerifyCertificate() { return NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED; } + +class PDFVerificationResultImpl final : public nsIPDFVerificationResult { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPDFVERIFICATIONRESULT + + PDFVerificationResultImpl(nsresult sigResult, nsresult certResult, + nsIX509Cert* cert) + : mSignatureResult(sigResult), + mCertificateResult(certResult), + mSignerCertificate(cert) {} + + private: + ~PDFVerificationResultImpl() = default; + + nsresult mSignatureResult; + nsresult mCertificateResult; + nsCOMPtr<nsIX509Cert> mSignerCertificate; +}; + +NS_IMPL_ISUPPORTS(PDFVerificationResultImpl, nsIPDFVerificationResult) + +NS_IMETHODIMP +PDFVerificationResultImpl::GetSignatureResult(nsresult* aResult) { + *aResult = mSignatureResult; + return NS_OK; +} + +NS_IMETHODIMP +PDFVerificationResultImpl::GetCertificateResult(nsresult* aResult) { + *aResult = mCertificateResult; + return NS_OK; +} + +NS_IMETHODIMP +PDFVerificationResultImpl::GetSignerCertificate(nsIX509Cert** aCert) { + NS_IF_ADDREF(*aCert = mSignerCertificate); + return NS_OK; +} + +void VerifyPKCS7Object( + const nsTArray<uint8_t>& pkcs7, const nsTArray<nsTArray<uint8_t>>& data, + nsIX509CertDB::PDFSignatureAlgorithm signatureType, + /* out */ nsTArray<RefPtr<PDFVerificationResultImpl>>& pdfVerifResults) { + if (signatureType != + nsIX509CertDB::PDFSignatureAlgorithm::ADBE_PKCS7_DETACHED) { + pdfVerifResults.AppendElement(new PDFVerificationResultImpl( + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, nullptr)); + return; + } + + if (pkcs7.Length() == 0 || data.Length() == 0) { + pdfVerifResults.AppendElement(new PDFVerificationResultImpl( + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, nullptr)); + return; + } + + SECItem certificateItem = {siBuffer, const_cast<uint8_t*>(pkcs7.Elements()), + static_cast<unsigned int>(pkcs7.Length())}; + + UniqueNSSCMSMessage cmsMsg( + NSS_CMSMessage_CreateFromDER(&certificateItem, + /* NSSCMSContentCallback */ nullptr, + /* cb_arg */ nullptr, + /* PK11PasswordFunc */ nullptr, + /* pwfn_args */ nullptr, + /* NSSCMSGetDecryptKeyCallback */ nullptr, + /* decrypt_key_cb_arg */ nullptr)); + + NSSCMSSignedData* signedData = GetSignedDataContent(cmsMsg.get()); + if (!signedData) { + pdfVerifResults.AppendElement(new PDFVerificationResultImpl( + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, + NS_ERROR_CMS_VERIFY_ERROR_PROCESSING, nullptr)); + return; + } + + nsTArray<VerifySignatureResult> signatureVerificationResults; + VerifySignature(signedData, data, signatureVerificationResults); + + for (auto& result : signatureVerificationResults) { + if (result.signatureVerificationResult != NS_OK) { + pdfVerifResults.AppendElement(new PDFVerificationResultImpl( + result.signatureVerificationResult, + NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED, nullptr)); + } else { + // The next patch will contain the certificate verification for each + // signerCert + nsCOMPtr<nsIX509Cert> cert( + new nsNSSCertificate(std::move(result.signerCert))); + pdfVerifResults.AppendElement(new PDFVerificationResultImpl( + result.signatureVerificationResult, + NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED, cert)); + } + } +} + +class VerifyPKCS7ObjectTask : public CryptoTask { + public: + VerifyPKCS7ObjectTask(const nsTArray<uint8_t>& aPkcs7, + const nsTArray<nsTArray<uint8_t>>& aData, + nsIX509CertDB::PDFSignatureAlgorithm aSignatureType, + RefPtr<Promise>& aPromise) + : mPkcs7(aPkcs7.Clone()), + mSignatureType(aSignatureType), + mPromise(new nsMainThreadPtrHolder<Promise>( + "VerifyPKCS7ObjectTask::mPromise", aPromise)) { + for (auto& n : aData) { + mData.AppendElement(n.Clone()); + } + } + + private: + virtual nsresult CalculateResult() override; + virtual void CallCallback(nsresult rv) override; + + const nsTArray<uint8_t> mPkcs7; + nsTArray<nsTArray<uint8_t>> mData; + nsIX509CertDB::PDFSignatureAlgorithm mSignatureType; + nsTArray<RefPtr<PDFVerificationResultImpl>> mPdfVerifResults; // out + nsMainThreadPtrHandle<Promise> mPromise; +}; + +NS_IMETHODIMP +nsNSSCertificateDB::AsyncVerifyPKCS7Object( + const nsTArray<uint8_t>& pkcs7, const nsTArray<nsTArray<uint8_t>>& data, + nsIX509CertDB::PDFSignatureAlgorithm signatureType, JSContext* aCx, + mozilla::dom::Promise** aPromise) { + NS_ENSURE_ARG_POINTER(aCx); + + nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!globalObject)) { + return NS_ERROR_UNEXPECTED; + } + + ErrorResult result; + RefPtr<Promise> promise = Promise::Create(globalObject, result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + RefPtr<VerifyPKCS7ObjectTask> task( + new VerifyPKCS7ObjectTask(pkcs7, data, signatureType, promise)); + nsresult rv = task->Dispatch(); + if (NS_FAILED(rv)) { + return rv; + } + + promise.forget(aPromise); + return NS_OK; +} + +nsresult VerifyPKCS7ObjectTask::CalculateResult() { + VerifyPKCS7Object(mPkcs7, mData, mSignatureType, mPdfVerifResults); + return NS_OK; +} + +void VerifyPKCS7ObjectTask::CallCallback(nsresult rv) { + if (NS_FAILED(rv)) { + mPromise->MaybeReject(rv); + } else { + mPromise->MaybeResolve(mPdfVerifResults); + } +} diff --git a/security/manager/ssl/moz.build b/security/manager/ssl/moz.build @@ -127,6 +127,7 @@ UNIFIED_SOURCES += [ "NSSSocketControl.cpp", "nsTLSSocketProvider.cpp", "OSKeyStore.cpp", + "PDFSignatureVerification.cpp", "PKCS11ModuleDB.cpp", "PSMRunnable.cpp", "PublicKeyPinningService.cpp", diff --git a/security/manager/ssl/nsIX509CertDB.idl b/security/manager/ssl/nsIX509CertDB.idl @@ -42,6 +42,23 @@ interface nsIOpenSignedAppFileCallback : nsISupports }; /** + * Return type to be used in asyncVerifyPKCS7Object for PDF Verification. + * Consists of 3 elements: + * signatureResult describes the result of verifying the hash of data against the signature + * stored in pkcs7 + * certificateResult describes the result of certificate verification + * If the signature verification has failed, certificate verification is not run. + * signerCertificate returns the signerCertificate from the pkcs7 message + * signerCertificate is null if the signature verification has failed (not equal to NS_OK). + */ +[scriptable, uuid(f2d9f5e4-1234-4d5a-b789-abcdef012345)] +interface nsIPDFVerificationResult : nsISupports { + readonly attribute nsresult signatureResult; + readonly attribute nsresult certificateResult; + readonly attribute nsIX509Cert signerCertificate; +}; + +/** * Callback type for use with asyncVerifyCertAtTime. * If aPRErrorCode is PRErrorCodeSuccess (i.e. 0), aVerifiedChain represents the * verified certificate chain determined by asyncVerifyCertAtTime. aHasEVPolicy @@ -377,4 +394,33 @@ interface nsIX509CertDB : nsISupports { */ [implicit_jscontext] Promise asyncVerify1QWAC(in nsIX509Cert cert, in Array<nsIX509Cert> collectedCerts); + + /** + * Verifies that the all the signatures in the PKCS7 CMS message are valid for the associated data. + * Additionally, for each of them, the function extracts the signing certificate and + * its certificate chain from the PKCS7 object, + * checks that the certificate was valid at the time of signing, + * and ensures the chain leads to a trusted root certificate. + * + * @param pkcs7 DER-encoded PKCS#7 binary data object. + * @param data The associated data that was signed. + * @param signatureType Currently we support only adbe.pkcs7.detached + * + * If resolved, AsyncVerifyPKCS7Object(pkcs7, data, signatureType) will return an Array of + * nsIPDFVerificationResult (See above for the interface details). + */ + + /* ISO 32000-2 */ + cenum PDFSignatureAlgorithm : 8 { + ADBE_PKCS7_DETACHED, + ADBE_PKCS7_SHA1 + }; + + [implicit_jscontext] + Promise asyncVerifyPKCS7Object( + in Array<uint8_t> pkcs7, + in Array<Array<uint8_t> > data, + in nsIX509CertDB_PDFSignatureAlgorithm signatureType + ); + }; diff --git a/security/manager/ssl/tests/unit/moz.build b/security/manager/ssl/tests/unit/moz.build @@ -4,7 +4,7 @@ # 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/. -DIRS += ["tlsserver", "test_trust_anchors", "test_signed_apps"] +DIRS += ["tlsserver", "test_trust_anchors", "test_pdf_verification", "test_signed_apps"] if not CONFIG["MOZ_NO_SMART_CARDS"]: DIRS += ["pkcs11testmodule"] diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_correct.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/cert_correct.p7s @@ -0,0 +1,27 @@ +-----BEGIN PKCS7----- +MIIEhwYJKoZIhvcNAQcCoIIEeDCCBHQCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBcxggGdMIIBmQIBATAnMA8xDTALBgNV +BAMMBFRlc3QCFE15DEOF+reiS7v4LjRWjBpnRzy3MA0GCWCGSAFlAwQCAQUAoEsw +GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAvBgkqhkiG9w0BCQQxIgQgxVXqtF0I +hFrp8Q1FKpm/ywb3SlC5iP5+SN0yN4m4juMwCwYJKoZIhvcNAQEBBIIBAG8PRgxU +0SjyvSIHnCvUznIHq/i1AkEIwOcOdmpZVp1vbHh5uykmrLRgdyRmNzoiT12mjC7E +O4udxw6zJIigzfYf0L4Axs/udWxEkk0dmzgxwjjv0epTYIwgwGZVS9f3ghiMif/e +M0/ZgEieIMQZww5FJ2HfAY5/O4DmdIk3cQQhA882rC41lxacjkjPN62M8u92Ghas +W/QmkoWgcERgLx34aEg7AhOIw7/6PfqB0z0HdWz7aEggyv/Kf3eQypoOmWY47HoQ +fWAXIWy+1CGwV6Cm0CnhfG/5NgYn8kuMZUo2Sw/esrSrqa2xynFCBz4tv5z+yoRi +XG5jwtUZcJVDH/U= +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_correct.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/cert_correct.pkcs7spec @@ -0,0 +1,4 @@ +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.p7s @@ -0,0 +1,26 @@ +-----BEGIN PKCS7----- +MIIEewYJKoZIhvcNAQcCoIIEbDCCBGgCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBcxggGRMIIBjQIBATAnMA8xDTALBgNV +BAMMBFRlc3QCFE15DEOF+reiS7v4LjRWjBpnRzy3MA0GCWCGSAFlAwQCAQUAoD8w +GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAjBgkqhkiG9w0BCQQxFgQUxVXqtF0I +hFrp8Q1FKpm/ywb3SlAwCwYJKoZIhvcNAQEBBIIBAJs0fm9Nq+yiqtzl73Hf0rW+ +UqaY5Epr2Xz3lMIEDi0MwngX5wWflSoto12mkpg9FmTd6TVoeNrV8vDqWc124SFc +my9iMrl09qqxbie8SExYkcVHFdVo33sUS4l2LMLPKN5L6jcGqs5QcqMx6gKwGwOk +WLiD1OamZckJb4A2839iKLHcqEp9Ogt/gkaWazFde6p4H2lCyYBoAUszIXx2mZDx +x0swBP3HobHVyvOkFINshGZno5PkwlANmoZPT7pasgHBqvXv93V2FlGWMzOmZTVg +Sdz20KGSdVV5lp06NM5S3razzA4u51/Jas6mehmbswVxS72Q0lkUUc9Fx/1bZn4= +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/cert_sha2_wrong_len.pkcs7spec @@ -0,0 +1,4 @@ +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.p7s @@ -0,0 +1,27 @@ +-----BEGIN PKCS7----- +MIIEhwYJKoZIhvcNAQcCoIIEeDCCBHQCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBcxggGdMIIBmQIBATAnMA8xDTALBgNV +BAMMBFRlc3QCFE15DEOF+reiS7v4LjRWjBpnRzy3MA0GCWCGSAFlAwQCAQUAoEsw +GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAvBgkqhkiG9w0BCQQxIgQgxVXqtF0I +hFrp8Q1FKpm/ywb3SlC5iP5+SN0yN4m4juIwCwYJKoZIhvcNAQEBBIIBAHxELWVb +vYpEh33hbh6/6/J3tzodO24tByW6YHGqedJswHpfQegvZG9ZF08TUpWLiK/qb54C +HV8veFHgDI1DPm3ocvAlSFdqMAqzg3CdH4khK1/n4ByWV2zGRp0zpoJGnFzHcj3t +dwUlBplEefKFCukmTcss8P/uifOb/iPbryUtR15k3DqZDIDAZJ+LMIBcrqnPsa3i +UdaSD6LK2sty4LtQRUfUock2P1UQV5GPbmeQ4knVTMh7ngkgAqQOj7iJSJjMoq9k +SdTRFG8/j/C6SnHLeYPDOw/Ct3rLZ7dC6bT2DHBmuCNIYv++ikY2MIud21Bg5Nrf +9dWkKRN+hBlTKRY= +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/cert_with_incorrect_signature.pkcs7spec @@ -0,0 +1,4 @@ +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee2 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.p7s @@ -0,0 +1,28 @@ +-----BEGIN PKCS7----- +MIIEhwYJKoZIhvcNAQcCoIIEeDCCBHQCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBcxggGdMIIBmQIBATAnMA8xDTALBgNV +BAMMBFRlc3QCFE15DEOF+reiS7v4LjRWjBpnRzy3MA0GCWCGSAFlAwQCAQUAoEsw +GAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAvBgkqhkiG9w0BCQQxIgQgQwwMTkZS +GJgAd0xXLjYK5W8CRQeedjyZV1G6FbolxFQwCwYJKoZIhvcNAQEBBIIBAI4HHInU +xn4gnoELQZb9Wi0CYcJEoexJq7r42f5CEhWvtbYzPWLSwMHVUjYuJ9ZuwWuCQNc4 +zrIzv1YviKIpH2xjcrFEeNmoINAOMo1WdoIRRk0gfiPHeVxOyHXyE6AdazuahCoc +WmtzVBeF0ukfp36LRzpjIr6iniYb21LBDLFvOKk3JaATe761qOpV/tLrM8Y94GZN +qZx4JgfeBgOd+j1PGColFZtIi1kqGEJDVnibEFr/5WUsPyQH8kJvFBQrou9aFslj +3AlGXwQ1AfT4r48issg2nFYQOQDm4cjTCBZrnIzE41LBdbuiDl/49Q/71x9jUOZD +Sg7esT94fjzG/BU= +-----END PKCS7----- + diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/certificate_two_data_inputs.pkcs7spec @@ -0,0 +1,4 @@ +sha256:430c0c4e4652189800774c572e360ae56f0245079e763c995751ba15ba25c454 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/data_correct.bin b/security/manager/ssl/tests/unit/test_pdf_verification/data_correct.bin @@ -0,0 +1 @@ + +\ No newline at end of file diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.p7s @@ -0,0 +1,26 @@ +-----BEGIN PKCS7----- +MIIEdgYJKoZIhvcNAQcCoIIEZzCCBGMCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBcxggGMMIIBiAIBATAnMA8xDTALBgNV +BAMMBFRlc3QCFE15DEOF+reiS7v4LjRWjBpnRzy3MAwGCCqGSIb3DQIFBQCgOzAY +BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMB8GCSqGSIb3DQEJBDESBBBrMb36f5v+ +ziYzgf+pG9apMAsGCSqGSIb3DQEBAQSCAQB0gvpRvf0KOQIu8j4q12Omyrm+OUc/ +V7j27W0DnzBGdT1q9RinhwkyS5sRnd/NcWMPoLnQBg+iC15N6MLKSg017CVnzj1I +2w36ih1DPVP082FUB/mFfgeMjupLZT9WVeL0x00kc+wKVrJZYMRmCrWBh/rumxFA +nWa+AUYWI/bd0BcXvCjVfHDHh+xit5YawaMQG/46luwEscG9d2e966DVtwN4lESo +Mh+MM1vS3oLY+9PnpvCr/e97DXI32/p6IY1Lj01wCxdIXF+OVrWdZyFtON8+1v4/ +0EmrTn/Ke/CAvyP2MQx5RXEPDqMGgyeKEdRP158WRb8W5fKE3iMRfEC3 +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/md5_signer_info.pkcs7spec @@ -0,0 +1,4 @@ +md5:6b31bdfa7f9bfece263381ffa91bd6a9 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/moz.build b/security/manager/ssl/tests/unit/test_pdf_verification/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + + +@template +def PDFVerificationCertGen(name, specification): + if not CONFIG["COMPILE_ENVIRONMENT"]: + return + + GENERATED_FILES += [name] + props = GENERATED_FILES[name] + props.script = "/security/manager/tools/pycms.py" + props.inputs = [specification] + files = TEST_HARNESS_FILES.xpcshell + for part in RELATIVEDIR.split("/"): + files = files[part] + files += ["!%s" % name] + + +# The first argument is the output, the second argument is the specification used to generate the cms message +# PDFVerificationCertGen('cert_correct.p7s', 'cert_correct.pkcs7spec') +# PDFVerificationCertGen('no_certificate.p7s', 'no_certificate.pkcs7spec') +# PDFVerificationCertGen('no_signer_info.p7s', 'no_signer_info.pkcs7spec') +# PDFVerificationCertGen('md5_signer_info.p7s', 'md5_signer_info.pkcs7spec') +# PDFVerificationCertGen('cert_sha2_wrong_len.p7s', 'cert_sha2_wrong_len.pkcs7spec') +# PDFVerificationCertGen('two_correct_signatures.p7s', 'two_correct_signatures.pkcs7spec') +# PDFVerificationCertGen('cert_with_incorrect_signature.p7s', 'cert_with_incorrect_signature.pkcs7spec') +# PDFVerificationCertGen('one_correct_one_incorrect_hash.p7s', 'one_correct_one_incorrect_hash.pkcs7spec') +# PDFVerificationCertGen('one_correct_one_incorrect_signature.p7s', 'one_correct_one_incorrect_signature.pkcs7spec') +# PDFVerificationCertGen('certificate_two_data_inputs.p7s', 'certificate_two_data_inputs.pkcs7spec') + +# To generate a new entry, add PDFVerificationCertGen, run mach build and copy from +# objdir/_tests/xpcshell/security/manager/ssl/tests/unit/test_pdf_verification/ +# to this directory. diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/no_certificate.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/no_certificate.p7s @@ -0,0 +1,12 @@ +-----BEGIN PKCS7----- +MIIB0QYJKoZIhvcNAQcCoIIBwjCCAb4CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHATGCAZ0wggGZAgEBMCcwDzENMAsGA1UEAwwEVGVzdAIUTXkMQ4X6t6JLu/gu +NFaMGmdHPLcwDQYJYIZIAWUDBAIBBQCgSzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN +AQcBMC8GCSqGSIb3DQEJBDEiBCDFVeq0XQiEWunxDUUqmb/LBvdKULmI/n5I3TI3 +ibiO4zALBgkqhkiG9w0BAQEEggEAbw9GDFTRKPK9IgecK9TOcger+LUCQQjA5w52 +allWnW9seHm7KSastGB3JGY3OiJPXaaMLsQ7i53HDrMkiKDN9h/QvgDGz+51bESS +TR2bODHCOO/R6lNgjCDAZlVL1/eCGIyJ/94zT9mASJ4gxBnDDkUnYd8Bjn87gOZ0 +iTdxBCEDzzasLjWXFpyOSM83rYzy73YaFqxb9CaShaBwRGAvHfhoSDsCE4jDv/o9 ++oHTPQd1bPtoSCDK/8p/d5DKmg6ZZjjsehB9YBchbL7UIbBXoKbQKeF8b/k2Bify +S4xlSjZLD96ytKuprbHKcUIHPi2/nP7KhGJcbmPC1RlwlUMf9Q== +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/no_certificate.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/no_certificate.pkcs7spec @@ -0,0 +1,5 @@ +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +erase:certificate +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.p7s @@ -0,0 +1,18 @@ +-----BEGIN PKCS7----- +MIIC5gYJKoZIhvcNAQcCoIIC1zCCAtMCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCArIwggKuMIIBlqADAgECAhRNeQxDhfq3oku7+C40VowaZ0c8tzANBgkq +hkiG9w0BAQsFADAPMQ0wCwYDVQQDDARUZXN0MCIYDzIwMjMxMTI4MDAwMDAwWhgP +MjAyNjAyMDUwMDAwMDBaMA8xDTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk +e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg +KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI +YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi +lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL +HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKej8jweSkGqBqWg7T20PO7YEBW2LQoPSgBOhFdi9RO7gs258gcV +xERpHwndp7Mf+IdfJVFcMaOPy+eRJ37y7r/lTjvfPYrVHvI+qQ/rxulWFUCaslG1 +2otfOTdF/UrH6V8oOJ9TYS0hDikZ1+LPVxEhuD8gMR47LbpNqhmeEZwdW7pboEcc +gTiBWzSh2IrfCSSkiSWjUl6MofUcTjH0ORepM1fZV2RNgo+WMUaWqSjdZH9jx9YX +BdgYBa9Jik4wI5WjPMl85CysZKb00llHoPZ8PI67jiFoLFZxgJAMc6jJEj5CstTK +RdXBvYQ+/HsHucAvNOoxAWRgc5XmpSyyCBc= +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/no_signer_info.pkcs7spec @@ -0,0 +1,5 @@ +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +erase:signerInfo +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.p7s @@ -0,0 +1,35 @@ +-----BEGIN PKCS7----- +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 +scpxQgc+Lb+c/sqEYlxuY8LVGXCVQx/1 +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_hash.pkcs7spec @@ -0,0 +1,5 @@ +sha1:6e14a407faae939957c80e641a836735bbdcad5a +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.p7s @@ -0,0 +1,35 @@ +-----BEGIN PKCS7----- +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 +scpxQgc+Lb+c/sqEYlxuY8LVGXCVQx/1 +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/one_correct_one_incorrect_signature.pkcs7spec @@ -0,0 +1,6 @@ +sha1:6e14a407faae939957b80e641a836735bbdcad5a +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +tamperDigest:sha1 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/poppler_data.bin b/security/manager/ssl/tests/unit/test_pdf_verification/poppler_data.bin Binary files differ. diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/poppler_signature.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/poppler_signature.p7s @@ -0,0 +1,3 @@ +-----BEGIN PKCS7----- +MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCCAsUwggLBMIIBqQIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpSU0EyMDQ4IHRlc3QgQ0EgZm9yIHBkZnNpZzAgFw0yNTA0MTYyMDA3MzhaGA8zMDI0MDgxNzIwMDczOFowJjEkMCIGA1UEAwwbUlNBMjA0OCB0ZXN0IGtleSBmb3IgcGRmc2lnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6BQLwBCzxPtkEdQO4WdNGqLDpVTyFoyeANrX88npFizB1bmDdoKkRaolIZ6qY4RkoToK/UUXfBP7K713hm+pLpBv1u7UpdOS3WxGveAyq9t5jeoq/VmTUCJ7ufNuem3Dl40ycUg8wFD4g1zg9F04HWWiCVVTgISZLt2BlZrjeu+bwIyFzPA9xhIYJUtgZYgj5xv7cgi4gbH6sLL1Cqm8BVhJ6xvQ+NsPazAEuH5F5UOMh00rCQezerxzV9FRoP0eTwKifWsj2Rggy7Ox/vt5OBlpU4+XwjPYY59AdmINU9BRwmjXAVOgJGsk2BkFBHumFA7HRgEER1i+0TbfrHS/QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCUI2hfOsV78n/b7aDVboJ/tNGCtu/WqBDW5CuI4ibaxBbew/Hoz5aSjlUAGu93f8okGEqO31I25XuxiriOLcsYgCZ9h25mLbI4RdgKxfYm6QyOShUKobcd4WutUWsL673zf8bsYXMkMQQIHaT7kmdbH0wQeRSt/ENCi8KD8DaZfFqAGZTNuU5oOqEY8YKI9ksW9zOJwrkqNuxSb1uo2l4x7gvYFpmZeHizoUQj5RvmC13aGf7n+MvhKXEHyEr+fmCLomeGYYD34tozhuPBZ2pnBHPitr0gpw1xwNhwaJcrOuyKlata0aIBiVr8DnVd5MaVWvOkIFqr1pNx4yvY11lxMYIBVTCCAVECAQEwKjAlMSMwIQYDVQQDDBpSU0EyMDQ4IHRlc3QgQ0EgZm9yIHBkZnNpZwIBATANBglghkgBZQMEAgEFADANBgkqhkiG9w0BAQEFAASCAQBiNCgM7UkUnXbnG7Z6D4aSUtNG3J/KgVL+GDBZywNPUpZpEfe9sHn6FRO9aPFd9tIyeJ6QfZ7pKToFZ2aLzugoGqSrOZh1MdTKPOmWnHbkMdG57oEg8jHM/LodEFiz4WPxPR9yXrP74TrtkIoxAt/dk3warOHuwUBaZL2I9uFztJkbaUJd94qXyXe+eXJ6sQ0qGY4dpzlCxGgkZex5gQnnCC1oAB9g2Oys/xNe8XutXYQtnZICLRpErYOKWObsy/1wCyqd5p8we4YdQ6UjLMGCs081TBHQzBnvN17ZBITmTTvVMNOmAd7xpJsjiJlGdbj/F/SXCv6JEM7sCXorl1CSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.p7s b/security/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.p7s @@ -0,0 +1,35 @@ +-----BEGIN PKCS7----- +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 +scpxQgc+Lb+c/sqEYlxuY8LVGXCVQx/1 +-----END PKCS7----- diff --git a/security/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.pkcs7spec b/security/manager/ssl/tests/unit/test_pdf_verification/two_correct_signatures.pkcs7spec @@ -0,0 +1,5 @@ +sha1:6e14a407faae939957b80e641a836735bbdcad5a +sha256:c555eab45d08845ae9f10d452a99bfcb06f74a50b988fe7e48dd323789b88ee3 +signer: +issuer:Test +subject:Test diff --git a/security/manager/ssl/tests/unit/test_signed_pdfs.js b/security/manager/ssl/tests/unit/test_signed_pdfs.js @@ -0,0 +1,428 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ +"use strict"; + +do_get_profile(); // must be called before getting nsIX509CertDB +const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB +); + +// If resolved, asyncVerifyPKCS7Object(pkcs7, data, signatureType) will return an array consisting +// of 3 elements for each SignerInfo of the PKCS7 message: +// - signatureResult - which describes the result of verifying the hash of data against the signature +// stored in pkcs7 +// - certificateResult - which describes the result of certificate verification +// If the signature verification has failed, certificate verification is not run. +// - signerCertificate - which returns the signerCertificate from the pkcs7 message +// signerCertificate is null if the signature verification has failed (not equal to NS_OK). + +// Empty PKCS7 message should return resolved promise with NS_ERROR_CMS_VERIFY_ERROR_PROCESSING +add_task(async function () { + info("Running PDF verification service test with empty input"); + let pkcs7 = new Uint8Array(); + let data = [new Uint8Array(0x10)]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + + equal(result.length, 1); + let firstSignatureResult = result[0]; + equal( + firstSignatureResult.signatureResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// Empty data message should return resolved promise with NS_ERROR_CMS_VERIFY_ERROR_PROCESSING +add_task(async function () { + info("Running PDF verification service test with empty input"); + let pkcs7 = new Uint8Array(0x10); + let data = [new Uint8Array()]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + + equal(result.length, 1); + let firstSignatureResult = result[0]; + equal( + firstSignatureResult.signatureResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// ADBE_PKCS7_SHA1 is not supported +add_task(async function () { + info("Running PDF verification service test with unsupported signature type"); + let pkcs7 = new Uint8Array(0x10); + let data = [new Uint8Array(0x10)]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_SHA1; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + + equal(result.length, 1); + let firstSignatureResult = result[0]; + equal( + firstSignatureResult.signatureResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +function pkcs7FromFile(certName) { + let certFile = do_get_file(`test_pdf_verification/${certName}.p7s`, false); + let certBytes = readFile(certFile); + let certBytesClean = certBytes + .replace("-----BEGIN PKCS7-----", "") + .replace("-----END PKCS7-----", "") + .replace(/\n/g, ""); + const binary = atob(certBytesClean); + const len = binary.length; + const bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes; +} + +function readBinFromFile(dataName) { + let dataFile = do_get_file(`test_pdf_verification/${dataName}.bin`, false); + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(dataFile, -1, 0, 0); + let available = fstream.available(); + let data = available > 0 ? NetUtil.readInputStream(fstream, available) : ""; + fstream.close(); + + return new Uint8Array(data); +} + +// PKCS7 CMS message with the correct signature should return resolve promise +// with signatureResult to be equal to NS_OK +// and the signerCertificate being not null. +// Currently, certificate verification is not supported. +add_task(async function () { + info("Running PDF verification service test with a correct signature"); + let pkcs7 = pkcs7FromFile("cert_correct"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + let firstSignatureResult = result[0]; + + equal(result.length, 1); + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); +}); + +// PKCS7 CMS message with the correct signature should return resolve promise +// with signatureResult to be equal to NS_OK +// and the signerCertificate being not null. +// Currently, certificate verification is not supported. +add_task(async function () { + info("Running PDF verification service test with a correct signature"); + let pkcs7 = pkcs7FromFile("certificate_two_data_inputs"); + // each dataPortion here is an Uint8Array + let dataPortion = readBinFromFile("data_correct"); + let data = [dataPortion, dataPortion]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + + equal(result.length, 1); + let firstSignatureResult = result[0]; + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); +}); + +// PKCS7 CMS message contains one SignerInfo with an incorrect signature. +add_task(async function () { + info("Running PDF verification service test with a incorrect signature"); + let pkcs7 = pkcs7FromFile("cert_with_incorrect_signature"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal( + firstSignatureResult.signatureResult, + getXPCOMStatusFromNSS(SEC_ERROR_PKCS7_BAD_SIGNATURE) + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// MD5 signatures are not supported. +add_task(async function () { + info("Running PDF verification service test with a md5 signature"); + let pkcs7 = pkcs7FromFile("md5_signer_info"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal( + firstSignatureResult.signatureResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// PKCS7 CMS message has 2 SignerInfo, both have correct signature (sha1/sha2) +add_task(async function () { + info( + "Running PDF verification service test with the CMS message containing two different SignerInfo, both have correct signatures" + ); + let pkcs7 = pkcs7FromFile("two_correct_signatures"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + // 2 SignerInfo results into 2 signature results + equal(result.length, 2); + let firstSignatureResult = result[0]; + let secondSignatureResult = result[1]; + + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); + + equal(secondSignatureResult.signatureResult, Cr.NS_OK); + equal( + secondSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + secondSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); +}); + +// PKCS7 CMS message has 2 SignerInfo, one SignerInfo has a wrong digest +// We consider that the signature is verified iff all the SignerInfo contain the correct signature. +add_task(async function () { + info( + "Running PDF verification service test with the CMS message containing two different SignerInfo, only one has a correct signature" + ); + let pkcs7 = pkcs7FromFile("one_correct_one_incorrect_hash"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 2); + let firstSignatureResult = result[0]; + let secondSignatureResult = result[1]; + + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); + + equal( + secondSignatureResult.signatureResult, + getXPCOMStatusFromNSS(SEC_ERROR_PKCS7_BAD_SIGNATURE) + ); + equal( + secondSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + equal(secondSignatureResult.signerCertificate, null); +}); + +// PKCS7 CMS message has 2 SignerInfo, one SignerInfo has a correct digest, but an incorrect signature +add_task(async function () { + info( + "Running PDF verification service test with the CMS message containing two different SignerInfo, only one has a correct signature" + ); + let pkcs7 = pkcs7FromFile("one_correct_one_incorrect_signature"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + let firstSignatureResult = result[0]; + let secondSignatureResult = result[1]; + + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); + + equal( + secondSignatureResult.signatureResult, + getXPCOMStatusFromNSS(SEC_ERROR_PKCS7_BAD_SIGNATURE) + ); + equal( + secondSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + equal(secondSignatureResult.signerCertificate, null); +}); + +// PKCS7 CMS message with no SignerInfo +add_task(async function () { + info("Running PDF verification service test with no SignerInfo"); + let pkcs7 = pkcs7FromFile("no_signer_info"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal( + firstSignatureResult.signatureResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_ERROR_PROCESSING + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// PKCS7 CMS message with no certificates +add_task(async function () { + info("Running PDF verification service test with an empty certificate list"); + let pkcs7 = pkcs7FromFile("no_certificate"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal(firstSignatureResult.signatureResult, Cr.NS_ERROR_CMS_VERIFY_NOCERT); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + equal(firstSignatureResult.signerCertificate, null); +}); + +// Test vector used in Poppler to test their implementation +// SignerCertificate subject name is "CN=RSA2048 test key for pdfsig" +add_task(async function () { + info("Running PDF verification service test with the Poppler TV"); + let pkcs7 = pkcs7FromFile("poppler_signature"); + let data = [readBinFromFile("poppler_data")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); + info( + "Certificate Subject Name: " + + firstSignatureResult.signerCertificate.subjectName + ); + Assert.equal( + firstSignatureResult.signerCertificate.subjectName, + "CN=RSA2048 test key for pdfsig" + ); +}); + +// Checking some of the properties of the returned certificate +// The certificate was generated to have the Issuer "Test" and the subjectName "Test" +add_task(async function () { + info("Running PDF verification service test with a correct signature"); + let pkcs7 = pkcs7FromFile("cert_correct"); + let data = [readBinFromFile("data_correct")]; + let signatureType = Ci.nsIX509CertDB.ADBE_PKCS7_DETACHED; + + let result = await certdb.asyncVerifyPKCS7Object(pkcs7, data, signatureType); + equal(result.length, 1); + let firstSignatureResult = result[0]; + + equal(firstSignatureResult.signatureResult, Cr.NS_OK); + equal( + firstSignatureResult.certificateResult, + Cr.NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED + ); + + ok( + firstSignatureResult.signerCertificate, + "Signer certificate should not be null/undefined" + ); + + info( + "Certificate Subject Name: " + + firstSignatureResult.signerCertificate.subjectName + ); + Assert.equal(firstSignatureResult.signerCertificate.subjectName, "CN=Test"); + + info( + "Certificate Issuer Name: " + + firstSignatureResult.signerCertificate.issuerName + ); + Assert.equal(firstSignatureResult.signerCertificate.issuerName, "CN=Test"); +}); diff --git a/security/manager/ssl/tests/unit/xpcshell.toml b/security/manager/ssl/tests/unit/xpcshell.toml @@ -37,6 +37,7 @@ support-files = [ "test_name_constraints/**", "test_ocsp_url/**", "test_onecrl/**", + "test_pdf_verification/**", "test_qwacs/**", "test_sdr_preexisting/**", "test_sdr_preexisting_with_password/**", @@ -289,6 +290,8 @@ run-sequentially = ["true"] # hardcoded ports ["test_signed_apps.js"] +["test_signed_pdfs.js"] + ["test_ssl_status.js"] run-sequentially = ["true"] # hardcoded ports diff --git a/security/manager/tools/pycms.py b/security/manager/tools/pycms.py @@ -12,6 +12,9 @@ The specification format is as follows: sha1:<hex string> sha256:<hex string> +md5:<hex string> +tamperDigest:sha1 - Only sha1 is supported +erase:{certificate, signerInfo} signer: <pycert specification> @@ -24,10 +27,14 @@ the SignedData. If neither hash is specified, the signerInfos will be an empty SET (i.e. there will be no actual signature information). The certificate specification must come last. +The script provides a possibility to tamper the hash while generating +SignedAttributes, such that the SignedAttributes signature will be incorrect +Erase allows specifying which PKCS7 field to strip (supports certificate or signerInfo) """ import base64 import sys +from enum import Enum from io import StringIO import pycert @@ -55,6 +62,15 @@ class UnknownDirectiveError(Error): return "Unknown directive %s" % repr(self.directive) +class FieldStrip(Enum): + CERTIFICATE = "certificate" + SIGNER_INFO = "signerInfo" + + +class HashToTamper(Enum): + SHA1 = "sha1" + + class CMS: """Utility class for reading a CMS specification and generating a CMS message""" @@ -62,6 +78,11 @@ class CMS: def __init__(self, paramStream): self.sha1 = "" self.sha256 = "" + self.md5 = "" + + self.fieldStrip = "" + self.tamperDigest = "" + signerSpecification = StringIO() readingSignerSpecification = False for line in paramStream.readlines(): @@ -73,6 +94,20 @@ class CMS: self.sha1 = line.strip()[len("sha1:") :] elif line.startswith("sha256:"): self.sha256 = line.strip()[len("sha256:") :] + elif line.startswith("md5:"): + self.md5 = line.strip()[len("md5:") :] + elif line.startswith("erase:"): + if line.strip()[len("erase:") :] == FieldStrip.CERTIFICATE.value: + self.fieldStrip = FieldStrip.CERTIFICATE + elif line.strip()[len("erase:") :] == FieldStrip.SIGNER_INFO.value: + self.fieldStrip = FieldStrip.SIGNER_INFO + else: + raise UnknownDirectiveError(line.strip()) + elif line.startswith("tamperDigest"): + if line.strip()[len("tamperDigest:") :] == HashToTamper.SHA1.value: + self.tamperDigest = HashToTamper.SHA1 + else: + raise UnknownDirectiveError(line.strip()) else: raise UnknownDirectiveError(line.strip()) signerSpecification.seek(0) @@ -115,6 +150,8 @@ class CMS: oidString = "1.3.14.3.2.26" elif pykeyHash == pykey.HASH_SHA256: oidString = "2.16.840.1.101.3.4.2.1" + elif pykeyHash == pykey.HASH_MD5: + oidString = "1.2.840.113549.2.5" else: raise pykey.UnknownHashAlgorithmError(pykeyHash) algorithmIdentifier = rfc2459.AlgorithmIdentifier() @@ -149,6 +186,19 @@ class CMS: signerInfo["digestEncryptionAlgorithm"] = rsa authenticatedAttributesEncoded = encoder.encode(authenticatedAttributesTBS) signature = self.signingKey.sign(authenticatedAttributesEncoded, pykeyHash) + if self.tamperDigest == HashToTamper.SHA1 and pykeyHash == pykey.HASH_SHA1: + digestValue = hex((int(digestValue[0], 16) + 1) % 16)[2:] + digestValue[1:] + authenticatedAttributesTBSTamperedHash = self.buildAuthenticatedAttributes( + digestValue + ) + authenticatedAttributesTamperedEncoded = encoder.encode( + authenticatedAttributesTBSTamperedHash + ) + # The signerInfo has an attribute with the initial hash + # But the tampered hash attributes are signed + signature = self.signingKey.sign( + authenticatedAttributesTamperedEncoded, pykeyHash + ) # signature will be a hexified bit string of the form # "'<hex bytes>'H". For some reason that's what BitString wants, # but since this is an OCTET STRING, we have to strip off the @@ -180,19 +230,26 @@ class CMS: )[0] extendedCertificateOrCertificate["certificate"] = certificate certificates[0] = extendedCertificateOrCertificate - signedData["certificates"] = certificates - signerInfos = rfc2315.SignerInfos() + if self.fieldStrip != FieldStrip.CERTIFICATE: + signedData["certificates"] = certificates - if len(self.sha1) > 0: - signerInfos[len(signerInfos)] = self.buildSignerInfo( - certificate, pykey.HASH_SHA1, self.sha1 - ) - if len(self.sha256) > 0: - signerInfos[len(signerInfos)] = self.buildSignerInfo( - certificate, pykey.HASH_SHA256, self.sha256 - ) - signedData["signerInfos"] = signerInfos + if self.fieldStrip != FieldStrip.SIGNER_INFO: + signerInfos = rfc2315.SignerInfos() + + if len(self.sha1) > 0: + signerInfos[len(signerInfos)] = self.buildSignerInfo( + certificate, pykey.HASH_SHA1, self.sha1 + ) + if len(self.sha256) > 0: + signerInfos[len(signerInfos)] = self.buildSignerInfo( + certificate, pykey.HASH_SHA256, self.sha256 + ) + if len(self.md5) > 0: + signerInfos[len(signerInfos)] = self.buildSignerInfo( + certificate, pykey.HASH_MD5, self.md5 + ) + signedData["signerInfos"] = signerInfos encoded = encoder.encode(signedData) anyTag = univ.Any(encoded).subtype( @@ -207,13 +264,22 @@ class CMS: der = self.toDER() b64 = base64.b64encode(der) while b64: - output += "\n" + b64[:64] + output += "\n" + b64[:64].decode("utf-8") b64 = b64[64:] output += "\n-----END PKCS7-----\n" return output +# The build harness will call this function with an output +# file-like object and a path to a file containing a +# specification. This will read the specification and output +# the cms message as PEM. +def main(output, inputPath): + with open(inputPath) as configStream: + output.write(CMS(configStream).toPEM() + "\n") + + # When run as a standalone program, this will read a specification from -# stdin and output the certificate as PEM to stdout. +# stdin and output the cms message as PEM. if __name__ == "__main__": print(CMS(sys.stdin).toPEM()) diff --git a/security/nss.symbols b/security/nss.symbols @@ -205,6 +205,7 @@ NSS_CMSSignedData_Destroy NSS_CMSSignedData_GetSignerInfo NSS_CMSSignedData_SignerInfoCount NSS_CMSSignerInfo_GetSigningCertificate +NSS_CMSSignerInfo_GetSigningTime NSS_CMSSignerInfo_Verify NSS_FindCertKEAType NSS_GetAlgorithmPolicy