-
Notifications
You must be signed in to change notification settings - Fork 2
/
certificate.rs
142 lines (125 loc) · 4.3 KB
/
certificate.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ChainItem {
pub der: pkcs8::der::Document,
}
impl ChainItem {
pub fn certificate(&self) -> x509_cert::Certificate {
use x509_cert::der::Decode as _;
x509_cert::Certificate::from_der(self.der.as_ref()).expect("der is a certificate")
}
}
pub(crate) async fn load_pem_chain_file(path: &str) -> Result<Vec<ChainItem>, crate::error::Error> {
tracing::trace!(message = "load_pem_chain_file", path = %path);
let content = match tokio::fs::read_to_string(path).await {
Ok(v) => v,
Err(e) => {
tracing::error!(message = "failed to load_pem_chain_file", path = %path, error = ?e);
return Err(e.into());
}
};
match crate::certificate::decode_pem_chain(&content) {
Ok(v) => Ok(v),
Err(e) => {
tracing::error!(message = "failed to decode_pem_chain", path = %path, error = ?e);
Err(e)
}
}
}
// Returns Vec<ChainItem>. The der (Vec<u8>) is guaranteed to be parseable as x509 certificate.
pub(crate) fn decode_pem_chain(input: &str) -> Result<Vec<ChainItem>, crate::error::Error> {
use x509_cert::der::Decode as _;
let mut result = Vec::new();
let pems = split_pems(input);
for pem in pems.into_iter() {
let (_label, der) = pem_rfc7468::decode_vec(pem.as_bytes())?;
x509_cert::Certificate::from_der(&der)?;
let der = pkcs8::der::Document::try_from(der)?;
result.push(ChainItem { der });
}
Ok(result)
}
const PEM_CERT_PREEB: &str = "-----BEGIN CERTIFICATE-----";
const PEM_CERT_POSTEB: &str = "-----END CERTIFICATE-----";
// This fn is not implemented accurately.
pub(crate) fn split_pems(input: &str) -> Vec<String> {
let mut result = Vec::new();
let mut head = None;
for line in input.lines() {
if line.starts_with(PEM_CERT_PREEB) {
head = Some(Vec::new());
}
if let Some(h) = head.as_mut() {
h.push(line);
}
if line.starts_with(PEM_CERT_POSTEB) {
if let Some(h) = head {
result.push(h);
head = Some(Vec::new());
}
}
}
if let Some(h) = head {
if !h.is_empty() {
result.push(h);
}
}
result.into_iter().map(|i| i.join("\n")).collect()
}
#[cfg(test)]
mod test {
use super::*;
const CERT_SINGLE: &str = include_str!("../tests/examples/cert.pem");
const CERT_CHAIN: &str = include_str!("../tests/examples/cert.chained.pem");
#[test]
fn test_parse_single() {
let chain = decode_pem_chain(CERT_SINGLE).unwrap();
assert_eq!(chain.len(), 1);
let cert = chain[0].certificate();
assert_eq!(
extract_cn(&cert.tbs_certificate.subject.0).unwrap(),
"leaf.test.invalid"
);
}
#[test]
fn test_parse_chain() {
let chain = decode_pem_chain(CERT_CHAIN).unwrap();
assert_eq!(chain.len(), 2);
let cert0 = chain[0].certificate();
assert_eq!(
extract_cn(&cert0.tbs_certificate.subject.0).unwrap(),
"leaf.test.invalid"
);
let cert1 = chain[1].certificate();
assert_eq!(
extract_cn(&cert1.tbs_certificate.subject.0).unwrap(),
"rolesanywhere test CA - Intermediate"
);
}
fn extract_cn<'a>(
subject: &'a Vec<x509_cert::name::RelativeDistinguishedName>,
) -> Option<&'a str> {
use x509_cert::der::Tagged as _;
for name in subject {
let frag = name.0.get(0).unwrap();
// id-at-commonName
if frag.oid == "2.5.4.3".parse().unwrap() {
let value = match frag.value.tag() {
x509_cert::der::Tag::Utf8String => frag
.value
.decode_as::<x509_cert::der::asn1::Utf8StringRef>()
.unwrap()
.as_str(),
x509_cert::der::Tag::PrintableString => frag
.value
.decode_as::<x509_cert::der::asn1::PrintableStringRef>()
.unwrap()
.as_str(),
_ => panic!("unknown tag"),
};
return Some(value);
}
}
None
}
}