Skip to main content

structured_pkix/
key.rs

1//! The public key a certificate carries.
2//!
3//! The key stays as it was encoded: re-encoding it here would change what a
4//! key identifier is computed over. [`rsa`] and [`ec`] read its structure in
5//! place; whether a key is usable remains the algorithm registry's decision.
6
7use crate::algorithm::{self, AlgorithmIdentifier};
8use crate::error::{Error, ErrorCode, Located, Result};
9use structured_asn1::memory::Work;
10use structured_asn1::{BitString, Element, Oid, Reader, Tag, universal};
11
12pub mod ec;
13pub mod rsa;
14
15/// A public key and the algorithm it belongs to.
16#[derive(Debug, Clone, Copy)]
17pub struct PublicKeyInfo<'a> {
18    algorithm: AlgorithmIdentifier<'a>,
19    key: BitString<'a>,
20    der: &'a [u8],
21    // Document offset of the first key octet, after the unused-bits octet.
22    key_offset: u64,
23}
24
25impl<'a> PublicKeyInfo<'a> {
26    /// Which algorithm this key belongs to.
27    #[must_use]
28    pub fn algorithm(self) -> AlgorithmIdentifier<'a> {
29        self.algorithm
30    }
31
32    /// The key itself.
33    #[must_use]
34    pub fn key(self) -> BitString<'a> {
35        self.key
36    }
37
38    /// The key bytes, where the key is a whole number of octets, which it is
39    /// for every algorithm a certificate carries.
40    #[must_use]
41    pub fn key_octets(self) -> Option<&'a [u8]> {
42        self.key.octets()
43    }
44
45    /// Where the first key octet sits in the whole document.
46    #[must_use]
47    pub fn key_offset(self) -> u64 {
48        self.key_offset
49    }
50
51    /// Whole key octets, or a located refusal of a padded bit string.
52    pub(crate) fn whole_octets(self, field: &'static str) -> Result<&'a [u8]> {
53        self.key_octets()
54            .ok_or_else(|| Error::new(ErrorCode::MalformedValue, field, self.key_offset))
55    }
56
57    /// The whole structure as it was encoded.
58    ///
59    /// What a fingerprint over the key information is taken of. The key
60    /// identifier the profile describes is a digest of [`Self::key_octets`]
61    /// instead, and the two are different values over different bytes.
62    #[must_use]
63    pub fn as_bytes(self) -> &'a [u8] {
64        self.der
65    }
66    /// Reads SPKI from an element in the caller's reader, retaining its budget.
67    ///
68    /// The structure follows RFC 5280 section 4.1.2.7:
69    /// <https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.7>.
70    /// Algorithm-specific restrictions belong to the cryptographic consumer.
71    /// No key bytes are copied or re-encoded, and no new parser budget is created.
72    ///
73    /// # Errors
74    ///
75    /// Reports malformed structure, trailing fields or an exhausted parent budget.
76    pub fn read_element(
77        parent: &Reader<'a>,
78        element: &Element<'a>,
79        field: &'static str,
80    ) -> Result<PublicKeyInfo<'a>> {
81        let fail = |code| Error::new(code, field, element.span().start);
82
83        if element.tag() != Tag::constructed(universal::SEQUENCE) {
84            return Err(fail(ErrorCode::Encoding(
85                structured_asn1::ErrorCode::UnexpectedTag,
86            )));
87        }
88
89        let mut inside = parent.enter(element).field(field)?;
90        let algorithm_element = inside
91            .next_element()
92            .field(field)?
93            .ok_or_else(|| fail(ErrorCode::MissingField))?;
94        let algorithm = algorithm::read(&inside, &algorithm_element, field)?;
95        let key_element = inside
96            .expect(Tag::primitive(universal::BIT_STRING))
97            .field(field)?;
98        let key = inside.bit_string(&key_element).field(field)?;
99
100        if !inside.is_empty() {
101            return Err(fail(ErrorCode::TrailingData));
102        }
103
104        Ok(PublicKeyInfo {
105            algorithm,
106            key,
107            der: element.as_bytes(),
108            // A primitive BIT STRING: one unused-bits octet, then the bits.
109            key_offset: key_element.contents_span().start + 1,
110        })
111    }
112}
113
114/// Paid identity of two OIDs: the length check, then the octets compared.
115pub(crate) fn same_oid<W: Work>(
116    left: Oid<'_>,
117    right: Oid<'_>,
118    execution: &W,
119    field: &'static str,
120    at: u64,
121) -> Result<bool> {
122    let charge = |amount| {
123        execution
124            .charge_work(amount)
125            .map_err(|reason| Error::resource(reason, field, at))
126    };
127    charge(1)?;
128    if left.as_bytes().len() != right.as_bytes().len() {
129        return Ok(false);
130    }
131    charge(left.as_bytes().len() as u64)?;
132    Ok(left == right)
133}
134
135#[cfg(test)]
136mod tests;