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(¶meters).field(PARAMETERS)?)
179 } else if parameters.tag() == Tag::primitive(universal::NULL) {
180 reader.null(¶meters).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}