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. Point and parameter bytes remain borrowed; nested-reader
120/// metadata uses the existing accounted scratch path.
121///
122/// # Errors
123///
124/// `UnexpectedField` for another algorithm, `MissingField` when no parameters
125/// are in force, `MalformedValue` for padded key bits or a point that is
126/// empty, the point at infinity, the hybrid form RFC 5480 §2.2 forbids or of
127/// unequal coordinate widths, and the located encoding or resource error
128/// otherwise. A resource refusal happens before the work it would fund.
129///
130/// # Examples
131///
132/// ```
133/// use std::sync::Arc;
134/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount}};
135/// use structured_pkix::{PublicKeyInfo, key::ec, path::WorkingKey};
136/// let memory = Memory::new(Arc::new(MemoryAccount::new(4096, 4096)));
137/// let parser = Parser::der(memory.clone());
138/// // id-ecPublicKey, prime256v1; compressed point with a one-octet x
139/// let spki = b"\x30\x1b\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\
140/// \x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x04\x00\x03\x05\x06";
141/// let mut reader = parser.read(spki, 0).unwrap();
142/// let element = reader.next_element().unwrap().unwrap();
143/// let key = PublicKeyInfo::read_element(&reader, &element, "subjectPublicKeyInfo")?;
144/// let read = ec::read(WorkingKey::standing(key), &memory)?;
145/// assert!(matches!(read.curve(), ec::Curve::Named(_)));
146/// assert!(matches!(read.point(), ec::Point::Compressed { odd: true, .. }));
147/// assert_eq!(read.point().coordinate_len(), 2);
148/// assert_eq!(read.offset(), 26);
149/// # Ok::<(), structured_pkix::Error>(())
150/// ```
151pub fn read<'a, W: Work>(key: WorkingKey<'a>, execution: &W) -> Result<PublicKey<'a>> {
152 let info = key.key();
153 let own = info.algorithm();
154 // The key's OID and point belong to its own source even when the curve
155 // comes from another certificate. Current funding cannot reset either.
156 let query = own.query_in(execution);
157 if !same_oid(
158 own.algorithm(),
159 crate::oid::EC_PUBLIC_KEY,
160 &query,
161 FIELD,
162 own.offset(),
163 )? {
164 return Err(Error::new(ErrorCode::UnexpectedField, FIELD, own.offset()));
165 }
166 // RFC 5280 §6.1.4(e) treats absent and NULL parameters alike for
167 // inheritance. Without an authority's parameters, a written NULL is the
168 // key's own `implicitCurve` choice.
169 let (source, inherited) = match (key.parameter_source(), own.parameters_nested()) {
170 (Some(source), _) => (source, own.has_no_parameters()),
171 (None, Some(null)) => (null, false),
172 (None, None) => {
173 return Err(Error::new(
174 ErrorCode::MissingField,
175 PARAMETERS,
176 own.offset(),
177 ));
178 }
179 };
180 let (reader, parameters) = source.open(execution, PARAMETERS)?;
181 let curve = if parameters.tag() == Tag::primitive(universal::OID) {
182 Curve::Named(reader.oid(¶meters).field(PARAMETERS)?)
183 } else if parameters.tag() == Tag::primitive(universal::NULL) {
184 reader.null(¶meters).field(PARAMETERS)?;
185 Curve::Implicit
186 } else if parameters.tag() == Tag::constructed(universal::SEQUENCE) {
187 Curve::Specified(parameters)
188 } else {
189 return Err(Error::new(
190 ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
191 PARAMETERS,
192 parameters.span().start,
193 ));
194 };
195
196 let offset = info.key_offset();
197 // Admit framing before both padded-bit refusal and prefix inspection.
198 query
199 .charge_work(1)
200 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
201 let octets = info.whole_octets(FIELD)?;
202 // SEC1 v2 §2.3.3: one prefix octet, then x, then y for the uncompressed
203 // form. The single zero octet is the point at infinity, which is not a
204 // public key (SEC1 v2 §3.2.2), and RFC 5480 §2.2 forbids the hybrid
205 // 0x06/0x07 form. https://www.secg.org/sec1-v2.pdf
206 let point = match octets {
207 [0x04, coordinates @ ..] if !coordinates.is_empty() && coordinates.len() % 2 == 0 => {
208 let (x, y) = coordinates.split_at(coordinates.len() / 2);
209 Point::Uncompressed { x, y }
210 }
211 [prefix @ (0x02 | 0x03), x @ ..] if !x.is_empty() => Point::Compressed {
212 x,
213 odd: *prefix == 0x03,
214 },
215 _ => return Err(Error::new(ErrorCode::MalformedValue, FIELD, offset)),
216 };
217 Ok(PublicKey {
218 curve,
219 parameters,
220 inherited,
221 point,
222 octets,
223 offset,
224 })
225}