picky_asn1_x509/
certificate.rs

1use crate::{
2    AlgorithmIdentifier, AuthorityKeyIdentifier, BasicConstraints, Extension, ExtensionView, Extensions, Name,
3    SubjectPublicKeyInfo, Validity, Version,
4};
5use picky_asn1::wrapper::{BitStringAsn1, ExplicitContextTag0, ExplicitContextTag3, IntegerAsn1};
6use serde::{de, Deserialize, Serialize};
7use 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, Eq)]
18pub struct Certificate {
19    pub tbs_certificate: TbsCertificate,
20    pub signature_algorithm: AlgorithmIdentifier,
21    pub signature_value: BitStringAsn1,
22}
23
24impl Certificate {
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
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
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
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
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, Eq)]
81pub struct TbsCertificate {
82    #[serde(skip_serializing_if = "version_is_default")]
83    pub version: ExplicitContextTag0<Version>,
84    pub serial_number: IntegerAsn1,
85    pub signature: AlgorithmIdentifier,
86    pub issuer: Name,
87    pub validity: Validity,
88    pub subject: Name,
89    pub subject_public_key_info: SubjectPublicKeyInfo,
90    // issuer_unique_id
91    // subject_unique_id
92    #[serde(skip_serializing_if = "extensions_are_empty")]
93    pub extensions: ExplicitContextTag3<Extensions>,
94}
95
96fn version_is_default(version: &Version) -> bool {
97    version == &Version::default()
98}
99
100// Implement Deserialize manually to support missing version field (i.e.: fallback as V1)
101impl<'de> de::Deserialize<'de> for TbsCertificate {
102    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103    where
104        D: de::Deserializer<'de>,
105    {
106        struct Visitor;
107
108        impl<'de> de::Visitor<'de> for Visitor {
109            type Value = TbsCertificate;
110
111            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
112                formatter.write_str("struct TBSCertificate")
113            }
114
115            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
116            where
117                V: de::SeqAccess<'de>,
118            {
119                let version: ExplicitContextTag0<Version> = seq.next_element().unwrap_or_default().unwrap_or_default();
120                let serial_number = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(1, &self))?;
121                let signature = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(2, &self))?;
122                let issuer = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(3, &self))?;
123                let validity = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(4, &self))?;
124                let subject = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(5, &self))?;
125                let subject_public_key_info = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(6, &self))?;
126                let extensions: ExplicitContextTag3<Extensions> = seq
127                    .next_element()?
128                    .unwrap_or_else(|| Some(Extensions(Vec::new()).into()))
129                    .unwrap_or_else(|| Extensions(Vec::new()).into());
130
131                if version.0 != Version::V3 && !(extensions.0).0.is_empty() {
132                    return Err(serde_invalid_value!(
133                        TbsCertificate,
134                        "Version is not V3, but Extensions are present",
135                        "no Extensions"
136                    ));
137                }
138
139                Ok(TbsCertificate {
140                    version,
141                    serial_number,
142                    signature,
143                    issuer,
144                    validity,
145                    subject,
146                    subject_public_key_info,
147                    extensions,
148                })
149            }
150        }
151
152        deserializer.deserialize_seq(Visitor)
153    }
154}
155
156fn extensions_are_empty(extensions: &Extensions) -> bool {
157    extensions.0.is_empty()
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::{DirectoryName, Extension, KeyIdentifier, KeyUsage};
164    use base64::{engine::general_purpose, Engine as _};
165    use num_bigint_dig::BigInt;
166    use picky_asn1::bit_string::BitString;
167    use picky_asn1::date::UTCTime;
168
169    #[test]
170    fn x509_v3_certificate() {
171        let encoded = general_purpose::STANDARD
172            .decode(
173                "MIIEGjCCAgKgAwIBAgIEN8NXxDANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQ\
174             DDBdjb250b3NvLmxvY2FsIEF1dGhvcml0eTAeFw0xOTEwMTcxNzQxMjhaFw0yMjEwM\
175             TYxNzQxMjhaMB0xGzAZBgNVBAMMEnRlc3QuY29udG9zby5sb2NhbDCCASIwDQYJKoZ\
176             IhvcNAQEBBQADggEPADCCAQoCggEBAMptALdk7xKj9JmFSycxlaTV47oLv5Aabir17\
177             f1WseAcZ492Mx0wqcJMmT8rVAusyfqvrhodHu4GELGBySo4KChLEuoEOGTNw/wEMtM\
178             6j1E9K7kig1iiuH9nf9oow7OUdix4+w7TWQWpwl1NekKdTtvLLtEGSjmG187CUqR6f\
179             NHYag+iVMV5Umc5VQadvAgva8qxOsPpDkN/E2df5gST7H5g3igaZtxUa3x7VreN3qJ\
180             P0+hYQiyM7KsgmdFAkKpHC6/k36H7SXtpzh0NbH5OJHifYsAP34WL+a6lAd0VM7UiI\
181             RMcLWA8HfmKL3p4bC+LFv5I0dvUUy1BTz1wHpRvVz8CAwEAAaNdMFswCQYDVR0TBAI\
182             wADAOBgNVHQ8BAf8EBAMCAaAwHQYDVR0OBBYEFCMimIgHf5c00sI9jZzeWoMLsR60M\
183             B8GA1UdIwQYMBaAFBbHC24DEnsUFLz/zmqB5cMCHo9OMA0GCSqGSIb3DQEBCwUAA4I\
184             CAQA1ehZTTBbes2DgGXwQugoV9PdOGMFEVT4dzrrluo/4exSfqLrNuY2NXVuNBKW4n\
185             DA5aD71Q/KUZ8Y8cV9qa8OBJQvQ0dd0qeHmeEYdDsj5YD4ECycKx9U1ZX5fi6tpSIX\
186             6DsietpCnrw4aTgbEOvMeQcuYCTP30Vpt+mYEKBlR/E2Vcl2zUD+67gqppSaC1RceL\
187             /8Cy6ZXlPqwmS2zqK9UhYVRKlEww8xSh/9CR9MmIDc4pHtCpMawcn6Dmo+A+LcKi5v\
188             /NIwvSJTei+h1gvRhvEOPcf4VZJMHXquNrxkMsKpuu7g/AYH7wl2MBaNaxyNlXY5e5\
189             OjxslrbRCfDab11YaJEONcBnapl/+Ajr70uVFN09tDXyk0EHYf75NiRztgVKclna26\
190             zP5qRb0JSYNQJW2kIIBX6DhU7kt6RcauF2hJ+jLWOF2vsAS8PdEr7vnR1EGOrrcQ3V\
191             UgMscNsDqf50YMi2Inu1Kt2t+QSvYs61ON39aVpqR67nskdUWzFCVgWQVezM1ZagoO\
192             yNp7WjRYl8hJ0YVZ7TRtP8nJOkZ6s046YHVWxMuGdqZfd/AUFb9xzzXjGRuuZ1JmSf\
193             +VBOFEe2MaPMyMQBeIs3Othz6Fcy6Am5F6c3It31WYJwiCa/NdbMIvGy1xvAN5kzR/\
194             Y6hkoQljoSr1rVuszJ9dtvuTccA==",
195            )
196            .expect("invalid base64");
197
198        // Issuer
199
200        let issuer: Name = DirectoryName::new_common_name("contoso.local Authority");
201        check_serde!(issuer: Name in encoded[34..70]);
202
203        // Validity
204
205        let validity = Validity {
206            not_before: UTCTime::new(2019, 10, 17, 17, 41, 28).unwrap().into(),
207            not_after: UTCTime::new(2022, 10, 16, 17, 41, 28).unwrap().into(),
208        };
209        check_serde!(validity: Validity in encoded[70..102]);
210
211        // Subject
212
213        let subject: Name = DirectoryName::new_common_name("test.contoso.local");
214        check_serde!(subject: Name in encoded[102..133]);
215
216        // SubjectPublicKeyInfo
217
218        let subject_public_key_info = SubjectPublicKeyInfo::new_rsa_key(
219            IntegerAsn1::from(encoded[165..422].to_vec()),
220            BigInt::from(65537).to_signed_bytes_be().into(),
221        );
222        check_serde!(subject_public_key_info: SubjectPublicKeyInfo in encoded[133..427]);
223
224        // Extensions
225
226        let mut key_usage = KeyUsage::new(7);
227        key_usage.set_digital_signature(true);
228        key_usage.set_key_encipherment(true);
229
230        let extensions = Extensions(vec![
231            Extension::new_basic_constraints(None, None).into_non_critical(),
232            Extension::new_key_usage(key_usage),
233            Extension::new_subject_key_identifier(&encoded[469..489]),
234            Extension::new_authority_key_identifier(KeyIdentifier::from(encoded[502..522].to_vec()), None, None),
235        ]);
236        check_serde!(extensions: Extensions in encoded[429..522]);
237
238        // SignatureAlgorithm
239
240        let signature_algorithm = AlgorithmIdentifier::new_sha256_with_rsa_encryption();
241        check_serde!(signature_algorithm: AlgorithmIdentifier in encoded[522..537]);
242
243        // TbsCertificate
244
245        let tbs_certificate = TbsCertificate {
246            version: ExplicitContextTag0(Version::V3),
247            serial_number: BigInt::from(935548868).to_signed_bytes_be().into(),
248            signature: signature_algorithm.clone(),
249            issuer,
250            validity,
251            subject,
252            subject_public_key_info,
253            extensions: extensions.into(),
254        };
255        check_serde!(tbs_certificate: TbsCertificate in encoded[4..522]);
256
257        // Full certificate
258
259        let certificate = Certificate {
260            tbs_certificate,
261            signature_algorithm,
262            signature_value: BitString::with_bytes(&encoded[542..1054]).into(),
263        };
264        check_serde!(certificate: Certificate in encoded);
265    }
266
267    #[test]
268    fn key_id() {
269        let encoded = general_purpose::STANDARD
270            .decode(
271                "MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQswCQYDVQQGEwJOTDER\
272                MA8GA1UECgwIUG9sYXJTU0wxGTAXBgNVBAMMEFBvbGFyU1NMIFRlc3QgQ0EwHhcN\
273                MTEwMjEyMTQ0NDA2WhcNMjEwMjEyMTQ0NDA2WjA8MQswCQYDVQQGEwJOTDERMA8G\
274                A1UECgwIUG9sYXJTU0wxGjAYBgNVBAMMEVBvbGFyU1NMIFNlcnZlciAxMIIBIjAN\
275                BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQIfPUBq1VVTi/027oJlLhVhXom/\
276                uOhFkNvuiBZS0/FDUEeWEllkh2v9K+BG+XO+3c+S4ZFb7Wagb4kpeUWA0INq1UFD\
277                d185fAkER4KwVzlw7aPsFRkeqDMIR8EFQqn9TMO0390GH00QUUBncxMPQPhtgSVf\
278                CrFTxjB+FTms+Vruf5KepgVb5xOXhbUjktnUJAbVCSWJdQfdphqPPwkZvq1lLGTr\
279                lZvc/kFeF6babFtpzAK6FCwWJJxK3M3Q91Jnc/EtoCP9fvQxyi1wyokLBNsupk9w\
280                bp7OvViJ4lNZnm5akmXiiD8MlBmj3eXonZUT7Snbq3AS3FrKaxerUoJUsQIDAQAB\
281                o00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQfdNY/KcF0dEU7BRIsPai9Q1kCpjAf\
282                BgNVHSMEGDAWgBS0WuSls97SUva51aaVD+s+vMf9/zANBgkqhkiG9w0BAQUFAAOC\
283                AQEAm9GKWy4Z6eS483GoR5omwx32meCStm/vFuW+nozRwqwTG5d2Etx4TPnz73s8\
284                fMtM1QB0QbfBDDHxfGymEsKwICmCkJszKE7c03j3mkddrrvN2eIYiL6358S3yHMj\
285                iLVCraRUoEm01k7iytjxrcKb//hxFvHoxD1tdMqbuvjMlTS86kJSrkUMDw68UzfL\
286                jvo3oVjiexfasjsICXFNoncjthKtS7v4zrsgXNPz92h58NgXnDtQU+Eb9tVA9kUs\
287                Ln/az3v5DdgrNoAO60zK1zYAmekLil7pgba/jBLPeAQ2fZVgFxttKv33nUnUBzKA\
288                Od8i323fM5dQS1qQpBjBc/5fPw==",
289            )
290            .expect("invalid base64");
291
292        let cert: Certificate = picky_asn1_der::from_bytes(&encoded).expect("intermediate cert");
293
294        pretty_assertions::assert_eq!(
295            hex::encode(cert.subject_key_identifier().unwrap()),
296            "1f74d63f29c17474453b05122c3da8bd435902a6"
297        );
298        pretty_assertions::assert_eq!(
299            hex::encode(cert.authority_key_identifier().unwrap().key_identifier().unwrap()),
300            "b45ae4a5b3ded252f6b9d5a6950feb3ebcc7fdff"
301        );
302    }
303
304    #[test]
305    fn missing_authority_key_identifier_field_accepted() {
306        let encoded = picky_test_data::MISSING_AUTH_KEY_ID;
307        let _decoded = picky_asn1_der::from_bytes::<Certificate>(encoded).unwrap();
308    }
309}