diff options
Diffstat (limited to 'vendor/elliptic-curve/src/public_key.rs')
-rw-r--r-- | vendor/elliptic-curve/src/public_key.rs | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/vendor/elliptic-curve/src/public_key.rs b/vendor/elliptic-curve/src/public_key.rs new file mode 100644 index 0000000..485b0ec --- /dev/null +++ b/vendor/elliptic-curve/src/public_key.rs @@ -0,0 +1,566 @@ +//! 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<C> +where + C: CurveArithmetic, +{ + point: AffinePoint<C>, +} + +impl<C> PublicKey<C> +where + C: CurveArithmetic, +{ + /// Convert an [`AffinePoint`] into a [`PublicKey`] + pub fn from_affine(point: AffinePoint<C>) -> Result<Self> { + if ProjectivePoint::<C>::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<C>) -> 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). + /// + /// <http://www.secg.org/sec1-v2.pdf> + #[cfg(feature = "sec1")] + pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> + where + FieldBytesSize<C>: ModulusSize, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + { + let point = EncodedPoint::<C>::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). + /// + /// <http://www.secg.org/sec1-v2.pdf> + #[cfg(all(feature = "alloc", feature = "sec1"))] + pub fn to_sec1_bytes(&self) -> Box<[u8]> + where + C: PointCompression, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, + { + EncodedPoint::<C>::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<C> { + &self.point + } + + /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve + pub fn to_projective(&self) -> ProjectivePoint<C> { + self.point.into() + } + + /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`] + pub fn to_nonidentity(&self) -> NonIdentity<AffinePoint<C>> { + 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<Self> + where + C: JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, + { + jwk.to_public_key::<C>() + } + + /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`]. + #[cfg(feature = "jwk")] + pub fn from_jwk_str(jwk: &str) -> Result<Self> + where + C: JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, + { + jwk.parse::<JwkEcKey>().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<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: 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<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, + { + self.to_jwk().to_string() + } +} + +impl<C> AsRef<AffinePoint<C>> for PublicKey<C> +where + C: CurveArithmetic, +{ + fn as_ref(&self) -> &AffinePoint<C> { + self.as_affine() + } +} + +impl<C> Copy for PublicKey<C> where C: CurveArithmetic {} + +#[cfg(feature = "sec1")] +impl<C> FromEncodedPoint<C> for PublicKey<C> +where + C: CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + /// Initialize [`PublicKey`] from an [`EncodedPoint`] + fn from_encoded_point(encoded_point: &EncodedPoint<C>) -> CtOption<Self> { + AffinePoint::<C>::from_encoded_point(encoded_point).and_then(|point| { + let is_identity = Choice::from(encoded_point.is_identity() as u8); + CtOption::new(PublicKey { point }, !is_identity) + }) + } +} + +#[cfg(feature = "sec1")] +impl<C> ToEncodedPoint<C> for PublicKey<C> +where + C: CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying + /// point compression + fn to_encoded_point(&self, compress: bool) -> EncodedPoint<C> { + self.point.to_encoded_point(compress) + } +} + +#[cfg(feature = "sec1")] +impl<C> From<PublicKey<C>> for CompressedPoint<C> +where + C: CurveArithmetic + PointCompression, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(public_key: PublicKey<C>) -> CompressedPoint<C> { + CompressedPoint::<C>::from(&public_key) + } +} + +#[cfg(feature = "sec1")] +impl<C> From<&PublicKey<C>> for CompressedPoint<C> +where + C: CurveArithmetic + PointCompression, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(public_key: &PublicKey<C>) -> CompressedPoint<C> { + CompressedPoint::<C>::clone_from_slice(public_key.to_encoded_point(true).as_bytes()) + } +} + +#[cfg(feature = "sec1")] +impl<C> From<PublicKey<C>> for EncodedPoint<C> +where + C: CurveArithmetic + PointCompression, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(public_key: PublicKey<C>) -> EncodedPoint<C> { + EncodedPoint::<C>::from(&public_key) + } +} + +#[cfg(feature = "sec1")] +impl<C> From<&PublicKey<C>> for EncodedPoint<C> +where + C: CurveArithmetic + PointCompression, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(public_key: &PublicKey<C>) -> EncodedPoint<C> { + public_key.to_encoded_point(C::COMPRESS_POINTS) + } +} + +impl<C, P> From<NonIdentity<P>> for PublicKey<C> +where + C: CurveArithmetic, + P: Copy + Into<AffinePoint<C>>, +{ + fn from(value: NonIdentity<P>) -> Self { + Self::from(&value) + } +} + +impl<C, P> From<&NonIdentity<P>> for PublicKey<C> +where + C: CurveArithmetic, + P: Copy + Into<AffinePoint<C>>, +{ + fn from(value: &NonIdentity<P>) -> Self { + Self { + point: value.to_point().into(), + } + } +} + +impl<C> From<PublicKey<C>> for NonIdentity<AffinePoint<C>> +where + C: CurveArithmetic, +{ + fn from(value: PublicKey<C>) -> Self { + Self::from(&value) + } +} + +impl<C> From<&PublicKey<C>> for NonIdentity<AffinePoint<C>> +where + C: CurveArithmetic, +{ + fn from(value: &PublicKey<C>) -> Self { + PublicKey::to_nonidentity(value) + } +} + +#[cfg(feature = "sec1")] +impl<C> PartialOrd for PublicKey<C> +where + C: CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "sec1")] +impl<C> Ord for PublicKey<C> +where + C: CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn cmp(&self, other: &Self) -> Ordering { + // TODO(tarcieri): more efficient implementation? + // This is implemented this way to reduce bounds for `AffinePoint<C>` + self.to_encoded_point(false) + .cmp(&other.to_encoded_point(false)) + } +} + +#[cfg(feature = "sec1")] +impl<C> TryFrom<CompressedPoint<C>> for PublicKey<C> +where + C: CurveArithmetic, + FieldBytesSize<C>: ModulusSize, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, +{ + type Error = Error; + + fn try_from(point: CompressedPoint<C>) -> Result<Self> { + Self::from_sec1_bytes(&point) + } +} + +#[cfg(feature = "sec1")] +impl<C> TryFrom<&CompressedPoint<C>> for PublicKey<C> +where + C: CurveArithmetic, + FieldBytesSize<C>: ModulusSize, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, +{ + type Error = Error; + + fn try_from(point: &CompressedPoint<C>) -> Result<Self> { + Self::from_sec1_bytes(point) + } +} + +#[cfg(feature = "sec1")] +impl<C> TryFrom<EncodedPoint<C>> for PublicKey<C> +where + C: CurveArithmetic, + FieldBytesSize<C>: ModulusSize, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, +{ + type Error = Error; + + fn try_from(point: EncodedPoint<C>) -> Result<Self> { + Self::from_sec1_bytes(point.as_bytes()) + } +} + +#[cfg(feature = "sec1")] +impl<C> TryFrom<&EncodedPoint<C>> for PublicKey<C> +where + C: CurveArithmetic, + FieldBytesSize<C>: ModulusSize, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, +{ + type Error = Error; + + fn try_from(point: &EncodedPoint<C>) -> Result<Self> { + Self::from_sec1_bytes(point.as_bytes()) + } +} + +#[cfg(feature = "pkcs8")] +impl<C> AssociatedAlgorithmIdentifier for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, +{ + type Params = ObjectIdentifier; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> = AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(C::OID), + }; +} + +#[cfg(feature = "pkcs8")] +impl<C> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + type Error = pkcs8::spki::Error; + + fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> { + Self::try_from(&spki) + } +} + +#[cfg(feature = "pkcs8")] +impl<C> TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + type Error = pkcs8::spki::Error; + + fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> { + 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<C> EncodePublicKey for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> { + 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<C> FromStr for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + type Err = Error; + + fn from_str(s: &str) -> Result<Self> { + Self::from_public_key_pem(s).map_err(|_| Error) + } +} + +#[cfg(feature = "pem")] +impl<C> ToString for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn to_string(&self) -> String { + self.to_public_key_pem(Default::default()) + .expect("PEM encoding error") + } +} + +#[cfg(feature = "serde")] +impl<C> Serialize for PublicKey<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> + 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<C> +where + C: AssociatedOid + CurveArithmetic, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> + 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<MockCurve>; + type PublicKey = super::PublicKey<MockCurve>; + + #[test] + fn from_encoded_point_rejects_identity() { + let identity = EncodedPoint::identity(); + assert!(bool::from( + PublicKey::from_encoded_point(&identity).is_none() + )); + } +} |