elliptic_curve/
public_key.rs

1//! Elliptic curve public keys.
2
3use crate::{
4    AffinePoint, Curve, Error, NonZeroScalar, ProjectiveArithmetic, ProjectivePoint, Result,
5};
6use core::fmt::Debug;
7use group::{Curve as _, Group};
8
9#[cfg(feature = "jwk")]
10use crate::{JwkEcKey, JwkParameters};
11
12#[cfg(all(feature = "sec1", feature = "pkcs8"))]
13use crate::{
14    pkcs8::{self, AssociatedOid, DecodePublicKey},
15    ALGORITHM_OID,
16};
17
18#[cfg(feature = "pem")]
19use core::str::FromStr;
20
21#[cfg(feature = "sec1")]
22use {
23    crate::{
24        sec1::{EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint},
25        FieldSize, PointCompression,
26    },
27    core::cmp::Ordering,
28    subtle::CtOption,
29};
30
31#[cfg(feature = "serde")]
32use serdect::serde::{de, ser, Deserialize, Serialize};
33
34#[cfg(all(feature = "alloc", feature = "pkcs8"))]
35use pkcs8::EncodePublicKey;
36
37#[cfg(any(feature = "jwk", feature = "pem"))]
38use alloc::string::{String, ToString};
39
40/// Elliptic curve public keys.
41///
42/// This is a wrapper type for [`AffinePoint`] which ensures an inner
43/// non-identity point and provides a common place to handle encoding/decoding.
44///
45/// # Parsing "SPKI" Keys
46///
47/// X.509 `SubjectPublicKeyInfo` (SPKI) is a commonly used format for encoding
48/// public keys, notably public keys corresponding to PKCS#8 private keys.
49/// (especially ones generated by OpenSSL).
50///
51/// Keys in SPKI format are either binary (ASN.1 BER/DER), or PEM encoded
52/// (ASCII) and begin with the following:
53///
54/// ```text
55/// -----BEGIN PUBLIC KEY-----
56/// ```
57///
58/// To decode an elliptic curve public key from SPKI, enable the `pkcs8`
59/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto
60/// elliptic curve crate) and use the
61/// [`elliptic_curve::pkcs8::DecodePublicKey`][`pkcs8::DecodePublicKey`]
62/// trait to parse it.
63///
64/// When the `pem` feature of this crate (or a specific RustCrypto elliptic
65/// curve crate) is enabled, a [`FromStr`] impl is also available.
66///
67/// # `serde` support
68///
69/// When the optional `serde` feature of this create is enabled, [`Serialize`]
70/// and [`Deserialize`] impls are provided for this type.
71///
72/// The serialization is binary-oriented and supports ASN.1 DER
73/// Subject Public Key Info (SPKI) as the encoding format.
74///
75/// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead.
76#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct PublicKey<C>
79where
80    C: Curve + ProjectiveArithmetic,
81{
82    point: AffinePoint<C>,
83}
84
85impl<C> PublicKey<C>
86where
87    C: Curve + ProjectiveArithmetic,
88{
89    /// Convert an [`AffinePoint`] into a [`PublicKey`]
90    pub fn from_affine(point: AffinePoint<C>) -> Result<Self> {
91        if ProjectivePoint::<C>::from(point).is_identity().into() {
92            Err(Error)
93        } else {
94            Ok(Self { point })
95        }
96    }
97
98    /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value
99    /// (i.e. a secret key represented as a raw scalar value)
100    pub fn from_secret_scalar(scalar: &NonZeroScalar<C>) -> Self {
101        // `NonZeroScalar` ensures the resulting point is not the identity
102        Self {
103            point: (C::ProjectivePoint::generator() * scalar.as_ref()).to_affine(),
104        }
105    }
106
107    /// Decode [`PublicKey`] (compressed or uncompressed) from the
108    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
109    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section
110    /// 2.3.3 (page 10).
111    ///
112    /// <http://www.secg.org/sec1-v2.pdf>
113    #[cfg(feature = "sec1")]
114    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self>
115    where
116        C: Curve,
117        FieldSize<C>: ModulusSize,
118        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
119    {
120        let point = EncodedPoint::<C>::from_bytes(bytes).map_err(|_| Error)?;
121        Option::from(Self::from_encoded_point(&point)).ok_or(Error)
122    }
123
124    /// Borrow the inner [`AffinePoint`] from this [`PublicKey`].
125    ///
126    /// In ECC, public keys are elliptic curve points.
127    pub fn as_affine(&self) -> &AffinePoint<C> {
128        &self.point
129    }
130
131    /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve
132    pub fn to_projective(&self) -> ProjectivePoint<C> {
133        self.point.into()
134    }
135
136    /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`].
137    #[cfg(feature = "jwk")]
138    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
139    pub fn from_jwk(jwk: &JwkEcKey) -> Result<Self>
140    where
141        C: Curve + JwkParameters,
142        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
143        FieldSize<C>: ModulusSize,
144    {
145        jwk.to_public_key::<C>()
146    }
147
148    /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`].
149    #[cfg(feature = "jwk")]
150    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
151    pub fn from_jwk_str(jwk: &str) -> Result<Self>
152    where
153        C: Curve + JwkParameters,
154        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
155        FieldSize<C>: ModulusSize,
156    {
157        jwk.parse::<JwkEcKey>().and_then(|jwk| Self::from_jwk(&jwk))
158    }
159
160    /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK).
161    #[cfg(feature = "jwk")]
162    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
163    pub fn to_jwk(&self) -> JwkEcKey
164    where
165        C: Curve + JwkParameters,
166        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
167        FieldSize<C>: ModulusSize,
168    {
169        self.into()
170    }
171
172    /// Serialize this public key as JSON Web Key (JWK) string.
173    #[cfg(feature = "jwk")]
174    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
175    pub fn to_jwk_string(&self) -> String
176    where
177        C: Curve + JwkParameters,
178        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
179        FieldSize<C>: ModulusSize,
180    {
181        self.to_jwk().to_string()
182    }
183}
184
185impl<C> AsRef<AffinePoint<C>> for PublicKey<C>
186where
187    C: Curve + ProjectiveArithmetic,
188{
189    fn as_ref(&self) -> &AffinePoint<C> {
190        self.as_affine()
191    }
192}
193
194impl<C> Copy for PublicKey<C> where C: Curve + ProjectiveArithmetic {}
195
196#[cfg(feature = "sec1")]
197#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
198impl<C> FromEncodedPoint<C> for PublicKey<C>
199where
200    C: Curve + ProjectiveArithmetic,
201    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
202    FieldSize<C>: ModulusSize,
203{
204    /// Initialize [`PublicKey`] from an [`EncodedPoint`]
205    fn from_encoded_point(encoded_point: &EncodedPoint<C>) -> CtOption<Self> {
206        AffinePoint::<C>::from_encoded_point(encoded_point).and_then(|point| {
207            let is_identity = ProjectivePoint::<C>::from(point).is_identity();
208            CtOption::new(PublicKey { point }, !is_identity)
209        })
210    }
211}
212
213#[cfg(feature = "sec1")]
214#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
215impl<C> ToEncodedPoint<C> for PublicKey<C>
216where
217    C: Curve + ProjectiveArithmetic,
218    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
219    FieldSize<C>: ModulusSize,
220{
221    /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying
222    /// point compression
223    fn to_encoded_point(&self, compress: bool) -> EncodedPoint<C> {
224        self.point.to_encoded_point(compress)
225    }
226}
227
228#[cfg(feature = "sec1")]
229#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
230impl<C> From<PublicKey<C>> for EncodedPoint<C>
231where
232    C: Curve + ProjectiveArithmetic + PointCompression,
233    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
234    FieldSize<C>: ModulusSize,
235{
236    fn from(public_key: PublicKey<C>) -> EncodedPoint<C> {
237        EncodedPoint::<C>::from(&public_key)
238    }
239}
240
241#[cfg(feature = "sec1")]
242#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
243impl<C> From<&PublicKey<C>> for EncodedPoint<C>
244where
245    C: Curve + ProjectiveArithmetic + PointCompression,
246    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
247    FieldSize<C>: ModulusSize,
248{
249    fn from(public_key: &PublicKey<C>) -> EncodedPoint<C> {
250        public_key.to_encoded_point(C::COMPRESS_POINTS)
251    }
252}
253
254#[cfg(feature = "sec1")]
255#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
256impl<C> PartialOrd for PublicKey<C>
257where
258    C: Curve + ProjectiveArithmetic,
259    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
260    FieldSize<C>: ModulusSize,
261{
262    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
263        Some(self.cmp(other))
264    }
265}
266
267#[cfg(feature = "sec1")]
268#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
269impl<C> Ord for PublicKey<C>
270where
271    C: Curve + ProjectiveArithmetic,
272    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
273    FieldSize<C>: ModulusSize,
274{
275    fn cmp(&self, other: &Self) -> Ordering {
276        // TODO(tarcieri): more efficient implementation?
277        // This is implemented this way to reduce bounds for `AffinePoint<C>`
278        self.to_encoded_point(false)
279            .cmp(&other.to_encoded_point(false))
280    }
281}
282
283#[cfg(all(feature = "pkcs8", feature = "sec1"))]
284#[cfg_attr(docsrs, doc(cfg(all(feature = "pkcs8", feature = "sec1"))))]
285impl<C> TryFrom<pkcs8::SubjectPublicKeyInfo<'_>> for PublicKey<C>
286where
287    C: Curve + AssociatedOid + ProjectiveArithmetic,
288    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
289    FieldSize<C>: ModulusSize,
290{
291    type Error = pkcs8::spki::Error;
292
293    fn try_from(spki: pkcs8::SubjectPublicKeyInfo<'_>) -> pkcs8::spki::Result<Self> {
294        spki.algorithm.assert_oids(ALGORITHM_OID, C::OID)?;
295        Self::from_sec1_bytes(spki.subject_public_key)
296            .map_err(|_| der::Tag::BitString.value_error().into())
297    }
298}
299
300#[cfg(all(feature = "pkcs8", feature = "sec1"))]
301#[cfg_attr(docsrs, doc(cfg(all(feature = "pkcs8", feature = "sec1"))))]
302impl<C> DecodePublicKey for PublicKey<C>
303where
304    C: Curve + AssociatedOid + ProjectiveArithmetic,
305    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
306    FieldSize<C>: ModulusSize,
307{
308}
309
310#[cfg(all(feature = "alloc", feature = "pkcs8"))]
311#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "pkcs8"))))]
312impl<C> EncodePublicKey for PublicKey<C>
313where
314    C: Curve + AssociatedOid + ProjectiveArithmetic,
315    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
316    FieldSize<C>: ModulusSize,
317{
318    fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> {
319        let algorithm = pkcs8::AlgorithmIdentifier {
320            oid: ALGORITHM_OID,
321            parameters: Some((&C::OID).into()),
322        };
323
324        let public_key_bytes = self.to_encoded_point(false);
325
326        pkcs8::SubjectPublicKeyInfo {
327            algorithm,
328            subject_public_key: public_key_bytes.as_ref(),
329        }
330        .try_into()
331    }
332}
333
334#[cfg(feature = "pem")]
335#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
336impl<C> FromStr for PublicKey<C>
337where
338    C: Curve + AssociatedOid + ProjectiveArithmetic,
339    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
340    FieldSize<C>: ModulusSize,
341{
342    type Err = Error;
343
344    fn from_str(s: &str) -> Result<Self> {
345        Self::from_public_key_pem(s).map_err(|_| Error)
346    }
347}
348
349#[cfg(feature = "pem")]
350#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
351impl<C> ToString for PublicKey<C>
352where
353    C: Curve + AssociatedOid + ProjectiveArithmetic,
354    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
355    FieldSize<C>: ModulusSize,
356{
357    fn to_string(&self) -> String {
358        self.to_public_key_pem(Default::default())
359            .expect("PEM encoding error")
360    }
361}
362
363#[cfg(feature = "serde")]
364#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
365impl<C> Serialize for PublicKey<C>
366where
367    C: Curve + AssociatedOid + ProjectiveArithmetic,
368    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
369    FieldSize<C>: ModulusSize,
370{
371    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
372    where
373        S: ser::Serializer,
374    {
375        let der = self.to_public_key_der().map_err(ser::Error::custom)?;
376        serdect::slice::serialize_hex_upper_or_bin(&der, serializer)
377    }
378}
379
380#[cfg(feature = "serde")]
381#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
382impl<'de, C> Deserialize<'de> for PublicKey<C>
383where
384    C: Curve + AssociatedOid + ProjectiveArithmetic,
385    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
386    FieldSize<C>: ModulusSize,
387{
388    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
389    where
390        D: de::Deserializer<'de>,
391    {
392        let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
393        Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
394    }
395}
396
397#[cfg(all(feature = "dev", test))]
398mod tests {
399    use crate::{dev::MockCurve, sec1::FromEncodedPoint};
400
401    type EncodedPoint = crate::sec1::EncodedPoint<MockCurve>;
402    type PublicKey = super::PublicKey<MockCurve>;
403
404    #[test]
405    fn from_encoded_point_rejects_identity() {
406        let identity = EncodedPoint::identity();
407        assert!(bool::from(
408            PublicKey::from_encoded_point(&identity).is_none()
409        ));
410    }
411}