1 use crate::{ 2 AlgorithmIdentifier, AuthorityKeyIdentifier, BasicConstraints, Extension, ExtensionView, Extensions, Name, 3 SubjectPublicKeyInfo, Validity, Version, 4 }; 5 use picky_asn1::wrapper::{ApplicationTag0, ApplicationTag3, BitStringAsn1, IntegerAsn1}; 6 use serde::{de, Deserialize, Serialize}; 7 use std::fmt; 8 9 /// [RFC 5280 #4.1](https://tools.ietf.org/html/rfc5280#section-4.1) 10 /// 11 /// ```not_rust 12 /// Certificate ::= SEQUENCE { 13 /// tbsCertificate TBSCertificate, 14 /// signatureAlgorithm AlgorithmIdentifier, 15 /// signatureValue BIT STRING } 16 /// ``` 17 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 18 pub struct Certificate { 19 pub tbs_certificate: TBSCertificate, 20 pub signature_algorithm: AlgorithmIdentifier, 21 pub signature_value: BitStringAsn1, 22 } 23 24 impl Certificate { h_find_extension(&self, key_identifier_oid: &oid::ObjectIdentifier) -> Option<&Extension>25 fn h_find_extension(&self, key_identifier_oid: &oid::ObjectIdentifier) -> Option<&Extension> { 26 (self.tbs_certificate.extensions.0) 27 .0 28 .iter() 29 .find(|ext| ext.extn_id() == key_identifier_oid) 30 } 31 subject_key_identifier(&self) -> Option<&[u8]>32 pub fn subject_key_identifier(&self) -> Option<&[u8]> { 33 let ext = self.h_find_extension(&crate::oids::subject_key_identifier())?; 34 match ext.extn_value() { 35 ExtensionView::SubjectKeyIdentifier(ski) => Some(&ski.0), 36 _ => None, 37 } 38 } 39 authority_key_identifier(&self) -> Option<&AuthorityKeyIdentifier>40 pub fn authority_key_identifier(&self) -> Option<&AuthorityKeyIdentifier> { 41 let ext = self.h_find_extension(&crate::oids::authority_key_identifier())?; 42 match ext.extn_value() { 43 ExtensionView::AuthorityKeyIdentifier(aki) => Some(aki), 44 _ => None, 45 } 46 } 47 basic_constraints(&self) -> Option<&BasicConstraints>48 pub fn basic_constraints(&self) -> Option<&BasicConstraints> { 49 let ext = self.h_find_extension(&crate::oids::basic_constraints())?; 50 match ext.extn_value() { 51 ExtensionView::BasicConstraints(bc) => Some(bc), 52 _ => None, 53 } 54 } 55 extensions(&self) -> &[Extension]56 pub fn extensions(&self) -> &[Extension] { 57 (self.tbs_certificate.extensions.0).0.as_slice() 58 } 59 } 60 61 /// [RFC 5280 #4.1](https://tools.ietf.org/html/rfc5280#section-4.1) 62 /// 63 /// ```not_rust 64 /// TBSCertificate ::= SEQUENCE { 65 /// version [0] EXPLICIT Version DEFAULT v1, 66 /// serialNumber CertificateSerialNumber, 67 /// signature AlgorithmIdentifier, 68 /// issuer Name, 69 /// validity Validity, 70 /// subject Name, 71 /// subjectPublicKeyInfo SubjectPublicKeyInfo, 72 /// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, 73 /// -- If present, version MUST be v2 or v3 74 /// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, 75 /// -- If present, version MUST be v2 or v3 76 /// extensions [3] EXPLICIT Extensions OPTIONAL 77 /// -- If present, version MUST be v3 78 /// } 79 /// ``` 80 #[derive(Serialize, Clone, Debug, PartialEq)] 81 pub struct TBSCertificate { 82 pub version: ApplicationTag0<Version>, 83 pub serial_number: IntegerAsn1, 84 pub signature: AlgorithmIdentifier, 85 pub issuer: Name, 86 pub validity: Validity, 87 pub subject: Name, 88 pub subject_public_key_info: SubjectPublicKeyInfo, 89 // issuer_unique_id 90 // subject_unique_id 91 pub extensions: ApplicationTag3<Extensions>, 92 } 93 94 // Implement Deserialize manually to return an easy to understand error on V1 certificates 95 // (aka ApplicationTag0 not present). 96 impl<'de> de::Deserialize<'de> for TBSCertificate { deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: de::Deserializer<'de>,97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 98 where 99 D: de::Deserializer<'de>, 100 { 101 struct Visitor; 102 103 impl<'de> de::Visitor<'de> for Visitor { 104 type Value = TBSCertificate; 105 106 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 107 formatter.write_str("struct TBSCertificate") 108 } 109 110 fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error> 111 where 112 V: de::SeqAccess<'de>, 113 { 114 Ok(TBSCertificate { 115 version: seq 116 .next_element() 117 .map_err(|_| { 118 de::Error::invalid_value( 119 de::Unexpected::Other( 120 "[TBSCertificate] V1 certificates unsupported. Only V3 certificates \ 121 are supported", 122 ), 123 &"a supported certificate", 124 ) 125 })? 126 .ok_or_else(|| de::Error::invalid_length(0, &self))?, 127 serial_number: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(1, &self))?, 128 signature: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(2, &self))?, 129 issuer: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(3, &self))?, 130 validity: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(4, &self))?, 131 subject: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(5, &self))?, 132 subject_public_key_info: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(6, &self))?, 133 extensions: seq.next_element()?.ok_or_else(|| de::Error::invalid_length(7, &self))?, 134 }) 135 } 136 } 137 138 deserializer.deserialize_seq(Visitor) 139 } 140 } 141 142 #[cfg(test)] 143 mod tests { 144 use super::*; 145 use crate::{DirectoryName, Extension, KeyIdentifier, KeyUsage}; 146 use num_bigint_dig::BigInt; 147 use picky_asn1::bit_string::BitString; 148 use picky_asn1::date::UTCTime; 149 150 #[test] x509_v3_certificate()151 fn x509_v3_certificate() { 152 let encoded = base64::decode( 153 "MIIEGjCCAgKgAwIBAgIEN8NXxDANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQ\ 154 DDBdjb250b3NvLmxvY2FsIEF1dGhvcml0eTAeFw0xOTEwMTcxNzQxMjhaFw0yMjEwM\ 155 TYxNzQxMjhaMB0xGzAZBgNVBAMMEnRlc3QuY29udG9zby5sb2NhbDCCASIwDQYJKoZ\ 156 IhvcNAQEBBQADggEPADCCAQoCggEBAMptALdk7xKj9JmFSycxlaTV47oLv5Aabir17\ 157 f1WseAcZ492Mx0wqcJMmT8rVAusyfqvrhodHu4GELGBySo4KChLEuoEOGTNw/wEMtM\ 158 6j1E9K7kig1iiuH9nf9oow7OUdix4+w7TWQWpwl1NekKdTtvLLtEGSjmG187CUqR6f\ 159 NHYag+iVMV5Umc5VQadvAgva8qxOsPpDkN/E2df5gST7H5g3igaZtxUa3x7VreN3qJ\ 160 P0+hYQiyM7KsgmdFAkKpHC6/k36H7SXtpzh0NbH5OJHifYsAP34WL+a6lAd0VM7UiI\ 161 RMcLWA8HfmKL3p4bC+LFv5I0dvUUy1BTz1wHpRvVz8CAwEAAaNdMFswCQYDVR0TBAI\ 162 wADAOBgNVHQ8BAf8EBAMCAaAwHQYDVR0OBBYEFCMimIgHf5c00sI9jZzeWoMLsR60M\ 163 B8GA1UdIwQYMBaAFBbHC24DEnsUFLz/zmqB5cMCHo9OMA0GCSqGSIb3DQEBCwUAA4I\ 164 CAQA1ehZTTBbes2DgGXwQugoV9PdOGMFEVT4dzrrluo/4exSfqLrNuY2NXVuNBKW4n\ 165 DA5aD71Q/KUZ8Y8cV9qa8OBJQvQ0dd0qeHmeEYdDsj5YD4ECycKx9U1ZX5fi6tpSIX\ 166 6DsietpCnrw4aTgbEOvMeQcuYCTP30Vpt+mYEKBlR/E2Vcl2zUD+67gqppSaC1RceL\ 167 /8Cy6ZXlPqwmS2zqK9UhYVRKlEww8xSh/9CR9MmIDc4pHtCpMawcn6Dmo+A+LcKi5v\ 168 /NIwvSJTei+h1gvRhvEOPcf4VZJMHXquNrxkMsKpuu7g/AYH7wl2MBaNaxyNlXY5e5\ 169 OjxslrbRCfDab11YaJEONcBnapl/+Ajr70uVFN09tDXyk0EHYf75NiRztgVKclna26\ 170 zP5qRb0JSYNQJW2kIIBX6DhU7kt6RcauF2hJ+jLWOF2vsAS8PdEr7vnR1EGOrrcQ3V\ 171 UgMscNsDqf50YMi2Inu1Kt2t+QSvYs61ON39aVpqR67nskdUWzFCVgWQVezM1ZagoO\ 172 yNp7WjRYl8hJ0YVZ7TRtP8nJOkZ6s046YHVWxMuGdqZfd/AUFb9xzzXjGRuuZ1JmSf\ 173 +VBOFEe2MaPMyMQBeIs3Othz6Fcy6Am5F6c3It31WYJwiCa/NdbMIvGy1xvAN5kzR/\ 174 Y6hkoQljoSr1rVuszJ9dtvuTccA==", 175 ) 176 .expect("invalid base64"); 177 178 // Issuer 179 180 let issuer: Name = DirectoryName::new_common_name("contoso.local Authority").into(); 181 check_serde!(issuer: Name in encoded[34..70]); 182 183 // Validity 184 185 let validity = Validity { 186 not_before: UTCTime::new(2019, 10, 17, 17, 41, 28).unwrap().into(), 187 not_after: UTCTime::new(2022, 10, 16, 17, 41, 28).unwrap().into(), 188 }; 189 check_serde!(validity: Validity in encoded[70..102]); 190 191 // Subject 192 193 let subject: Name = DirectoryName::new_common_name("test.contoso.local").into(); 194 check_serde!(subject: Name in encoded[102..133]); 195 196 // SubjectPublicKeyInfo 197 198 let subject_public_key_info = SubjectPublicKeyInfo::new_rsa_key( 199 IntegerAsn1::from(encoded[165..422].to_vec()), 200 BigInt::from(65537).to_signed_bytes_be().into(), 201 ); 202 check_serde!(subject_public_key_info: SubjectPublicKeyInfo in encoded[133..427]); 203 204 // Extensions 205 206 let mut key_usage = KeyUsage::new(7); 207 key_usage.set_digital_signature(true); 208 key_usage.set_key_encipherment(true); 209 210 let extensions = Extensions(vec![ 211 Extension::new_basic_constraints(None, None).into_non_critical(), 212 Extension::new_key_usage(key_usage), 213 Extension::new_subject_key_identifier(&encoded[469..489]), 214 Extension::new_authority_key_identifier(KeyIdentifier::from(encoded[502..522].to_vec()), None, None), 215 ]); 216 check_serde!(extensions: Extensions in encoded[429..522]); 217 218 // SignatureAlgorithm 219 220 let signature_algorithm = AlgorithmIdentifier::new_sha256_with_rsa_encryption(); 221 check_serde!(signature_algorithm: AlgorithmIdentifier in encoded[522..537]); 222 223 // TBSCertificate 224 225 let tbs_certificate = TBSCertificate { 226 version: ApplicationTag0(Version::V3).into(), 227 serial_number: BigInt::from(935548868).to_signed_bytes_be().into(), 228 signature: signature_algorithm.clone(), 229 issuer, 230 validity, 231 subject, 232 subject_public_key_info, 233 extensions: extensions.into(), 234 }; 235 check_serde!(tbs_certificate: TBSCertificate in encoded[4..522]); 236 237 // Full certificate 238 239 let certificate = Certificate { 240 tbs_certificate, 241 signature_algorithm, 242 signature_value: BitString::with_bytes(&encoded[542..1054]).into(), 243 }; 244 check_serde!(certificate: Certificate in encoded); 245 } 246 247 #[test] key_id()248 fn key_id() { 249 let encoded = base64::decode( 250 "MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQswCQYDVQQGEwJOTDER\ 251 MA8GA1UECgwIUG9sYXJTU0wxGTAXBgNVBAMMEFBvbGFyU1NMIFRlc3QgQ0EwHhcN\ 252 MTEwMjEyMTQ0NDA2WhcNMjEwMjEyMTQ0NDA2WjA8MQswCQYDVQQGEwJOTDERMA8G\ 253 A1UECgwIUG9sYXJTU0wxGjAYBgNVBAMMEVBvbGFyU1NMIFNlcnZlciAxMIIBIjAN\ 254 BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQIfPUBq1VVTi/027oJlLhVhXom/\ 255 uOhFkNvuiBZS0/FDUEeWEllkh2v9K+BG+XO+3c+S4ZFb7Wagb4kpeUWA0INq1UFD\ 256 d185fAkER4KwVzlw7aPsFRkeqDMIR8EFQqn9TMO0390GH00QUUBncxMPQPhtgSVf\ 257 CrFTxjB+FTms+Vruf5KepgVb5xOXhbUjktnUJAbVCSWJdQfdphqPPwkZvq1lLGTr\ 258 lZvc/kFeF6babFtpzAK6FCwWJJxK3M3Q91Jnc/EtoCP9fvQxyi1wyokLBNsupk9w\ 259 bp7OvViJ4lNZnm5akmXiiD8MlBmj3eXonZUT7Snbq3AS3FrKaxerUoJUsQIDAQAB\ 260 o00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQfdNY/KcF0dEU7BRIsPai9Q1kCpjAf\ 261 BgNVHSMEGDAWgBS0WuSls97SUva51aaVD+s+vMf9/zANBgkqhkiG9w0BAQUFAAOC\ 262 AQEAm9GKWy4Z6eS483GoR5omwx32meCStm/vFuW+nozRwqwTG5d2Etx4TPnz73s8\ 263 fMtM1QB0QbfBDDHxfGymEsKwICmCkJszKE7c03j3mkddrrvN2eIYiL6358S3yHMj\ 264 iLVCraRUoEm01k7iytjxrcKb//hxFvHoxD1tdMqbuvjMlTS86kJSrkUMDw68UzfL\ 265 jvo3oVjiexfasjsICXFNoncjthKtS7v4zrsgXNPz92h58NgXnDtQU+Eb9tVA9kUs\ 266 Ln/az3v5DdgrNoAO60zK1zYAmekLil7pgba/jBLPeAQ2fZVgFxttKv33nUnUBzKA\ 267 Od8i323fM5dQS1qQpBjBc/5fPw==", 268 ) 269 .expect("invalid base64"); 270 271 let cert: Certificate = picky_asn1_der::from_bytes(&encoded).expect("intermediate cert"); 272 273 pretty_assertions::assert_eq!( 274 hex::encode(&cert.subject_key_identifier().unwrap()), 275 "1f74d63f29c17474453b05122c3da8bd435902a6" 276 ); 277 pretty_assertions::assert_eq!( 278 hex::encode(&cert.authority_key_identifier().unwrap().key_identifier().unwrap()), 279 "b45ae4a5b3ded252f6b9d5a6950feb3ebcc7fdff" 280 ); 281 } 282 } 283