// Copyright (c) Meta Platforms, Inc. and affiliates. // // This source code is dual-licensed under either the MIT license found in the // LICENSE-MIT file in the root directory of this source tree or the Apache // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. You may select, at your option, one of the above-listed // licenses. //! ECDSA implementation for [`elliptic_curve`] [`Group`] implementations to //! support [`SigmaI`](crate::SigmaI). use core::marker::PhantomData; use derive_where::derive_where; use digest::core_api::BlockSizeUser; use digest::{FixedOutputReset, HashMarker}; use ecdsa::{PrimeCurve, SignatureSize, hazmat}; use elliptic_curve::{ CurveArithmetic, Field, FieldBytes, FieldBytesEncoding, FieldBytesSize, NonZeroScalar, PrimeField, Scalar, }; use generic_array::{ArrayLength, GenericArray}; use rand::{CryptoRng, RngCore}; use zeroize::Zeroize; use super::{Message, MessageBuilder, SignatureProtocol}; use crate::ciphersuite::CipherSuite; use crate::errors::ProtocolError; use crate::key_exchange::group::Group; use crate::key_exchange::group::elliptic_curve::NonIdentity; pub use crate::key_exchange::sigma_i::shared::PreHash; use crate::serialization::SliceExt; /// ECDSA for [`SigmaI`](crate::SigmaI). /// /// The ["verification state"](Self::VerifyState) is the pre-hash for the /// message to be verified. pub struct Ecdsa(PhantomData<(G, H)>); impl SignatureProtocol for Ecdsa where G: CurveArithmetic + Group, Pk = NonIdentity> + PrimeCurve, SignatureSize: ArrayLength, H: Clone + Default + BlockSizeUser + FixedOutputReset> + HashMarker, { type Group = G; type Signature = Signature; type SignatureLen = SignatureSize; type VerifyState = PreHash; // We use a manual implementation of `RandomizedPrehashSigner` to use the same // hash for the message as for generating `k`. See // https://github.com/RustCrypto/signatures/issues/949. fn sign<'a, R: CryptoRng + RngCore, CS: CipherSuite, KE: Group>( sk: &::Sk, rng: &mut R, message: &Message, ) -> (Self::Signature, Self::VerifyState) { let hash = message.hash::(); ( Signature(sign::<_, G, H>(sk, rng, &hash.sign.finalize_fixed())), PreHash(hash.verify.finalize_fixed()), ) } fn verify( pk: &::Pk, _: MessageBuilder<'_, CS>, state: Self::VerifyState, signature: &Self::Signature, ) -> Result<(), ProtocolError> { verify(pk, &state.0, &signature.0) } fn serialize_signature(signature: &Self::Signature) -> GenericArray { signature.0.to_bytes() } fn deserialize_take_signature(bytes: &mut &[u8]) -> Result { ecdsa::Signature::from_bytes(&bytes.take_array("signature")?) .map(Signature) .map_err(|_| ProtocolError::SerializationError) } } fn sign(sk: &NonZeroScalar, rng: &mut R, pre_hash: &[u8]) -> ecdsa::Signature where R: CryptoRng + RngCore, C: CurveArithmetic + PrimeCurve, SignatureSize: ArrayLength, H: Default + BlockSizeUser + FixedOutputReset> + HashMarker, { let repr = sk.to_repr(); let order = C::ORDER.encode_field_bytes(); let z = hazmat::bits2field::(pre_hash).expect("hash output can not be shorter than a scalar"); // This can only fail if the computed `r` or `s` are zero, in which case we just // retry with a new `k`. See https://github.com/RustCrypto/signatures/pull/951. loop { let mut ad = FieldBytes::::default(); rng.fill_bytes(&mut ad); let k = Scalar::::from_repr(rfc6979::generate_k::(&repr, &order, &z, &ad)).unwrap(); if let Ok((signature, _)) = hazmat::sign_prehashed::(sk, k, &z) { break signature; } } } fn verify( pk: &NonIdentity, pre_hash: &[u8], signature: &ecdsa::Signature, ) -> Result<(), ProtocolError> where C: CurveArithmetic + PrimeCurve, SignatureSize: ArrayLength, { let z = hazmat::bits2field::(pre_hash).expect("hash output can not be shorter than a scalar"); hazmat::verify_prehashed(&pk.0.to_point(), &z, signature) .map_err(|_| ProtocolError::InvalidLoginError) } /// Wrapper around [`ecdsa::Signature`] to implement [`Zeroize`]. // TODO: remove after https://github.com/RustCrypto/signatures/pull/948. #[derive_where(Clone, Debug, Eq, PartialEq)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = "") )] pub struct Signature(pub ecdsa::Signature) where SignatureSize: ArrayLength; impl Zeroize for Signature where SignatureSize: ArrayLength, { fn zeroize(&mut self) { self.0 = ecdsa::Signature::from_scalars( Into::>::into(Scalar::::ONE), Into::>::into(Scalar::::ONE), ) .expect("failed to create `Signature` with non-zero `Scalar`s"); } } #[test] fn ecdsa() { use std::vec; use digest::Digest; use p256::ecdsa::signature::{DigestVerifier, RandomizedDigestSigner}; use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; use p256::{NistP256, PublicKey}; use rand::rngs::OsRng; use sha2::Sha256; use crate::tests::mock_rng::CycleRng; let mut rng = CycleRng::new(vec![1; 32]); let mut message = [0; 1024]; OsRng.fill_bytes(&mut message); let hash = Sha256::new_with_prefix(message); let sk = NistP256::random_sk(&mut OsRng); let signing_key = SigningKey::from(sk); let signature: Signature = signing_key.sign_digest_with_rng(&mut rng, hash.clone()); let custom_signature = sign::<_, _, Sha256>(&sk, &mut rng, &hash.clone().finalize()); assert_eq!(signature, custom_signature); let pk = NistP256::public_key(sk); let verifying_key = VerifyingKey::from(PublicKey::from(pk.0)); verifying_key .verify_digest(hash.clone(), &signature) .unwrap(); verify(&pk, &hash.finalize(), &custom_signature).unwrap(); }