elliptic_curve/
public_key.rs

1//! Elliptic curve public keys.
2
3use crate::{
4    point::NonIdentity, AffinePoint, CurveArithmetic, Error, NonZeroScalar, ProjectivePoint, Result,
5};
6use core::fmt::Debug;
7use group::{Curve, Group};
8
9#[cfg(feature = "jwk")]
10use crate::{JwkEcKey, JwkParameters};
11
12#[cfg(feature = "pkcs8")]
13use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier};
14
15#[cfg(feature = "pem")]
16use core::str::FromStr;
17
18#[cfg(feature = "sec1")]
19use {
20    crate::{
21        point::PointCompression,
22        sec1::{CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint},
23        FieldBytesSize,
24    },
25    core::cmp::Ordering,
26    subtle::{Choice, CtOption},
27};
28
29#[cfg(all(feature = "alloc", feature = "pkcs8"))]
30use pkcs8::EncodePublicKey;
31
32#[cfg(all(feature = "alloc", feature = "sec1"))]
33use alloc::boxed::Box;
34
35#[cfg(any(feature = "jwk", feature = "pem"))]
36use alloc::string::{String, ToString};
37
38#[cfg(feature = "serde")]
39use serdect::serde::{de, ser, Deserialize, Serialize};
40
41#[cfg(any(feature = "pem", feature = "serde"))]
42use pkcs8::DecodePublicKey;
43
44#[cfg(all(feature = "sec1", feature = "pkcs8"))]
45use {
46    crate::{
47        pkcs8::{self, AssociatedOid},
48        ALGORITHM_OID,
49    },
50    pkcs8::der,
51};
52
53/// Elliptic curve public keys.
54///
55/// This is a wrapper type for [`AffinePoint`] which ensures an inner
56/// non-identity point and provides a common place to handle encoding/decoding.
57///
58/// # Parsing "SPKI" Keys
59///
60/// X.509 `SubjectPublicKeyInfo` (SPKI) is a commonly used format for encoding
61/// public keys, notably public keys corresponding to PKCS#8 private keys.
62/// (especially ones generated by OpenSSL).
63///
64/// Keys in SPKI format are either binary (ASN.1 BER/DER), or PEM encoded
65/// (ASCII) and begin with the following:
66///
67/// ```text
68/// -----BEGIN PUBLIC KEY-----
69/// ```
70///
71/// To decode an elliptic curve public key from SPKI, enable the `pkcs8`
72/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto
73/// elliptic curve crate) and use the
74/// [`elliptic_curve::pkcs8::DecodePublicKey`][`pkcs8::DecodePublicKey`]
75/// trait to parse it.
76///
77/// When the `pem` feature of this crate (or a specific RustCrypto elliptic
78/// curve crate) is enabled, a [`FromStr`] impl is also available.
79///
80/// # `serde` support
81///
82/// When the optional `serde` feature of this create is enabled, [`Serialize`]
83/// and [`Deserialize`] impls are provided for this type.
84///
85/// The serialization is binary-oriented and supports ASN.1 DER
86/// Subject Public Key Info (SPKI) as the encoding format.
87///
88/// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead.
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct PublicKey<C>
91where
92    C: CurveArithmetic,
93{
94    point: AffinePoint<C>,
95}
96
97impl<C> PublicKey<C>
98where
99    C: CurveArithmetic,
100{
101    /// Convert an [`AffinePoint`] into a [`PublicKey`]
102    pub fn from_affine(point: AffinePoint<C>) -> Result<Self> {
103        if ProjectivePoint::<C>::from(point).is_identity().into() {
104            Err(Error)
105        } else {
106            Ok(Self { point })
107        }
108    }
109
110    /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value
111    /// (i.e. a secret key represented as a raw scalar value)
112    pub fn from_secret_scalar(scalar: &NonZeroScalar<C>) -> Self {
113        // `NonZeroScalar` ensures the resulting point is not the identity
114        Self {
115            point: (C::ProjectivePoint::generator() * scalar.as_ref()).to_affine(),
116        }
117    }
118
119    /// Decode [`PublicKey`] (compressed or uncompressed) from the
120    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
121    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section
122    /// 2.3.3 (page 10).
123    ///
124    /// <http://www.secg.org/sec1-v2.pdf>
125    #[cfg(feature = "sec1")]
126    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self>
127    where
128        FieldBytesSize<C>: ModulusSize,
129        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
130    {
131        let point = EncodedPoint::<C>::from_bytes(bytes).map_err(|_| Error)?;
132        Option::from(Self::from_encoded_point(&point)).ok_or(Error)
133    }
134
135    /// Convert this [`PublicKey`] into the
136    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
137    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3
138    /// (page 10).
139    ///
140    /// <http://www.secg.org/sec1-v2.pdf>
141    #[cfg(all(feature = "alloc", feature = "sec1"))]
142    pub fn to_sec1_bytes(&self) -> Box<[u8]>
143    where
144        C: PointCompression,
145        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
146        FieldBytesSize<C>: ModulusSize,
147    {
148        EncodedPoint::<C>::from(self).to_bytes()
149    }
150
151    /// Borrow the inner [`AffinePoint`] from this [`PublicKey`].
152    ///
153    /// In ECC, public keys are elliptic curve points.
154    pub fn as_affine(&self) -> &AffinePoint<C> {
155        &self.point
156    }
157
158    /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve
159    pub fn to_projective(&self) -> ProjectivePoint<C> {
160        self.point.into()
161    }
162
163    /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`]
164    pub fn to_nonidentity(&self) -> NonIdentity<AffinePoint<C>> {
165        NonIdentity::new_unchecked(self.point)
166    }
167
168    /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`].
169    #[cfg(feature = "jwk")]
170    pub fn from_jwk(jwk: &JwkEcKey) -> Result<Self>
171    where
172        C: JwkParameters,
173        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
174        FieldBytesSize<C>: ModulusSize,
175    {
176        jwk.to_public_key::<C>()
177    }
178
179    /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`].
180    #[cfg(feature = "jwk")]
181    pub fn from_jwk_str(jwk: &str) -> Result<Self>
182    where
183        C: JwkParameters,
184        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
185        FieldBytesSize<C>: ModulusSize,
186    {
187        jwk.parse::<JwkEcKey>().and_then(|jwk| Self::from_jwk(&jwk))
188    }
189
190    /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK).
191    #[cfg(feature = "jwk")]
192    pub fn to_jwk(&self) -> JwkEcKey
193    where
194        C: JwkParameters,
195        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
196        FieldBytesSize<C>: ModulusSize,
197    {
198        self.into()
199    }
200
201    /// Serialize this public key as JSON Web Key (JWK) string.
202    #[cfg(feature = "jwk")]
203    pub fn to_jwk_string(&self) -> String
204    where
205        C: JwkParameters,
206        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
207        FieldBytesSize<C>: ModulusSize,
208    {
209        self.to_jwk().to_string()
210    }
211}
212
213impl<C> AsRef<AffinePoint<C>> for PublicKey<C>
214where
215    C: CurveArithmetic,
216{
217    fn as_ref(&self) -> &AffinePoint<C> {
218        self.as_affine()
219    }
220}
221
222impl<C> Copy for PublicKey<C> where C: CurveArithmetic {}
223
224#[cfg(feature = "sec1")]
225impl<C> FromEncodedPoint<C> for PublicKey<C>
226where
227    C: CurveArithmetic,
228    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
229    FieldBytesSize<C>: ModulusSize,
230{
231    /// Initialize [`PublicKey`] from an [`EncodedPoint`]
232    fn from_encoded_point(encoded_point: &EncodedPoint<C>) -> CtOption<Self> {
233        AffinePoint::<C>::from_encoded_point(encoded_point).and_then(|point| {
234            // Defeating the point of `subtle`, but the use case is specifically a public key
235            let is_identity = Choice::from(u8::from(encoded_point.is_identity()));
236            CtOption::new(PublicKey { point }, !is_identity)
237        })
238    }
239}
240
241#[cfg(feature = "sec1")]
242impl<C> ToEncodedPoint<C> for PublicKey<C>
243where
244    C: CurveArithmetic,
245    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
246    FieldBytesSize<C>: ModulusSize,
247{
248    /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying
249    /// point compression
250    fn to_encoded_point(&self, compress: bool) -> EncodedPoint<C> {
251        self.point.to_encoded_point(compress)
252    }
253}
254
255#[cfg(feature = "sec1")]
256impl<C> From<PublicKey<C>> for CompressedPoint<C>
257where
258    C: CurveArithmetic + PointCompression,
259    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
260    FieldBytesSize<C>: ModulusSize,
261{
262    fn from(public_key: PublicKey<C>) -> CompressedPoint<C> {
263        CompressedPoint::<C>::from(&public_key)
264    }
265}
266
267#[cfg(feature = "sec1")]
268impl<C> From<&PublicKey<C>> for CompressedPoint<C>
269where
270    C: CurveArithmetic + PointCompression,
271    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
272    FieldBytesSize<C>: ModulusSize,
273{
274    fn from(public_key: &PublicKey<C>) -> CompressedPoint<C> {
275        CompressedPoint::<C>::clone_from_slice(public_key.to_encoded_point(true).as_bytes())
276    }
277}
278
279#[cfg(feature = "sec1")]
280impl<C> From<PublicKey<C>> for EncodedPoint<C>
281where
282    C: CurveArithmetic + PointCompression,
283    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
284    FieldBytesSize<C>: ModulusSize,
285{
286    fn from(public_key: PublicKey<C>) -> EncodedPoint<C> {
287        EncodedPoint::<C>::from(&public_key)
288    }
289}
290
291#[cfg(feature = "sec1")]
292impl<C> From<&PublicKey<C>> for EncodedPoint<C>
293where
294    C: CurveArithmetic + PointCompression,
295    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
296    FieldBytesSize<C>: ModulusSize,
297{
298    fn from(public_key: &PublicKey<C>) -> EncodedPoint<C> {
299        public_key.to_encoded_point(C::COMPRESS_POINTS)
300    }
301}
302
303impl<C, P> From<NonIdentity<P>> for PublicKey<C>
304where
305    C: CurveArithmetic,
306    P: Copy + Into<AffinePoint<C>>,
307{
308    fn from(value: NonIdentity<P>) -> Self {
309        Self::from(&value)
310    }
311}
312
313impl<C, P> From<&NonIdentity<P>> for PublicKey<C>
314where
315    C: CurveArithmetic,
316    P: Copy + Into<AffinePoint<C>>,
317{
318    fn from(value: &NonIdentity<P>) -> Self {
319        Self {
320            point: value.to_point().into(),
321        }
322    }
323}
324
325impl<C> From<PublicKey<C>> for NonIdentity<AffinePoint<C>>
326where
327    C: CurveArithmetic,
328{
329    fn from(value: PublicKey<C>) -> Self {
330        Self::from(&value)
331    }
332}
333
334impl<C> From<&PublicKey<C>> for NonIdentity<AffinePoint<C>>
335where
336    C: CurveArithmetic,
337{
338    fn from(value: &PublicKey<C>) -> Self {
339        PublicKey::to_nonidentity(value)
340    }
341}
342
343#[cfg(feature = "sec1")]
344impl<C> PartialOrd for PublicKey<C>
345where
346    C: CurveArithmetic,
347    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
348    FieldBytesSize<C>: ModulusSize,
349{
350    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
351        Some(self.cmp(other))
352    }
353}
354
355#[cfg(feature = "sec1")]
356impl<C> Ord for PublicKey<C>
357where
358    C: CurveArithmetic,
359    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
360    FieldBytesSize<C>: ModulusSize,
361{
362    fn cmp(&self, other: &Self) -> Ordering {
363        // TODO(tarcieri): more efficient implementation?
364        // This is implemented this way to reduce bounds for `AffinePoint<C>`
365        self.to_encoded_point(false)
366            .cmp(&other.to_encoded_point(false))
367    }
368}
369
370#[cfg(feature = "sec1")]
371impl<C> TryFrom<CompressedPoint<C>> for PublicKey<C>
372where
373    C: CurveArithmetic,
374    FieldBytesSize<C>: ModulusSize,
375    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
376{
377    type Error = Error;
378
379    fn try_from(point: CompressedPoint<C>) -> Result<Self> {
380        Self::from_sec1_bytes(&point)
381    }
382}
383
384#[cfg(feature = "sec1")]
385impl<C> TryFrom<&CompressedPoint<C>> for PublicKey<C>
386where
387    C: CurveArithmetic,
388    FieldBytesSize<C>: ModulusSize,
389    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
390{
391    type Error = Error;
392
393    fn try_from(point: &CompressedPoint<C>) -> Result<Self> {
394        Self::from_sec1_bytes(point)
395    }
396}
397
398#[cfg(feature = "sec1")]
399impl<C> TryFrom<EncodedPoint<C>> for PublicKey<C>
400where
401    C: CurveArithmetic,
402    FieldBytesSize<C>: ModulusSize,
403    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
404{
405    type Error = Error;
406
407    fn try_from(point: EncodedPoint<C>) -> Result<Self> {
408        Self::from_sec1_bytes(point.as_bytes())
409    }
410}
411
412#[cfg(feature = "sec1")]
413impl<C> TryFrom<&EncodedPoint<C>> for PublicKey<C>
414where
415    C: CurveArithmetic,
416    FieldBytesSize<C>: ModulusSize,
417    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
418{
419    type Error = Error;
420
421    fn try_from(point: &EncodedPoint<C>) -> Result<Self> {
422        Self::from_sec1_bytes(point.as_bytes())
423    }
424}
425
426#[cfg(feature = "pkcs8")]
427impl<C> AssociatedAlgorithmIdentifier for PublicKey<C>
428where
429    C: AssociatedOid + CurveArithmetic,
430{
431    type Params = ObjectIdentifier;
432
433    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> = AlgorithmIdentifier {
434        oid: ALGORITHM_OID,
435        parameters: Some(C::OID),
436    };
437}
438
439#[cfg(feature = "pkcs8")]
440impl<C> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C>
441where
442    C: AssociatedOid + CurveArithmetic,
443    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
444    FieldBytesSize<C>: ModulusSize,
445{
446    type Error = pkcs8::spki::Error;
447
448    fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
449        Self::try_from(&spki)
450    }
451}
452
453#[cfg(feature = "pkcs8")]
454impl<C> TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C>
455where
456    C: AssociatedOid + CurveArithmetic,
457    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
458    FieldBytesSize<C>: ModulusSize,
459{
460    type Error = pkcs8::spki::Error;
461
462    fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
463        spki.algorithm.assert_oids(ALGORITHM_OID, C::OID)?;
464
465        let public_key_bytes = spki
466            .subject_public_key
467            .as_bytes()
468            .ok_or_else(|| der::Tag::BitString.value_error())?;
469
470        Self::from_sec1_bytes(public_key_bytes)
471            .map_err(|_| der::Tag::BitString.value_error().into())
472    }
473}
474
475#[cfg(all(feature = "alloc", feature = "pkcs8"))]
476impl<C> EncodePublicKey for PublicKey<C>
477where
478    C: AssociatedOid + CurveArithmetic,
479    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
480    FieldBytesSize<C>: ModulusSize,
481{
482    fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> {
483        let public_key_bytes = self.to_encoded_point(false);
484        let subject_public_key = der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes())?;
485
486        pkcs8::SubjectPublicKeyInfo {
487            algorithm: Self::ALGORITHM_IDENTIFIER,
488            subject_public_key,
489        }
490        .try_into()
491    }
492}
493
494#[cfg(feature = "pem")]
495impl<C> FromStr for PublicKey<C>
496where
497    C: AssociatedOid + CurveArithmetic,
498    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
499    FieldBytesSize<C>: ModulusSize,
500{
501    type Err = Error;
502
503    fn from_str(s: &str) -> Result<Self> {
504        Self::from_public_key_pem(s).map_err(|_| Error)
505    }
506}
507
508#[cfg(feature = "pem")]
509impl<C> ToString for PublicKey<C>
510where
511    C: AssociatedOid + CurveArithmetic,
512    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
513    FieldBytesSize<C>: ModulusSize,
514{
515    fn to_string(&self) -> String {
516        self.to_public_key_pem(Default::default())
517            .expect("PEM encoding error")
518    }
519}
520
521#[cfg(feature = "serde")]
522impl<C> Serialize for PublicKey<C>
523where
524    C: AssociatedOid + CurveArithmetic,
525    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
526    FieldBytesSize<C>: ModulusSize,
527{
528    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
529    where
530        S: ser::Serializer,
531    {
532        let der = self.to_public_key_der().map_err(ser::Error::custom)?;
533        serdect::slice::serialize_hex_upper_or_bin(&der, serializer)
534    }
535}
536
537#[cfg(feature = "serde")]
538impl<'de, C> Deserialize<'de> for PublicKey<C>
539where
540    C: AssociatedOid + CurveArithmetic,
541    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
542    FieldBytesSize<C>: ModulusSize,
543{
544    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
545    where
546        D: de::Deserializer<'de>,
547    {
548        let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
549        Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
550    }
551}
552
553#[cfg(all(feature = "dev", test))]
554mod tests {
555    use crate::{dev::MockCurve, sec1::FromEncodedPoint};
556
557    type EncodedPoint = crate::sec1::EncodedPoint<MockCurve>;
558    type PublicKey = super::PublicKey<MockCurve>;
559
560    #[test]
561    fn from_encoded_point_rejects_identity() {
562        let identity = EncodedPoint::identity();
563        assert!(bool::from(
564            PublicKey::from_encoded_point(&identity).is_none()
565        ));
566    }
567}