1use alloc::string::String;
7use alloc::vec::Vec;
8
9use rustls_pki_types::pem::PemObject;
10use rustls_pki_types::{CertificateDer, TrustAnchor};
11
12#[derive(Debug, Clone)]
15pub struct IdentityConfig {
16 pub identity_cert_pem: Vec<u8>,
18 pub identity_ca_pem: Vec<u8>,
20 pub identity_key_pem: Option<Vec<u8>>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum PkiError {
31 InvalidPem(String),
33 NoCertInPem,
35 CertInvalid(String),
37 EmptyTrustAnchors,
39}
40
41impl core::fmt::Display for PkiError {
42 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 match self {
44 Self::InvalidPem(m) => write!(f, "invalid PEM: {m}"),
45 Self::NoCertInPem => write!(f, "no certificate in PEM"),
46 Self::CertInvalid(m) => write!(f, "certificate invalid: {m}"),
47 Self::EmptyTrustAnchors => write!(f, "trust-anchor bundle is empty"),
48 }
49 }
50}
51
52impl std::error::Error for PkiError {}
53
54#[derive(Debug, Clone)]
56pub(crate) struct ParsedIdentity {
57 pub cert_der: Vec<u8>,
60 pub trust_anchors_der: Vec<Vec<u8>>,
63 pub private_key_pkcs8_der: Option<Vec<u8>>,
66 pub key_algo: CertKeyAlgo,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum CertKeyAlgo {
74 EcdsaP256Sha256,
76 RsaPssSha256,
78 Unknown,
80}
81
82impl ParsedIdentity {
83 pub fn from_config(cfg: &IdentityConfig) -> Result<Self, PkiError> {
85 let cert_der = first_cert_der(&cfg.identity_cert_pem)?;
86 let trust_anchors_der = all_certs_der(&cfg.identity_ca_pem)?;
87 if trust_anchors_der.is_empty() {
88 return Err(PkiError::EmptyTrustAnchors);
89 }
90 verify_cert_chain(&cert_der, &trust_anchors_der)?;
91 let key_algo = detect_cert_algo(&cert_der);
92 let private_key_pkcs8_der = match cfg.identity_key_pem.as_deref() {
93 Some(pem) => Some(parse_pkcs8_pem(pem)?),
94 None => None,
95 };
96 Ok(Self {
97 cert_der,
98 trust_anchors_der,
99 private_key_pkcs8_der,
100 key_algo,
101 })
102 }
103
104 pub fn verify_remote_der(&self, remote_cert_der: &[u8]) -> Result<(), PkiError> {
107 verify_cert_chain(remote_cert_der, &self.trust_anchors_der)
108 }
109}
110
111fn first_cert_der(pem: &[u8]) -> Result<Vec<u8>, PkiError> {
112 let certs = all_certs_der(pem)?;
113 certs.into_iter().next().ok_or(PkiError::NoCertInPem)
114}
115
116fn all_certs_der(pem: &[u8]) -> Result<Vec<Vec<u8>>, PkiError> {
117 let mut out = Vec::new();
120 for item in CertificateDer::pem_slice_iter(pem) {
121 let cert = item.map_err(|e| PkiError::InvalidPem(alloc::format!("{e:?}")))?;
122 out.push(cert.as_ref().to_vec());
123 }
124 Ok(out)
125}
126
127fn verify_cert_chain(end_entity_der: &[u8], trust_anchors_der: &[Vec<u8>]) -> Result<(), PkiError> {
128 let ee = CertificateDer::from_slice(end_entity_der);
129 let end_entity = webpki::EndEntityCert::try_from(&ee)
130 .map_err(|e| PkiError::CertInvalid(alloc::format!("parse: {e:?}")))?;
131
132 let ta_certs: Vec<CertificateDer<'_>> = trust_anchors_der
137 .iter()
138 .map(|b| CertificateDer::from_slice(b))
139 .collect();
140 let mut anchors: Vec<TrustAnchor<'_>> = Vec::with_capacity(ta_certs.len());
141 for ta_cert in &ta_certs {
142 let ta = webpki::anchor_from_trusted_cert(ta_cert)
143 .map_err(|e| PkiError::CertInvalid(alloc::format!("trust-anchor: {e:?}")))?;
144 anchors.push(ta);
145 }
146
147 let now = rustls_pki_types::UnixTime::now();
149
150 let algs = webpki::ALL_VERIFICATION_ALGS;
152
153 end_entity
157 .verify_for_usage(
158 algs,
159 &anchors,
160 &[],
161 now,
162 webpki::KeyUsage::client_auth(),
163 None,
164 None,
165 )
166 .map_err(|e| PkiError::CertInvalid(alloc::format!("verify: {e:?}")))?;
167
168 Ok(())
169}
170
171pub(crate) fn detect_cert_algo_pub(der: &[u8]) -> CertKeyAlgo {
174 detect_cert_algo(der)
175}
176
177fn detect_cert_algo(der: &[u8]) -> CertKeyAlgo {
184 const ECDSA_OID: &[u8] = &[0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
188 const P256_OID: &[u8] = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
189 const RSA_OID: &[u8] = &[
190 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
191 ];
192 if contains_subseq(der, ECDSA_OID) && contains_subseq(der, P256_OID) {
193 CertKeyAlgo::EcdsaP256Sha256
194 } else if contains_subseq(der, RSA_OID) {
195 CertKeyAlgo::RsaPssSha256
196 } else {
197 CertKeyAlgo::Unknown
198 }
199}
200
201fn contains_subseq(haystack: &[u8], needle: &[u8]) -> bool {
202 if needle.is_empty() || haystack.len() < needle.len() {
203 return false;
204 }
205 haystack.windows(needle.len()).any(|w| w == needle)
206}
207
208fn parse_pkcs8_pem(pem: &[u8]) -> Result<Vec<u8>, PkiError> {
209 use rustls_pki_types::PrivatePkcs8KeyDer;
210 PrivatePkcs8KeyDer::pem_slice_iter(pem)
211 .next()
212 .ok_or(PkiError::NoCertInPem)?
213 .map(|k| k.secret_pkcs8_der().to_vec())
214 .map_err(|e| PkiError::InvalidPem(alloc::format!("{e:?}")))
215}
216
217#[cfg(test)]
218#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
219mod mutation_killers {
220 use super::*;
221
222 #[test]
225 fn pki_error_display_messages_are_specific() {
226 assert_eq!(
227 alloc::format!(
228 "{}",
229 PkiError::InvalidPem(alloc::string::String::from("bad"))
230 ),
231 "invalid PEM: bad"
232 );
233 assert_eq!(
234 alloc::format!("{}", PkiError::NoCertInPem),
235 "no certificate in PEM"
236 );
237 assert_eq!(
238 alloc::format!(
239 "{}",
240 PkiError::CertInvalid(alloc::string::String::from("expired"))
241 ),
242 "certificate invalid: expired"
243 );
244 assert_eq!(
245 alloc::format!("{}", PkiError::EmptyTrustAnchors),
246 "trust-anchor bundle is empty"
247 );
248 }
249
250 #[test]
253 fn detect_cert_algo_requires_both_ecdsa_oids() {
254 let only_ecdsa = [0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
255 assert_eq!(detect_cert_algo(&only_ecdsa), CertKeyAlgo::Unknown);
256
257 let only_p256 = [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
258 assert_eq!(detect_cert_algo(&only_p256), CertKeyAlgo::Unknown);
259
260 let mut both = Vec::new();
261 both.extend_from_slice(&only_ecdsa);
262 both.extend_from_slice(&only_p256);
263 assert_eq!(detect_cert_algo(&both), CertKeyAlgo::EcdsaP256Sha256);
264 }
265
266 #[test]
269 fn contains_subseq_not_constant_true() {
270 assert!(!contains_subseq(b"abc", b"xyz"));
271 assert!(!contains_subseq(b"", b"x"));
272 assert!(!contains_subseq(b"a", b"abc"));
273 }
274
275 #[test]
278 fn contains_subseq_empty_needle_returns_false() {
279 assert!(!contains_subseq(b"abc", b""));
280 assert!(!contains_subseq(b"", b""));
281 }
282
283 #[test]
286 fn contains_subseq_exact_match_at_equal_length() {
287 assert!(contains_subseq(b"abc", b"abc"));
288 assert!(contains_subseq(b"\x06\x07\x2A", b"\x06\x07\x2A"));
289 }
290
291 #[test]
294 fn contains_subseq_no_match_returns_false() {
295 assert!(!contains_subseq(b"abcde", b"xyz"));
296 assert!(!contains_subseq(b"\x01\x02\x03\x04", b"\x05\x06"));
297 }
298}