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#[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#[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 #[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
100impl<'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 let issuer: Name = DirectoryName::new_common_name("contoso.local Authority");
201 check_serde!(issuer: Name in encoded[34..70]);
202
203 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 let subject: Name = DirectoryName::new_common_name("test.contoso.local");
214 check_serde!(subject: Name in encoded[102..133]);
215
216 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 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 let signature_algorithm = AlgorithmIdentifier::new_sha256_with_rsa_encryption();
241 check_serde!(signature_algorithm: AlgorithmIdentifier in encoded[522..537]);
242
243 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 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}