x509_certificate_printer/
lib.rs1use std::cmp::min;
2use std::fmt::Write as _;
3use std::net::{Ipv4Addr, Ipv6Addr};
4use base64::{Engine, engine::general_purpose::STANDARD};
5use x509_parser::extensions::GeneralName;
6use x509_parser::objects;
7use x509_parser::prelude::*;
8use x509_parser::public_key::PublicKey;
9use x509_parser::signature_algorithm::SignatureAlgorithm;
10
11fn format_hex_dump(bytes: &[u8], max_len: usize) -> String {
14 let m = min(bytes.len(), max_len);
15 let hex: String = bytes[..m].iter().map(|b| format!("{:02x}", b)).collect();
16 if bytes.len() > max_len {
17 format!("{}... <continued>", hex)
18 } else {
19 hex
20 }
21}
22
23fn format_oid(oid: &asn1_rs::Oid) -> String {
24 match objects::oid2sn(oid, objects::oid_registry()) {
25 Ok(s) => s.to_owned(),
26 _ => format!("{oid}"),
27 }
28}
29
30fn format_generalname(gn: &GeneralName) -> String {
31 match gn {
32 GeneralName::DNSName(name) => format!("DNSName:{name}"),
33 GeneralName::DirectoryName(n) => format!("DirName:{n}"),
34 GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{obj:?}"),
35 GeneralName::IPAddress(n) => format!("IPAddress:{n:?}"),
36 GeneralName::OtherName(oid, n) => format!("OtherName:{oid}, {n:?}"),
37 GeneralName::RFC822Name(n) => format!("RFC822Name:{n}"),
38 GeneralName::RegisteredID(oid) => format!("RegisteredID:{oid}"),
39 GeneralName::URI(n) => format!("URI:{n}"),
40 GeneralName::X400Address(obj) => format!("X400Address:{obj:?}"),
41 GeneralName::Invalid(_, _) => "Invalid".to_string(),
42 }
43}
44
45fn format_x509_extension(oid: &asn1_rs::Oid, ext: &X509Extension) -> String {
46 let mut output = String::new();
47 writeln!(
48 output,
49 " [crit:{} l:{}] {}: ",
50 ext.critical,
51 ext.value.len(),
52 format_oid(oid)
53 )
54 .unwrap();
55 match ext.parsed_extension() {
56 ParsedExtension::AuthorityKeyIdentifier(aki) => {
57 output.push_str(" X509v3 Authority Key Identifier\n");
58 if let Some(key_id) = &aki.key_identifier {
59 output.push_str(&format!(" Key Identifier: {key_id:x}\n"));
60 }
61 if let Some(issuer) = &aki.authority_cert_issuer {
62 for name in issuer {
63 output.push_str(&format!(" Cert Issuer: {name}\n"));
64 }
65 }
66 if let Some(serial) = &aki.authority_cert_serial {
67 output.push_str(&format!(" Cert Serial: {}\n", format_serial(serial)));
68 }
69 }
70 ParsedExtension::BasicConstraints(bc) => {
71 output.push_str(&format!(" X509v3 CA: {}\n", bc.ca));
72 }
73 ParsedExtension::CRLDistributionPoints(points) => {
74 output.push_str(" X509v3 CRL Distribution Points:\n");
75 for point in points.iter() {
76 if let Some(name) = &point.distribution_point {
77 output.push_str(&format!(" Full Name: {name:?}\n"));
78 }
79 if let Some(reasons) = &point.reasons {
80 output.push_str(&format!(" Reasons: {reasons}\n"));
81 }
82 if let Some(crl_issuer) = &point.crl_issuer {
83 output.push_str(" CRL Issuer: ");
84 for gn in crl_issuer {
85 output.push_str(&format!("{} ", format_generalname(gn)));
86 }
87 output.push('\n');
88 }
89 output.push('\n');
90 }
91 }
92 ParsedExtension::KeyUsage(ku) => {
93 output.push_str(&format!(" X509v3 Key Usage: {ku}\n"));
94 }
95 ParsedExtension::NSCertType(ty) => {
96 output.push_str(&format!(" Netscape Cert Type: {ty}\n"));
97 }
98 ParsedExtension::SubjectAlternativeName(san) => {
99 for name in &san.general_names {
100 let s = match name {
101 GeneralName::DNSName(s) => format!("DNS:{s}"),
102 GeneralName::IPAddress(bytes) => {
103 let ip = match bytes.len() {
104 4 => {
105 let ip = Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]);
106 format!("{ip}")
107 }
108 16 => {
109 let octets: [u8; 16] = (*bytes).try_into().unwrap();
110 let ip = Ipv6Addr::from(octets);
111 format!("{ip}")
112 }
113 l => format!("invalid (len={l})"),
114 };
115 format!("IP Address:{ip}")
116 }
117 _ => format!("{name:?}"),
118 };
119 output.push_str(&format!(" X509v3 SAN: {s}\n"));
120 }
121 }
122 ParsedExtension::SubjectKeyIdentifier(subject_key_id) => {
123 output.push_str(&format!(
124 " X509v3 Subject Key Identifier: {}\n",
125 hex::encode(subject_key_id.0)
126 ));
127 }
128 x => output.push_str(&format!(" {x:?}\n")),
129 }
130 output
131}
132
133fn format_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) -> String {
134 let mut output = String::new();
135 let indent = " ".repeat(level);
136 writeln!(output, "{}Oid: {}", indent, format_oid(&alg.algorithm)).unwrap();
137 if let Some(parameter) = &alg.parameters {
138 let s = match parameter.tag() {
139 asn1_rs::Tag::Oid => {
140 let oid = parameter.as_oid().unwrap();
141 format_oid(&oid)
142 }
143 _ => format!("{}", parameter.tag()),
144 };
145 writeln!(output, "{}Parameter: <PRESENT> {}", indent, s).unwrap();
146 let bytes = parameter.as_bytes();
147 output.push_str(&format_hex_dump(bytes, 32));
148 } else {
149 output.push_str(&format!("{}Parameter: <ABSENT>", indent));
150 }
151 output
152}
153
154fn format_serial(b: &[u8]) -> String {
155 b.iter()
156 .map(|b| format!("{:02x}", b))
157 .collect::<Vec<_>>()
158 .join(":")
159}
160
161fn format_number_to_hex_with_colon(b: &[u8], row_size: usize) -> Vec<String> {
162 let mut v = Vec::with_capacity(1 + b.len() / row_size);
163 for r in b.chunks(row_size) {
164 let s = r.iter().fold(String::with_capacity(3 * r.len()), |a, b| {
165 a + &format!("{b:02x}:")
166 });
167 v.push(s)
168 }
169 v
170}
171
172pub trait PrettyPrinter {
173 fn pretty_print(&self) -> String;
174 fn to_pem(&self) -> String;
175}
176
177impl PrettyPrinter for X509Certificate<'_> {
178 fn pretty_print(&self) -> String {
179 format_x509_info(self)
180 }
181
182 fn to_pem(&self) -> String {
183 let encoded = STANDARD.encode(self.as_raw());
184 let lines: Vec<String> = encoded
185 .as_bytes()
186 .chunks(64)
187 .map(|chunk| std::str::from_utf8(chunk).unwrap().to_string())
188 .collect();
189 format!(
190 "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----",
191 lines.join("\n")
192 )
193 }
194}
195
196fn format_x509_info(x509: &X509Certificate) -> String {
197 let mut output = String::new();
198
199 let version = x509.version();
200 if version.0 < 3 {
201 writeln!(output, " Version: {}", version).unwrap();
202 } else {
203 writeln!(output, " Version: INVALID({})", version.0).unwrap();
204 }
205 writeln!(
206 output,
207 " Serial: {}",
208 x509.tbs_certificate.raw_serial_as_string()
209 )
210 .unwrap();
211 writeln!(output, " Subject: {}", x509.subject()).unwrap();
212 writeln!(output, " Issuer: {}", x509.issuer()).unwrap();
213 output.push_str(" Validity:\n");
214 writeln!(output, " NotBefore: {}", x509.validity().not_before).unwrap();
215 writeln!(output, " NotAfter: {}", x509.validity().not_after).unwrap();
216 writeln!(output, " is_valid: {}", x509.validity().is_valid()).unwrap();
217 output.push_str(" Subject Public Key Info:\n");
218 output.push_str(&format_x509_ski(x509.public_key()));
219 output.push_str(&format_x509_signature_algorithm(
220 &x509.signature_algorithm,
221 4,
222 ));
223
224 output.push_str(" Signature Value:\n");
225 let sig_bytes: &[u8] = match &x509.signature_value.data {
226 std::borrow::Cow::Borrowed(b) => b,
227 std::borrow::Cow::Owned(v) => v.as_slice(),
228 };
229 for l in format_number_to_hex_with_colon(sig_bytes, 16) {
230 writeln!(output, " {l}").unwrap();
231 }
232 output.push_str(" Extensions:\n");
233 for ext in x509.extensions() {
234 output.push_str(&format_x509_extension(&ext.oid, ext));
235 }
236 output.push('\n');
237 output
238}
239
240fn format_x509_signature_algorithm(
241 signature_algorithm: &AlgorithmIdentifier,
242 indent: usize,
243) -> String {
244 let mut output = String::new();
245 match SignatureAlgorithm::try_from(signature_algorithm) {
246 Ok(sig_alg) => {
247 output.push_str(" Signature Algorithm: ");
248 match sig_alg {
249 SignatureAlgorithm::DSA => output.push_str("DSA\n"),
250 SignatureAlgorithm::ECDSA => output.push_str("ECDSA\n"),
251 SignatureAlgorithm::ED25519 => output.push_str("ED25519\n"),
252 SignatureAlgorithm::RSA => output.push_str("RSA\n"),
253 SignatureAlgorithm::RSASSA_PSS(params) => {
254 output.push_str("RSASSA-PSS\n");
255 let indent_s = " ".repeat(indent + 2);
256 output.push_str(&format!(
257 "{}Hash Algorithm: {}\n",
258 indent_s,
259 format_oid(params.hash_algorithm_oid()),
260 ));
261 output.push_str(&format!("{}Mask Generation Function: ", indent_s));
262 if let Ok(mask_gen) = params.mask_gen_algorithm() {
263 output.push_str(&format!(
264 "{}/{}\n",
265 format_oid(&mask_gen.mgf),
266 format_oid(&mask_gen.hash),
267 ));
268 } else {
269 output.push_str("INVALID\n");
270 }
271 output.push_str(&format!(
272 "{}Salt Length: {}\n",
273 indent_s,
274 params.salt_length()
275 ));
276 }
277 SignatureAlgorithm::RSAAES_OAEP(params) => {
278 output.push_str("RSAAES-OAEP\n");
279 let indent_s = " ".repeat(indent + 2);
280 output.push_str(&format!(
281 "{}Hash Algorithm: {}\n",
282 indent_s,
283 format_oid(params.hash_algorithm_oid()),
284 ));
285 output.push_str(&format!("{}Mask Generation Function: ", indent_s));
286 if let Ok(mask_gen) = params.mask_gen_algorithm() {
287 output.push_str(&format!(
288 "{}/{}\n",
289 format_oid(&mask_gen.mgf),
290 format_oid(&mask_gen.hash),
291 ));
292 } else {
293 output.push_str("INVALID\n");
294 }
295 output.push_str(&format!(
296 "{}pSourceFunc: {}\n",
297 indent_s,
298 format_oid(¶ms.p_source_alg().algorithm),
299 ));
300 }
301 }
302 }
303 Err(e) => {
304 output.push_str(&format!("Could not parse signature algorithm: {e}\n"));
305 output.push_str(" Signature Algorithm:\n");
306 output.push_str(&format_x509_digest_algorithm(signature_algorithm, indent));
307 }
308 }
309 output
310}
311
312fn format_x509_ski(public_key: &SubjectPublicKeyInfo) -> String {
313 let mut output = String::new();
314 output.push_str(" Public Key Algorithm:\n");
315 output.push_str(&format_x509_digest_algorithm(&public_key.algorithm, 6));
316 output.push('\n');
317 match public_key.parsed() {
318 Ok(PublicKey::RSA(rsa)) => {
319 output.push_str(&format!(" RSA Public Key: ({} bit)\n", rsa.key_size()));
320 for l in format_number_to_hex_with_colon(rsa.modulus, 16) {
321 output.push_str(&format!(" {l}\n"));
322 }
323 if let Ok(e) = rsa.try_exponent() {
324 output.push_str(&format!(" exponent: 0x{e:x} ({e})\n"));
325 } else {
326 output.push_str(" exponent: <INVALID>:\n");
327 output.push_str(&format_hex_dump(rsa.exponent, 32));
328 output.push('\n');
329 }
330 }
331 Ok(PublicKey::EC(ec)) => {
332 output.push_str(&format!(" EC Public Key: ({} bit)\n", ec.key_size()));
333 for l in format_number_to_hex_with_colon(ec.data(), 16) {
334 output.push_str(&format!(" {l}\n"));
335 }
336 }
337 Ok(PublicKey::DSA(y)) => {
338 output.push_str(&format!(" DSA Public Key: ({} bit)\n", 8 * y.len()));
339 for l in format_number_to_hex_with_colon(y, 16) {
340 output.push_str(&format!(" {l}\n"));
341 }
342 }
343 Ok(PublicKey::GostR3410(y)) => {
344 output.push_str(&format!(
345 " GOST R 34.10-94 Public Key: ({} bit)\n",
346 8 * y.len()
347 ));
348 for l in format_number_to_hex_with_colon(y, 16) {
349 output.push_str(&format!(" {l}\n"));
350 }
351 }
352 Ok(PublicKey::GostR3410_2012(y)) => {
353 output.push_str(&format!(
354 " GOST R 34.10-2012 Public Key: ({} bit)\n",
355 8 * y.len()
356 ));
357 for l in format_number_to_hex_with_colon(y, 16) {
358 output.push_str(&format!(" {l}\n"));
359 }
360 }
361 Ok(PublicKey::Unknown(b)) => {
362 output.push_str(" Unknown key type\n");
363 output.push_str(&format_hex_dump(b, 256));
364 output.push('\n');
365 }
366 Err(_) => {
367 output.push_str(" INVALID PUBLIC KEY\n");
368 }
369 }
370 output
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use x509_parser::parse_x509_certificate;
377
378 #[test]
379 fn pretty_print_with_invalid_certificate_returns_error() {
380 let invalid_der = vec![0x30, 0x82, 0x01, 0xff];
381 let result = parse_x509_certificate(&invalid_der);
382 assert!(result.is_err());
383 }
384
385 #[test]
386 fn pretty_print_with_valid_certificate_succeeds() {
387 let der_cert = hex::decode("308204263082028ea003020102021100e3c99a0ad2a86c42bd9384e972d09952300d06092a864886f70d01010b05003073311e301c060355040a13156d6b6365727420646576656c6f706d656e7420434131243022060355040b0c1b74696c6c69407a757365202854696c6d616e204261756d616e6e29312b302906035504030c226d6b636572742074696c6c69407a757365202854696c6d616e204261756d616e6e29301e170d3236303330353131333234375a170d3238303630353130333234375a304f31273025060355040a131e6d6b6365727420646576656c6f706d656e7420636572746966696361746531243022060355040b0c1b74696c6c69407a757365202854696c6d616e204261756d616e6e2930820122300d06092a864886f70d01010105000382010f003082010a0282010100a36f6785351ad9fd76436b2ce650be1662849b8e21aa68b2c23f4f3bf38a1eb9abf983a653b0d807cc9d0d1b37d313aa1a90a815e531d1dfb2c790bc547090fb9c35bc77df437421a3a2c1ba18013aedfdb65b038acdb8a26719bd18111219db47f8ca6db4e090dad717722f2c0c59725562a3eae2ce6c3996232562ccda4f95c8b64e2da31526dc7f4bdca753a9023a6494a4b7dda11e278af6b917fe0fa6de568ebb03ab801b8272396e2ce4098b1ee8059c666e654237f4d470b9e53ba1c0fc46df54f9dd469cb415091b99186a10e9f378d5074d3fb4aa73bc561326e142e60eed93396ac0bb5040cc8df5c7f81e8bd0594c6e4d06b050664627c33fa3f90203010001a3593057300e0603551d0f0101ff0404030205a030130603551d25040c300a06082b06010505070301301f0603551d230418301680142e997fa48849b3d77b61d74d24c8b999ada1fa62300f0603551d1104083006820474657374300d06092a864886f70d01010b05000382018100b0e65c60ddbace88d4fbadfdef32165381555e83522036a46af625a28cdf331754c7e08a8603b019d5b09aa3b13629a5cd8c0784787b607d04b4c42e47f8983a35f69d082d8feba9c625de3f7ef793837fc045e3358a24249717ae393da23c516343ed3784edf82684e9f1c6394b0b28a9cf869260296ba893815488d19dae49cfa9557135b6450891601118996d03f3c90ba6409deb4ea7bd07e7288ff8575382f28e0837a13fb0fa860cd7880b80f7253c936955a803d5110f384163d4292c9f1e3c413ae2a3b018ae3daef136c1b9bd5888bf8702717e36f85d7bb7b79306bccd6aa4b0206ff903bd6e6d61eaad2d5c62b9417308176faf8a08bac86a5fc42009a418567d1fd0e26eb187a8c7b4208747b62beb81b0284868335d6ea95b2253c4f6203f4e1c2c8d3f0e0075ba7c5b0a92d961425d8c15f1112dc7df857d77904bbc850883c3404067a872a4bae988d954ba9001ac99b75ef7b917b527528fb1a9b54bd17ecd7537f70086ed070e9a2d8e0d83f19b148d1b941456f7283c9b").unwrap();
388
389 let (_, x509) = parse_x509_certificate(&der_cert).unwrap();
390
391 let output = x509.pretty_print();
392 assert!(output.contains("Subject:"));
393 assert!(output.contains("Issuer:"));
394 assert!(output.contains("Serial:"));
395 }
396
397 #[test]
398 fn to_pem_with_valid_certificate_succeeds() {
399 let der_cert = hex::decode("308204263082028ea003020102021100e3c99a0ad2a86c42bd9384e972d09952300d06092a864886f70d01010b05003073311e301c060355040a13156d6b6365727420646576656c6f706d656e7420434131243022060355040b0c1b74696c6c69407a757365202854696c6d616e204261756d616e6e29312b302906035504030c226d6b636572742074696c6c69407a757365202854696c6d616e204261756d616e6e29301e170d3236303330353131333234375a170d3238303630353130333234375a304f31273025060355040a131e6d6b6365727420646576656c6f706d656e7420636572746966696361746531243022060355040b0c1b74696c6c69407a757365202854696c6d616e204261756d616e6e2930820122300d06092a864886f70d01010105000382010f003082010a0282010100a36f6785351ad9fd76436b2ce650be1662849b8e21aa68b2c23f4f3bf38a1eb9abf983a653b0d807cc9d0d1b37d313aa1a90a815e531d1dfb2c790bc547090fb9c35bc77df437421a3a2c1ba18013aedfdb65b038acdb8a26719bd18111219db47f8ca6db4e090dad717722f2c0c59725562a3eae2ce6c3996232562ccda4f95c8b64e2da31526dc7f4bdca753a9023a6494a4b7dda11e278af6b917fe0fa6de568ebb03ab801b8272396e2ce4098b1ee8059c666e654237f4d470b9e53ba1c0fc46df54f9dd469cb415091b99186a10e9f378d5074d3fb4aa73bc561326e142e60eed93396ac0bb5040cc8df5c7f81e8bd0594c6e4d06b050664627c33fa3f90203010001a3593057300e0603551d0f0101ff0404030205a030130603551d25040c300a06082b06010505070301301f0603551d230418301680142e997fa48849b3d77b61d74d24c8b999ada1fa62300f0603551d1104083006820474657374300d06092a864886f70d01010b05000382018100b0e65c60ddbace88d4fbadfdef32165381555e83522036a46af625a28cdf331754c7e08a8603b019d5b09aa3b13629a5cd8c0784787b607d04b4c42e47f8983a35f69d082d8feba9c625de3f7ef793837fc045e3358a24249717ae393da23c516343ed3784edf82684e9f1c6394b0b28a9cf869260296ba893815488d19dae49cfa9557135b6450891601118996d03f3c90ba6409deb4ea7bd07e7288ff8575382f28e0837a13fb0fa860cd7880b80f7253c936955a803d5110f384163d4292c9f1e3c413ae2a3b018ae3daef136c1b9bd5888bf8702717e36f85d7bb7b79306bccd6aa4b0206ff903bd6e6d61eaad2d5c62b9417308176faf8a08bac86a5fc42009a418567d1fd0e26eb187a8c7b4208747b62beb81b0284868335d6ea95b2253c4f6203f4e1c2c8d3f0e0075ba7c5b0a92d961425d8c15f1112dc7df857d77904bbc850883c3404067a872a4bae988d954ba9001ac99b75ef7b917b527528fb1a9b54bd17ecd7537f70086ed070e9a2d8e0d83f19b148d1b941456f7283c9b").unwrap();
400
401 let (_, x509) = parse_x509_certificate(&der_cert).unwrap();
402 let pem = x509.to_pem();
403
404 assert!(pem.contains("-----BEGIN CERTIFICATE-----"));
405 assert!(pem.contains("-----END CERTIFICATE-----"));
406 }
407}