diff --git a/Cargo.lock b/Cargo.lock index c618c8c3..0df25b3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,13 +54,19 @@ version = "0.14.0-pre" dependencies = [ "belt-hash", "criterion", + "der", + "digest", "elliptic-curve", "hex", "hex-literal", + "hkdf", + "hmac", + "pkcs8", "primeorder", "proptest", "rand_core", "rfc6979", + "sec1", "signature", ] diff --git a/bign256/Cargo.toml b/bign256/Cargo.toml index b9761ee2..54c1af92 100644 --- a/bign256/Cargo.toml +++ b/bign256/Cargo.toml @@ -25,22 +25,32 @@ primeorder = { version = "=0.14.0-pre.0", optional = true, path = "../primeorder signature = { version = "=2.3.0-pre.3", optional = true } belt-hash = { version = "=0.2.0-pre.3", optional = true, default-features = false } rfc6979 = { version = "=0.5.0-pre.3", optional = true } +rand_core = "0.6.4" +pkcs8 = { version = "0.11.0-pre.0", optional = true } +sec1 = { version = "0.8.0-pre.1", optional = true } +der = { version = "0.8.0-pre.0" } + +digest = { version = "0.11.0-pre.8", optional = true } +hkdf = { version = "0.13.0-pre.3", optional = true } +hmac = { version = "0.13.0-pre.3", optional = true } [dev-dependencies] criterion = "0.5" hex-literal = "0.4" proptest = "1" rand_core = { version = "0.6", features = ["getrandom"] } -hex = {version = "0.4" } +hex = { version = "0.4" } [features] -default = ["arithmetic", "pkcs8", "std", "dsa"] +default = ["arithmetic", "pkcs8", "std", "ecdsa", "pem", "ecdh"] alloc = ["elliptic-curve/alloc", "primeorder?/alloc"] std = ["alloc", "elliptic-curve/std", "signature?/std"] -dsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"] +ecdsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"] arithmetic = ["dep:primeorder", "elliptic-curve/arithmetic"] -pkcs8 = ["elliptic-curve/pkcs8"] +pem = ["pkcs8", "sec1/pem"] +pkcs8 = ["dep:pkcs8"] +ecdh = ["arithmetic", "elliptic-curve/ecdh", "dep:digest", "dep:hkdf", "dep:hmac", "dep:belt-hash", "alloc"] [[bench]] name = "field" diff --git a/bign256/src/ecdh.rs b/bign256/src/ecdh.rs new file mode 100644 index 00000000..34b491e1 --- /dev/null +++ b/bign256/src/ecdh.rs @@ -0,0 +1,218 @@ +//! Elliptic Curve Diffie-Hellman Support. +//! +//! # ECDH Ephemeral (ECDHE) Usage +//! +//! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers +//! using a randomly generated set of keys for each exchange. +//! +//! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE] +//! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship +//! can be used to determine the authenticity of the ephemeral keys, such as +//! a digital signature. Without such an additional step, ECDHE is insecure! +//! (see security warning below) +//! +//! See the documentation for the [`EphemeralSecret`] type for more information +//! on performing ECDH ephemeral key exchanges. +//! +//! # Static ECDH Usage +//! +//! Static ECDH key exchanges are supported via the low-level +//! [`diffie_hellman`] function. +//! +//! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange +//! [SIGMA]: https://webee.technion.ac.il/~hugo/sigma-pdf.pdf + +// use crate::{ +// point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar, +// ProjectivePoint, PublicKey, +// }; +// use core::borrow::Borrow; +// use digest::{crypto_common::BlockSizeUser, Digest}; +// use group::Curve as _; +// use hkdf::{hmac::SimpleHmac, Hkdf}; +// use rand_core::CryptoRngCore; +// use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::{AffinePoint, FieldBytes, NonZeroScalar, ProjectivePoint, PublicKey}; +use belt_hash::BeltHash; +use core::borrow::Borrow; +use elliptic_curve::point::AffineCoordinates; +use elliptic_curve::zeroize::{Zeroize, ZeroizeOnDrop}; +use hkdf::Hkdf; +use hmac::SimpleHmac; +use rand_core::CryptoRngCore; + +/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function. +/// +/// Whenever possible, we recommend using the high-level ECDH ephemeral API +/// provided by [`EphemeralSecret`]. +/// +/// However, if you are implementing a protocol which requires a static scalar +/// value as part of an ECDH exchange, this API can be used to compute a +/// [`SharedSecret`] from that value. +/// +/// Note that this API operates on the low-level [`NonZeroScalar`] and +/// [`AffinePoint`] types. If you are attempting to use the higher-level +/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will +/// need to use the following conversions: +/// +/// ```ignore +/// let shared_secret = elliptic_curve::ecdh::diffie_hellman( +/// secret_key.to_nonzero_scalar(), +/// public_key.as_affine() +/// ); +/// ``` +pub fn diffie_hellman( + secret_key: impl Borrow, + public_key: impl Borrow, +) -> SharedSecret { + let public_point = ProjectivePoint::from(*public_key.borrow()); + #[allow(clippy::arithmetic_side_effects)] + let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine(); + SharedSecret::new(secret_point) +} + +/// Ephemeral Diffie-Hellman Secret. +/// +/// These are ephemeral "secret key" values which are deliberately designed +/// to avoid being persisted. +/// +/// To perform an ephemeral Diffie-Hellman exchange, do the following: +/// +/// - Have each participant generate an [`EphemeralSecret`] value +/// - Compute the [`PublicKey`] for that value +/// - Have each peer provide their [`PublicKey`] to their counterpart +/// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`] +/// to compute a [`SharedSecret`] value. +/// +/// # ⚠️ SECURITY WARNING ⚠️ +/// +/// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a +/// further authentication step are trivially vulnerable to man-in-the-middle +/// attacks! +/// +/// These exchanges should be performed in the context of a protocol which +/// takes further steps to authenticate the peers in a key exchange. +pub struct EphemeralSecret { + scalar: NonZeroScalar, +} + +impl EphemeralSecret { + /// Generate a cryptographically random [`EphemeralSecret`]. + pub fn random(rng: &mut impl CryptoRngCore) -> Self { + Self { + scalar: NonZeroScalar::random(rng), + } + } + + /// Get the public key associated with this ephemeral secret. + /// + /// The `compress` flag enables point compression. + pub fn public_key(&self) -> PublicKey { + PublicKey::from_secret_scalar(&self.scalar) + } + + /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the + /// public key of the other participant in the exchange. + pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret { + diffie_hellman(self.scalar, public_key.as_affine()) + } +} + +impl From<&EphemeralSecret> for PublicKey { + fn from(ephemeral_secret: &EphemeralSecret) -> Self { + ephemeral_secret.public_key() + } +} + +impl Zeroize for EphemeralSecret { + fn zeroize(&mut self) { + self.scalar.zeroize() + } +} + +impl ZeroizeOnDrop for EphemeralSecret {} + +impl Drop for EphemeralSecret { + fn drop(&mut self) { + self.zeroize(); + } +} + +/// Shared secret value computed via ECDH key agreement. +pub struct SharedSecret { + /// Computed secret value + secret_bytes: FieldBytes, +} + +impl SharedSecret { + /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve. + #[inline] + fn new(point: AffinePoint) -> Self { + Self { + secret_bytes: point.x(), + } + } + + /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to + /// extract entropy from this shared secret. + /// + /// This method can be used to transform the shared secret into uniformly + /// random values which are suitable as key material. + /// + /// The `D` type parameter is a cryptographic digest function. + /// `sha2::Sha256` is a common choice for use with HKDF. + /// + /// The `salt` parameter can be used to supply additional randomness. + /// Some examples include: + /// + /// - randomly generated (but authenticated) string + /// - fixed application-specific value + /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise) + /// + /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key + /// material. + /// + /// [HKDF]: https://en.wikipedia.org/wiki/HKDF + pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf> { + Hkdf::new(salt, &self.secret_bytes) + } + + /// This value contains the raw serialized x-coordinate of the elliptic curve + /// point computed from a Diffie-Hellman exchange, serialized as bytes. + /// + /// When in doubt, use [`SharedSecret::extract`] instead. + /// + /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️ + /// + /// This value is not uniformly random and should not be used directly + /// as a cryptographic key for anything which requires that property + /// (e.g. symmetric ciphers). + /// + /// Instead, the resulting value should be used as input to a Key Derivation + /// Function (KDF) or cryptographic hash function to produce a symmetric key. + /// The [`SharedSecret::extract`] function will do this for you. + pub fn raw_secret_bytes(&self) -> &FieldBytes { + &self.secret_bytes + } +} + +impl From for SharedSecret { + /// NOTE: this impl is intended to be used by curve implementations to + /// instantiate a [`SharedSecret`] value from their respective + /// [`AffinePoint`] type. + /// + /// Curve implementations should provide the field element representing + /// the affine x-coordinate as `secret_bytes`. + fn from(secret_bytes: FieldBytes) -> Self { + Self { secret_bytes } + } +} + +impl ZeroizeOnDrop for SharedSecret {} + +impl Drop for SharedSecret { + fn drop(&mut self) { + self.secret_bytes.zeroize() + } +} diff --git a/bign256/src/dsa.rs b/bign256/src/ecdsa.rs similarity index 95% rename from bign256/src/dsa.rs rename to bign256/src/ecdsa.rs index ffd084a7..5197eaf0 100644 --- a/bign256/src/dsa.rs +++ b/bign256/src/ecdsa.rs @@ -9,21 +9,21 @@ //! # fn example() -> Result<(), Box> { //! use rand_core::OsRng; // requires 'getrandom` feature //! use bign256::{ -//! dsa::{Signature, SigningKey, signature::Signer}, +//! ecdsa::{Signature, SigningKey, signature::Signer}, //! SecretKey //! }; //! //! // Signing //! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()` //! let signing_key = SigningKey::new(&secret_key)?; -//! let verifying_key_bytes = signing_key.verifying_key().to_sec1_bytes(); +//! let verifying_key_bytes = signing_key.verifying_key().to_bytes(); //! let message = b"test message"; //! let signature: Signature = signing_key.sign(message); //! //! // Verifying -//! use bign256::dsa::{VerifyingKey, signature::Verifier}; +//! use bign256::ecdsa::{VerifyingKey, signature::Verifier}; //! -//! let verifying_key = VerifyingKey::from_sec1_bytes(&verifying_key_bytes)?; +//! let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)?; //! verifying_key.verify(message, &signature)?; //! # Ok(()) //! # } diff --git a/bign256/src/dsa/signing.rs b/bign256/src/ecdsa/signing.rs similarity index 100% rename from bign256/src/dsa/signing.rs rename to bign256/src/ecdsa/signing.rs diff --git a/bign256/src/dsa/verifying.rs b/bign256/src/ecdsa/verifying.rs similarity index 90% rename from bign256/src/dsa/verifying.rs rename to bign256/src/ecdsa/verifying.rs index 64ce212f..51cab335 100644 --- a/bign256/src/dsa/verifying.rs +++ b/bign256/src/ecdsa/verifying.rs @@ -14,6 +14,9 @@ //! 9. Return YES. //! ``` +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + use super::{Signature, BELT_OID}; use crate::{ AffinePoint, BignP256, EncodedPoint, FieldBytes, Hash, ProjectivePoint, PublicKey, Scalar, @@ -26,13 +29,11 @@ use elliptic_curve::{ array::{consts::U32, typenum::Unsigned, Array}, group::GroupEncoding, ops::{LinearCombination, Reduce}, - sec1::ToEncodedPoint, Curve, Field, Group, }; use signature::{hazmat::PrehashVerifier, Error, Result, Verifier}; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; +use elliptic_curve::sec1::ToEncodedPoint; /// Bign256 public key used for verifying signatures are valid for a given /// message. @@ -89,21 +90,16 @@ impl VerifyingKey { hasher.finalize_fixed() } - /// Initialize [`VerifyingKey`] from a SEC1-encoded public key. - pub fn from_sec1_bytes(bytes: &[u8]) -> Result { - let public_key = PublicKey::from_sec1_bytes(bytes).map_err(|_| Error::new())?; + /// Parse a [`VerifyingKey`] from a byte slice. + pub fn from_bytes(bytes: &[u8]) -> Result { + let public_key = PublicKey::from_bytes(bytes).map_err(|_| Error::new())?; Self::new(public_key) } - /// Convert this [`VerifyingKey`] 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). - /// - /// + /// Serialize the [`VerifyingKey`] as a byte array. #[cfg(feature = "alloc")] - pub fn to_sec1_bytes(&self) -> Box<[u8]> { - self.public_key.to_sec1_bytes() + pub fn to_bytes(&self) -> Box<[u8]> { + self.public_key.to_bytes() } } diff --git a/bign256/src/lib.rs b/bign256/src/lib.rs index 2f6c5862..135b9f6b 100644 --- a/bign256/src/lib.rs +++ b/bign256/src/lib.rs @@ -1,4 +1,3 @@ -// #![no_std] #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] @@ -30,32 +29,37 @@ #[allow(unused_extern_crates)] extern crate alloc; +pub use elliptic_curve::{self, bigint::U256}; +use elliptic_curve::{bigint::ArrayEncoding, consts::U32, Error, FieldBytesEncoding}; + +#[cfg(feature = "arithmetic")] +pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint}; + +/// Bign256 result type +pub type Result = core::result::Result; + #[cfg(feature = "arithmetic")] pub mod arithmetic; #[cfg(any(feature = "test-vectors", test))] pub mod test_vectors; -#[cfg(feature = "dsa")] -pub mod dsa; - -pub use elliptic_curve::{self, bigint::U256}; - +#[cfg(feature = "ecdh")] +pub mod ecdh; +#[cfg(feature = "ecdsa")] +pub mod ecdsa; #[cfg(feature = "arithmetic")] -pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint}; +pub mod public_key; +#[cfg(feature = "arithmetic")] +pub mod secret_key; #[cfg(feature = "pkcs8")] -pub use elliptic_curve::pkcs8; - -use elliptic_curve::{ - array::Array, - bigint::ArrayEncoding, - consts::{U32, U33}, - FieldBytesEncoding, -}; +#[allow(dead_code)] +const ALGORITHM_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.2.1"); -#[cfg(feature = "dsa")] -type Hash = belt_hash::digest::Output; +#[cfg(feature = "ecdsa")] +type Hash = digest::Output; /// Order of BIGN P-256's elliptic curve group (i.e. scalar modulus) in hexadecimal. const ORDER_HEX: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD95C8ED60DFB4DFC7E5ABF99263D6607"; @@ -103,12 +107,9 @@ impl elliptic_curve::point::PointCompaction for BignP256 { #[cfg(feature = "pkcs8")] impl pkcs8::AssociatedOid for BignP256 { const OID: pkcs8::ObjectIdentifier = - pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.1"); + pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.3.1"); } -/// Compressed SEC1-encoded BIGN P256 curve point. -pub type CompressedPoint = Array; - /// BIGN P-256 field element serialized as bytes. /// /// Byte array containing a serialized field element value (base field or scalar). @@ -132,14 +133,26 @@ impl FieldBytesEncoding for U256 { pub type NonZeroScalar = elliptic_curve::NonZeroScalar; /// BIGN P-256 public key. +// #[cfg(feature = "arithmetic")] +// pub type PublicKey = elliptic_curve::PublicKey; + +/// Generic scalar type with primitive functionality.# #[cfg(feature = "arithmetic")] -pub type PublicKey = elliptic_curve::PublicKey; +pub type ScalarPrimitive = elliptic_curve::ScalarPrimitive; -/// BIGN P-256 secret key. -pub type SecretKey = elliptic_curve::SecretKey; +/// Elliptic curve BignP256 public key. +#[cfg(feature = "arithmetic")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicKey { + point: elliptic_curve::AffinePoint, +} -#[cfg(not(feature = "arithmetic"))] -impl elliptic_curve::sec1::ValidatePublicKey for BignP256 {} +/// Elliptic curve BignP256 Secret Key +#[cfg(feature = "arithmetic")] +#[derive(Copy, Clone, Debug)] +pub struct SecretKey { + inner: ScalarPrimitive, +} /// Bit representation of a BIGN P-256 scalar field element. #[cfg(feature = "bits")] diff --git a/bign256/src/public_key.rs b/bign256/src/public_key.rs new file mode 100644 index 00000000..c2209293 --- /dev/null +++ b/bign256/src/public_key.rs @@ -0,0 +1,210 @@ +//! Public key types and traits + +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, fmt}; +use core::{fmt::Display, str::FromStr}; + +use elliptic_curve::{ + array::Array, + point::NonIdentity, + sec1::{FromEncodedPoint, ToEncodedPoint}, + AffinePoint, CurveArithmetic, Error, Group, +}; +use pkcs8::{ + spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier}, + AssociatedOid, DecodePublicKey, EncodePublicKey, ObjectIdentifier, +}; + +use crate::{BignP256, EncodedPoint, NonZeroScalar, ProjectivePoint, PublicKey, ALGORITHM_OID}; + +impl PublicKey { + /// 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 + #[allow(clippy::arithmetic_side_effects)] + Self { + point: (::ProjectivePoint::generator() * scalar.as_ref()) + .to_affine(), + } + } + + /// 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(self.point).unwrap() + } + + /// Get [`PublicKey`] from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut bytes = Array::clone_from_slice(bytes); + // It is because public_key in little endian + bytes[..32].reverse(); + bytes[32..].reverse(); + + let point = EncodedPoint::from_untagged_bytes(&bytes); + let affine = AffinePoint::::from_encoded_point(&point); + if affine.is_none().into() { + Err(Error) + } else { + Ok(Self { + point: affine.unwrap(), + }) + } + } + + /// Get [`PublicKey`] from encoded point + pub fn from_encoded_point(point: EncodedPoint) -> Result { + let affine = AffinePoint::::from_encoded_point(&point); + if affine.is_none().into() { + Err(Error) + } else { + Ok(Self { + point: affine.unwrap(), + }) + } + } + + #[cfg(feature = "alloc")] + /// Get bytes from [`PublicKey`] + pub fn to_bytes(&self) -> Box<[u8]> { + let mut bytes = self.point.to_encoded_point(false).to_bytes(); + bytes[1..32 + 1].reverse(); + bytes[33..].reverse(); + bytes[1..].to_vec().into_boxed_slice() + } + + #[cfg(feature = "alloc")] + /// Get encoded point from [`PublicKey`] + pub fn to_encoded_point(&self) -> EncodedPoint { + self.point.to_encoded_point(false) + } +} + +impl AsRef> for PublicKey { + fn as_ref(&self) -> &AffinePoint { + self.as_affine() + } +} +impl Copy for PublicKey {} +impl From>> for PublicKey { + fn from(value: NonIdentity>) -> Self { + Self::from(&value) + } +} + +impl From<&NonIdentity>> for PublicKey { + fn from(value: &NonIdentity>) -> Self { + Self { + point: value.to_point(), + } + } +} + +impl From for NonIdentity> { + fn from(value: PublicKey) -> Self { + Self::from(&value) + } +} + +impl From<&PublicKey> for NonIdentity> { + fn from(value: &PublicKey) -> Self { + PublicKey::to_nonidentity(value) + } +} + +#[cfg(feature = "pkcs8")] +impl AssociatedAlgorithmIdentifier for PublicKey { + type Params = ObjectIdentifier; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(BignP256::OID), + }; +} + +#[cfg(feature = "pkcs8")] +impl TryFrom> for PublicKey { + 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 { + type Error = pkcs8::spki::Error; + + fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { + spki.algorithm.assert_oids(ALGORITHM_OID, BignP256::OID)?; + + let public_key_bytes = spki + .subject_public_key + .as_bytes() + .ok_or_else(|| der::Tag::BitString.value_error())?; + + Self::from_bytes(public_key_bytes).map_err(|_| pkcs8::spki::Error::KeyMalformed) + } +} + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +impl EncodePublicKey for PublicKey { + fn to_public_key_der(&self) -> pkcs8::spki::Result { + let pk_bytes = self.to_bytes(); + let subject_public_key = der::asn1::BitStringRef::new(0, &pk_bytes)?; + + pkcs8::SubjectPublicKeyInfo { + algorithm: Self::ALGORITHM_IDENTIFIER, + subject_public_key, + } + .try_into() + } +} + +impl From for EncodedPoint { + fn from(value: PublicKey) -> Self { + value.point.to_encoded_point(false) + } +} + +#[cfg(feature = "pem")] +impl FromStr for PublicKey { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_public_key_pem(s).map_err(|_| Error) + } +} + +#[cfg(feature = "pem")] +impl Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + self.to_public_key_pem(Default::default()) + .expect("PEM encoding error") + ) + } +} diff --git a/bign256/src/secret_key.rs b/bign256/src/secret_key.rs new file mode 100644 index 00000000..00d111b9 --- /dev/null +++ b/bign256/src/secret_key.rs @@ -0,0 +1,161 @@ +//! Bign256 secret key. + +use core::str::FromStr; +use der::SecretDocument; + +use elliptic_curve::{array::typenum::Unsigned, zeroize::Zeroizing, Error}; +use pkcs8::{ + spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier}, + AssociatedOid, DecodePrivateKey, EncodePrivateKey, ObjectIdentifier, +}; + +#[cfg(feature = "arithmetic")] +use crate::FieldBytes; +#[cfg(feature = "arithmetic")] +use crate::{elliptic_curve::rand_core::CryptoRngCore, BignP256, NonZeroScalar, Result}; +use crate::{PublicKey, ScalarPrimitive, SecretKey, ALGORITHM_OID}; + +impl SecretKey { + const MIN_SIZE: usize = 24; + + /// Generate a random [`SecretKey`]. + #[cfg(feature = "arithmetic")] + pub fn random(rng: &mut impl CryptoRngCore) -> Self { + Self { + inner: NonZeroScalar::random(rng).into(), + } + } + + /// Borrow the inner secret [`elliptic_curve::ScalarPrimitive`] value. + /// + /// # ⚠️ Warning + /// + /// This value is key material. + /// + /// Please treat it with the care it deserves! + pub fn as_scalar_primitive(&self) -> &ScalarPrimitive { + &self.inner + } + + /// Get the secret [`elliptic_curve::NonZeroScalar`] value for this key. + /// + /// # ⚠️ Warning + /// + /// This value is key material. + /// + /// Please treat it with the care it deserves! + #[cfg(feature = "arithmetic")] + pub fn to_nonzero_scalar(&self) -> NonZeroScalar { + (*self).into() + } + + /// Get the [`PublicKey`] which corresponds to this secret key + #[cfg(feature = "arithmetic")] + pub fn public_key(&self) -> PublicKey { + PublicKey::from_secret_scalar(&self.to_nonzero_scalar()) + } + + /// Deserialize secret key from an encoded secret scalar. + pub fn from_bytes(bytes: &FieldBytes) -> Result { + let inner: ScalarPrimitive = + Option::from(ScalarPrimitive::from_bytes(bytes)).ok_or(Error)?; + + if inner.is_zero().into() { + return Err(Error); + } + + Ok(Self { inner }) + } + + /// Deserialize secret key from an encoded secret scalar passed as a byte slice. + /// + /// The slice is expected to be a minimum of 24-bytes (192-bytes) and at most + /// `C::FieldBytesSize` bytes in length. + /// + /// Byte slices shorter than the field size are handled by zero padding the input. + /// + /// NOTE: this function is variable-time with respect to the input length. To avoid a timing + /// sidechannel, always ensure that the input has been pre-padded to `C::FieldBytesSize`. + pub fn from_slice(slice: &[u8]) -> Result { + if slice.len() == ::FieldBytesSize::USIZE { + Self::from_bytes(FieldBytes::from_slice(slice)) + } else if (Self::MIN_SIZE..::FieldBytesSize::USIZE) + .contains(&slice.len()) + { + let mut bytes = Zeroizing::new(FieldBytes::default()); + let offset = ::FieldBytesSize::USIZE + .saturating_sub(slice.len()); + bytes[offset..].copy_from_slice(slice); + Self::from_bytes(&bytes) + } else { + Err(Error) + } + } + + /// Serialize raw secret scalar as a big endian integer. + pub fn to_bytes(&self) -> FieldBytes { + self.inner.to_bytes() + } +} + +impl From for NonZeroScalar { + fn from(secret_key: SecretKey) -> NonZeroScalar { + secret_key.to_nonzero_scalar() + } +} + +#[cfg(feature = "arithmetic")] +impl From for SecretKey { + fn from(scalar: NonZeroScalar) -> SecretKey { + SecretKey::from(&scalar) + } +} + +#[cfg(feature = "arithmetic")] +impl From<&NonZeroScalar> for SecretKey { + fn from(scalar: &NonZeroScalar) -> SecretKey { + SecretKey { + inner: scalar.into(), + } + } +} + +impl AssociatedAlgorithmIdentifier for SecretKey { + type Params = ObjectIdentifier; + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(BignP256::OID), + }; +} + +impl TryFrom> for SecretKey { + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { + private_key_info + .algorithm + .assert_oids(ALGORITHM_OID, BignP256::OID)?; + Self::from_slice(private_key_info.private_key).map_err(|_| pkcs8::Error::KeyMalformed) + } +} + +#[cfg(feature = "pem")] +impl FromStr for SecretKey { + type Err = Error; + fn from_str(s: &str) -> core::result::Result { + Self::from_pkcs8_pem(s).map_err(|_| Error) + } +} + +impl EncodePrivateKey for SecretKey { + fn to_pkcs8_der(&self) -> pkcs8::Result { + let algorithm_identifier = pkcs8::AlgorithmIdentifierRef { + oid: ALGORITHM_OID, + parameters: Some((&BignP256::OID).into()), + }; + + let ec_private_key = self.to_bytes(); + let pkcs8_key = pkcs8::PrivateKeyInfo::new(algorithm_identifier, &ec_private_key); + Ok(SecretDocument::encode_msg(&pkcs8_key)?) + } +} diff --git a/bign256/tests/dsa.rs b/bign256/tests/dsa.rs index 4ef5428d..96c88604 100644 --- a/bign256/tests/dsa.rs +++ b/bign256/tests/dsa.rs @@ -1,23 +1,24 @@ //! bign256 DSA Tests -#![cfg(feature = "dsa")] +#![cfg(feature = "ecdsa")] + +use elliptic_curve::ops::Reduce; +use hex_literal::hex; +use proptest::prelude::*; use bign256::{ - dsa::{ + ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, NonZeroScalar, Scalar, U256, }; -use elliptic_curve::ops::Reduce; -use hex_literal::hex; -use proptest::prelude::*; -const PUBLIC_KEY: [u8; 65] = hex!( - "04 - D07F8590A8F77BF84F1EF10C6DE44CF5DDD52B4C9DE4CE3FE0799D1750561ABD - 909AD9B92A4DB89A4A050959DA2E0C1926281B466D68913417C8E86103A6C67A" +const PUBLIC_KEY: [u8; 64] = hex!( + "BD1A5650 179D79E0 3FCEE49D 4C2BD5DD F54CE46D 0CF11E4F F87BF7A8 90857FD0" + "7AC6A603 61E8C817 3491686D 461B2826 190C2EDA 5909054A 9AB84D2A B9D99A90" ); + const MSG: &[u8] = b"testing"; const SIG: [u8; 48] = hex!( "63F59C523FF1780851143114FFBC5C13" @@ -26,7 +27,7 @@ const SIG: [u8; 48] = hex!( #[test] fn verify_test_vector() { - let vk = VerifyingKey::from_sec1_bytes(&PUBLIC_KEY).unwrap(); + let vk = VerifyingKey::from_bytes(&PUBLIC_KEY).unwrap(); let sig = Signature::try_from(&SIG).unwrap(); assert!(vk.verify(MSG, &sig).is_ok()); } diff --git a/bign256/tests/ecdh.rs b/bign256/tests/ecdh.rs new file mode 100644 index 00000000..7b070d8f --- /dev/null +++ b/bign256/tests/ecdh.rs @@ -0,0 +1,32 @@ +#![cfg(feature = "ecdh")] +#[test] +fn ecdh() { + use bign256::{ecdh::EphemeralSecret, EncodedPoint, PublicKey}; + use rand_core::OsRng; // requires 'getrandom' feature + + // Alice + let alice_secret = EphemeralSecret::random(&mut OsRng); + let alice_pk_bytes = EncodedPoint::from(alice_secret.public_key()); + + // Bob + let bob_secret = EphemeralSecret::random(&mut OsRng); + let bob_pk_bytes = EncodedPoint::from(bob_secret.public_key()); + + // Alice decodes Bob's serialized public key and computes a shared secret from it + let bob_public = + PublicKey::from_encoded_point(bob_pk_bytes).expect("bob's public key is invalid!"); // In real usage, don't panic, handle this! + + let alice_shared = alice_secret.diffie_hellman(&bob_public); + + // Bob decodes Alice's serialized public key and computes the same shared secret + let alice_public = + PublicKey::from_encoded_point(alice_pk_bytes).expect("alice's public key is invalid!"); // In real usage, don't panic, handle this! + + let bob_shared = bob_secret.diffie_hellman(&alice_public); + + // Both participants arrive on the same shared secret + assert_eq!( + alice_shared.raw_secret_bytes(), + bob_shared.raw_secret_bytes() + ); +} diff --git a/bign256/tests/examples/pkcs8-private.der b/bign256/tests/examples/pkcs8-private.der new file mode 100644 index 00000000..4b293bb7 Binary files /dev/null and b/bign256/tests/examples/pkcs8-private.der differ diff --git a/bign256/tests/examples/pkcs8-private.pem b/bign256/tests/examples/pkcs8-private.pem new file mode 100644 index 00000000..e581587f --- /dev/null +++ b/bign256/tests/examples/pkcs8-private.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MD8CAQAwGAYKKnAAAgAiZS0CAQYKKnAAAgAiZS0DAQQgH2a1uEtzOWdFM/AynHTy +GDQoH+0HMkKeDHkjX8Jz4mk= +-----END PRIVATE KEY----- diff --git a/bign256/tests/examples/pkcs8-public.der b/bign256/tests/examples/pkcs8-public.der new file mode 100644 index 00000000..f5b0cf25 Binary files /dev/null and b/bign256/tests/examples/pkcs8-public.der differ diff --git a/bign256/tests/examples/pkcs8-public.pem b/bign256/tests/examples/pkcs8-public.pem new file mode 100644 index 00000000..3495bfcc --- /dev/null +++ b/bign256/tests/examples/pkcs8-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MF0wGAYKKnAAAgAiZS0CAQYKKnAAAgAiZS0DAQNBALLYmXRs6y04kBzvQkY56jD9 +onIL58G6PwS8MV3yQSupOA6o7OD3p7p+qWUtusU7gnvSwvtZhIaY3i6mdZYF65Y= +-----END PUBLIC KEY----- diff --git a/bign256/tests/pkcs8.rs b/bign256/tests/pkcs8.rs new file mode 100644 index 00000000..7d434a6f --- /dev/null +++ b/bign256/tests/pkcs8.rs @@ -0,0 +1,87 @@ +#![cfg(feature = "pkcs8")] + +use hex_literal::hex; +use pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey}; + +use bign256::{PublicKey, SecretKey}; + +const PKCS8_PRIVATE_KEY_DER: &[u8; 65] = include_bytes!("examples/pkcs8-private.der"); +#[cfg(feature = "pem")] +const PKCS8_PRIVATE_KEY_PEM: &str = include_str!("examples/pkcs8-private.pem"); +const PKCS8_PUBLIC_KEY_DER: &[u8; 95] = include_bytes!("examples/pkcs8-public.der"); +#[cfg(feature = "pem")] +const PKCS8_PUBLIC_KEY_PEM: &str = include_str!("examples/pkcs8-public.pem"); + +#[test] +fn decode_pkcs8_private_key_from_der() { + let secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap(); + let expected_scalar = hex!("1F66B5B84B7339674533F0329C74F21834281FED0732429E0C79235FC273E269"); + assert_eq!(secret_key.to_bytes().as_slice(), &expected_scalar[..]); +} + +#[test] +fn decode_pkcs8_public_key_from_der() { + let public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + let expected_point = hex!("\ + B2 D8 99 74 6C EB 2D 38 90 1C EF 42 46 39 EA 30 FD A2 72 0B E7 C1 BA 3F 04 BC 31 5D F2 41 2B A9 \ + 38 0E A8 EC E0 F7 A7 BA 7E A9 65 2D BA C5 3B 82 7B D2 C2 FB 59 84 86 98 DE 2E A6 75 96 05 EB 96\ + "); + assert_eq!(public_key.to_bytes().as_ref(), &expected_point[..]); +} + +#[test] +#[cfg(feature = "pem")] +fn decode_pkcs8_private_key_from_pem() { + let secret_key = PKCS8_PRIVATE_KEY_PEM.parse::().unwrap(); + + // Ensure key parses equivalently to DER + let der_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap(); + assert_eq!(secret_key.to_bytes(), der_key.to_bytes()); +} + +#[test] +#[cfg(feature = "pem")] +fn decode_pkcs8_public_key_from_pem() { + let public_key = PKCS8_PUBLIC_KEY_PEM.parse::().unwrap(); + + // Ensure key parses equivalently to DER + let der_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + assert_eq!(public_key, der_key); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_pkcs8_private_key_to_der() { + let original_secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap(); + let reencoded_secret_key = original_secret_key.to_pkcs8_der(); + assert_eq!( + reencoded_secret_key.unwrap().to_bytes().to_vec(), + &PKCS8_PRIVATE_KEY_DER[..] + ); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_pkcs8_public_key_to_der() { + let original_public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + let reencoded_public_key = original_public_key.to_public_key_der().unwrap(); + assert_eq!(reencoded_public_key.as_ref(), &PKCS8_PUBLIC_KEY_DER[..]); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_pkcs8_private_key_to_pem() { + let original_secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap(); + let reencoded_secret_key = original_secret_key + .to_pkcs8_pem(Default::default()) + .unwrap(); + assert_eq!(reencoded_secret_key.as_str(), PKCS8_PRIVATE_KEY_PEM); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_pkcs8_public_key_to_pem() { + let original_public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + let reencoded_public_key = original_public_key.to_string(); + assert_eq!(reencoded_public_key.as_str(), PKCS8_PUBLIC_KEY_PEM); +}