ECDSA signatures and public keys in CTAP (#358)

* ECDSA signatures and public keys in CTAP

* adds one constant usage

* documents pub functions in ECDSA

* typo: involved

* extends wrong length test
This commit is contained in:
kaczmarczyck
2021-08-04 13:39:49 +02:00
committed by GitHub
parent 7bb4960730
commit b7a3e06cf4
4 changed files with 215 additions and 43 deletions

View File

@@ -120,7 +120,6 @@ impl PointP256 {
}
// Computes n1*G + n2*self
#[cfg(feature = "std")]
pub fn points_mul(&self, n1: &ExponentP256, n2: &ExponentP256) -> PointP256 {
let p = self.to_affine();
let p1 = PointProjective::scalar_base_mul(n1);

View File

@@ -21,11 +21,9 @@ use super::rng256::Rng256;
use super::{Hash256, HashBlockSize64Bytes};
use alloc::vec;
use alloc::vec::Vec;
#[cfg(test)]
use arrayref::array_mut_ref;
#[cfg(feature = "std")]
use arrayref::array_ref;
use arrayref::mut_array_refs;
use arrayref::array_mut_ref;
use arrayref::{array_ref, mut_array_refs};
use core::marker::PhantomData;
pub const NBYTES: usize = int256::NBYTES;
@@ -150,6 +148,7 @@ impl SecKey {
}
}
/// Creates a private key from the exponent's bytes, or None if checks fail.
pub fn from_bytes(bytes: &[u8; 32]) -> Option<SecKey> {
let k = NonZeroExponentP256::from_int_checked(Int256::from_bin(bytes));
// The branching here is fine because all this reveals is whether the key was invalid.
@@ -160,12 +159,16 @@ impl SecKey {
Some(SecKey { k })
}
/// Writes a private key's exponent's bytes to the passed in array.
pub fn to_bytes(&self, bytes: &mut [u8; 32]) {
self.k.to_int().to_bin(bytes);
}
}
impl Signature {
pub const BYTES_LENGTH: usize = 2 * int256::NBYTES;
/// Converts a signature to its ASN1 DER representation.
pub fn to_asn1_der(&self) -> Vec<u8> {
const DER_INTEGER_TYPE: u8 = 0x02;
const DER_DEF_LENGTH_SEQUENCE: u8 = 0x30;
@@ -193,28 +196,28 @@ impl Signature {
encoding
}
#[cfg(feature = "std")]
pub fn from_bytes(bytes: &[u8]) -> Option<Signature> {
if bytes.len() != 64 {
None
} else {
let r =
NonZeroExponentP256::from_int_checked(Int256::from_bin(array_ref![bytes, 0, 32]));
let s =
NonZeroExponentP256::from_int_checked(Int256::from_bin(array_ref![bytes, 32, 32]));
if bool::from(r.is_none()) || bool::from(s.is_none()) {
return None;
}
let r = r.unwrap();
let s = s.unwrap();
Some(Signature { r, s })
/// Creates a signature from the exponents' bytes, or None if checks fail.
pub fn from_bytes(bytes: &[u8; Signature::BYTES_LENGTH]) -> Option<Signature> {
let r_bytes_ref = array_ref![bytes, 0, int256::NBYTES];
let r = NonZeroExponentP256::from_int_checked(Int256::from_bin(r_bytes_ref));
let s_bytes_ref = array_ref![bytes, int256::NBYTES, int256::NBYTES];
let s = NonZeroExponentP256::from_int_checked(Int256::from_bin(s_bytes_ref));
if bool::from(r.is_none()) || bool::from(s.is_none()) {
return None;
}
let r = r.unwrap();
let s = s.unwrap();
Some(Signature { r, s })
}
#[cfg(test)]
fn to_bytes(&self, bytes: &mut [u8; 64]) {
self.r.to_int().to_bin(array_mut_ref![bytes, 0, 32]);
self.s.to_int().to_bin(array_mut_ref![bytes, 32, 32]);
#[cfg(feature = "std")]
pub fn to_bytes(&self, bytes: &mut [u8; Signature::BYTES_LENGTH]) {
self.r
.to_int()
.to_bin(array_mut_ref![bytes, 0, int256::NBYTES]);
self.s
.to_int()
.to_bin(array_mut_ref![bytes, int256::NBYTES, int256::NBYTES]);
}
}
@@ -222,6 +225,12 @@ impl PubKey {
#[cfg(feature = "with_ctap1")]
const UNCOMPRESSED_LENGTH: usize = 1 + 2 * int256::NBYTES;
/// Creates a new PubKey from its coordinates on the elliptic curve.
pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option<PubKey> {
PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y))
.map(|p| PubKey { p })
}
#[cfg(feature = "std")]
pub fn from_bytes_uncompressed(bytes: &[u8]) -> Option<PubKey> {
PointP256::from_bytes_uncompressed_vartime(bytes).map(|p| PubKey { p })
@@ -252,12 +261,12 @@ impl PubKey {
self.p.gety().to_int().to_bin(y);
}
#[cfg(feature = "std")]
pub fn verify_vartime<H>(&self, msg: &[u8], sign: &Signature) -> bool
where
H: Hash256,
{
let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg)));
/// Verifies if the data's hash matches its signature.
///
/// This function is not a constant time implementation, and does not resist side channel
/// attacks. Only use if all data involved is public knowledge.
pub fn verify_hash_vartime(&self, hash: &[u8; NBYTES], sign: &Signature) -> bool {
let m = ExponentP256::modn(Int256::from_bin(hash));
let v = sign.s.inv();
let u = &m * v.as_exponent();
@@ -267,6 +276,14 @@ impl PubKey {
ExponentP256::modn(u.to_int()) == *sign.r.as_exponent()
}
#[cfg(feature = "std")]
pub fn verify_vartime<H>(&self, msg: &[u8], sign: &Signature) -> bool
where
H: Hash256,
{
self.verify_hash_vartime(&H::hash(msg), sign)
}
}
struct Rfc6979<H>
@@ -442,6 +459,21 @@ mod test {
test_rfc6979(msg, k, r, s);
}
/** Tests that sign and verify hashes are consistent **/
// Test that signed message hashes are correctly verified.
#[test]
fn test_sign_rfc6979_verify_hash_random() {
let mut rng = ThreadRng256 {};
for _ in 0..ITERATIONS {
let msg = rng.gen_uniform_u8x32();
let sk = SecKey::gensk(&mut rng);
let pk = sk.genpk();
let sign = sk.sign_rfc6979::<Sha256>(&msg);
assert!(pk.verify_hash_vartime(&Sha256::hash(&msg), &sign));
}
}
/** Tests that sign and verify are consistent **/
// Test that signed messages are correctly verified.
#[test]
@@ -537,7 +569,8 @@ mod test {
let sig_bytes = sig.as_ref();
let pk = PubKey::from_bytes_uncompressed(public_key_bytes).unwrap();
let sign = Signature::from_bytes(sig_bytes).unwrap();
let sign =
Signature::from_bytes(array_ref![sig_bytes, 0, Signature::BYTES_LENGTH]).unwrap();
assert!(pk.verify_vartime::<Sha256>(&msg_bytes, &sign));
}
}