Skip to main content

structured_pkix/key/
ec.rs

1//! The elliptic-curve public key a certificate carries, read in place.
2//!
3//! RFC 5480 §2.1.1 writes the curve as `ECParameters ::= CHOICE { namedCurve
4//! OBJECT IDENTIFIER, implicitCurve NULL, specifiedCurve SpecifiedECDomain }`
5//! and allows only `namedCurve` in PKIX; §2.2 carries the point as the SEC1
6//! octets themselves. [`read`] reports which choice was written rather than
7//! refusing the other two, and refuses only a point no curve can have. Which
8//! curves are supported, their coordinate widths and whether the point lies on
9//! the curve belong to the provider that verifies with the key.
10//! <https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1>
11
12use super::same_oid;
13use crate::error::{Error, ErrorCode, Located, Result};
14use crate::path::WorkingKey;
15use structured_asn1::memory::Work;
16use structured_asn1::{Element, Oid, Tag, universal};
17
18const FIELD: &str = "subjectPublicKey.ec";
19const PARAMETERS: &str = "subjectPublicKeyInfo.algorithm.parameters";
20
21/// Which `ECParameters` choice was written.
22#[derive(Debug, Clone, Copy)]
23pub enum Curve<'a> {
24    /// `namedCurve`, the only choice RFC 5480 §2.1.1 permits in PKIX.
25    Named(Oid<'a>),
26    /// `implicitCurve`: the curve is known only out of band.
27    Implicit,
28    /// `specifiedCurve`: explicit domain parameters, kept encoded.
29    Specified(Element<'a>),
30}
31
32/// A SEC1 point as written (SEC1 v2 §2.3.3).
33#[derive(Debug, Clone, Copy)]
34pub enum Point<'a> {
35    /// `0x04 || X || Y`, with both coordinates of one width.
36    Uncompressed {
37        /// The x coordinate.
38        x: &'a [u8],
39        /// The y coordinate.
40        y: &'a [u8],
41    },
42    /// `0x02 || X` or `0x03 || X`, naming the parity of y.
43    Compressed {
44        /// The x coordinate.
45        x: &'a [u8],
46        /// Whether y is odd (`0x03`).
47        odd: bool,
48    },
49}
50
51impl Point<'_> {
52    /// The coordinate width in octets, to compare with the width of the
53    /// curve the caller recognized.
54    #[must_use]
55    pub fn coordinate_len(self) -> usize {
56        match self {
57            Self::Uncompressed { x, .. } | Self::Compressed { x, .. } => x.len(),
58        }
59    }
60}
61
62/// An elliptic-curve public key, as the certificate wrote it.
63#[derive(Debug, Clone, Copy)]
64pub struct PublicKey<'a> {
65    curve: Curve<'a>,
66    parameters: Element<'a>,
67    inherited: bool,
68    point: Point<'a>,
69    octets: &'a [u8],
70    offset: u64,
71}
72
73impl<'a> PublicKey<'a> {
74    /// The curve choice in force.
75    #[must_use]
76    pub fn curve(self) -> Curve<'a> {
77        self.curve
78    }
79
80    /// The original `ECParameters` element the curve was read from.
81    #[must_use]
82    pub fn parameters(self) -> Element<'a> {
83        self.parameters
84    }
85
86    /// Whether the parameters were written by an authority above this key
87    /// rather than by the key itself (RFC 5280 §6.1.4(e)).
88    #[must_use]
89    pub fn is_inherited(self) -> bool {
90        self.inherited
91    }
92
93    /// The point, split into its coordinates.
94    #[must_use]
95    pub fn point(self) -> Point<'a> {
96        self.point
97    }
98
99    /// The whole original point encoding, prefix octet included.
100    #[must_use]
101    pub fn as_bytes(self) -> &'a [u8] {
102        self.octets
103    }
104
105    /// Where the point encoding starts in the whole document.
106    #[must_use]
107    pub fn offset(self) -> u64 {
108        self.offset
109    }
110}
111
112/// Reads the elliptic-curve key carried by `key` under the current `execution`.
113///
114/// The key's algorithm must be `id-ecPublicKey`. Its own parameters apply
115/// where written; a key that writes none or NULL stands in the parameters
116/// inherited along the path, and a lone NULL is `implicitCurve`. Parameters
117/// are decoded with their original parser session, offsets, encoding and
118/// depth, which may belong to the authority's certificate; `execution` pays
119/// for every step. Nothing is copied or allocated.
120///
121/// # Errors
122///
123/// `UnexpectedField` for another algorithm, `MissingField` when no parameters
124/// are in force, `MalformedValue` for padded key bits or a point that is
125/// empty, the point at infinity, the hybrid form RFC 5480 §2.2 forbids or of
126/// unequal coordinate widths, and the located encoding or resource error
127/// otherwise. A resource refusal happens before the work it would fund.
128///
129/// # Examples
130///
131/// ```
132/// use std::sync::Arc;
133/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
134/// use structured_pkix::{PublicKeyInfo, key::ec, path::WorkingKey};
135/// let memory = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
136/// let parser = Parser::der(memory.clone());
137/// // id-ecPublicKey, prime256v1; compressed point with a one-octet x
138/// let spki = b"\x30\x1b\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\
139///              \x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x04\x00\x03\x05\x06";
140/// let mut reader = parser.read(spki, 0).unwrap();
141/// let element = reader.next_element().unwrap().unwrap();
142/// let key = PublicKeyInfo::read_element(&reader, &element, "subjectPublicKeyInfo")?;
143/// let read = ec::read(WorkingKey::standing(key), &memory)?;
144/// assert!(matches!(read.curve(), ec::Curve::Named(_)));
145/// assert!(matches!(read.point(), ec::Point::Compressed { odd: true, .. }));
146/// assert_eq!(read.point().coordinate_len(), 2);
147/// assert_eq!(read.offset(), 26);
148/// # Ok::<(), structured_pkix::Error>(())
149/// ```
150pub fn read<'a, W: Work>(key: WorkingKey<'a>, execution: &W) -> Result<PublicKey<'a>> {
151    let info = key.key();
152    let own = info.algorithm();
153    if !same_oid(
154        own.algorithm(),
155        crate::oid::EC_PUBLIC_KEY,
156        execution,
157        FIELD,
158        own.offset(),
159    )? {
160        return Err(Error::new(ErrorCode::UnexpectedField, FIELD, own.offset()));
161    }
162    // RFC 5280 §6.1.4(e) treats absent and NULL parameters alike for
163    // inheritance. Without an authority's parameters, a written NULL is the
164    // key's own `implicitCurve` choice.
165    let (source, inherited) = match (key.parameter_source(), own.parameters_nested()) {
166        (Some(source), _) => (source, own.has_no_parameters()),
167        (None, Some(null)) => (null, false),
168        (None, None) => {
169            return Err(Error::new(
170                ErrorCode::MissingField,
171                PARAMETERS,
172                own.offset(),
173            ));
174        }
175    };
176    let (reader, parameters) = source.open(execution, PARAMETERS)?;
177    let curve = if parameters.tag() == Tag::primitive(universal::OID) {
178        Curve::Named(reader.oid(&parameters).field(PARAMETERS)?)
179    } else if parameters.tag() == Tag::primitive(universal::NULL) {
180        reader.null(&parameters).field(PARAMETERS)?;
181        Curve::Implicit
182    } else if parameters.tag() == Tag::constructed(universal::SEQUENCE) {
183        Curve::Specified(parameters)
184    } else {
185        return Err(Error::new(
186            ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
187            PARAMETERS,
188            parameters.span().start,
189        ));
190    };
191
192    let octets = info.whole_octets(FIELD)?;
193    let offset = info.key_offset();
194    execution
195        .charge_work(1)
196        .map_err(|reason| Error::resource(reason, FIELD, offset))?;
197    // SEC1 v2 §2.3.3: one prefix octet, then x, then y for the uncompressed
198    // form. The single zero octet is the point at infinity, which is not a
199    // public key (SEC1 v2 §3.2.2), and RFC 5480 §2.2 forbids the hybrid
200    // 0x06/0x07 form. https://www.secg.org/sec1-v2.pdf
201    let point = match octets {
202        [0x04, coordinates @ ..] if !coordinates.is_empty() && coordinates.len() % 2 == 0 => {
203            let (x, y) = coordinates.split_at(coordinates.len() / 2);
204            Point::Uncompressed { x, y }
205        }
206        [prefix @ (0x02 | 0x03), x @ ..] if !x.is_empty() => Point::Compressed {
207            x,
208            odd: *prefix == 0x03,
209        },
210        _ => return Err(Error::new(ErrorCode::MalformedValue, FIELD, offset)),
211    };
212    Ok(PublicKey {
213        curve,
214        parameters,
215        inherited,
216        point,
217        octets,
218        offset,
219    })
220}