app_store_server_library/
chain_verifier.rs1use crate::chain_verifier::ChainVerificationFailureReason::{CertificateExpired, InvalidCertificate, InvalidChainLength, InvalidEffectiveDate};
2use thiserror::Error;
3
4use x509_parser::certificate::X509Certificate;
5use x509_parser::der_parser::asn1_rs::oid;
6use x509_parser::error::X509Error;
7use x509_parser::prelude::{ASN1Time, FromDer};
8
9#[derive(Error, Debug, PartialEq)]
10pub enum ChainVerifierError {
11 #[error("VerificationFailure: [{0}]")]
12 VerificationFailure(ChainVerificationFailureReason),
13
14 #[error("InternalX509Error: [{0}]")]
15 InternalX509Error(#[from] X509Error),
16
17 #[error("InternalDecodeError: [{0}]")]
18 InternalDecodeError(#[from] base64::DecodeError),
19}
20
21#[derive(Error, Debug, PartialEq)]
22pub enum ChainVerificationFailureReason {
23 #[error("InvalidAppIdentifier")]
24 InvalidAppIdentifier,
25
26 #[error("InvalidIssuer")]
27 InvalidIssuer,
28
29 #[error("InvalidCertificate")]
30 InvalidCertificate,
31
32 #[error("InvalidChainLength")]
33 InvalidChainLength,
34
35 #[error("InvalidChain")]
36 InvalidChain,
37
38 #[error("InvalidEnvironment")]
39 InvalidEffectiveDate,
40
41 #[error("CertificateExpired")]
42 CertificateExpired,
43
44 #[error("CertificateRevoked")]
45 CertificateRevoked,
46}
47
48const EXPECTED_CHAIN_LENGTH: usize = 3;
49
50pub fn verify_chain(
85 certificates: &Vec<Vec<u8>>,
86 root_certificates: &Vec<Vec<u8>>,
87 effective_date: Option<u64>,
88) -> Result<Vec<u8>, ChainVerifierError> {
89 if root_certificates.is_empty() {
90 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
91 }
92
93 if certificates.len() != EXPECTED_CHAIN_LENGTH {
94 return Err(ChainVerifierError::VerificationFailure(InvalidChainLength));
95 }
96
97 let leaf_certificate = &certificates[0];
98 let Ok(leaf_certificate) = X509Certificate::from_der(leaf_certificate.as_slice()) else {
99 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
100 };
101 let leaf_certificate = leaf_certificate.1;
102
103 let Some(_) = leaf_certificate.get_extension_unique(&oid!(1.2.840.113635.100.6.11.1))?
104 else {
105 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
106 };
107
108 let intermediate_certificate = &certificates[1];
109 let Ok(intermediate_certificate) =
110 X509Certificate::from_der(intermediate_certificate.as_slice())
111 else {
112 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
113 };
114 let intermediate_certificate = intermediate_certificate.1;
115
116 let Some(_) =
117 intermediate_certificate.get_extension_unique(&oid!(1.2.840.113635.100.6.2.1))?
118 else {
119 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
120 };
121
122 let mut root_certificate: Option<X509Certificate> = None;
123
124 for cert in root_certificates {
125 let Ok(cert) = X509Certificate::from_der(&cert) else {
126 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
127 };
128
129 match intermediate_certificate.verify_signature(Some(cert.1.public_key())) {
130 Ok(_) => (),
131 Err(_) => continue,
132 }
133
134 root_certificate = Some(cert.1)
135 }
136
137 let Some(root_certificate) = root_certificate else {
138 return Err(ChainVerifierError::VerificationFailure(InvalidCertificate));
139 };
140
141 verify_chain_impl(&leaf_certificate, &intermediate_certificate, &root_certificate, effective_date)
142}
143
144fn verify_chain_impl(leaf: &X509Certificate, intermediate: &X509Certificate, root_certificate: &X509Certificate, effective_date: Option<u64>) -> Result<Vec<u8>, ChainVerifierError> {
145 leaf.verify_signature(Some(intermediate.public_key()))?;
146
147 if let Some(date) = effective_date {
148 let Ok(time) = ASN1Time::from_timestamp(i64::try_from(date).unwrap()) else {
149 return Err(ChainVerifierError::VerificationFailure(
150 InvalidEffectiveDate,
151 ));
152 };
153
154 if !(root_certificate.validity.is_valid_at(time) &&
155 leaf.validity.is_valid_at(time) &&
156 intermediate.validity.is_valid_at(time)) {
157 return Err(ChainVerifierError::VerificationFailure(CertificateExpired));
158 }
159 }
160
161 let k = leaf.public_key().raw.to_vec();
162
163 #[cfg(all(feature = "ocsp"))] {
165 use crate::chain_verifier_ocsp::check_ocsp_status;
166 match check_ocsp_status(leaf, intermediate) {
168 Ok(()) => {
169 }
171 Err(ChainVerifierError::VerificationFailure(ChainVerificationFailureReason::CertificateRevoked)) => {
172 return Err(ChainVerifierError::VerificationFailure(ChainVerificationFailureReason::CertificateRevoked));
174 }
175 Err(e) => {
176 eprintln!("OCSP check failed (non-fatal): {:?}", e);
178 }
179 }
180 };
181
182 Ok(k)
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::utils::StringExt;
189 use base64::engine::general_purpose::STANDARD;
190 use base64::{DecodeError, Engine};
191 extern crate base64;
192
193 const ROOT_CA_BASE64_ENCODED: &str = "MIIBgjCCASmgAwIBAgIJALUc5ALiH5pbMAoGCCqGSM49BAMDMDYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDdXBlcnRpbm8wHhcNMjMwMTA1MjEzMDIyWhcNMzMwMTAyMjEzMDIyWjA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc+/Bl+gospo6tf9Z7io5tdKdrlN1YdVnqEhEDXDShzdAJPQijamXIMHf8xWWTa1zgoYTxOKpbuJtDplz1XriTaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDRwAwRAIgemWQXnMAdTad2JDJWng9U4uBBL5mA7WI05H7oH7c6iQCIHiRqMjNfzUAyiu9h6rOU/K+iTR0I/3Y/NSWsXHX+acc";
194 const INTERMEDIATE_CA_BASE64_ENCODED: &str = "MIIBnzCCAUWgAwIBAgIBCzAKBggqhkjOPQQDAzA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMB4XDTIzMDEwNTIxMzEwNVoXDTMzMDEwMTIxMzEwNVowRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xFTATBgNVBAoMDEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBUN5V9rKjfRiMAIojEA0Av5Mp0oF+O0cL4gzrTF178inUHugj7Et46NrkQ7hKgMVnjogq45Q1rMs+cMHVNILWqjNTAzMA8GA1UdEwQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMBAGCiqGSIb3Y2QGAgEEAgUAMAoGCCqGSM49BAMDA0gAMEUCIQCmsIKYs41ullssHX4rVveUT0Z7Is5/hLK1lFPTtun3hAIgc2+2RG5+gNcFVcs+XJeEl4GZ+ojl3ROOmll+ye7dynQ=";
195 const LEAF_CERT_BASE64_ENCODED: &str = "MIIBoDCCAUagAwIBAgIBDDAKBggqhkjOPQQDAzBFMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUN1cGVydGlubzEVMBMGA1UECgwMSW50ZXJtZWRpYXRlMB4XDTIzMDEwNTIxMzEzNFoXDTMzMDEwMTIxMzEzNFowPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xDTALBgNVBAoMBExlYWYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATitYHEaYVuc8g9AjTOwErMvGyPykPa+puvTI8hJTHZZDLGas2qX1+ErxgQTJgVXv76nmLhhRJH+j25AiAI8iGsoy8wLTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAKBggqhkjOPQQDAwNIADBFAiBX4c+T0Fp5nJ5QRClRfu5PSByRvNPtuaTsk0vPB3WAIAIhANgaauAj/YP9s0AkEhyJhxQO/6Q2zouZ+H1CIOehnMzQ";
196
197 const INTERMEDIATE_CA_INVALID_OID_BASE64_ENCODED: &str = "MIIBnjCCAUWgAwIBAgIBDTAKBggqhkjOPQQDAzA2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ3VwZXJ0aW5vMB4XDTIzMDEwNTIxMzYxNFoXDTMzMDEwMTIxMzYxNFowRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xFTATBgNVBAoMDEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBUN5V9rKjfRiMAIojEA0Av5Mp0oF+O0cL4gzrTF178inUHugj7Et46NrkQ7hKgMVnjogq45Q1rMs+cMHVNILWqjNTAzMA8GA1UdEwQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMBAGCiqGSIb3Y2QGAgIEAgUAMAoGCCqGSM49BAMDA0cAMEQCIFROtTE+RQpKxNXETFsf7Mc0h+5IAsxxo/X6oCC/c33qAiAmC5rn5yCOOEjTY4R1H1QcQVh+eUwCl13NbQxWCuwxxA==";
198 const LEAF_CERT_FOR_INTERMEDIATE_CA_INVALID_OID_BASE64_ENCODED: &str = "MIIBnzCCAUagAwIBAgIBDjAKBggqhkjOPQQDAzBFMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUN1cGVydGlubzEVMBMGA1UECgwMSW50ZXJtZWRpYXRlMB4XDTIzMDEwNTIxMzY1OFoXDTMzMDEwMTIxMzY1OFowPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xDTALBgNVBAoMBExlYWYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATitYHEaYVuc8g9AjTOwErMvGyPykPa+puvTI8hJTHZZDLGas2qX1+ErxgQTJgVXv76nmLhhRJH+j25AiAI8iGsoy8wLTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAKBggqhkjOPQQDAwNHADBEAiAUAs+gzYOsEXDwQquvHYbcVymyNqDtGw9BnUFp2YLuuAIgXxQ3Ie9YU0cMqkeaFd+lyo0asv9eyzk6stwjeIeOtTU=";
199 const LEAF_CERT_INVALID_OID_BASE64_ENCODED: &str = "MIIBoDCCAUagAwIBAgIBDzAKBggqhkjOPQQDAzBFMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUN1cGVydGlubzEVMBMGA1UECgwMSW50ZXJtZWRpYXRlMB4XDTIzMDEwNTIxMzczMVoXDTMzMDEwMTIxMzczMVowPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xDTALBgNVBAoMBExlYWYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATitYHEaYVuc8g9AjTOwErMvGyPykPa+puvTI8hJTHZZDLGas2qX1+ErxgQTJgVXv76nmLhhRJH+j25AiAI8iGsoy8wLTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsCBAIFADAKBggqhkjOPQQDAwNIADBFAiAb+7S3i//bSGy7skJY9+D4VgcQLKFeYfIMSrUCmdrFqwIhAIMVwzD1RrxPRtJyiOCXLyibIvwcY+VS73HYfk0O9lgz";
200
201 const LEAF_CERT_PUBLIC_KEY_BASE64_ENCODED: &str = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4rWBxGmFbnPIPQI0zsBKzLxsj8pD2vqbr0yPISUx2WQyxmrNql9fhK8YEEyYFV7++p5i4YUSR/o9uQIgCPIhrA==";
202
203 const REAL_APPLE_ROOT_BASE64_ENCODED: &str = "MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtfTjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySrMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM6BgD56KyKA==";
204 const REAL_APPLE_MULTI_ROOT_BASE64_ENCODED: [&'static str; 4] = [
205 "MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh",
206 "MIIFujCCBKKgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhjELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFEFwcGxlIENvbXB1dGVyLCBJbmMuMS0wKwYDVQQLEyRBcHBsZSBDb21wdXRlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxKTAnBgNVBAMTIEFwcGxlIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTA1MDIxMDAwMTgxNFoXDTI1MDIxMDAwMTgxNFowgYYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBcHBsZSBDb21wdXRlciwgSW5jLjEtMCsGA1UECxMkQXBwbGUgQ29tcHV0ZXIgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSkwJwYDVQQDEyBBcHBsZSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOSRqQkfkdseR1DrBe1eeYQt6zaiV0xV7IsZid75S2z1B6siMALoGD74UAnTf0GomPnRymacJGsR0KO75Bsqwx+VnnoMpEeLW9QWNzPLxA9NzhRp0ckZcvVdDtV/X5vyJQO6VY9NXQ3xZDUjFUsVWR2zlPf2nJ7PULrBWFBnjwi0IPfLrCwgb3C2PwEwjLdDzw+dPfMrSSgayP7OtbkO2V4c1ss9tTqt9A8OAJILsSEWLnTVPA3bYharo3GSR1NVwa8vQbP4++NwzeajTEV+H0xrUJZBicR0YgsQg0GHM4qBsTBY7FoEMoxos48d3mVz/2deZbxJ2HafMxRloXeUyS0CAwEAAaOCAi8wggIrMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjAfBgNVHSMEGDAWgBQr0GlHlHYJ/vRrjS5ApvdHTX8IXjCCASkGA1UdIASCASAwggEcMIIBGAYJKoZIhvdjZAUBMIIBCTBBBggrBgEFBQcCARY1aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L3Rlcm1zLmh0bWwwgcMGCCsGAQUFBwICMIG2GoGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9mIHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kgYW5kIGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4wRAYDVR0fBD0wOzA5oDegNYYzaHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L3Jvb3QuY3JsMFUGCCsGAQUFBwEBBEkwRzBFBggrBgEFBQcwAoY5aHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5L2Nhc2lnbmVycy5odG1sMA0GCSqGSIb3DQEBBQUAA4IBAQCd2i0oWC99dgS5BNM+zrdmY06PL9T+S61yvaM5xlJNBZhS9YlRASR5vhoy9+VEi0tEBzmC1lrKtCBe2a4VXR2MHTK/ODFiSF3H4ZCx+CRA+F9Ym1FdV53B5f88zHIhbsTp6aF31ywXJsM/65roCwO66bNKcuszCVut5mIxauivL9WvHld2j383LS4CXN1jyfJxuCZA3xWNdUQ/eb3mHZnhQyw+rW++uaT+DjUZUWOxw961kj5ReAFziqQjyqSI8R5cH0EWLX6VCqrpiUGYGxrdyyC/R14MJsVVNU3GMIuZZxTHCR+6R8faAQmHJEKVvRNgGQrv6n8Obs3BREM6StXj",
207 "MIIFkjCCA3qgAwIBAgIIAeDltYNno+AwDQYJKoZIhvcNAQEMBQAwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEcyMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxMDA5WhcNMzkwNDMwMTgxMDA5WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgREkhI2imKScUcx+xuM23+TfvgHN6sXuI2pyT5f1BrTM65MFQn5bPW7SXmMLYFN14UIhHF6Kob0vuy0gmVOKTvKkmMXT5xZgM4+xb1hYjkWpIMBDLyyED7Ul+f9sDx47pFoFDVEovy3d6RhiPw9bZyLgHaC/YuOQhfGaFjQQscp5TBhsRTL3b2CtcM0YM/GlMZ81fVJ3/8E7j4ko380yhDPLVoACVdJ2LT3VXdRCCQgzWTxb+4Gftr49wIQuavbfqeQMpOhYV4SbHXw8EwOTKrfl+q04tvny0aIWhwZ7Oj8ZhBbZF8+NfbqOdfIRqMM78xdLe40fTgIvS/cjTf94FNcX1RoeKz8NMoFnNvzcytN31O661A4T+B/fc9Cj6i8b0xlilZ3MIZgIxbdMYs0xBTJh0UT8TUgWY8h2czJxQI6bR3hDRSj4n4aJgXv8O7qhOTH11UL6jHfPsNFL4VPSQ08prcdUFmIrQB1guvkJ4M6mL4m1k8COKWNORj3rw31OsMiANDC1CvoDTdUE0V+1ok2Az6DGOeHwOx4e7hqkP0ZmUoNwIx7wHHHtHMn23KVDpA287PT0aLSmWaasZobNfMmRtHsHLDd4/E92GcdB/O/WuhwpyUgquUoue9G7q5cDmVF8Up8zlYNPXEpMZ7YLlmQ1A/bmH8DvmGqmAMQ0uVAgMBAAGjQjBAMB0GA1UdDgQWBBTEmRNsGAPCe8CjoA1/coB6HHcmjTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAUabz4vS4PZO/Lc4Pu1vhVRROTtHlznldgX/+tvCHM/jvlOV+3Gp5pxy+8JS3ptEwnMgNCnWefZKVfhidfsJxaXwU6s+DDuQUQp50DhDNqxq6EWGBeNjxtUVAeKuowM77fWM3aPbn+6/Gw0vsHzYmE1SGlHKy6gLti23kDKaQwFd1z4xCfVzmMX3zybKSaUYOiPjjLUKyOKimGY3xn83uamW8GrAlvacp/fQ+onVJv57byfenHmOZ4VxG/5IFjPoeIPmGlFYl5bRXOJ3riGQUIUkhOb9iZqmxospvPyFgxYnURTbImHy99v6ZSYA7LNKmp4gDBDEZt7Y6YUX6yfIjyGNzv1aJMbDZfGKnexWoiIqrOEDCzBL/FePwN983csvMmOa/orz6JopxVtfnJBtIRD6e/J/JzBrsQzwBvDR4yGn1xuZW7AYJNpDrFEobXsmII9oDMJELuDY++ee1KG++P+w8j2Ud5cAeh6Squpj9kuNsJnfdBrRkBof0Tta6SqoWqPQFZ2aWuuJVecMsXUmPgEkrihLHdoBR37q9ZV0+N0djMenl9MU/S60EinpxLK8JQzcPqOMyT/RFtm2XNuyE9QoB6he7hY1Ck3DDUOUUi78/w0EP3SIEIwiKum1xRKtzCTrJ+VKACd+66eYWyi4uTLLT3OUEVLLUNIAytbwPF+E=",
208 "MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtfTjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySrMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM6BgD56KyKA==",
209 ];
210 const REAL_APPLE_INTERMEDIATE_BASE64_ENCODED: &str = "MIIDFjCCApygAwIBAgIUIsGhRwp0c2nvU4YSycafPTjzbNcwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMjEwMzE3MjAzNzEwWhcNMzYwMzE5MDAwMDAwWjB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbsQKC94PrlWmZXnXgtxzdVJL8T0SGYngDRGpngn3N6PT8JMEb7FDi4bBmPhCnZ3/sq6PF/cGcKXWsL5vOteRhyJ45x3ASP7cOB+aao90fcpxSv/EZFbniAbNgZGhIhpIo4H6MIH3MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNhZzMwNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwHQYDVR0OBBYEFD8vlCNR01DJmig97bB85c+lkGKZMA4GA1UdDwEB/wQEAwIBBjAQBgoqhkiG92NkBgIBBAIFADAKBggqhkjOPQQDAwNoADBlAjBAXhSq5IyKogMCPtw490BaB677CaEGJXufQB/EqZGd6CSjiCtOnuMTbXVXmxxcxfkCMQDTSPxarZXvNrkxU3TkUMI33yzvFVVRT4wxWJC994OsdcZ4+RGNsYDyR5gmdr0nDGg=";
211 const REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED: &str = "MIIEMDCCA7agAwIBAgIQaPoPldvpSoEH0lBrjDPv9jAKBggqhkjOPQQDAzB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTIxMDgyNTAyNTAzNFoXDTIzMDkyNDAyNTAzM1owgZIxQDA+BgNVBAMMN1Byb2QgRUNDIE1hYyBBcHAgU3RvcmUgYW5kIGlUdW5lcyBTdG9yZSBSZWNlaXB0IFNpZ25pbmcxLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOoTcaPcpeipNL9eQ06tCu7pUcwdCXdN8vGqaUjd58Z8tLxiUC0dBeA+euMYggh1/5iAk+FMxUFmA2a1r4aCZ8SjggIIMIICBDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFD8vlCNR01DJmig97bB85c+lkGKZMHAGCCsGAQUFBwEBBGQwYjAtBggrBgEFBQcwAoYhaHR0cDovL2NlcnRzLmFwcGxlLmNvbS93d2RyZzYuZGVyMDEGCCsGAQUFBzABhiVodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLXd3ZHJnNjAyMIIBHgYDVR0gBIIBFTCCAREwggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wHQYDVR0OBBYEFCOCmMBq//1L5imvVmqX1oCYeqrMMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAKBggqhkjOPQQDAwNoADBlAjEAl4JB9GJHixP2nuibyU1k3wri5psGIxPME05sFKq7hQuzvbeyBu82FozzxmbzpogoAjBLSFl0dZWIYl2ejPV+Di5fBnKPu8mymBQtoE/H2bES0qAs8bNueU3CBjjh1lwnDsI=";
212 const EFFECTIVE_DATE: u64 = 1681312846;
213
214 #[test]
215 #[cfg(all(feature = "ocsp"))]
216 fn test_apple_chain_is_valid_with_ocsp() -> Result<(), ChainVerifierError> {
217 let root = crate::chain_verifier::tests::REAL_APPLE_ROOT_BASE64_ENCODED.as_der_bytes().unwrap();
218 let leaf = crate::chain_verifier::tests::REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED
219 .as_der_bytes()
220 .unwrap();
221 let intermediate = crate::chain_verifier::tests::REAL_APPLE_INTERMEDIATE_BASE64_ENCODED
222 .as_der_bytes()
223 .unwrap();
224 let chain = vec![leaf.clone(), intermediate, root.clone()];
225
226 let _public_key = verify_chain(&chain, &vec![root], Some(crate::chain_verifier::tests::EFFECTIVE_DATE)).unwrap();
227 Ok(())
228 }
229
230 #[test]
231 #[cfg(not(feature = "ocsp"))]
232 fn test_valid_chain_without_ocsp() -> Result<(), ChainVerifierError> {
233 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
234 let leaf = LEAF_CERT_BASE64_ENCODED.as_der_bytes().unwrap();
235 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
236 let chain = vec![leaf.clone(), intermediate, root.clone()];
237
238 let public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE))?;
239 assert_eq!(
240 LEAF_CERT_PUBLIC_KEY_BASE64_ENCODED.as_der_bytes().unwrap(),
241 public_key
242 );
243 Ok(())
244 }
245
246 #[test]
247 #[cfg(not(feature = "ocsp"))]
248 fn test_valid_chain_invalid_intermediate_oid_without_ocsp() -> Result<(), ChainVerifierError> {
249 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
250 let leaf = LEAF_CERT_FOR_INTERMEDIATE_CA_INVALID_OID_BASE64_ENCODED
251 .as_der_bytes()
252 .unwrap();
253 let intermediate = INTERMEDIATE_CA_INVALID_OID_BASE64_ENCODED
254 .as_der_bytes()
255 .unwrap();
256 let chain = vec![leaf.clone(), intermediate, root.clone()];
257
258 let public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE));
259 assert_eq!(
260 public_key.expect_err("Expect error"),
261 ChainVerifierError::VerificationFailure(InvalidCertificate)
262 );
263 Ok(())
264 }
265
266 #[test]
267 #[cfg(not(feature = "ocsp"))]
268 fn test_valid_chain_invalid_leaf_oid_without_ocsp() -> Result<(), ChainVerifierError> {
269 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
270 let leaf = LEAF_CERT_INVALID_OID_BASE64_ENCODED.as_der_bytes().unwrap();
271 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
272 let chain = vec![leaf.clone(), intermediate, root.clone()];
273
274 let public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE));
275 assert_eq!(
276 public_key.expect_err("Expect error"),
277 ChainVerifierError::VerificationFailure(InvalidCertificate)
278 );
279 Ok(())
280 }
281
282 #[test]
283 #[cfg(not(feature = "ocsp"))]
284 fn test_invalid_chain_length() -> Result<(), ChainVerifierError> {
285 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
286 let leaf = LEAF_CERT_BASE64_ENCODED.as_der_bytes().unwrap();
287 let intermediate = INTERMEDIATE_CA_INVALID_OID_BASE64_ENCODED
288 .as_der_bytes()
289 .unwrap();
290 let chain = vec![leaf.clone(), intermediate];
291
292 let public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE));
293 assert_eq!(
294 public_key.expect_err("Expect error"),
295 ChainVerifierError::VerificationFailure(InvalidChainLength)
296 );
297 Ok(())
298 }
299
300 #[test]
301 fn test_invalid_base64_in_certificate_list() -> Result<(), ChainVerifierError> {
302 assert_eq!(
303 "abc".as_der_bytes().expect_err("Expect Error"),
304 DecodeError::InvalidPadding
305 );
306 Ok(())
307 }
308
309 #[test]
310 #[cfg(not(feature = "ocsp"))]
311 fn test_invalid_data_in_certificate_list() -> Result<(), ChainVerifierError> {
312 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
313 let leaf = STANDARD.encode("abc").as_der_bytes().unwrap();
314 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
315 let chain = vec![leaf.clone(), intermediate, root.clone()];
316
317 let public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE));
318 assert_eq!(
319 public_key.expect_err("Expect error"),
320 ChainVerifierError::VerificationFailure(InvalidCertificate)
321 );
322 Ok(())
323 }
324
325 #[test]
326 #[cfg(not(feature = "ocsp"))]
327 fn test_malformed_root_cert() -> Result<(), ChainVerifierError> {
328 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
329 let malformed_root = STANDARD.encode("abc").as_der_bytes().unwrap();
330 let leaf = LEAF_CERT_BASE64_ENCODED.as_der_bytes().unwrap();
331 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
332 let chain = vec![leaf.clone(), intermediate, root.clone()];
333
334 let public_key = verify_chain(&chain, &vec![malformed_root], Some(EFFECTIVE_DATE));
335 assert_eq!(
336 public_key.expect_err("Expect error"),
337 ChainVerifierError::VerificationFailure(InvalidCertificate)
338 );
339 Ok(())
340 }
341
342 #[test]
343 #[cfg(not(feature = "ocsp"))]
344 fn test_chain_different_than_root_certificate() -> Result<(), ChainVerifierError> {
345 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
346 let real_root = REAL_APPLE_ROOT_BASE64_ENCODED.as_der_bytes().unwrap();
347 let leaf = LEAF_CERT_BASE64_ENCODED.as_der_bytes().unwrap();
348 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
349 let chain = vec![leaf.clone(), intermediate, root.clone()];
350
351 let public_key = verify_chain(&chain, &vec![real_root], Some(EFFECTIVE_DATE));
352 assert_eq!(
353 public_key.expect_err("Expect error"),
354 ChainVerifierError::VerificationFailure(InvalidCertificate)
355 );
356 Ok(())
357 }
358
359 #[test]
360 #[cfg(not(feature = "ocsp"))]
361 fn test_valid_expired_chain() -> Result<(), ChainVerifierError> {
362 let root = ROOT_CA_BASE64_ENCODED.as_der_bytes().unwrap();
363 let leaf = LEAF_CERT_BASE64_ENCODED.as_der_bytes().unwrap();
364 let intermediate = INTERMEDIATE_CA_BASE64_ENCODED.as_der_bytes().unwrap();
365 let chain = vec![leaf.clone(), intermediate, root.clone()];
366
367 let public_key = verify_chain(&chain, &vec![root], Some(2280946846));
368 assert_eq!(
369 public_key.expect_err("Expect error"),
370 ChainVerifierError::VerificationFailure(CertificateExpired)
371 );
372 Ok(())
373 }
374
375 #[test]
376 #[cfg(not(feature = "ocsp"))]
377 fn test_apple_chain_is_valid() -> Result<(), ChainVerifierError> {
378 let root = REAL_APPLE_ROOT_BASE64_ENCODED.as_der_bytes().unwrap();
379 let leaf = REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED
380 .as_der_bytes()
381 .unwrap();
382 let intermediate = REAL_APPLE_INTERMEDIATE_BASE64_ENCODED
383 .as_der_bytes()
384 .unwrap();
385 let chain = vec![leaf.clone(), intermediate, root.clone()];
386
387 let _public_key = verify_chain(&chain, &vec![root], Some(EFFECTIVE_DATE)).unwrap();
388 Ok(())
389 }
390
391 #[test]
392 #[cfg(not(feature = "ocsp"))]
393 fn test_apple_chain_is_valid_multi_root() -> Result<(), ChainVerifierError> {
394 let root = REAL_APPLE_ROOT_BASE64_ENCODED.as_der_bytes()?;
395 let leaf = REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED
396 .as_der_bytes()?;
397 let intermediate = REAL_APPLE_INTERMEDIATE_BASE64_ENCODED
398 .as_der_bytes()?;
399 let chain = vec![leaf.clone(), intermediate, root.clone()];
400
401 let multi_root: Vec<_> = REAL_APPLE_MULTI_ROOT_BASE64_ENCODED
402 .into_iter()
403 .map(|str| str.as_der_bytes().unwrap())
404 .collect();
405
406 let _public_key = verify_chain(&chain, &multi_root, Some(EFFECTIVE_DATE))?;
407 Ok(())
408 }
409}