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}