//! Elliptic curve public keys. use crate::{ point::NonIdentity, AffinePoint, CurveArithmetic, Error, NonZeroScalar, ProjectivePoint, Result, }; use core::fmt::Debug; use group::{Curve, Group}; #[cfg(feature = "jwk")] use crate::{JwkEcKey, JwkParameters}; #[cfg(feature = "pkcs8")] use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier}; #[cfg(feature = "pem")] use core::str::FromStr; #[cfg(feature = "sec1")] use { crate::{ point::PointCompression, sec1::{CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}, FieldBytesSize, }, core::cmp::Ordering, subtle::{Choice, CtOption}, }; #[cfg(all(feature = "alloc", feature = "pkcs8"))] use pkcs8::EncodePublicKey; #[cfg(all(feature = "alloc", feature = "sec1"))] use alloc::boxed::Box; #[cfg(any(feature = "jwk", feature = "pem"))] use alloc::string::{String, ToString}; #[cfg(feature = "serde")] use serdect::serde::{de, ser, Deserialize, Serialize}; #[cfg(any(feature = "pem", feature = "serde"))] use pkcs8::DecodePublicKey; #[cfg(all(feature = "sec1", feature = "pkcs8"))] use { crate::{ pkcs8::{self, AssociatedOid}, ALGORITHM_OID, }, pkcs8::der, }; /// Elliptic curve public keys. /// /// This is a wrapper type for [`AffinePoint`] which ensures an inner /// non-identity point and provides a common place to handle encoding/decoding. /// /// # Parsing "SPKI" Keys /// /// X.509 `SubjectPublicKeyInfo` (SPKI) is a commonly used format for encoding /// public keys, notably public keys corresponding to PKCS#8 private keys. /// (especially ones generated by OpenSSL). /// /// Keys in SPKI format are either binary (ASN.1 BER/DER), or PEM encoded /// (ASCII) and begin with the following: /// /// ```text /// -----BEGIN PUBLIC KEY----- /// ``` /// /// To decode an elliptic curve public key from SPKI, enable the `pkcs8` /// feature of this crate (or the `pkcs8` feature of a specific RustCrypto /// elliptic curve crate) and use the /// [`elliptic_curve::pkcs8::DecodePublicKey`][`pkcs8::DecodePublicKey`] /// trait to parse it. /// /// When the `pem` feature of this crate (or a specific RustCrypto elliptic /// curve crate) is enabled, a [`FromStr`] impl is also available. /// /// # `serde` support /// /// When the optional `serde` feature of this create is enabled, [`Serialize`] /// and [`Deserialize`] impls are provided for this type. /// /// The serialization is binary-oriented and supports ASN.1 DER /// Subject Public Key Info (SPKI) as the encoding format. /// /// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead. #[derive(Clone, Debug, Eq, PartialEq)] pub struct PublicKey where C: CurveArithmetic, { point: AffinePoint, } impl PublicKey where C: CurveArithmetic, { /// Convert an [`AffinePoint`] into a [`PublicKey`] pub fn from_affine(point: AffinePoint) -> Result { if ProjectivePoint::::from(point).is_identity().into() { Err(Error) } else { Ok(Self { point }) } } /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value /// (i.e. a secret key represented as a raw scalar value) pub fn from_secret_scalar(scalar: &NonZeroScalar) -> Self { // `NonZeroScalar` ensures the resulting point is not the identity Self { point: (C::ProjectivePoint::generator() * scalar.as_ref()).to_affine(), } } /// Decode [`PublicKey`] (compressed or uncompressed) from the /// `Elliptic-Curve-Point-to-Octet-String` encoding described in /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section /// 2.3.3 (page 10). /// /// #[cfg(feature = "sec1")] pub fn from_sec1_bytes(bytes: &[u8]) -> Result where FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, { let point = EncodedPoint::::from_bytes(bytes).map_err(|_| Error)?; Option::from(Self::from_encoded_point(&point)).ok_or(Error) } /// Convert this [`PublicKey`] into the /// `Elliptic-Curve-Point-to-Octet-String` encoding described in /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3 /// (page 10). /// /// #[cfg(all(feature = "alloc", feature = "sec1"))] pub fn to_sec1_bytes(&self) -> Box<[u8]> where C: PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { EncodedPoint::::from(self).to_bytes() } /// Borrow the inner [`AffinePoint`] from this [`PublicKey`]. /// /// In ECC, public keys are elliptic curve points. pub fn as_affine(&self) -> &AffinePoint { &self.point } /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve pub fn to_projective(&self) -> ProjectivePoint { self.point.into() } /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`] pub fn to_nonidentity(&self) -> NonIdentity> { NonIdentity::new_unchecked(self.point) } /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`]. #[cfg(feature = "jwk")] pub fn from_jwk(jwk: &JwkEcKey) -> Result where C: JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { jwk.to_public_key::() } /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`]. #[cfg(feature = "jwk")] pub fn from_jwk_str(jwk: &str) -> Result where C: JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) } /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK). #[cfg(feature = "jwk")] pub fn to_jwk(&self) -> JwkEcKey where C: JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { self.into() } /// Serialize this public key as JSON Web Key (JWK) string. #[cfg(feature = "jwk")] pub fn to_jwk_string(&self) -> String where C: JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { self.to_jwk().to_string() } } impl AsRef> for PublicKey where C: CurveArithmetic, { fn as_ref(&self) -> &AffinePoint { self.as_affine() } } impl Copy for PublicKey where C: CurveArithmetic {} #[cfg(feature = "sec1")] impl FromEncodedPoint for PublicKey where C: CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { /// Initialize [`PublicKey`] from an [`EncodedPoint`] fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { AffinePoint::::from_encoded_point(encoded_point).and_then(|point| { // Defeating the point of `subtle`, but the use case is specifically a public key let is_identity = Choice::from(u8::from(encoded_point.is_identity())); CtOption::new(PublicKey { point }, !is_identity) }) } } #[cfg(feature = "sec1")] impl ToEncodedPoint for PublicKey where C: CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying /// point compression fn to_encoded_point(&self, compress: bool) -> EncodedPoint { self.point.to_encoded_point(compress) } } #[cfg(feature = "sec1")] impl From> for CompressedPoint where C: CurveArithmetic + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn from(public_key: PublicKey) -> CompressedPoint { CompressedPoint::::from(&public_key) } } #[cfg(feature = "sec1")] impl From<&PublicKey> for CompressedPoint where C: CurveArithmetic + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn from(public_key: &PublicKey) -> CompressedPoint { CompressedPoint::::clone_from_slice(public_key.to_encoded_point(true).as_bytes()) } } #[cfg(feature = "sec1")] impl From> for EncodedPoint where C: CurveArithmetic + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn from(public_key: PublicKey) -> EncodedPoint { EncodedPoint::::from(&public_key) } } #[cfg(feature = "sec1")] impl From<&PublicKey> for EncodedPoint where C: CurveArithmetic + PointCompression, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn from(public_key: &PublicKey) -> EncodedPoint { public_key.to_encoded_point(C::COMPRESS_POINTS) } } impl From> for PublicKey where C: CurveArithmetic, P: Copy + Into>, { fn from(value: NonIdentity

) -> Self { Self::from(&value) } } impl From<&NonIdentity

> for PublicKey where C: CurveArithmetic, P: Copy + Into>, { fn from(value: &NonIdentity

) -> Self { Self { point: value.to_point().into(), } } } impl From> for NonIdentity> where C: CurveArithmetic, { fn from(value: PublicKey) -> Self { Self::from(&value) } } impl From<&PublicKey> for NonIdentity> where C: CurveArithmetic, { fn from(value: &PublicKey) -> Self { PublicKey::to_nonidentity(value) } } #[cfg(feature = "sec1")] impl PartialOrd for PublicKey where C: CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[cfg(feature = "sec1")] impl Ord for PublicKey where C: CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn cmp(&self, other: &Self) -> Ordering { // TODO(tarcieri): more efficient implementation? // This is implemented this way to reduce bounds for `AffinePoint` self.to_encoded_point(false) .cmp(&other.to_encoded_point(false)) } } #[cfg(feature = "sec1")] impl TryFrom> for PublicKey where C: CurveArithmetic, FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, { type Error = Error; fn try_from(point: CompressedPoint) -> Result { Self::from_sec1_bytes(&point) } } #[cfg(feature = "sec1")] impl TryFrom<&CompressedPoint> for PublicKey where C: CurveArithmetic, FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, { type Error = Error; fn try_from(point: &CompressedPoint) -> Result { Self::from_sec1_bytes(point) } } #[cfg(feature = "sec1")] impl TryFrom> for PublicKey where C: CurveArithmetic, FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, { type Error = Error; fn try_from(point: EncodedPoint) -> Result { Self::from_sec1_bytes(point.as_bytes()) } } #[cfg(feature = "sec1")] impl TryFrom<&EncodedPoint> for PublicKey where C: CurveArithmetic, FieldBytesSize: ModulusSize, AffinePoint: FromEncodedPoint + ToEncodedPoint, { type Error = Error; fn try_from(point: &EncodedPoint) -> Result { Self::from_sec1_bytes(point.as_bytes()) } } #[cfg(feature = "pkcs8")] impl AssociatedAlgorithmIdentifier for PublicKey where C: AssociatedOid + CurveArithmetic, { type Params = ObjectIdentifier; const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = AlgorithmIdentifier { oid: ALGORITHM_OID, parameters: Some(C::OID), }; } #[cfg(feature = "pkcs8")] impl TryFrom> for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { type Error = pkcs8::spki::Error; fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { Self::try_from(&spki) } } #[cfg(feature = "pkcs8")] impl TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { type Error = pkcs8::spki::Error; fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { spki.algorithm.assert_oids(ALGORITHM_OID, C::OID)?; let public_key_bytes = spki .subject_public_key .as_bytes() .ok_or_else(|| der::Tag::BitString.value_error())?; Self::from_sec1_bytes(public_key_bytes) .map_err(|_| der::Tag::BitString.value_error().into()) } } #[cfg(all(feature = "alloc", feature = "pkcs8"))] impl EncodePublicKey for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn to_public_key_der(&self) -> pkcs8::spki::Result { let public_key_bytes = self.to_encoded_point(false); let subject_public_key = der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes())?; pkcs8::SubjectPublicKeyInfo { algorithm: Self::ALGORITHM_IDENTIFIER, subject_public_key, } .try_into() } } #[cfg(feature = "pem")] impl FromStr for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { type Err = Error; fn from_str(s: &str) -> Result { Self::from_public_key_pem(s).map_err(|_| Error) } } #[cfg(feature = "pem")] impl ToString for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn to_string(&self) -> String { self.to_public_key_pem(Default::default()) .expect("PEM encoding error") } } #[cfg(feature = "serde")] impl Serialize for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn serialize(&self, serializer: S) -> core::result::Result where S: ser::Serializer, { let der = self.to_public_key_der().map_err(ser::Error::custom)?; serdect::slice::serialize_hex_upper_or_bin(&der, serializer) } } #[cfg(feature = "serde")] impl<'de, C> Deserialize<'de> for PublicKey where C: AssociatedOid + CurveArithmetic, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { fn deserialize(deserializer: D) -> core::result::Result where D: de::Deserializer<'de>, { let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; Self::from_public_key_der(&der_bytes).map_err(de::Error::custom) } } #[cfg(all(feature = "dev", test))] mod tests { use crate::{dev::MockCurve, sec1::FromEncodedPoint}; type EncodedPoint = crate::sec1::EncodedPoint; type PublicKey = super::PublicKey; #[test] fn from_encoded_point_rejects_identity() { let identity = EncodedPoint::identity(); assert!(bool::from( PublicKey::from_encoded_point(&identity).is_none() )); } }