|
| 1 | +// Copyright (c) 2023 The MobileCoin Foundation |
| 2 | + |
| 3 | +//! Verifier(s) for [`CertificationData`] |
| 4 | +
|
| 5 | +use p256::ecdsa::signature::Verifier; |
| 6 | +use p256::ecdsa::{Signature, VerifyingKey}; |
| 7 | +use x509_cert::der::{Decode, Encode}; |
| 8 | +use x509_cert::Certificate as X509Certificate; |
| 9 | + |
| 10 | +/// Offset from the start of a certificate to the "to be signed" (TBS) portion |
| 11 | +/// of the certificate. |
| 12 | +const TBS_OFFSET: usize = 4; |
| 13 | + |
| 14 | +pub type Result<T> = core::result::Result<T, Error>; |
| 15 | + |
| 16 | +/// Error type for decoding and verifying certificates. |
| 17 | +#[derive(Debug, displaydoc::Display, PartialEq, Eq)] |
| 18 | +pub enum Error { |
| 19 | + /// An error occurred decoding the signature from a certificate |
| 20 | + SignatureDecoding, |
| 21 | + /// The certification signature does not match with the verifying key |
| 22 | + SignatureVerification, |
| 23 | + /// An error occurred decoding the certificate |
| 24 | + CertificateDecoding(x509_cert::der::Error), |
| 25 | + /// An error occurred decoding the key from a certificate |
| 26 | + KeyDecoding, |
| 27 | +} |
| 28 | + |
| 29 | +impl From<x509_cert::der::Error> for Error { |
| 30 | + fn from(src: x509_cert::der::Error) -> Self { |
| 31 | + Error::CertificateDecoding(src) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// A certificate whose signature has not been verified. |
| 36 | +#[derive(Debug, PartialEq, Eq)] |
| 37 | +pub struct UnverifiedCertificate<'a> { |
| 38 | + // In order to verify the signature, we need to access the original DER |
| 39 | + // bytes |
| 40 | + der_bytes: &'a [u8], |
| 41 | + certificate: X509Certificate, |
| 42 | + // The signature and key are persisted here since they are fallible |
| 43 | + // operations and it's more ergonomic to fail fast than fail later for a |
| 44 | + // bad key or signature |
| 45 | + signature: Signature, |
| 46 | + key: VerifyingKey, |
| 47 | +} |
| 48 | + |
| 49 | +/// A certificate whose signature has been verified. |
| 50 | +#[derive(Debug, PartialEq, Eq)] |
| 51 | +pub struct VerifiedCertificate<'a> { |
| 52 | + _der_bytes: &'a [u8], |
| 53 | + _certificate: X509Certificate, |
| 54 | + _signature: Signature, |
| 55 | + _key: VerifyingKey, |
| 56 | +} |
| 57 | + |
| 58 | +impl<'a> UnverifiedCertificate<'a> { |
| 59 | + /// Verify the certificate signature. |
| 60 | + pub fn verify(self, key: &VerifyingKey) -> Result<VerifiedCertificate<'a>> { |
| 61 | + let tbs_length = self.certificate.tbs_certificate.encoded_len()?; |
| 62 | + let tbs_size = u32::from(tbs_length) as usize; |
| 63 | + let tbs_contents = &self.der_bytes[TBS_OFFSET..tbs_size + TBS_OFFSET]; |
| 64 | + key.verify(tbs_contents, &self.signature) |
| 65 | + .map_err(|_| Error::SignatureVerification)?; |
| 66 | + Ok(VerifiedCertificate { |
| 67 | + _der_bytes: self.der_bytes, |
| 68 | + _certificate: self.certificate, |
| 69 | + _signature: self.signature, |
| 70 | + _key: self.key, |
| 71 | + }) |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +/// Convert a DER-encoded certificate into an [`UnverifiedCertificate`]. |
| 76 | +impl<'a> TryFrom<&'a [u8]> for UnverifiedCertificate<'a> { |
| 77 | + type Error = Error; |
| 78 | + |
| 79 | + fn try_from(der_bytes: &'a [u8]) -> ::core::result::Result<Self, Self::Error> { |
| 80 | + let certificate = X509Certificate::from_der(der_bytes)?; |
| 81 | + let signature_bytes = certificate |
| 82 | + .signature |
| 83 | + .as_bytes() |
| 84 | + .ok_or(Error::SignatureDecoding)?; |
| 85 | + let signature = |
| 86 | + Signature::from_der(signature_bytes).map_err(|_| Error::SignatureDecoding)?; |
| 87 | + let key = VerifyingKey::from_sec1_bytes( |
| 88 | + certificate |
| 89 | + .tbs_certificate |
| 90 | + .subject_public_key_info |
| 91 | + .subject_public_key |
| 92 | + .as_bytes() |
| 93 | + .ok_or(Error::KeyDecoding)?, |
| 94 | + ) |
| 95 | + .map_err(|_| Error::KeyDecoding)?; |
| 96 | + Ok(UnverifiedCertificate { |
| 97 | + der_bytes, |
| 98 | + certificate, |
| 99 | + signature, |
| 100 | + key, |
| 101 | + }) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +#[cfg(test)] |
| 106 | +mod test { |
| 107 | + extern crate alloc; |
| 108 | + |
| 109 | + use super::*; |
| 110 | + use const_oid::ObjectIdentifier; |
| 111 | + use yare::parameterized; |
| 112 | + |
| 113 | + const LEAF_CERT: &str = " |
| 114 | + -----BEGIN CERTIFICATE----- |
| 115 | + MIIEjzCCBDSgAwIBAgIVAPtJxlxRlleZOb/spRh9U8K7AT/3MAoGCCqGSM49BAMC |
| 116 | + MHExIzAhBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQK |
| 117 | + DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV |
| 118 | + BAgMAkNBMQswCQYDVQQGEwJVUzAeFw0yMjA2MTMyMTQ2MzRaFw0yOTA2MTMyMTQ2 |
| 119 | + MzRaMHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgQ2VydGlmaWNhdGUxGjAYBgNV |
| 120 | + BAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkG |
| 121 | + A1UECAwCQ0ExCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE |
| 122 | + j/Ee1lkGJofDX745Ks5qxqu7Mk7Mqcwkx58TCSTsabRCSvobSl/Ts8b0dltKUW3j |
| 123 | + qRd+SxnPEWJ+jUw+SpzwWaOCAqgwggKkMB8GA1UdIwQYMBaAFNDoqtp11/kuSReY |
| 124 | + PHsUZdDV8llNMGwGA1UdHwRlMGMwYaBfoF2GW2h0dHBzOi8vYXBpLnRydXN0ZWRz |
| 125 | + ZXJ2aWNlcy5pbnRlbC5jb20vc2d4L2NlcnRpZmljYXRpb24vdjMvcGNrY3JsP2Nh |
| 126 | + PXByb2Nlc3NvciZlbmNvZGluZz1kZXIwHQYDVR0OBBYEFKy9gk624HzNnDyCw7QW |
| 127 | + nhmVfE31MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMIIB1AYJKoZIhvhN |
| 128 | + AQ0BBIIBxTCCAcEwHgYKKoZIhvhNAQ0BAQQQ36FQl3ntUr3KUwbEFvmRGzCCAWQG |
| 129 | + CiqGSIb4TQENAQIwggFUMBAGCyqGSIb4TQENAQIBAgERMBAGCyqGSIb4TQENAQIC |
| 130 | + AgERMBAGCyqGSIb4TQENAQIDAgECMBAGCyqGSIb4TQENAQIEAgEEMBAGCyqGSIb4 |
| 131 | + TQENAQIFAgEBMBEGCyqGSIb4TQENAQIGAgIAgDAQBgsqhkiG+E0BDQECBwIBBjAQ |
| 132 | + BgsqhkiG+E0BDQECCAIBADAQBgsqhkiG+E0BDQECCQIBADAQBgsqhkiG+E0BDQEC |
| 133 | + CgIBADAQBgsqhkiG+E0BDQECCwIBADAQBgsqhkiG+E0BDQECDAIBADAQBgsqhkiG |
| 134 | + +E0BDQECDQIBADAQBgsqhkiG+E0BDQECDgIBADAQBgsqhkiG+E0BDQECDwIBADAQ |
| 135 | + BgsqhkiG+E0BDQECEAIBADAQBgsqhkiG+E0BDQECEQIBCzAfBgsqhkiG+E0BDQEC |
| 136 | + EgQQERECBAGABgAAAAAAAAAAADAQBgoqhkiG+E0BDQEDBAIAADAUBgoqhkiG+E0B |
| 137 | + DQEEBAYAkG7VAAAwDwYKKoZIhvhNAQ0BBQoBADAKBggqhkjOPQQDAgNJADBGAiEA |
| 138 | + 1XJi0ht4hw8YtC6E4rYscp9bF+7UOhVGeKePA5TW2FQCIQCIUAaewOuWOIvstZN4 |
| 139 | + V8Zu8NFCC4vFg+cZqO6QfezEaA== |
| 140 | + -----END CERTIFICATE----- |
| 141 | + "; |
| 142 | + |
| 143 | + const INTERMEDIATE_CA: &str = " |
| 144 | + -----BEGIN CERTIFICATE----- |
| 145 | + MIICmDCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC |
| 146 | + MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD |
| 147 | + b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw |
| 148 | + CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHExIzAh |
| 149 | + BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl |
| 150 | + bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB |
| 151 | + MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg |
| 152 | + tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i |
| 153 | + HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww |
| 154 | + UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl |
| 155 | + cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFNDo |
| 156 | + qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG |
| 157 | + AQH/AgEAMAoGCCqGSM49BAMCA0gAMEUCIQCJgTbtVqOyZ1m3jqiAXM6QYa6r5sWS |
| 158 | + 4y/G7y8uIJGxdwIgRqPvBSKzzQagBLQq5s5A70pdoiaRJ8z/0uDz4NgV91k= |
| 159 | + -----END CERTIFICATE----- |
| 160 | + "; |
| 161 | + |
| 162 | + const ROOT_CA: &str = " |
| 163 | + -----BEGIN CERTIFICATE----- |
| 164 | + MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw |
| 165 | + aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv |
| 166 | + cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ |
| 167 | + BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG |
| 168 | + A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 |
| 169 | + aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT |
| 170 | + AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 |
| 171 | + 1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB |
| 172 | + uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ |
| 173 | + MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 |
| 174 | + ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV |
| 175 | + Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI |
| 176 | + KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg |
| 177 | + AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= |
| 178 | + -----END CERTIFICATE----- |
| 179 | + "; |
| 180 | + |
| 181 | + #[parameterized( |
| 182 | + root = { ROOT_CA }, |
| 183 | + intermediate = { INTERMEDIATE_CA }, |
| 184 | + leaf = { LEAF_CERT }, |
| 185 | + )] |
| 186 | + fn try_from_der(pem: &str) { |
| 187 | + let dedent = textwrap::dedent(pem); |
| 188 | + let (_, der_bytes) = pem_rfc7468::decode_vec(dedent.trim().as_bytes()) |
| 189 | + .expect("Failed to decode DER from PEM"); |
| 190 | + assert!(UnverifiedCertificate::try_from(der_bytes.as_slice()).is_ok()); |
| 191 | + } |
| 192 | + |
| 193 | + #[test] |
| 194 | + fn certificate_decoding_error_with_invalid_der() { |
| 195 | + let pem = textwrap::dedent(ROOT_CA); |
| 196 | + let (_, der_bytes) = |
| 197 | + pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 198 | + assert!(matches!( |
| 199 | + UnverifiedCertificate::try_from(&der_bytes.as_slice()[1..]), |
| 200 | + Err(Error::CertificateDecoding(_)) |
| 201 | + )); |
| 202 | + } |
| 203 | + |
| 204 | + #[test] |
| 205 | + fn signature_decoding_error() { |
| 206 | + let pem = textwrap::dedent(ROOT_CA); |
| 207 | + let (_, mut der_bytes) = |
| 208 | + pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 209 | + |
| 210 | + // The signature is and the end of the certificate. |
| 211 | + // If iether of the points are 0 it will fail to decode so we force the |
| 212 | + // last point to 0 |
| 213 | + let last_point = der_bytes.len() - 32; |
| 214 | + der_bytes[last_point..].copy_from_slice(&[0; 32]); |
| 215 | + |
| 216 | + assert_eq!( |
| 217 | + UnverifiedCertificate::try_from(der_bytes.as_slice()), |
| 218 | + Err(Error::SignatureDecoding) |
| 219 | + ); |
| 220 | + } |
| 221 | + |
| 222 | + #[test] |
| 223 | + fn key_decoding_error() { |
| 224 | + let pem = textwrap::dedent(ROOT_CA); |
| 225 | + let (_, mut der_bytes) = |
| 226 | + pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 227 | + |
| 228 | + // There isn't a good way to get the offset to the key, so we look for |
| 229 | + // the bytes that represent the key object identifier (OID) |
| 230 | + let key_oid = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); |
| 231 | + let key_oid_bytes = key_oid.as_bytes(); |
| 232 | + let key_oid_offset = der_bytes |
| 233 | + .windows(key_oid_bytes.len()) |
| 234 | + .position(|window| window == key_oid_bytes) |
| 235 | + .expect("Failed to find key OID"); |
| 236 | + |
| 237 | + // 2 Bytes for the key tag [TYPE, SIZE] |
| 238 | + let key_offset = key_oid_offset + key_oid_bytes.len() + 2; |
| 239 | + let key_end = key_offset + 64; |
| 240 | + der_bytes[key_offset..key_end].copy_from_slice(&[0; 64]); |
| 241 | + |
| 242 | + assert_eq!( |
| 243 | + UnverifiedCertificate::try_from(der_bytes.as_slice()), |
| 244 | + Err(Error::KeyDecoding) |
| 245 | + ); |
| 246 | + } |
| 247 | + |
| 248 | + #[test] |
| 249 | + fn verify_root_certificate() { |
| 250 | + let root = textwrap::dedent(ROOT_CA); |
| 251 | + let (_, der_bytes) = |
| 252 | + pem_rfc7468::decode_vec(root.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 253 | + let cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 254 | + .expect("Failed to decode certificate from DER"); |
| 255 | + |
| 256 | + // The root certificate is self-signed, ideally this key will be stored |
| 257 | + // by the application. |
| 258 | + let key = cert.key; |
| 259 | + |
| 260 | + assert_eq!(cert.verify(&key).is_ok(), true); |
| 261 | + } |
| 262 | + |
| 263 | + #[test] |
| 264 | + fn verify_intermediate_certificate() { |
| 265 | + let root = textwrap::dedent(ROOT_CA); |
| 266 | + let (_, der_bytes) = |
| 267 | + pem_rfc7468::decode_vec(root.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 268 | + let root_cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 269 | + .expect("Failed to decode certificate from DER"); |
| 270 | + |
| 271 | + let intermediate = textwrap::dedent(INTERMEDIATE_CA); |
| 272 | + let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes()) |
| 273 | + .expect("Failed to decode DER from PEM"); |
| 274 | + let cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 275 | + .expect("Failed to decode certificate from DER"); |
| 276 | + |
| 277 | + assert_eq!(cert.verify(&root_cert.key).is_ok(), true); |
| 278 | + } |
| 279 | + |
| 280 | + #[test] |
| 281 | + fn verify_leaf_certificate() { |
| 282 | + let intermediate = textwrap::dedent(INTERMEDIATE_CA); |
| 283 | + let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes()) |
| 284 | + .expect("Failed to decode DER from PEM"); |
| 285 | + let intermediate_cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 286 | + .expect("Failed to decode certificate from DER"); |
| 287 | + |
| 288 | + let leaf = textwrap::dedent(LEAF_CERT); |
| 289 | + let (_, der_bytes) = |
| 290 | + pem_rfc7468::decode_vec(leaf.trim().as_bytes()).expect("Failed to decode DER from PEM"); |
| 291 | + let cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 292 | + .expect("Failed to decode certificate from DER"); |
| 293 | + |
| 294 | + assert_eq!(cert.verify(&intermediate_cert.key).is_ok(), true); |
| 295 | + } |
| 296 | + |
| 297 | + #[test] |
| 298 | + fn verify_certificate_fails_with_wrong_key() { |
| 299 | + let intermediate = textwrap::dedent(INTERMEDIATE_CA); |
| 300 | + let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes()) |
| 301 | + .expect("Failed to decode DER from PEM"); |
| 302 | + let intermediate_cert = UnverifiedCertificate::try_from(der_bytes.as_slice()) |
| 303 | + .expect("Failed to decode certificate from DER"); |
| 304 | + |
| 305 | + // The intermediate cert should *not* be self signed so using it's key |
| 306 | + // should fail verification |
| 307 | + let key = intermediate_cert.key; |
| 308 | + |
| 309 | + assert_eq!( |
| 310 | + intermediate_cert.verify(&key), |
| 311 | + Err(Error::SignatureVerification) |
| 312 | + ); |
| 313 | + } |
| 314 | +} |
0 commit comments