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:
Mark Goodwin 2016-04-18 14:55:56 +01:00
Родитель a86c730a63
Коммит fccc28a54a
6 изменённых файлов: 86 добавлений и 65 удалений

Просмотреть файл

@ -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',
#)