stackforge_core/layer/tls/
cert.rs1#[derive(Debug, Clone)]
8pub struct TlsCertificate {
9 pub der: Vec<u8>,
11}
12
13impl TlsCertificate {
14 #[must_use]
16 pub fn from_der(der: Vec<u8>) -> Self {
17 Self { der }
18 }
19
20 #[must_use]
22 pub fn from_pem(pem: &str) -> Option<Self> {
23 let lines: Vec<&str> = pem
24 .lines()
25 .filter(|line| !line.starts_with("-----"))
26 .collect();
27 let b64 = lines.join("");
28 let der = base64_decode(&b64)?;
29 Some(Self { der })
30 }
31
32 #[must_use]
34 pub fn as_der(&self) -> &[u8] {
35 &self.der
36 }
37
38 #[must_use]
40 pub fn len(&self) -> usize {
41 self.der.len()
42 }
43
44 #[must_use]
46 pub fn is_empty(&self) -> bool {
47 self.der.is_empty()
48 }
49
50 #[must_use]
56 pub fn subject_cn(&self) -> Option<String> {
57 let cn_oid = [0x55, 0x04, 0x03];
58 find_nth_string_after_oid(&self.der, &cn_oid, 1)
59 .or_else(|| find_nth_string_after_oid(&self.der, &cn_oid, 0))
60 }
61
62 #[must_use]
64 pub fn issuer_cn(&self) -> Option<String> {
65 let cn_oid = [0x55, 0x04, 0x03];
66 find_nth_string_after_oid(&self.der, &cn_oid, 0)
67 }
68}
69
70#[must_use]
72pub fn parse_pem_chain(pem: &str) -> Vec<TlsCertificate> {
73 let mut certs = Vec::new();
74 let mut in_cert = false;
75 let mut current = String::new();
76
77 for line in pem.lines() {
78 if line.contains("BEGIN CERTIFICATE") {
79 in_cert = true;
80 current.clear();
81 current.push_str(line);
82 current.push('\n');
83 } else if line.contains("END CERTIFICATE") {
84 current.push_str(line);
85 current.push('\n');
86 if let Some(cert) = TlsCertificate::from_pem(¤t) {
87 certs.push(cert);
88 }
89 in_cert = false;
90 } else if in_cert {
91 current.push_str(line);
92 current.push('\n');
93 }
94 }
95
96 certs
97}
98
99fn base64_decode(input: &str) -> Option<Vec<u8>> {
101 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
102
103 let input: Vec<u8> = input
104 .bytes()
105 .filter(|&b| b != b'\n' && b != b'\r' && b != b' ')
106 .collect();
107 if input.is_empty() {
108 return Some(Vec::new());
109 }
110
111 let mut output = Vec::with_capacity(input.len() * 3 / 4);
112 let mut buf: u32 = 0;
113 let mut bits = 0;
114
115 for &byte in &input {
116 let val = if byte == b'=' {
117 continue;
118 } else if let Some(pos) = TABLE.iter().position(|&b| b == byte) {
119 pos as u32
120 } else {
121 return None;
122 };
123
124 buf = (buf << 6) | val;
125 bits += 6;
126
127 if bits >= 8 {
128 bits -= 8;
129 output.push((buf >> bits) as u8);
130 buf &= (1 << bits) - 1;
131 }
132 }
133
134 Some(output)
135}
136
137fn find_nth_string_after_oid(data: &[u8], oid: &[u8], n: usize) -> Option<String> {
141 let mut count = 0;
142 for i in 0..data.len().saturating_sub(oid.len()) {
143 if data[i..].starts_with(oid) {
144 let pos = i + oid.len();
145 if pos + 2 > data.len() {
146 continue;
147 }
148 let tag = data[pos];
149 if tag == 0x0C || tag == 0x13 || tag == 0x16 {
151 let len = data[pos + 1] as usize;
152 if pos + 2 + len <= data.len() {
153 if count == n {
154 return String::from_utf8(data[pos + 2..pos + 2 + len].to_vec()).ok();
155 }
156 count += 1;
157 }
158 }
159 }
160 }
161 None
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_from_der() {
170 let cert = TlsCertificate::from_der(vec![0x30, 0x82, 0x01, 0x00]);
171 assert_eq!(cert.len(), 4);
172 assert!(!cert.is_empty());
173 }
174
175 #[test]
176 fn test_base64_decode() {
177 assert_eq!(base64_decode("SGVsbG8="), Some(b"Hello".to_vec()));
178 assert_eq!(base64_decode(""), Some(Vec::new()));
179 assert_eq!(base64_decode("YQ=="), Some(b"a".to_vec()));
180 }
181
182 #[test]
183 fn test_from_pem() {
184 let pem = "-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n";
185 let cert = TlsCertificate::from_pem(pem);
186 assert!(cert.is_some());
187 }
188
189 #[test]
190 fn test_find_cn() {
191 let mut data = Vec::new();
193 data.extend_from_slice(&[0x30, 0x20]); data.extend_from_slice(&[0x06, 0x03, 0x55, 0x04, 0x03]); data.extend_from_slice(&[0x0C, 0x04]); data.extend_from_slice(b"test");
197
198 let result = find_nth_string_after_oid(&data, &[0x55, 0x04, 0x03], 0);
199 assert_eq!(result, Some("test".to_string()));
200 }
201}