attestation_validator/lib.rs
1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4pub mod yubico;
5
6use std::{
7 collections::HashMap,
8 io::{BufReader, Read, Seek},
9 ops::{Index, Range, RangeFrom},
10};
11
12use x509_parser::{
13 error::X509Error,
14 pem::Pem,
15 prelude::{FromDer, X509Certificate},
16};
17
18/// Error when parsing artifacts or performing validation.
19#[non_exhaustive]
20#[derive(Debug, thiserror::Error)]
21pub enum Error {
22 /// X509 error.
23 #[error("X509 error")]
24 X509(#[from] x509_parser::error::X509Error),
25
26 /// PEM format error.
27 #[error("PEM error")]
28 Pem(#[from] x509_parser::error::PEMError),
29
30 /// Expected OID has not been found.
31 #[error("Expected to find OID {oid} but it is missing")]
32 OidMissing {
33 /// The OID that was expected.
34 oid: String,
35 },
36
37 /// Unexpected field value.
38 #[error(
39 "Field {field} has unexpected value {actual} (expected {expected}) when inspecting {oid}"
40 )]
41 UnexpectedFieldValue {
42 /// Extension being inspected.
43 oid: String,
44
45 /// Field description.
46 field: String,
47
48 /// Expected value.
49 expected: String,
50
51 /// Actual value.
52 actual: String,
53 },
54
55 /// Expected to find a certificate.
56 #[error("Certificate is missing")]
57 CertificateMissing,
58}
59
60impl From<x509_parser::asn1_rs::Err<x509_parser::error::X509Error>> for Error {
61 fn from(value: x509_parser::asn1_rs::Err<x509_parser::error::X509Error>) -> Self {
62 use x509_parser::nom::Err;
63 let inner = match value {
64 Err::Incomplete(_) => X509Error::Generic,
65 Err::Error(e) | Err::Failure(e) => e,
66 };
67 Self::X509(inner)
68 }
69}
70
71/// Library-specific result type.
72pub type Result<T> = std::result::Result<T, Error>;
73
74/// Attestation validator.
75///
76/// # Examples
77///
78/// ```
79/// # fn main() -> testresult::TestResult {
80/// let mut validator = attestation_validator::Validator::default();
81///
82/// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
83///
84/// let extensions = validator.leaf_extensions()?;
85/// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
86/// # Ok(()) }
87/// ```
88#[derive(Default, Debug)]
89pub struct Validator {
90 cert_chain: Vec<OwnedCert>,
91}
92
93#[derive(Debug)]
94struct OwnedCert {
95 contents: Vec<u8>,
96}
97
98impl OwnedCert {
99 fn new(contents: Vec<u8>) -> Self {
100 Self { contents }
101 }
102}
103
104impl Validator {
105 fn add_and_validate(&mut self, der: Vec<u8>) -> Result<()> {
106 if let Some(parent) = self.cert_chain.last() {
107 let parent = X509Certificate::from_der(&parent.contents)?.1;
108 let current = X509Certificate::from_der(&der)?.1;
109 current.verify_signature(Some(parent.public_key()))?;
110 }
111 self.cert_chain.push(OwnedCert::new(der));
112 Ok(())
113 }
114
115 /// Adds one or more PEM-encoded certificates from the reader to the certificate chain.
116 ///
117 /// Adding certificate triggers chain validation.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// # fn main() -> testresult::TestResult {
123 /// let mut validator = attestation_validator::Validator::default();
124 ///
125 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
126 /// # Ok(()) }
127 /// ```
128 ///
129 /// # Errors
130 ///
131 /// Returns an error if PEM parsing fails or certificate chain validation fails.
132 pub fn add_from_pem(&mut self, reader: impl Read + Seek) -> Result<()> {
133 for pem in Pem::iter_from_reader(BufReader::new(reader)) {
134 let pem = pem?;
135 self.add_and_validate(pem.contents)?;
136 }
137 Ok(())
138 }
139
140 /// Adds one raw, binary DER-encoded certificate to the certificate chain.
141 ///
142 /// Adding certificate triggers chain validation.
143 ///
144 /// # Examples
145 ///
146 /// ```
147 /// # fn main() -> testresult::TestResult {
148 /// let mut validator = attestation_validator::Validator::default();
149 ///
150 /// validator.add_from_der(std::fs::read("hsm-attestation-cert.cer")?)?;
151 /// # Ok(()) }
152 /// ```
153 ///
154 /// # Errors
155 ///
156 /// Returns an error if DER parsing fails or certificate chain validation fails.
157 pub fn add_from_der(&mut self, der: Vec<u8>) -> Result<()> {
158 self.add_and_validate(der)?;
159 Ok(())
160 }
161
162 /// Returns [extensions][Extensions] present in the last certificate in the chain (leaf).
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// # fn main() -> testresult::TestResult {
168 /// let mut validator = attestation_validator::Validator::default();
169 ///
170 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
171 ///
172 /// let extensions = validator.leaf_extensions()?;
173 /// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
174 /// # Ok(()) }
175 /// ```
176 ///
177 /// # Errors
178 ///
179 /// Returns an error if DER parsing of the last certificate fails, if there are no certificates in the chain or no extensions.
180 pub fn leaf_extensions(&self) -> Result<Extensions> {
181 let leaf = self.cert_chain.last().ok_or(Error::CertificateMissing)?;
182 let leaf = X509Certificate::from_der(&leaf.contents)?.1;
183 let ext = leaf.extensions_map()?;
184 Ok(Extensions(
185 ext.into_iter()
186 .map(|(k, v)| (k.to_string(), v.value.to_vec()))
187 .collect(),
188 ))
189 }
190
191 /// Returns a raw, unparsed PKIX of the public key in ASN.1 DER form (see [RFC 5280, Section 4.1]).
192 ///
193 /// # Examples
194 ///
195 /// ```
196 /// # fn main() -> testresult::TestResult {
197 /// let mut validator = attestation_validator::Validator::default();
198 ///
199 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
200 ///
201 /// let public_key_der = validator.leaf_public_key()?;
202 /// assert_eq!(public_key_der[0..3], [48, 42, 48]);
203 /// # Ok(()) }
204 /// ```
205 ///
206 /// # Errors
207 ///
208 /// Returns an error if DER parsing of the last certificate fails, if there are no certificates in the chain.
209 ///
210 /// [RFC 5280, Section 4.1]: https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1
211 pub fn leaf_public_key(&self) -> Result<Vec<u8>> {
212 let leaf = self.cert_chain.last().ok_or(Error::CertificateMissing)?;
213 let leaf = X509Certificate::from_der(&leaf.contents)?.1;
214 Ok(leaf.public_key().raw.into())
215 }
216}
217
218/// Represents one particular extension.
219///
220/// # Examples
221///
222/// ```
223/// # fn main() -> testresult::TestResult {
224/// let mut validator = attestation_validator::Validator::default();
225///
226/// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
227///
228/// let extensions = validator.leaf_extensions()?;
229/// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
230/// # Ok(()) }
231/// ```
232#[derive(Debug)]
233pub struct Extension<'a> {
234 name: &'a str,
235 value: &'a [u8],
236}
237
238impl Extension<'_> {
239 pub(crate) fn assert_value_byte(
240 &self,
241 index: usize,
242 expected_value: u8,
243 context: &str,
244 ) -> Result<()> {
245 let actual = self.value[index];
246 if actual != expected_value {
247 return Err(Error::UnexpectedFieldValue {
248 oid: self.name.into(),
249 field: context.into(),
250 expected: expected_value.to_string(),
251 actual: actual.to_string(),
252 });
253 }
254 Ok(())
255 }
256
257 /// Returns the extension OID (name).
258 ///
259 /// # Examples
260 ///
261 /// ```
262 /// # fn main() -> testresult::TestResult {
263 /// let mut validator = attestation_validator::Validator::default();
264 ///
265 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
266 ///
267 /// let extensions = validator.leaf_extensions()?;
268 /// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
269 /// assert_eq!(extension.name(), "1.3.6.1.4.1.41482.4.1");
270 /// # Ok(()) }
271 /// ```
272 #[must_use]
273 pub fn name(&self) -> &str {
274 self.name
275 }
276}
277
278impl Index<usize> for Extension<'_> {
279 type Output = u8;
280
281 /// Returns a byte of extension value at given position.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// # fn main() -> testresult::TestResult {
287 /// let mut validator = attestation_validator::Validator::default();
288 ///
289 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
290 ///
291 /// let extensions = validator.leaf_extensions()?;
292 /// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
293 /// assert_eq!(extension[0], 4);
294 /// # Ok(()) }
295 /// ```
296 fn index(&self, index: usize) -> &Self::Output {
297 &self.value[index]
298 }
299}
300
301impl Index<Range<usize>> for Extension<'_> {
302 type Output = [u8];
303
304 /// Returns byte slice of extension value for given range.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// # fn main() -> testresult::TestResult {
310 /// let mut validator = attestation_validator::Validator::default();
311 ///
312 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
313 ///
314 /// let extensions = validator.leaf_extensions()?;
315 /// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
316 /// assert_eq!(extension[0..2], [4, 3]);
317 /// # Ok(()) }
318 /// ```
319 fn index(&self, index: Range<usize>) -> &Self::Output {
320 &self.value[index]
321 }
322}
323
324impl Index<RangeFrom<usize>> for Extension<'_> {
325 type Output = [u8];
326
327 /// Returns byte slice of extension value for given range.
328 ///
329 /// # Examples
330 ///
331 /// ```
332 /// # fn main() -> testresult::TestResult {
333 /// let mut validator = attestation_validator::Validator::default();
334 ///
335 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
336 ///
337 /// let extensions = validator.leaf_extensions()?;
338 /// let extension = extensions.get("1.3.6.1.4.1.41482.4.1")?;
339 /// assert_eq!(extension[2..], [2, 4, 1]);
340 /// # Ok(()) }
341 /// ```
342 fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
343 &self.value[index]
344 }
345}
346
347/// Represents certificate extensions.
348///
349/// Use [`Validator::leaf_extensions`] to retrieve the [`Extensions`] object for the leaf
350/// certificate (last certificate added to the validator).
351///
352/// # Examples
353///
354/// ```
355/// # fn main() -> testresult::TestResult {
356/// let mut validator = attestation_validator::Validator::default();
357///
358/// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
359///
360/// let extensions = validator.leaf_extensions()?;
361/// # Ok(()) }
362/// ```
363#[derive(Debug)]
364pub struct Extensions(HashMap<String, Vec<u8>>);
365
366impl Extensions {
367 /// Returns inner object holding a map from stringified OIDs to their raw values.
368 ///
369 /// # Examples
370 ///
371 /// ```
372 /// # fn main() -> testresult::TestResult {
373 /// let mut validator = attestation_validator::Validator::default();
374 ///
375 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
376 ///
377 /// let map = validator.leaf_extensions()?.into_inner();
378 ///
379 /// assert!(map.contains_key("1.3.6.1.4.1.41482.4.1"));
380 /// # Ok(()) }
381 #[must_use]
382 pub fn into_inner(self) -> HashMap<String, Vec<u8>> {
383 self.0
384 }
385
386 /// Returns a value of the extension if it is present or an error otherwise.
387 ///
388 /// # Examples
389 ///
390 /// ```
391 /// # fn main() -> testresult::TestResult {
392 /// let mut validator = attestation_validator::Validator::default();
393 ///
394 /// validator.add_from_pem(std::fs::File::open("hsm-attestation-pem")?)?;
395 ///
396 /// let extension = validator.leaf_extensions()?.get("1.3.6.1.4.1.41482.4.1")?;
397 /// # Ok(()) }
398 /// ```
399 ///
400 /// # Errors
401 ///
402 /// If the value for given `oid` is missing this function returns [`Error::OidMissing`].
403 pub fn get<'a>(&'a self, oid: &'a str) -> Result<Extension<'a>> {
404 self.0
405 .get(oid)
406 .map(|v| Extension {
407 name: oid,
408 value: &v[..],
409 })
410 .ok_or_else(|| Error::OidMissing { oid: oid.into() })
411 }
412}