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