зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1265085 - Replace verification source with a SAN in the content signature verifier interface. r=Cykesiopka,r=fkiefer
This change replaces the hardcoded 'sourceis' in nsIContentSignatureVerifier and ContentSignatureVerifier.cpp with a string parameter which allows the caller to specify which hostname the signing certificate must be valid for. This allows us to create and use new signing certificates without having to wait for new sources to ride the trains. MozReview-Commit-ID: KGpOVOuJrk3
This commit is contained in:
Родитель
a86c730a63
Коммит
fccc28a54a
|
@ -42,12 +42,13 @@ ContentSignatureVerifier::~ContentSignatureVerifier()
|
|||
shutdown(calledFromObject);
|
||||
}
|
||||
|
||||
nsresult
|
||||
NS_IMETHODIMP
|
||||
ContentSignatureVerifier::VerifyContentSignature(
|
||||
const nsACString& aData, const nsACString& aCSHeader,
|
||||
const nsACString& aCertChain, const uint32_t aSource, bool* _retval)
|
||||
const nsACString& aCertChain, const nsACString& aName, bool* _retval)
|
||||
{
|
||||
nsresult rv = CreateContext(aData, aCSHeader, aCertChain, aSource);
|
||||
NS_ENSURE_ARG(_retval);
|
||||
nsresult rv = CreateContext(aData, aCSHeader, aCertChain, aName);
|
||||
if (NS_FAILED(rv)) {
|
||||
*_retval = false;
|
||||
CSVerifier_LOG(("CSVerifier: Signature verification failed\n"));
|
||||
|
@ -125,13 +126,14 @@ ReadChainIntoCertList(const nsACString& aCertChain, CERTCertList* aCertList,
|
|||
}
|
||||
|
||||
// Create a context for a content signature verification.
|
||||
// It sets signature, certificate chain, and context that shold be used to
|
||||
// verify the data. The optional data parameter is added to the data to verify.
|
||||
// It sets signature, certificate chain and name that should be used to verify
|
||||
// the data. The data parameter is the first part of the data to verify (this
|
||||
// can be the empty string).
|
||||
NS_IMETHODIMP
|
||||
ContentSignatureVerifier::CreateContext(const nsACString& aData,
|
||||
const nsACString& aCSHeader,
|
||||
const nsACString& aCertChain,
|
||||
const uint32_t aSource)
|
||||
const nsACString& aName)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
|
@ -186,24 +188,10 @@ ContentSignatureVerifier::CreateContext(const nsACString& aData,
|
|||
}
|
||||
|
||||
// Check the SAN
|
||||
nsAutoCString hostname;
|
||||
|
||||
Input hostnameInput;
|
||||
|
||||
switch (aSource) {
|
||||
case ABOUT_NEWTAB:
|
||||
hostname = "remote-newtab-signer.mozilla.org";
|
||||
break;
|
||||
case ONECRL:
|
||||
hostname = "oneCRL-signer.mozilla.org";
|
||||
break;
|
||||
default:
|
||||
CSVerifier_LOG(("CSVerifier: bad context\n"));
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
result = hostnameInput.Init(uint8_t_ptr_cast(hostname.BeginReading()),
|
||||
hostname.Length());
|
||||
result = hostnameInput.Init(uint8_t_ptr_cast(aName.BeginReading()),
|
||||
aName.Length());
|
||||
if (result != Success) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
|
|
@ -24,17 +24,6 @@
|
|||
interface nsIContentSignatureVerifier : nsISupports
|
||||
{
|
||||
|
||||
/**
|
||||
* Verification sources.
|
||||
* If the verification is from ABOUT_NEWTAB, the content signature can only be
|
||||
* verified with a certificate chain where the end entity is valid for the
|
||||
* hostname "remote-newtab-signer.mozilla.org".
|
||||
* If the verification is from ONECRL, the end entity must be valid for the
|
||||
* hostname "oneCRL-signer.mozilla.org"
|
||||
*/
|
||||
const unsigned long ABOUT_NEWTAB = 0;
|
||||
const unsigned long ONECRL = 1;
|
||||
|
||||
/**
|
||||
* Verifies that the data matches the data that was used to generate the
|
||||
* signature.
|
||||
|
@ -44,30 +33,29 @@ interface nsIContentSignatureVerifier : nsISupports
|
|||
* url-safe base64 encoded.
|
||||
* @param aCertificateChain The certificate chain to use for verification.
|
||||
* PEM encoded string.
|
||||
* @param aSource The source of this verification (one of the
|
||||
* values defined above).
|
||||
* @param aName The (host)name for which the end entity must
|
||||
be valid.
|
||||
* @returns true if the signature matches the data and aCertificateChain is
|
||||
* valid within aContext, false if not.
|
||||
*/
|
||||
boolean verifyContentSignature(in ACString aData, in ACString aSignature,
|
||||
in ACString aCertificateChain,
|
||||
in unsigned long aSource);
|
||||
in ACString aName);
|
||||
|
||||
/**
|
||||
* Creates a context to verify a content signature against data that is added
|
||||
* later with update calls.
|
||||
*
|
||||
* @param aData The first chunk of data to be tested.
|
||||
* This parameter is optional.
|
||||
* @param aContentSignatureHeader The signature of the data, url-safe base64
|
||||
* encoded.
|
||||
* @param aCertificateChain The certificate chain to use for
|
||||
* verification. PEM encoded string.
|
||||
* @param aSource The source of this verification (one of the
|
||||
* values defined above).
|
||||
* @param aName The (host)name for which the end entity must
|
||||
be valid.
|
||||
*/
|
||||
void createContext(in ACString aData, in ACString aSignature,
|
||||
in ACString aCertificateChain, in unsigned long aSource);
|
||||
in ACString aCertificateChain, in ACString aName);
|
||||
|
||||
/**
|
||||
* Adds data to the context that was used to generate the signature.
|
||||
|
|
|
@ -11,6 +11,9 @@ const PREF_SIGNATURE_ROOT = "security.content.signature.root_hash";
|
|||
|
||||
const TEST_DATA_DIR = "test_content_signing/";
|
||||
|
||||
const ONECRL_NAME = "oneCRL-signer.mozilla.org";
|
||||
const ABOUT_NEWTAB_NAME = "remote-newtab-signer.mozilla.org";
|
||||
|
||||
function getSignatureVerifier() {
|
||||
return Cc["@mozilla.org/security/contentsignatureverifier;1"]
|
||||
.createInstance(Ci.nsIContentSignatureVerifier);
|
||||
|
@ -55,23 +58,24 @@ function run_test() {
|
|||
let oneCRLRSAKeyChain = loadChain(TEST_DATA_DIR + "content_signing",
|
||||
["onecrl_RSA_ee", "int", "root"]);
|
||||
|
||||
let noSANChain = loadChain(TEST_DATA_DIR + "content_signing",
|
||||
["onecrl_no_SAN_ee", "int", "root"]);
|
||||
|
||||
// Check good signatures from good certificates with the correct SAN
|
||||
let chain1 = oneCRLChain.join("\n");
|
||||
let verifier = getSignatureVerifier();
|
||||
ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
|
||||
verifier.ONECRL),
|
||||
ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
|
||||
"A OneCRL signature should verify with the OneCRL chain");
|
||||
let chain2 = remoteNewTabChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
|
||||
verifier.ABOUT_NEWTAB),
|
||||
ABOUT_NEWTAB_NAME),
|
||||
"A newtab signature should verify with the newtab chain");
|
||||
|
||||
// Check a bad signature when a good chain is provided
|
||||
chain1 = oneCRLChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, BAD_SIGNATURE, chain1,
|
||||
verifier.ONECRL),
|
||||
ok(!verifier.verifyContentSignature(DATA, BAD_SIGNATURE, chain1, ONECRL_NAME),
|
||||
"A bad signature should not verify");
|
||||
|
||||
// Check a good signature from cert with good SAN but a different key than the
|
||||
|
@ -79,7 +83,7 @@ function run_test() {
|
|||
let badKeyChain = oneCRLBadKeyChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, badKeyChain,
|
||||
verifier.ONECRL),
|
||||
ONECRL_NAME),
|
||||
"A signature should not verify if the signing key is wrong");
|
||||
|
||||
// Check a good signature from cert with good SAN but a different key than the
|
||||
|
@ -87,35 +91,59 @@ function run_test() {
|
|||
let rsaKeyChain = oneCRLBadKeyChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, rsaKeyChain,
|
||||
verifier.ONECRL),
|
||||
ONECRL_NAME),
|
||||
"A signature should not verify if the signing key is wrong (RSA)");
|
||||
|
||||
// Check a good signature from cert with good SAN but with chain missing root
|
||||
let missingRoot = [oneCRLChain[0], oneCRLChain[1]].join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingRoot,
|
||||
verifier.ONECRL),
|
||||
ONECRL_NAME),
|
||||
"A signature should not verify if the chain is incomplete (missing root)");
|
||||
|
||||
// Check a good signature from cert with good SAN but with no path to root
|
||||
let missingInt = [oneCRLChain[0], oneCRLChain[2]].join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingInt,
|
||||
verifier.ONECRL),
|
||||
ONECRL_NAME),
|
||||
"A signature should not verify if the chain is incomplete (missing int)");
|
||||
|
||||
// Check good signatures from good certificates with incorrect SANs
|
||||
// Check good signatures from good certificates with the wrong SANs
|
||||
chain1 = oneCRLChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
|
||||
verifier.ABOUT_NEWTAB),
|
||||
"A OneCRL signature should not verify if the signer has the newtab SAN");
|
||||
ABOUT_NEWTAB_NAME),
|
||||
"A OneCRL signature should not verify if we require the newtab SAN");
|
||||
|
||||
chain2 = remoteNewTabChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
|
||||
verifier.ONECRL),
|
||||
"A newtab signature should not verify if the signer has the OneCRL SAN");
|
||||
ONECRL_NAME),
|
||||
"A newtab signature should not verify if we require the OneCRL SAN");
|
||||
|
||||
// Check good signatures with good chains with some other invalid names
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ""),
|
||||
"A signature should not verify if the SANs do not match an empty name");
|
||||
|
||||
let relatedName = "subdomain." + ONECRL_NAME;
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
|
||||
relatedName),
|
||||
"A signature should not verify if the SANs do not match a related name");
|
||||
|
||||
let randomName = "\xb1\x9bU\x1c\xae\xaa3\x19H\xdb\xed\xa1\xa1\xe0\x81\xfb" +
|
||||
"\xb2\x8f\x1cP\xe5\x8b\x9c\xc2s\xd3\x1f\x8e\xbbN";
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, randomName),
|
||||
"A signature should not verify if the SANs do not match a random name");
|
||||
|
||||
// check good signatures with chains that have strange or missing SANs
|
||||
chain1 = noSANChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
|
||||
ONECRL_NAME),
|
||||
"A signature should not verify if the SANs do not match a supplied name");
|
||||
|
||||
// Check malformed signature data
|
||||
chain1 = oneCRLChain.join("\n");
|
||||
|
@ -139,8 +167,7 @@ function run_test() {
|
|||
for (let badSig of bad_signatures) {
|
||||
throws(() => {
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.verifyContentSignature(DATA, badSig, chain1,
|
||||
verifier.ONECRL);
|
||||
verifier.verifyContentSignature(DATA, badSig, chain1, ONECRL_NAME);
|
||||
}, /NS_ERROR/, `Bad or malformed signature "${badSig}" should be rejected`);
|
||||
}
|
||||
|
||||
|
@ -175,7 +202,7 @@ function run_test() {
|
|||
throws(() => {
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, badChain,
|
||||
verifier.ONECRL);
|
||||
ONECRL_NAME);
|
||||
}, /NS_ERROR/, `Bad chain data starting "${badChain.substring(0, 80)}" ` +
|
||||
"should be rejected");
|
||||
}
|
||||
|
@ -184,14 +211,14 @@ function run_test() {
|
|||
// combination is provided
|
||||
chain1 = oneCRLChain.join("\n");
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
verifier.update(DATA);
|
||||
ok(verifier.end(),
|
||||
"A good signature should verify using the stream interface");
|
||||
|
||||
// Check that the streaming interface works with multiple update calls
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
for (let c of DATA) {
|
||||
verifier.update(c);
|
||||
}
|
||||
|
@ -203,7 +230,7 @@ function run_test() {
|
|||
verifier = getSignatureVerifier();
|
||||
let start = DATA.substring(0, 5);
|
||||
let rest = DATA.substring(start.length);
|
||||
verifier.createContext(start, GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext(start, GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
for (let c of rest) {
|
||||
verifier.update(c);
|
||||
}
|
||||
|
@ -212,23 +239,22 @@ function run_test() {
|
|||
|
||||
// Check that a bad chain / data combination fails
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
ok(!verifier.end(),
|
||||
"A bad signature should fail using the stream interface");
|
||||
|
||||
// Check that re-creating a context throws ...
|
||||
verifier = getSignatureVerifier();
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext("", GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
|
||||
// ... firstly, creating a context explicitly
|
||||
throws(() => {
|
||||
verifier.createContext(DATA, GOOD_SIGNATURE, chain1, verifier.ONECRL);
|
||||
verifier.createContext(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
}, /NS_ERROR/, "Ensure a verifier cannot be re-used with createContext");
|
||||
|
||||
// ... secondly, by calling verifyContentSignature
|
||||
throws(() => {
|
||||
verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
|
||||
verifier.ONECRL);
|
||||
verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME);
|
||||
}, /NS_ERROR/, "Ensure a verifier cannot be re-used with verifyContentSignature");
|
||||
|
||||
run_next_test();
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICHDCCAQagAwIBAgIUTeyXrUzlo8XY7YXkbLMw/fzVANQwCwYJKoZIhvcNAQEL
|
||||
MBExDzANBgNVBAMMBmludC1DQTAiGA8yMDE0MTEyNzAwMDAwMFoYDzIwMTcwMjA0
|
||||
MDAwMDAwWjAUMRIwEAYDVQQDDAllZS1pbnQtQ0EwdjAQBgcqhkjOPQIBBgUrgQQA
|
||||
IgNiAAShaHJDNitcexiJ83kVRhWhxz+0je6GPgIpFdtgjiUt5LcTLajOmOgxU05q
|
||||
nAwLCcjWOa3oMgbluoE0c6EfozDgXajJbkOD/ieHPalxA74oiM/wAvBa9xof3cyD
|
||||
dKpuqc6jFzAVMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAsGCSqGSIb3DQEBCwOCAQEA
|
||||
i3LD0MmaiOG7TvVV6z0ULN5iAMuj1BaTSHHv+Nu4ghSEuVzaTH7AfC8I8ZZr4T4A
|
||||
6zb7SQoUUy+6ltmNyNh2foL4Yr0MM9IOeyw61Qni17v0VSEzue/01ZuzgXnPYGcd
|
||||
JcXrlvBRsDEMvl5vf+mXx9jjcJfTqlR6oWAi4+WTlbZeEgZoSD6t/FN771wmXtR0
|
||||
6ELD0QNODXKbWhYVUkVNzLogjvcTV/x9Ud07G8Fq69DP/GmEtF/Vre4zhrlnNnMR
|
||||
N5FW8ynKH+dL7BYxxwPd23ABAIcemF6MR7reoIHazQJEwzMW7+E9Cpf4lLJnDSQ1
|
||||
h6xWbLCpvfvHPWIYI+Gy3w==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,4 @@
|
|||
issuer:int-CA
|
||||
subject:ee-int-CA
|
||||
subjectKey:secp384r1
|
||||
extension:extKeyUsage:codeSigning
|
|
@ -9,6 +9,7 @@
|
|||
# 'content_signing_root.pem',
|
||||
# 'content_signing_int.pem',
|
||||
# 'content_signing_onecrl_ee.pem',
|
||||
# 'content_signing_onecrl_no_SAN_ee.pem',
|
||||
# 'content_signing_onecrl_wrong_key_ee.pem',
|
||||
# 'content_signing_remote_newtab_ee.pem',
|
||||
#)
|
||||
|
|
Загрузка…
Ссылка в новой задаче