rc_crypto/
contentsignature.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::str;
6
7use base64::{
8    engine::general_purpose::{STANDARD, URL_SAFE},
9    Engine,
10};
11
12use crate::error::*;
13use crate::signature;
14
15/// Verify content signatures, with the ECDSA P384 curve and SHA-384 hashing (NIST384p / secp384r1).
16///
17/// These signatures are typically used to guarantee integrity of data between our servers and clients.
18/// This is a critical part of systems like Remote Settings or the experiment platform.
19///
20/// The equivalent implementation for Gecko is ``security/manager/ssl/nsIContentSignatureVerifier.idl``.
21///
22/// Decode a string with colon separated hexadecimal pairs into an array of bytes
23/// (eg. "3C:01:44" -> [60, 1, 68]).
24fn decode_root_hash(input: &str) -> Result<Vec<u8>> {
25    let bytes_hex = input.split(':');
26
27    let mut result: Vec<u8> = vec![];
28    for byte_hex in bytes_hex {
29        let byte = match hex::decode(byte_hex) {
30            Ok(v) => v,
31            Err(_) => return Err(ErrorKind::RootHashFormatError(input.to_string()).into()),
32        };
33        result.extend(byte);
34    }
35
36    Ok(result)
37}
38
39/// Split a certificate chain in PEM format into a list of certificates bytes,
40/// decoded from base64.
41fn split_pem(pem_content: &[u8]) -> Result<Vec<Vec<u8>>> {
42    let pem_str = match str::from_utf8(pem_content) {
43        Ok(v) => v,
44        Err(e) => {
45            return Err(ErrorKind::PEMFormatError(e.to_string()).into());
46        }
47    };
48
49    let pem_lines = pem_str.split('\n');
50
51    let mut blocks: Vec<Vec<u8>> = vec![];
52    let mut block: Vec<u8> = vec![];
53    let mut read = false;
54    for line in pem_lines {
55        if line.contains("-----BEGIN CERTIFICATE") {
56            read = true;
57        } else if line.contains("-----END CERTIFICATE") {
58            read = false;
59            let decoded = match STANDARD.decode(&block) {
60                Ok(v) => v,
61                Err(e) => return Err(ErrorKind::PEMFormatError(e.to_string()).into()),
62            };
63            blocks.push(decoded);
64            block.clear();
65        } else if read {
66            block.extend_from_slice(line.as_bytes());
67        }
68    }
69    if read {
70        return Err(ErrorKind::PEMFormatError("Missing end header".into()).into());
71    }
72    if blocks.is_empty() {
73        return Err(ErrorKind::PEMFormatError("Missing PEM data".into()).into());
74    }
75
76    Ok(blocks)
77}
78
79/// Verify that the signature matches the input data.
80///
81/// The data must be prefixed with ``Content-Signature:\u{0}``.
82/// The signature must be provided as base 64 url-safe encoded.
83/// The certificate chain, provided as PEM, must be valid at the provided current time.
84/// The root certificate content must match the provided root hash, and the leaf
85/// subject name must match the provided hostname.
86pub fn verify(
87    input: &[u8],
88    signature: &[u8],
89    pem_bytes: &[u8],
90    seconds_since_epoch: u64,
91    root_sha256_hash: &str,
92    hostname: &str,
93) -> Result<()> {
94    let certificates = split_pem(pem_bytes)?;
95
96    let mut certificates_slices: Vec<&[u8]> = vec![];
97    for certificate in &certificates {
98        certificates_slices.push(certificate);
99    }
100
101    let root_hash_bytes = decode_root_hash(root_sha256_hash)?;
102
103    nss::pkixc::verify_code_signing_certificate_chain(
104        certificates_slices,
105        seconds_since_epoch,
106        &root_hash_bytes,
107        hostname,
108    )
109    .map_err(|err| match err.kind() {
110        nss::ErrorKind::CertificateIssuerError => ErrorKind::CertificateIssuerError,
111        nss::ErrorKind::CertificateValidityError => ErrorKind::CertificateValidityError,
112        nss::ErrorKind::CertificateSubjectError => ErrorKind::CertificateSubjectError,
113        _ => ErrorKind::CertificateChainError(err.to_string()),
114    })?;
115
116    let leaf_cert = certificates.first().unwrap(); // PEM parse fails if len == 0.
117
118    let public_key_bytes = match nss::cert::extract_ec_public_key(leaf_cert) {
119        Ok(bytes) => bytes,
120        Err(err) => return Err(ErrorKind::CertificateContentError(err.to_string()).into()),
121    };
122
123    let signature_bytes = match URL_SAFE.decode(signature) {
124        Ok(b) => b,
125        Err(err) => return Err(ErrorKind::SignatureContentError(err.to_string()).into()),
126    };
127
128    // Since signature is NIST384p / secp384r1, we can perform a few safety checks.
129    if signature_bytes.len() != 96 {
130        return Err(ErrorKind::SignatureContentError(format!(
131            "signature contains {} bytes instead of {}",
132            signature_bytes.len(),
133            96
134        ))
135        .into());
136    }
137    if public_key_bytes.len() != 96 + 1 {
138        // coordinates with x04 prefix.
139        return Err(ErrorKind::CertificateContentError(format!(
140            "public key contains {} bytes instead of {}",
141            public_key_bytes.len(),
142            97
143        ))
144        .into());
145    }
146
147    let signature_alg = &signature::ECDSA_P384_SHA384;
148    let public_key = signature::UnparsedPublicKey::new(signature_alg, &public_key_bytes);
149    // Note that if the provided key type or curve is incorrect here, the signature will
150    // be considered as invalid.
151    match public_key.verify(input, &signature_bytes) {
152        Ok(_) => Ok(()),
153        Err(err) => Err(ErrorKind::SignatureMismatchError(err.to_string()).into()),
154    }
155}
156
157#[cfg(test)]
158mod test {
159    use super::*;
160
161    const ROOT_HASH: &str = "3C:01:44:6A:BE:90:36:CE:A9:A0:9A:CA:A3:A5:20:AC:62:8F:20:A7:AE:32:CE:86:1C:B2:EF:B7:0F:A0:C7:45";
162    const VALID_CERT_CHAIN: &[u8] = b"\
163-----BEGIN CERTIFICATE-----
164MIIDBjCCAougAwIBAgIIFml6g0ldRGowCgYIKoZIzj0EAwMwgaMxCzAJBgNVBAYT
165AlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3pp
166bGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTFFMEMGA1UEAww8Q29u
167dGVudCBTaWduaW5nIEludGVybWVkaWF0ZS9lbWFpbEFkZHJlc3M9Zm94c2VjQG1v
168emlsbGEuY29tMB4XDTIxMDIwMzE1MDQwNVoXDTIxMDQyNDE1MDQwNVowgakxCzAJ
169BgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFp
170biBWaWV3MRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMRcwFQYDVQQLEw5D
171bG91ZCBTZXJ2aWNlczE2MDQGA1UEAxMtcmVtb3RlLXNldHRpbmdzLmNvbnRlbnQt
172c2lnbmF0dXJlLm1vemlsbGEub3JnMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8pKb
173HX4IiD0SCy+NO7gwKqRRZ8IhGd8PTaIHIBgM6RDLRyDeswXgV+2kGUoHyzkbNKZt
174zlrS3AhqeUCtl1g6ECqSmZBbRTjCpn/UCpCnMLL0T0goxtAB8Rmi3CdM0cBUo4GD
175MIGAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME
176GDAWgBQlZawrqt0eUz/t6OdN45oKfmzy6DA4BgNVHREEMTAvgi1yZW1vdGUtc2V0
177dGluZ3MuY29udGVudC1zaWduYXR1cmUubW96aWxsYS5vcmcwCgYIKoZIzj0EAwMD
178aQAwZgIxAPh43Bxl4MxPT6Ra1XvboN5O2OvIn2r8rHvZPWR/jJ9vcTwH9X3F0aLJ
1799FiresnsLAIxAOoAcREYB24gFBeWxbiiXaG7TR/yM1/MXw4qxbN965FFUaoB+5Bc
180fS8//SQGTlCqKQ==
181-----END CERTIFICATE-----
182-----BEGIN CERTIFICATE-----
183MIIF2jCCA8KgAwIBAgIEAQAAADANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC
184VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRwwGgYDVQQK
185ExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0LmFkZG9ucy5zaWdu
186aW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytzdGFnZXJvb3RhZGRv
187bnNAbW96aWxsYS5jb20wHhcNMjEwMTExMDAwMDAwWhcNMjQxMTE0MjA0ODU5WjCB
188ozELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAt
189BgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMUUw
190QwYDVQQDDDxDb250ZW50IFNpZ25pbmcgSW50ZXJtZWRpYXRlL2VtYWlsQWRkcmVz
191cz1mb3hzZWNAbW96aWxsYS5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARw1dyE
192xV5aNiHJPa/fVHO6kxJn3oZLVotJ0DzFZA9r1sQf8i0+v78Pg0/c3nTAyZWfkULz
193vOpKYK/GEGBtisxCkDJ+F3NuLPpSIg3fX25pH0LE15fvASBVcr8tKLVHeOmjggG6
194MIIBtjAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAK
195BggrBgEFBQcDAzAdBgNVHQ4EFgQUJWWsK6rdHlM/7ejnTeOaCn5s8ugwgdkGA1Ud
196IwSB0TCBzoAUhtg0HE5Y0RNcmV/YQpjtFA8Z8l2hga+kgawwgakxCzAJBgNVBAYT
197AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEcMBoGA1UE
198ChMTQWRkb25zIFRlc3QgU2lnbmluZzEkMCIGA1UEAxMbdGVzdC5hZGRvbnMuc2ln
199bmluZy5yb290LmNhMTEwLwYJKoZIhvcNAQkBFiJzZWNvcHMrc3RhZ2Vyb290YWRk
200b25zQG1vemlsbGEuY29tggRgJZg7MDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRk
201b25zLmFsbGl6b20ub3JnL2NhL2NybC5wZW0wTgYDVR0eBEcwRaBDMCCCHi5jb250
202ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzAfgh1jb250ZW50LXNpZ25hdHVyZS5t
203b3ppbGxhLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEAtGTTzcPzpcdf07kIeRs9vPMx
204qiF8ylW5L/IQ2NzT3sFFAvPW1vW1wZC0xAHMsuVyo+BTGrv+4mlD0AUR9acRfiTZ
2059qyZ3sJbyhQwJAXLKU4YpnzuFOf58T/yOnOdwpH2ky/0FuHskMyfXaAz2Az4JXJH
206TCgggqfdZNvsZ5eOnQlKoC5NadMa8oTI5sd4SyR5ANUPAtYok931MvVSz3IMbwTr
207v4PPWXdl9SGXuOknSqdY6/bS1LGvC2KprsT+PBlvVtS6YgZOH0uCgTTLpnrco87O
208ErzC2PJBA1Ftn3Mbaou6xy7O+YX+reJ6soNUV+0JHOuKj0aTXv0c+lXEAh4Y8nea
209UGhW6+MRGYMOP2NuKv8s2+CtNH7asPq3KuTQpM5RerjdouHMIedX7wpNlNk0CYbg
210VMJLxZfAdwcingLWda/H3j7PxMoAm0N+eA24TGDQPC652ZakYk4MQL/45lm0A5f0
211xLGKEe6JMZcTBQyO7ANWcrpVjKMiwot6bY6S2xU17mf/h7J32JXZJ23OPOKpMS8d
212mljj4nkdoYDT35zFuS1z+5q6R5flLca35vRHzC3XA0H/XJvgOKUNLEW/IiJIqLNi
213ab3Ao0RubuX+CAdFML5HaJmkyuJvL3YtwIOwe93RGcGRZSKZsnMS+uY5QN8+qKQz
214LC4GzWQGSCGDyD+JCVw=
215-----END CERTIFICATE-----
216-----BEGIN CERTIFICATE-----
217MIIHbDCCBVSgAwIBAgIEYCWYOzANBgkqhkiG9w0BAQwFADCBqTELMAkGA1UEBhMC
218VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRwwGgYDVQQK
219ExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0LmFkZG9ucy5zaWdu
220aW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytzdGFnZXJvb3RhZGRv
221bnNAbW96aWxsYS5jb20wHhcNMjEwMjExMjA0ODU5WhcNMjQxMTE0MjA0ODU5WjCB
222qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBW
223aWV3MRwwGgYDVQQKExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0
224LmFkZG9ucy5zaWduaW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytz
225dGFnZXJvb3RhZGRvbnNAbW96aWxsYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
226DwAwggIKAoICAQDKRVty/FRsO4Ech6EYleyaKgAueaLYfMSsAIyPC/N8n/P8QcH8
227rjoiMJrKHRlqiJmMBSmjUZVzZAP0XJku0orLKWPKq7cATt+xhGY/RJtOzenMMsr5
228eN02V3GzUd1jOShUpERjzXdaO3pnfZqhdqNYqP9ocqQpyno7bZ3FZQ2vei+bF52k
22951uPioTZo+1zduoR/rT01twGtZm3QpcwU4mO74ysyxxgqEy3kpojq8Nt6haDwzrj
230khV9M6DGPLHZD71QaUiz5lOhD9CS8x0uqXhBhwMUBBkHsUDSxbN4ZhjDDWpCmwaD
231OtbJMUJxDGPCr9qj49QESccb367OeXLrfZ2Ntu/US2Bw9EDfhyNsXr9dg9NHj5yf
2324sDUqBHG0W8zaUvJx5T2Ivwtno1YZLyJwQW5pWeWn8bEmpQKD2KS/3y2UjlDg+YM
233NdNASjFe0fh6I5NCFYmFWA73DpDGlUx0BtQQU/eZQJ+oLOTLzp8d3dvenTBVnKF+
234uwEmoNfZwc4TTWJOhLgwxA4uK+Paaqo4Ap2RGS2ZmVkPxmroB3gL5n3k3QEXvULh
2357v8Psk4+MuNWnxudrPkN38MGJo7ju7gDOO8h1jLD4tdfuAqbtQLduLXzT4DJPA4y
236JBTFIRMIpMqP9CovaS8VPtMFLTrYlFh9UnEGpCeLPanJr+VEj7ae5sc8YwIDAQAB
237o4IBmDCCAZQwDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwFgYDVR0lAQH/
238BAwwCgYIKwYBBQUHAwMwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk
239IENlcnRpZmljYXRlMDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRkb25zLm1vemls
240bGEub3JnL2NhL2NybC5wZW0wHQYDVR0OBBYEFIbYNBxOWNETXJlf2EKY7RQPGfJd
241MIHZBgNVHSMEgdEwgc6AFIbYNBxOWNETXJlf2EKY7RQPGfJdoYGvpIGsMIGpMQsw
242CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcx
243HDAaBgNVBAoTE0FkZG9ucyBUZXN0IFNpZ25pbmcxJDAiBgNVBAMTG3Rlc3QuYWRk
244b25zLnNpZ25pbmcucm9vdC5jYTExMC8GCSqGSIb3DQEJARYic2Vjb3BzK3N0YWdl
245cm9vdGFkZG9uc0Btb3ppbGxhLmNvbYIEYCWYOzANBgkqhkiG9w0BAQwFAAOCAgEA
246nowyJv8UaIV7NA0B3wkWratq6FgA1s/PzetG/ZKZDIW5YtfUvvyy72HDAwgKbtap
247Eog6zGI4L86K0UGUAC32fBjE5lWYEgsxNM5VWlQjbgTG0dc3dYiufxfDFeMbAPmD
248DzpIgN3jHW2uRqa/MJ+egHhv7kGFL68uVLboqk/qHr+SOCc1LNeSMCuQqvHwwM0+
249AU1GxhzBWDkealTS34FpVxF4sT5sKLODdIS5HXJr2COHHfYkw2SW/Sfpt6fsOwaF
2502iiDaK4LPWHWhhIYa6yaynJ+6O6KPlpvKYCChaTOVdc+ikyeiSO6AakJykr5Gy7d
251PkkK7MDCxuY6psHj7iJQ59YK7ujQB8QYdzuXBuLLo5hc5gBcq3PJs0fLT2YFcQHA
252dj+olGaDn38T0WI8ycWaFhQfKwATeLWfiQepr8JfoNlC2vvSDzGUGfdAfZfsJJZ8
2535xZxahHoTFGS0mDRfXqzKH5uD578GgjOZp0fULmzkcjWsgzdpDhadGjExRZFKlAy
254iKv8cXTONrGY0fyBDKennuX0uAca3V0Qm6v2VRp+7wG/pywWwc5n+04qgxTQPxgO
2556pPB9UUsNbaLMDR5QPYAWrNhqJ7B07XqIYJZSwGP5xB9NqUZLF4z+AOMYgWtDpmg
256IKdcFKAt3fFrpyMhlfIKkLfmm0iDjmfmIXbDGBJw9SE=
257-----END CERTIFICATE-----";
258    const VALID_INPUT: &[u8] =
259        b"Content-Signature:\x00{\"data\":[],\"last_modified\":\"1603992731957\"}";
260    const VALID_SIGNATURE: &[u8] = b"fJJcOpwdnkjEWFeHXfdOJN6GaGLuDTPGzQOxA2jn6ldIleIk6KqMhZcy2GZv2uYiGwl6DERWwpaoUfQFLyCAOcVjck1qlaaEFZGY1BQba9p99xEc9FNQ3YPPfvSSZqsw";
261    const VALID_HOSTNAME: &str = "remote-settings.content-signature.mozilla.org";
262
263    const INVALID_CERTIFICATE: &[u8] = b"\
264    -----BEGIN CERTIFICATE-----
265    invalidCertificategIFiJLFfdxFlYwCgYIKoZIzj0EAwMwgaMxCzAJBgNVBAYT
266    AlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3pp
267    bGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTFFMEMGA1UEAww8Q29u
268    dGVudCBTaWduaW5nIEludGVybWVkaWF0ZS9lbWFpbEFkZHJlc3M9Zm94c2VjQG1v
269    emlsbGEuY29tMB4XDTIwMDYxNjE3MTYxNVoXDTIwMDkwNDE3MTYxNVowgakxCzAJ
270    BgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFp
271    biBWaWV3MRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMRcwFQYDVQQLEw5D
272    bG91ZCBTZXJ2aWNlczE2MDQGA1UEAxMtcmVtb3RlLXNldHRpbmdzLmNvbnRlbnQt
273    c2lnbmF0dXJlLm1vemlsbGEub3JnMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEDmOX
274    N5IGlUqCvu6xkOKr020Eo3kY2uPdJO0ZihVUoglk1ktQPss184OajFOMKm/BJX4W
275    IsZUzQoRL8NgGfZDwBjT95Q87lhOWEWs5AU/nMXIYwDp7rpUPaUqw0QLMikdo4GD
276    MIGAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME
277    GDAWgBSgHUoXT4zCKzVF8WPx2nBwp8744TA4BgNVHREEMTAvgi1yZW1vdGUtc2V0
278    dGluZ3MuY29udGVudC1zaWduYXR1cmUubW96aWxsYS5vcmcwCgYIKoZIzj0EAwMD
279    aQAwZgIxAJvyynyPqRmRMqf95FPH5xfcoT3jb/2LOkUifGDtjtZ338ScpT2glUK8
280    HszKVANqXQIxAIygMaeTiD9figEusmHMthBdFoIoHk31x4MHukAy+TWZ863X6/V2
281    6/ZrZMpinvalid==
282    -----END CERTIFICATE-----";
283
284    #[test]
285    fn test_decode_root_hash() {
286        nss::ensure_initialized();
287        assert!(decode_root_hash("meh!").is_err());
288        assert!(decode_root_hash("3C:rr:44").is_err());
289
290        let result = decode_root_hash(ROOT_HASH).unwrap();
291        assert_eq!(
292            result,
293            vec![
294                60, 1, 68, 106, 190, 144, 54, 206, 169, 160, 154, 202, 163, 165, 32, 172, 98, 143,
295                32, 167, 174, 50, 206, 134, 28, 178, 239, 183, 15, 160, 199, 69
296            ]
297        );
298    }
299
300    #[test]
301    fn test_split_pem() {
302        assert!(split_pem(b"meh!").is_err());
303
304        assert!(split_pem(
305            b"-----BEGIN CERTIFICATE-----
306invalidCertificate
307-----END CERTIFICATE-----"
308        )
309        .is_err());
310
311        assert!(split_pem(
312            b"-----BEGIN CERTIFICATE-----
313bGxhIEFNTyBQcm9kdWN0aW9uIFNp
314-----BEGIN CERTIFICATE-----"
315        )
316        .is_err());
317
318        let result = split_pem(
319            b"-----BEGIN CERTIFICATE-----
320AQID
321BAUG
322-----END CERTIFICATE-----
323-----BEGIN CERTIFICATE-----
324/f7/
325-----END CERTIFICATE-----",
326        )
327        .unwrap();
328        assert_eq!(result, vec![vec![1, 2, 3, 4, 5, 6], vec![253, 254, 255]]);
329    }
330
331    #[test]
332    fn test_verify_fails_if_invalid() {
333        nss::ensure_initialized();
334        assert!(verify(
335            b"msg",
336            b"sig",
337            b"-----BEGIN CERTIFICATE-----
338fdfeff
339-----END CERTIFICATE-----",
340            42,
341            ROOT_HASH,
342            "remotesettings.firefox.com",
343        )
344        .is_err());
345    }
346
347    #[test]
348    fn test_verify_fails_if_cert_has_expired() {
349        nss::ensure_initialized();
350        assert!(verify(
351            VALID_INPUT,
352            VALID_SIGNATURE,
353            VALID_CERT_CHAIN,
354            1215559719, // July 9, 2008
355            ROOT_HASH,
356            VALID_HOSTNAME,
357        )
358        .is_err());
359    }
360
361    #[test]
362    fn test_verify_fails_if_bad_certificate_chain() {
363        nss::ensure_initialized();
364        assert!(verify(
365            VALID_INPUT,
366            VALID_SIGNATURE,
367            INVALID_CERTIFICATE,
368            1615559719, // March 12, 2021
369            ROOT_HASH,
370            VALID_HOSTNAME,
371        )
372        .is_err());
373    }
374
375    #[test]
376    fn test_verify_fails_if_mismatch() {
377        nss::ensure_initialized();
378        assert!(verify(
379            b"msg",
380            VALID_SIGNATURE,
381            VALID_CERT_CHAIN,
382            1615559719, // March 12, 2021
383            ROOT_HASH,
384            VALID_HOSTNAME,
385        )
386        .is_err());
387    }
388
389    #[test]
390    fn test_verify_fails_if_bad_hostname() {
391        nss::ensure_initialized();
392        assert!(verify(
393            VALID_INPUT,
394            VALID_SIGNATURE,
395            VALID_CERT_CHAIN,
396            1615559719, // March 12, 2021
397            ROOT_HASH,
398            "some.hostname.org",
399        )
400        .is_err());
401    }
402
403    #[test]
404    fn test_verify_fails_if_bad_root_hash() {
405        nss::ensure_initialized();
406        assert!(verify(
407            VALID_INPUT,
408            VALID_SIGNATURE,
409            VALID_CERT_CHAIN,
410            1615559719, // March 12, 2021
411            "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
412            VALID_HOSTNAME,
413        )
414        .is_err());
415    }
416
417    #[test]
418    fn test_verify_succeeds_if_valid() {
419        nss::ensure_initialized();
420        verify(
421            VALID_INPUT,
422            VALID_SIGNATURE,
423            VALID_CERT_CHAIN,
424            1615559719, // March 12, 2021
425            ROOT_HASH,
426            VALID_HOSTNAME,
427        )
428        .unwrap();
429    }
430}