diff --git a/libraries/crypto/src/ecdsa.rs b/libraries/crypto/src/ecdsa.rs index 13d35777..dd5830fc 100644 --- a/libraries/crypto/src/ecdsa.rs +++ b/libraries/crypto/src/ecdsa.rs @@ -276,7 +276,6 @@ impl PubKey { ExponentP256::modn(u.to_int()) == *sign.r.as_exponent() } - #[cfg(feature = "std")] pub fn verify_vartime(&self, msg: &[u8], sign: &Signature) -> bool where H: Hash256, diff --git a/libraries/opensk/Cargo.toml b/libraries/opensk/Cargo.toml index 65239641..60525722 100644 --- a/libraries/opensk/Cargo.toml +++ b/libraries/opensk/Cargo.toml @@ -22,6 +22,8 @@ subtle = { version = "2.2", default-features = false, features = ["nightly"] } arbitrary = { version = "0.4.7", features = ["derive"], optional = true } rand = { version = "0.8.4", optional = true } ed25519-compact = { version = "1", default-features = false, optional = true } +p256 = { version = "0.13.0", features = ["ecdh"], optional = true } +rand_core = { version = "0.6.4", optional = true } [features] debug_ctap = [] @@ -30,6 +32,7 @@ with_ctap1 = ["crypto/with_ctap1"] vendor_hid = [] fuzz = ["arbitrary", "std"] ed25519 = ["ed25519-compact"] +rust_crypto = ["p256", "rand_core"] [dev-dependencies] enum-iterator = "0.6.0" diff --git a/libraries/opensk/src/api/crypto/ecdh.rs b/libraries/opensk/src/api/crypto/ecdh.rs new file mode 100644 index 00000000..ecd5b897 --- /dev/null +++ b/libraries/opensk/src/api/crypto/ecdh.rs @@ -0,0 +1,53 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::EC_FIELD_SIZE; +use rng256::Rng256; + +/// Container for all ECDH cryptographic material. +pub trait Ecdh { + type SecretKey: SecretKey; + type PublicKey: PublicKey; + type SharedSecret: SharedSecret; +} + +/// ECDH ephemeral key. +pub trait SecretKey { + type PublicKey: PublicKey; + type SharedSecret: SharedSecret; + + /// Generates a new random secret key. + fn random(rng: &mut impl Rng256) -> Self; + + /// Computes the corresponding public key for this private key. + fn public_key(&self) -> Self::PublicKey; + + /// Computes the shared secret when using Elliptic-curve Diffie–Hellman. + fn diffie_hellman(&self, public_key: &Self::PublicKey) -> Self::SharedSecret; +} + +/// ECDH public key. +pub trait PublicKey: Sized { + /// Creates a public key from its coordinates. + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option; + + /// Writes the public key coordinates into the passed in parameters. + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]); +} + +/// ECDH shared secret. +pub trait SharedSecret { + /// Exports the x component of the point computed by Diffie–Hellman. + fn raw_secret_bytes(&self) -> [u8; EC_FIELD_SIZE]; +} diff --git a/libraries/opensk/src/api/crypto/ecdsa.rs b/libraries/opensk/src/api/crypto/ecdsa.rs new file mode 100644 index 00000000..c0cdf8d3 --- /dev/null +++ b/libraries/opensk/src/api/crypto/ecdsa.rs @@ -0,0 +1,72 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{EC_FIELD_SIZE, EC_SIGNATURE_SIZE}; +use alloc::vec::Vec; +use rng256::Rng256; + +/// Container for all ECDSA cryptographic material. +pub trait Ecdsa { + type SecretKey: SecretKey; + type PublicKey: PublicKey; + type Signature: Signature; +} + +/// ECDSA signing key. +pub trait SecretKey: Sized { + type PublicKey: PublicKey; + type Signature: Signature; + + /// Generates a new random secret key. + fn random(rng: &mut impl Rng256) -> Self; + + /// Creates a signing key from its representation in bytes. + fn from_slice(bytes: &[u8; EC_FIELD_SIZE]) -> Option; + + /// Computes the corresponding public key for this private key. + fn public_key(&self) -> Self::PublicKey; + + /// Signs the message. + /// + /// For hashing, SHA256 is used implicitly. + fn sign(&self, message: &[u8]) -> Self::Signature; + + /// Writes the signing key bytes into the passed in parameter. + fn to_slice(&self, bytes: &mut [u8; EC_FIELD_SIZE]); +} + +/// ECDSA verifying key. +pub trait PublicKey: Sized { + type Signature: Signature; + + /// Creates a public key from its coordinates. + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option; + + /// Verifies if the signature matches the message. + /// + /// For hashing, SHA256 is used implicitly. + fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool; + + /// Writes the public key coordinates into the passed in parameters. + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]); +} + +/// ECDSA signature. +pub trait Signature: Sized { + /// Creates a signature from its affine coordinates, represented as concatenated bytes. + fn from_slice(bytes: &[u8; EC_SIGNATURE_SIZE]) -> Option; + + /// Encodes the signatures as ASN1 DER. + fn to_der(&self) -> Vec; +} diff --git a/libraries/opensk/src/api/crypto/mod.rs b/libraries/opensk/src/api/crypto/mod.rs new file mode 100644 index 00000000..b90a0b59 --- /dev/null +++ b/libraries/opensk/src/api/crypto/mod.rs @@ -0,0 +1,37 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod ecdh; +pub mod ecdsa; +#[cfg(feature = "rust_crypto")] +pub mod rust_crypto; +#[cfg(not(feature = "rust_crypto"))] +pub mod software_crypto; +#[cfg(feature = "rust_crypto")] +pub use rust_crypto as software_crypto; + +use self::ecdh::Ecdh; +use self::ecdsa::Ecdsa; + +/// The size of field elements in the elliptic curve P256. +pub const EC_FIELD_SIZE: usize = 32; + +/// The size of a serialized ECDSA signature. +pub const EC_SIGNATURE_SIZE: usize = 2 * EC_FIELD_SIZE; + +/// Necessary cryptographic primitives for CTAP. +pub trait Crypto { + type Ecdh: Ecdh; + type Ecdsa: Ecdsa; +} diff --git a/libraries/opensk/src/api/crypto/rust_crypto.rs b/libraries/opensk/src/api/crypto/rust_crypto.rs new file mode 100644 index 00000000..ef86f58e --- /dev/null +++ b/libraries/opensk/src/api/crypto/rust_crypto.rs @@ -0,0 +1,249 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This cryptography implementation is an alternative for our own library. +//! +//! You can use it with the `rust_crypto` feature. An example call to cargo test is in +//! `run_desktop_tests.sh`. It is currently impossible to use it with our version of TockOS due to +//! a compiler version imcompatibility. +//! +//! If you want to use OpenSK outside of Tock v1, maybe this is useful for you though! + +use crate::api::crypto::{ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE}; +use core::convert::TryFrom; +use p256::ecdh::EphemeralSecret; +use p256::ecdsa::signature::{SignatureEncoding, Signer, Verifier}; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use p256::elliptic_curve::sec1::ToEncodedPoint; +// TODO: implement CryptoRngCore for our Rng instead +use rand_core::OsRng; +use rng256::Rng256; + +pub struct SoftwareCrypto; +pub struct SoftwareEcdh; +pub struct SoftwareEcdsa; + +impl Crypto for SoftwareCrypto { + type Ecdh = SoftwareEcdh; + type Ecdsa = SoftwareEcdsa; +} + +impl ecdh::Ecdh for SoftwareEcdh { + type SecretKey = SoftwareEcdhSecretKey; + type PublicKey = SoftwareEcdhPublicKey; + type SharedSecret = SoftwareEcdhSharedSecret; +} + +pub struct SoftwareEcdhSecretKey { + ephemeral_secret: EphemeralSecret, +} + +impl ecdh::SecretKey for SoftwareEcdhSecretKey { + type PublicKey = SoftwareEcdhPublicKey; + type SharedSecret = SoftwareEcdhSharedSecret; + + fn random(_rng: &mut impl Rng256) -> Self { + let ephemeral_secret = EphemeralSecret::random(&mut OsRng); + Self { ephemeral_secret } + } + + fn public_key(&self) -> Self::PublicKey { + let public_key = self.ephemeral_secret.public_key(); + SoftwareEcdhPublicKey { public_key } + } + + fn diffie_hellman(&self, public_key: &SoftwareEcdhPublicKey) -> Self::SharedSecret { + let shared_secret = self.ephemeral_secret.diffie_hellman(&public_key.public_key); + SoftwareEcdhSharedSecret { shared_secret } + } +} + +pub struct SoftwareEcdhPublicKey { + public_key: p256::PublicKey, +} + +impl ecdh::PublicKey for SoftwareEcdhPublicKey { + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option { + let encoded_point: p256::EncodedPoint = + p256::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false); + let public_key = p256::PublicKey::from_sec1_bytes(encoded_point.as_bytes()).ok()?; + Some(Self { public_key }) + } + + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]) { + let point = self.public_key.to_encoded_point(false); + x.copy_from_slice(point.x().unwrap()); + y.copy_from_slice(point.y().unwrap()); + } +} + +pub struct SoftwareEcdhSharedSecret { + shared_secret: p256::ecdh::SharedSecret, +} + +impl ecdh::SharedSecret for SoftwareEcdhSharedSecret { + fn raw_secret_bytes(&self) -> [u8; EC_FIELD_SIZE] { + let mut bytes = [0; EC_FIELD_SIZE]; + bytes.copy_from_slice(self.shared_secret.raw_secret_bytes().as_slice()); + bytes + } +} + +impl ecdsa::Ecdsa for SoftwareEcdsa { + type SecretKey = SoftwareEcdsaSecretKey; + type PublicKey = SoftwareEcdsaPublicKey; + type Signature = SoftwareEcdsaSignature; +} + +pub struct SoftwareEcdsaSecretKey { + signing_key: SigningKey, +} + +impl ecdsa::SecretKey for SoftwareEcdsaSecretKey { + type PublicKey = SoftwareEcdsaPublicKey; + type Signature = SoftwareEcdsaSignature; + + fn random(_rng: &mut impl Rng256) -> Self { + let signing_key = SigningKey::random(&mut OsRng); + SoftwareEcdsaSecretKey { signing_key } + } + + fn from_slice(bytes: &[u8; EC_FIELD_SIZE]) -> Option { + let signing_key = SigningKey::from_slice(bytes).ok()?; + Some(SoftwareEcdsaSecretKey { signing_key }) + } + + fn public_key(&self) -> Self::PublicKey { + let verifying_key = VerifyingKey::from(&self.signing_key); + SoftwareEcdsaPublicKey { verifying_key } + } + + fn sign(&self, message: &[u8]) -> Self::Signature { + let signature = self.signing_key.sign(message); + SoftwareEcdsaSignature { signature } + } + + fn to_slice(&self, bytes: &mut [u8; EC_FIELD_SIZE]) { + bytes.copy_from_slice(&self.signing_key.to_bytes()); + } +} + +pub struct SoftwareEcdsaPublicKey { + verifying_key: VerifyingKey, +} + +impl ecdsa::PublicKey for SoftwareEcdsaPublicKey { + type Signature = SoftwareEcdsaSignature; + + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option { + let encoded_point: p256::EncodedPoint = + p256::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false); + let verifying_key = VerifyingKey::from_encoded_point(&encoded_point).ok()?; + Some(SoftwareEcdsaPublicKey { verifying_key }) + } + + fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool { + self.verifying_key + .verify(message, &signature.signature) + .is_ok() + } + + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]) { + let point = self.verifying_key.to_encoded_point(false); + x.copy_from_slice(point.x().unwrap()); + y.copy_from_slice(point.y().unwrap()); + } +} + +pub struct SoftwareEcdsaSignature { + signature: p256::ecdsa::Signature, +} + +impl ecdsa::Signature for SoftwareEcdsaSignature { + fn from_slice(bytes: &[u8; EC_SIGNATURE_SIZE]) -> Option { + // Assumes EC_SIGNATURE_SIZE == 2 * EC_FIELD_SIZE + let r = &bytes[..EC_FIELD_SIZE]; + let s = &bytes[EC_FIELD_SIZE..]; + let r = p256::NonZeroScalar::try_from(r).ok()?; + let s = p256::NonZeroScalar::try_from(s).ok()?; + let r = p256::FieldBytes::from(r); + let s = p256::FieldBytes::from(s); + let signature = p256::ecdsa::Signature::from_scalars(r, s).ok()?; + Some(SoftwareEcdsaSignature { signature }) + } + + fn to_der(&self) -> Vec { + self.signature.to_der().to_vec() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::crypto::ecdh::{ + PublicKey as EcdhPublicKey, SecretKey as EcdhSecretKey, SharedSecret, + }; + use crate::api::crypto::ecdsa::{PublicKey as EcdsaPublicKey, SecretKey as EcdsaSecretKey}; + use crate::env::test::TestEnv; + + #[test] + fn test_shared_secret_symmetric() { + let mut env = TestEnv::default(); + let private1 = SoftwareEcdhSecretKey::random(env.rng()); + let private2 = SoftwareEcdhSecretKey::random(env.rng()); + let pub1 = private1.public_key(); + let pub2 = private2.public_key(); + let shared1 = private1.diffie_hellman(&pub2); + let shared2 = private2.diffie_hellman(&pub1); + assert_eq!(shared1.raw_secret_bytes(), shared2.raw_secret_bytes()); + } + + #[test] + fn test_ecdh_public_key_from_to_bytes() { + let mut env = TestEnv::default(); + let first_key = SoftwareEcdhSecretKey::random(env.rng()); + let first_public = first_key.public_key(); + let mut x = [0; EC_FIELD_SIZE]; + let mut y = [0; EC_FIELD_SIZE]; + first_public.to_coordinates(&mut x, &mut y); + let new_public = SoftwareEcdhPublicKey::from_coordinates(&x, &y).unwrap(); + let mut new_x = [0; EC_FIELD_SIZE]; + let mut new_y = [0; EC_FIELD_SIZE]; + new_public.to_coordinates(&mut new_x, &mut new_y); + assert_eq!(x, new_x); + assert_eq!(y, new_y); + } + + #[test] + fn test_sign_verify() { + let mut env = TestEnv::default(); + let private_key = SoftwareEcdsaSecretKey::random(env.rng()); + let public_key = private_key.public_key(); + let message = [0x12, 0x34, 0x56, 0x78]; + let signature = private_key.sign(&message); + assert!(public_key.verify(&message, &signature)); + } + + #[test] + fn test_ecdsa_secret_key_from_to_bytes() { + let mut env = TestEnv::default(); + let first_key = SoftwareEcdsaSecretKey::random(env.rng()); + let mut key_bytes = [0; EC_FIELD_SIZE]; + first_key.to_slice(&mut key_bytes); + let second_key = SoftwareEcdsaSecretKey::from_slice(&key_bytes).unwrap(); + let mut new_bytes = [0; EC_FIELD_SIZE]; + second_key.to_slice(&mut new_bytes); + assert_eq!(key_bytes, new_bytes); + } +} diff --git a/libraries/opensk/src/api/crypto/software_crypto.rs b/libraries/opensk/src/api/crypto/software_crypto.rs new file mode 100644 index 00000000..debf0e57 --- /dev/null +++ b/libraries/opensk/src/api/crypto/software_crypto.rs @@ -0,0 +1,213 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::api::crypto::{ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE}; +use alloc::vec::Vec; +use rng256::Rng256; + +pub struct SoftwareCrypto; +pub struct SoftwareEcdh; +pub struct SoftwareEcdsa; + +impl Crypto for SoftwareCrypto { + type Ecdh = SoftwareEcdh; + type Ecdsa = SoftwareEcdsa; +} + +impl ecdh::Ecdh for SoftwareEcdh { + type SecretKey = SoftwareEcdhSecretKey; + type PublicKey = SoftwareEcdhPublicKey; + type SharedSecret = SoftwareEcdhSharedSecret; +} + +pub struct SoftwareEcdhSecretKey { + sec_key: crypto::ecdh::SecKey, +} + +impl ecdh::SecretKey for SoftwareEcdhSecretKey { + type PublicKey = SoftwareEcdhPublicKey; + type SharedSecret = SoftwareEcdhSharedSecret; + + fn random(rng: &mut impl Rng256) -> Self { + let sec_key = crypto::ecdh::SecKey::gensk(rng); + Self { sec_key } + } + + fn public_key(&self) -> Self::PublicKey { + let pub_key = self.sec_key.genpk(); + SoftwareEcdhPublicKey { pub_key } + } + + fn diffie_hellman(&self, public_key: &SoftwareEcdhPublicKey) -> Self::SharedSecret { + let shared_secret = self.sec_key.exchange_x(&public_key.pub_key); + SoftwareEcdhSharedSecret { shared_secret } + } +} + +pub struct SoftwareEcdhPublicKey { + pub_key: crypto::ecdh::PubKey, +} + +impl ecdh::PublicKey for SoftwareEcdhPublicKey { + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option { + crypto::ecdh::PubKey::from_coordinates(x, y).map(|k| Self { pub_key: k }) + } + + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]) { + self.pub_key.to_coordinates(x, y); + } +} + +pub struct SoftwareEcdhSharedSecret { + shared_secret: [u8; EC_FIELD_SIZE], +} + +impl ecdh::SharedSecret for SoftwareEcdhSharedSecret { + fn raw_secret_bytes(&self) -> [u8; EC_FIELD_SIZE] { + self.shared_secret + } +} + +impl ecdsa::Ecdsa for SoftwareEcdsa { + type SecretKey = SoftwareEcdsaSecretKey; + type PublicKey = SoftwareEcdsaPublicKey; + type Signature = SoftwareEcdsaSignature; +} + +pub struct SoftwareEcdsaSecretKey { + sec_key: crypto::ecdsa::SecKey, +} + +impl ecdsa::SecretKey for SoftwareEcdsaSecretKey { + type PublicKey = SoftwareEcdsaPublicKey; + type Signature = SoftwareEcdsaSignature; + + fn random(rng: &mut impl Rng256) -> Self { + let sec_key = crypto::ecdsa::SecKey::gensk(rng); + Self { sec_key } + } + + fn from_slice(bytes: &[u8; EC_FIELD_SIZE]) -> Option { + crypto::ecdsa::SecKey::from_bytes(bytes).map(|k| Self { sec_key: k }) + } + + fn public_key(&self) -> Self::PublicKey { + let pub_key = self.sec_key.genpk(); + SoftwareEcdsaPublicKey { pub_key } + } + + fn sign(&self, message: &[u8]) -> Self::Signature { + let signature = self.sec_key.sign_rfc6979::(message); + SoftwareEcdsaSignature { signature } + } + + fn to_slice(&self, bytes: &mut [u8; EC_FIELD_SIZE]) { + self.sec_key.to_bytes(bytes); + } +} + +pub struct SoftwareEcdsaPublicKey { + pub_key: crypto::ecdsa::PubKey, +} + +impl ecdsa::PublicKey for SoftwareEcdsaPublicKey { + type Signature = SoftwareEcdsaSignature; + + fn from_coordinates(x: &[u8; EC_FIELD_SIZE], y: &[u8; EC_FIELD_SIZE]) -> Option { + crypto::ecdsa::PubKey::from_coordinates(x, y).map(|k| Self { pub_key: k }) + } + + fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool { + self.pub_key + .verify_vartime::(message, &signature.signature) + } + + fn to_coordinates(&self, x: &mut [u8; EC_FIELD_SIZE], y: &mut [u8; EC_FIELD_SIZE]) { + self.pub_key.to_coordinates(x, y); + } +} + +pub struct SoftwareEcdsaSignature { + signature: crypto::ecdsa::Signature, +} + +impl ecdsa::Signature for SoftwareEcdsaSignature { + fn from_slice(bytes: &[u8; EC_SIGNATURE_SIZE]) -> Option { + crypto::ecdsa::Signature::from_bytes(bytes).map(|s| SoftwareEcdsaSignature { signature: s }) + } + + fn to_der(&self) -> Vec { + self.signature.to_asn1_der() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::crypto::ecdh::{ + PublicKey as EcdhPublicKey, SecretKey as EcdhSecretKey, SharedSecret, + }; + use crate::api::crypto::ecdsa::{PublicKey as EcdsaPublicKey, SecretKey as EcdsaSecretKey}; + use crate::env::test::TestEnv; + + #[test] + fn test_shared_secret_symmetric() { + let mut env = TestEnv::default(); + let private1 = SoftwareEcdhSecretKey::random(env.rng()); + let private2 = SoftwareEcdhSecretKey::random(env.rng()); + let pub1 = private1.public_key(); + let pub2 = private2.public_key(); + let shared1 = private1.diffie_hellman(&pub2); + let shared2 = private2.diffie_hellman(&pub1); + assert_eq!(shared1.raw_secret_bytes(), shared2.raw_secret_bytes()); + } + + #[test] + fn test_ecdh_public_key_from_to_bytes() { + let mut env = TestEnv::default(); + let first_key = SoftwareEcdhSecretKey::random(env.rng()); + let first_public = first_key.public_key(); + let mut x = [0; EC_FIELD_SIZE]; + let mut y = [0; EC_FIELD_SIZE]; + first_public.to_coordinates(&mut x, &mut y); + let new_public = SoftwareEcdhPublicKey::from_coordinates(&x, &y).unwrap(); + let mut new_x = [0; EC_FIELD_SIZE]; + let mut new_y = [0; EC_FIELD_SIZE]; + new_public.to_coordinates(&mut new_x, &mut new_y); + assert_eq!(x, new_x); + assert_eq!(y, new_y); + } + + #[test] + fn test_sign_verify() { + let mut env = TestEnv::default(); + let private_key = SoftwareEcdsaSecretKey::random(env.rng()); + let public_key = private_key.public_key(); + let message = [0x12, 0x34, 0x56, 0x78]; + let signature = private_key.sign(&message); + assert!(public_key.verify(&message, &signature)); + } + + #[test] + fn test_ecdsa_secret_key_from_to_bytes() { + let mut env = TestEnv::default(); + let first_key = SoftwareEcdsaSecretKey::random(env.rng()); + let mut key_bytes = [0; EC_FIELD_SIZE]; + first_key.to_slice(&mut key_bytes); + let second_key = SoftwareEcdsaSecretKey::from_slice(&key_bytes).unwrap(); + let mut new_bytes = [0; EC_FIELD_SIZE]; + second_key.to_slice(&mut new_bytes); + assert_eq!(key_bytes, new_bytes); + } +} diff --git a/libraries/opensk/src/api/key_store.rs b/libraries/opensk/src/api/key_store.rs index 529556c3..14438cbd 100644 --- a/libraries/opensk/src/api/key_store.rs +++ b/libraries/opensk/src/api/key_store.rs @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::api::crypto::ecdsa::SecretKey as _; +use crate::env::{EcdsaSk, Env}; use alloc::vec::Vec; -use crypto::ecdsa::SecKey; use persistent_store::StoreError; use rng256::Rng256; -use crate::env::Env; - /// Provides storage for secret keys. /// /// Implementations may use the environment store: [`STORAGE_KEY`] is reserved for this usage. @@ -63,7 +62,7 @@ impl KeyStore for T { } fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result<[u8; 32], Error> { - match SecKey::from_bytes(seed) { + match EcdsaSk::::from_slice(seed) { None => Err(Error), Some(_) => Ok(*seed), } @@ -71,7 +70,7 @@ impl KeyStore for T { fn generate_ecdsa_seed(&mut self) -> Result<[u8; 32], Error> { let mut seed = [0; 32]; - SecKey::gensk(self.rng()).to_bytes(&mut seed); + EcdsaSk::::random(self.rng()).to_slice(&mut seed); Ok(seed) } diff --git a/libraries/opensk/src/api/mod.rs b/libraries/opensk/src/api/mod.rs index 345e988f..414e6bcd 100644 --- a/libraries/opensk/src/api/mod.rs +++ b/libraries/opensk/src/api/mod.rs @@ -20,6 +20,7 @@ pub mod attestation_store; pub mod clock; pub mod connection; +pub mod crypto; pub mod customization; pub mod firmware_protection; pub mod key_store; diff --git a/libraries/opensk/src/ctap/client_pin.rs b/libraries/opensk/src/ctap/client_pin.rs index 32d9c7f4..9d1d8bc5 100644 --- a/libraries/opensk/src/ctap/client_pin.rs +++ b/libraries/opensk/src/ctap/client_pin.rs @@ -20,8 +20,12 @@ use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret}; use super::response::{AuthenticatorClientPinResponse, ResponseData}; use super::status_code::Ctap2StatusCode; use super::token_state::PinUvAuthTokenState; +#[cfg(test)] +use crate::api::crypto::ecdh::SecretKey as _; use crate::api::customization::Customization; use crate::ctap::storage; +#[cfg(test)] +use crate::env::EcdhSk; use crate::env::Env; use alloc::boxed::Box; use alloc::str; @@ -105,8 +109,8 @@ pub enum PinPermission { } pub struct ClientPin { - pin_protocol_v1: PinProtocol, - pin_protocol_v2: PinProtocol, + pin_protocol_v1: PinProtocol, + pin_protocol_v2: PinProtocol, consecutive_pin_mismatches: u8, pin_uv_auth_token_state: PinUvAuthTokenState, } @@ -122,7 +126,7 @@ impl ClientPin { } /// Gets a reference to the PIN protocol of the given version. - fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol { + fn get_pin_protocol(&self, pin_uv_auth_protocol: PinUvAuthProtocol) -> &PinProtocol { match pin_uv_auth_protocol { PinUvAuthProtocol::V1 => &self.pin_protocol_v1, PinUvAuthProtocol::V2 => &self.pin_protocol_v2, @@ -133,7 +137,7 @@ impl ClientPin { fn get_mut_pin_protocol( &mut self, pin_uv_auth_protocol: PinUvAuthProtocol, - ) -> &mut PinProtocol { + ) -> &mut PinProtocol { match pin_uv_auth_protocol { PinUvAuthProtocol::V1 => &mut self.pin_protocol_v1, PinUvAuthProtocol::V2 => &mut self.pin_protocol_v2, @@ -558,20 +562,21 @@ impl ClientPin { #[cfg(test)] pub fn new_test( env: &mut E, - key_agreement_key: crypto::ecdh::SecKey, + key_agreement_key: EcdhSk, pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], pin_uv_auth_protocol: PinUvAuthProtocol, ) -> Self { + let random_key = EcdhSk::::random(env.rng()); let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol { - PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(env.rng())), - PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(env.rng()), key_agreement_key), + PinUvAuthProtocol::V1 => (key_agreement_key, random_key), + PinUvAuthProtocol::V2 => (random_key, key_agreement_key), }; let mut pin_uv_auth_token_state = PinUvAuthTokenState::new(); pin_uv_auth_token_state.set_permissions(0xFF); pin_uv_auth_token_state.begin_using_pin_uv_auth_token(env); Self { - pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token), - pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token), + pin_protocol_v1: PinProtocol::::new_test(key_agreement_key_v1, pin_uv_auth_token), + pin_protocol_v2: PinProtocol::::new_test(key_agreement_key_v2, pin_uv_auth_token), consecutive_pin_mismatches: 0, pin_uv_auth_token_state, } @@ -583,6 +588,7 @@ mod test { use super::super::pin_protocol::authenticate_pin_uv_auth_token; use super::*; use crate::env::test::TestEnv; + use crate::env::EcdhSk; use alloc::vec; /// Stores a PIN hash corresponding to the dummy PIN "1234". @@ -613,9 +619,9 @@ mod test { pin_uv_auth_protocol: PinUvAuthProtocol, ) -> (ClientPin, Box) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); - let pk = key_agreement_key.genpk(); - let key_agreement = CoseKey::from(pk); + let key_agreement_key = EcdhSk::::random(env.rng()); + let pk = key_agreement_key.public_key(); + let key_agreement = CoseKey::from_ecdh_public_key(pk); let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH]; let client_pin = ClientPin::::new_test( &mut env, @@ -1165,7 +1171,7 @@ mod test { fn test_helper_decrypt_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let pin_protocol = PinProtocol::new(env.rng()); + let pin_protocol = PinProtocol::::new(env.rng()); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) .unwrap(); @@ -1209,7 +1215,7 @@ mod test { fn test_helper_check_and_store_new_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let pin_protocol = PinProtocol::new(env.rng()); + let pin_protocol = PinProtocol::::new(env.rng()); let shared_secret = pin_protocol .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) .unwrap(); diff --git a/libraries/opensk/src/ctap/command.rs b/libraries/opensk/src/ctap/command.rs index c67c2f83..6e3c5671 100644 --- a/libraries/opensk/src/ctap/command.rs +++ b/libraries/opensk/src/ctap/command.rs @@ -627,7 +627,6 @@ mod test { }; use super::super::ES256_CRED_PARAM; use super::*; - use crate::env::test::TestEnv; use cbor::{cbor_array, cbor_map}; #[test] @@ -740,11 +739,7 @@ mod test { #[test] fn test_from_cbor_client_pin_parameters() { - let mut env = TestEnv::default(); - let sk = crypto::ecdh::SecKey::gensk(env.rng()); - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk); - + let cose_key = CoseKey::example_ecdh_pubkey(); let cbor_value = cbor_map! { 0x01 => 1, 0x02 => ClientPinSubCommand::GetPinRetries, diff --git a/libraries/opensk/src/ctap/config_command.rs b/libraries/opensk/src/ctap/config_command.rs index 5f293c20..91371cd9 100644 --- a/libraries/opensk/src/ctap/config_command.rs +++ b/libraries/opensk/src/ctap/config_command.rs @@ -123,15 +123,17 @@ pub fn process_config( #[cfg(test)] mod test { use super::*; + use crate::api::crypto::ecdh::SecretKey as _; use crate::api::customization::Customization; use crate::ctap::data_formats::PinUvAuthProtocol; use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token; use crate::env::test::TestEnv; + use crate::env::EcdhSk; #[test] fn test_process_enable_enterprise_attestation() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -162,7 +164,7 @@ mod test { #[test] fn test_process_toggle_always_uv() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -201,7 +203,7 @@ mod test { fn test_helper_process_toggle_always_uv_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -275,7 +277,7 @@ mod test { #[test] fn test_process_set_min_pin_length() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -323,7 +325,7 @@ mod test { #[test] fn test_process_set_min_pin_length_rp_ids() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -403,7 +405,7 @@ mod test { #[test] fn test_process_set_min_pin_length_force_pin_change_implicit() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -430,7 +432,7 @@ mod test { #[test] fn test_process_set_min_pin_length_force_pin_change_explicit() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -465,7 +467,7 @@ mod test { #[test] fn test_process_config_vendor_prototype() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, diff --git a/libraries/opensk/src/ctap/credential_id.rs b/libraries/opensk/src/ctap/credential_id.rs index a5a17b00..5d6359f9 100644 --- a/libraries/opensk/src/ctap/credential_id.rs +++ b/libraries/opensk/src/ctap/credential_id.rs @@ -268,10 +268,12 @@ pub fn decrypt_credential_id( #[cfg(test)] mod test { use super::*; + use crate::api::crypto::ecdsa::SecretKey as _; use crate::api::customization::Customization; use crate::ctap::credential_id::CBOR_CREDENTIAL_ID_SIZE; use crate::ctap::SignatureAlgorithm; use crate::env::test::TestEnv; + use crate::env::EcdsaSk; use crypto::hmac::hmac_256; const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80; @@ -380,13 +382,13 @@ mod test { /// This is a copy of the function that genereated deprecated key handles. fn legacy_encrypt_to_credential_id( env: &mut impl Env, - private_key: crypto::ecdsa::SecKey, + private_key: EcdsaSk, application: &[u8; 32], ) -> Result, Ctap2StatusCode> { let aes_enc_key = crypto::aes256::EncryptionKey::new(&env.key_store().key_handle_encryption()?); let mut plaintext = [0; 64]; - private_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); + private_key.to_slice(array_mut_ref!(plaintext, 0, 32)); plaintext[32..64].copy_from_slice(application); let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; diff --git a/libraries/opensk/src/ctap/credential_management.rs b/libraries/opensk/src/ctap/credential_management.rs index 838738d0..6d7a024f 100644 --- a/libraries/opensk/src/ctap/credential_management.rs +++ b/libraries/opensk/src/ctap/credential_management.rs @@ -359,7 +359,9 @@ mod test { use super::super::pin_protocol::authenticate_pin_uv_auth_token; use super::super::CtapState; use super::*; + use crate::api::crypto::ecdh::SecretKey as _; use crate::env::test::TestEnv; + use crate::env::EcdhSk; use rng256::Rng256; const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]); @@ -384,7 +386,7 @@ mod test { fn test_helper_process_get_creds_metadata(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -468,7 +470,7 @@ mod test { #[test] fn test_process_enumerate_rps_with_uv() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -563,7 +565,7 @@ mod test { #[test] fn test_process_enumerate_rps_completeness() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -646,7 +648,7 @@ mod test { #[test] fn test_process_enumerate_credentials_with_uv() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -749,7 +751,7 @@ mod test { #[test] fn test_process_delete_credential() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -821,7 +823,7 @@ mod test { #[test] fn test_process_update_user_information() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, diff --git a/libraries/opensk/src/ctap/crypto_wrapper.rs b/libraries/opensk/src/ctap/crypto_wrapper.rs index d549aac1..4c9bc801 100644 --- a/libraries/opensk/src/ctap/crypto_wrapper.rs +++ b/libraries/opensk/src/ctap/crypto_wrapper.rs @@ -12,16 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::api::crypto::ecdsa::{SecretKey as _, Signature}; use crate::api::key_store::KeyStore; use crate::ctap::data_formats::{extract_array, extract_byte_string, CoseKey, SignatureAlgorithm}; use crate::ctap::status_code::Ctap2StatusCode; -use crate::env::Env; +use crate::env::{EcdsaSk, Env}; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; use crypto::cbc::{cbc_decrypt, cbc_encrypt}; -use crypto::ecdsa; -use crypto::sha256::Sha256; use rng256::Rng256; use sk_cbor as cbor; use sk_cbor::{cbor_array, cbor_bytes, cbor_int}; @@ -129,7 +128,7 @@ impl PrivateKey { } /// Returns the ECDSA private key. - pub fn ecdsa_key(&self, env: &mut impl Env) -> Result { + pub fn ecdsa_key(&self, env: &mut E) -> Result, Ctap2StatusCode> { match self { PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed), #[allow(unreachable_patterns)] @@ -141,7 +140,7 @@ impl PrivateKey { pub fn get_pub_key(&self, env: &mut impl Env) -> Result { Ok(match self { PrivateKey::Ecdsa(ecdsa_seed) => { - CoseKey::from(ecdsa_key_from_seed(env, ecdsa_seed)?.genpk()) + CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key()) } #[cfg(feature = "ed25519")] PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()), @@ -155,9 +154,9 @@ impl PrivateKey { message: &[u8], ) -> Result, Ctap2StatusCode> { Ok(match self { - PrivateKey::Ecdsa(ecdsa_seed) => ecdsa_key_from_seed(env, ecdsa_seed)? - .sign_rfc6979::(message) - .to_asn1_der(), + PrivateKey::Ecdsa(ecdsa_seed) => { + ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der() + } #[cfg(feature = "ed25519")] PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(), }) @@ -182,12 +181,12 @@ impl PrivateKey { } } -fn ecdsa_key_from_seed( - env: &mut impl Env, +fn ecdsa_key_from_seed( + env: &mut E, seed: &[u8; 32], -) -> Result { +) -> Result, Ctap2StatusCode> { let ecdsa_bytes = env.key_store().derive_ecdsa(seed)?; - Ok(ecdsa::SecKey::from_bytes(&ecdsa_bytes).unwrap()) + Ok(EcdsaSk::::from_slice(&ecdsa_bytes).unwrap()) } impl From<&PrivateKey> for cbor::Value { @@ -334,10 +333,10 @@ mod test { let mut env = TestEnv::default(); let private_key = PrivateKey::new_ecdsa(&mut env); let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap(); - let public_key = ecdsa_key.genpk(); + let public_key = ecdsa_key.public_key(); assert_eq!( private_key.get_pub_key(&mut env), - Ok(CoseKey::from(public_key)) + Ok(CoseKey::from_ecdsa_public_key(public_key)) ); } @@ -347,7 +346,7 @@ mod test { let message = [0x5A; 32]; let private_key = PrivateKey::new_ecdsa(&mut env); let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap(); - let signature = ecdsa_key.sign_rfc6979::(&message).to_asn1_der(); + let signature = ecdsa_key.sign(&message).to_der(); assert_eq!( private_key.sign_and_encode(&mut env, &message), Ok(signature) diff --git a/libraries/opensk/src/ctap/ctap1.rs b/libraries/opensk/src/ctap/ctap1.rs index dd9e0ba2..ea2ce20b 100644 --- a/libraries/opensk/src/ctap/ctap1.rs +++ b/libraries/opensk/src/ctap/ctap1.rs @@ -17,9 +17,11 @@ use super::credential_id::{decrypt_credential_id, encrypt_to_credential_id}; use super::crypto_wrapper::PrivateKey; use super::CtapState; use crate::api::attestation_store::{self, Attestation, AttestationStore}; +use crate::api::crypto::ecdsa::{self, SecretKey as _, Signature}; +use crate::api::crypto::EC_FIELD_SIZE; use crate::env::Env; use alloc::vec::Vec; -use arrayref::array_ref; +use arrayref::{array_ref, mut_array_refs}; use core::convert::TryFrom; // For now, they're the same thing with apdu.rs containing the authoritative definition @@ -156,6 +158,17 @@ impl TryFrom<&[u8]> for U2fCommand { } } +fn to_uncompressed(public_key: &impl ecdsa::PublicKey) -> [u8; 1 + 2 * EC_FIELD_SIZE] { + // Formatting according to: + // https://tools.ietf.org/id/draft-jivsov-ecc-compact-05.html#overview + const B0_BYTE_MARKER: u8 = 0x04; + let mut representation = [0; 1 + 2 * EC_FIELD_SIZE]; + let (marker, x, y) = mut_array_refs![&mut representation, 1, EC_FIELD_SIZE, EC_FIELD_SIZE]; + marker[0] = B0_BYTE_MARKER; + public_key.to_coordinates(x, y); + representation +} + pub struct Ctap1Command {} impl Ctap1Command { @@ -245,7 +258,7 @@ impl Ctap1Command { let sk = private_key .ecdsa_key(env) .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; - let pk = sk.genpk(); + let pk = sk.public_key(); let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None) .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; if key_handle.len() > 0xFF { @@ -263,7 +276,7 @@ impl Ctap1Command { let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len()); response.push(Ctap1Command::LEGACY_BYTE); - let user_pk = pk.to_uncompressed(); + let user_pk = to_uncompressed(&pk); response.extend_from_slice(&user_pk); response.push(key_handle.len() as u8); response.extend(key_handle.clone()); @@ -327,10 +340,10 @@ impl Ctap1Command { ) .map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?; signature_data.extend(&challenge); - let signature = ecdsa_key.sign_rfc6979::(&signature_data); + let signature = ecdsa_key.sign(&signature_data); let mut response = signature_data[application.len()..application.len() + 5].to_vec(); - response.extend(signature.to_asn1_der()); + response.extend(signature.to_der()); Ok(response) } else { Err(Ctap1StatusCode::SW_WRONG_DATA) diff --git a/libraries/opensk/src/ctap/data_formats.rs b/libraries/opensk/src/ctap/data_formats.rs index 7abb0ec1..3889d848 100644 --- a/libraries/opensk/src/ctap/data_formats.rs +++ b/libraries/opensk/src/ctap/data_formats.rs @@ -14,13 +14,13 @@ use super::crypto_wrapper::PrivateKey; use super::status_code::Ctap2StatusCode; +use crate::api::crypto::{ecdh, ecdsa, EC_FIELD_SIZE}; use alloc::string::String; use alloc::vec::Vec; #[cfg(feature = "fuzz")] use arbitrary::Arbitrary; use arrayref::array_ref; use core::convert::TryFrom; -use crypto::{ecdh, ecdsa}; #[cfg(test)] use enum_iterator::IntoEnumIterator; use sk_cbor as cbor; @@ -728,8 +728,8 @@ impl PublicKeyCredentialSource { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct CoseKey { - x_bytes: [u8; ecdh::NBYTES], - y_bytes: [u8; ecdh::NBYTES], + x_bytes: [u8; EC_FIELD_SIZE], + y_bytes: [u8; EC_FIELD_SIZE], algorithm: i64, key_type: i64, curve: i64, @@ -748,6 +748,78 @@ impl CoseKey { const P_256_CURVE: i64 = 1; #[cfg(feature = "ed25519")] const ED25519_CURVE: i64 = 6; + + pub fn from_ecdh_public_key(pk: impl ecdh::PublicKey) -> Self { + let mut x_bytes = [0; EC_FIELD_SIZE]; + let mut y_bytes = [0; EC_FIELD_SIZE]; + pk.to_coordinates(&mut x_bytes, &mut y_bytes); + CoseKey { + x_bytes, + y_bytes, + algorithm: CoseKey::ECDH_ALGORITHM, + key_type: CoseKey::EC2_KEY_TYPE, + curve: CoseKey::P_256_CURVE, + } + } + + pub fn from_ecdsa_public_key(pk: impl ecdsa::PublicKey) -> Self { + let mut x_bytes = [0; EC_FIELD_SIZE]; + let mut y_bytes = [0; EC_FIELD_SIZE]; + pk.to_coordinates(&mut x_bytes, &mut y_bytes); + CoseKey { + x_bytes, + y_bytes, + algorithm: ES256_ALGORITHM, + key_type: CoseKey::EC2_KEY_TYPE, + curve: CoseKey::P_256_CURVE, + } + } + + /// Returns the x and y coordinates, if the key is an ECDH public key. + pub fn try_into_ecdh_coordinates( + self, + ) -> Result<([u8; EC_FIELD_SIZE], [u8; EC_FIELD_SIZE]), Ctap2StatusCode> { + let CoseKey { + x_bytes, + y_bytes, + algorithm, + key_type, + curve, + } = self; + + // Since algorithm can be used for different COSE key types, we check + // whether the current type is correct for ECDH. For an OpenSSH bugfix, + // the algorithm ES256_ALGORITHM is allowed here too. + // https://github.com/google/OpenSK/issues/90 + if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + if key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + Ok((x_bytes, y_bytes)) + } + + #[cfg(test)] + pub fn example_ecdh_pubkey() -> Self { + let x_bytes = [ + 0x74, 0x4A, 0x48, 0xA0, 0xDC, 0x56, 0x9A, 0x42, 0x0B, 0x3F, 0x58, 0xBF, 0xD8, 0xD9, + 0x62, 0xCF, 0x3A, 0xEA, 0xB1, 0x5A, 0x32, 0x03, 0xC1, 0xA4, 0x23, 0x8B, 0x57, 0x75, + 0x74, 0xA4, 0x29, 0x50, + ]; + let y_bytes = [ + 0xCD, 0x93, 0x26, 0x4A, 0xAF, 0x2A, 0xBA, 0xD1, 0x09, 0x3D, 0x2E, 0xD6, 0x8C, 0xC0, + 0x59, 0xB1, 0xD9, 0xAB, 0xD7, 0x81, 0x71, 0x60, 0x35, 0xFE, 0xFF, 0xE8, 0xE1, 0x94, + 0x05, 0x60, 0xA0, 0xBC, + ]; + CoseKey { + x_bytes, + y_bytes, + algorithm: CoseKey::ECDH_ALGORITHM, + key_type: CoseKey::EC2_KEY_TYPE, + curve: CoseKey::P_256_CURVE, + } + } } // This conversion accepts both ECDH and ECDSA. @@ -767,17 +839,15 @@ impl TryFrom for CoseKey { } let algorithm = extract_integer(ok_or_missing(algorithm)?)?; - let nbytes = match algorithm { - CoseKey::ECDH_ALGORITHM => ecdh::NBYTES, - ES256_ALGORITHM => ecdsa::NBYTES, - _ => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), + if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); }; let x_bytes = extract_byte_string(ok_or_missing(x_bytes)?)?; - if x_bytes.len() != nbytes { + if x_bytes.len() != EC_FIELD_SIZE { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } let y_bytes = extract_byte_string(ok_or_missing(y_bytes)?)?; - if y_bytes.len() != nbytes { + if y_bytes.len() != EC_FIELD_SIZE { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } let curve = extract_integer(ok_or_missing(curve)?)?; @@ -790,8 +860,8 @@ impl TryFrom for CoseKey { } Ok(CoseKey { - x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES], - y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES], + x_bytes: *array_ref![x_bytes.as_slice(), 0, EC_FIELD_SIZE], + y_bytes: *array_ref![y_bytes.as_slice(), 0, EC_FIELD_SIZE], algorithm, key_type, curve, @@ -819,36 +889,6 @@ impl From for cbor::Value { } } -impl From for CoseKey { - fn from(pk: ecdh::PubKey) -> Self { - let mut x_bytes = [0; ecdh::NBYTES]; - let mut y_bytes = [0; ecdh::NBYTES]; - pk.to_coordinates(&mut x_bytes, &mut y_bytes); - CoseKey { - x_bytes, - y_bytes, - algorithm: CoseKey::ECDH_ALGORITHM, - key_type: CoseKey::EC2_KEY_TYPE, - curve: CoseKey::P_256_CURVE, - } - } -} - -impl From for CoseKey { - fn from(pk: ecdsa::PubKey) -> Self { - let mut x_bytes = [0; ecdsa::NBYTES]; - let mut y_bytes = [0; ecdsa::NBYTES]; - pk.to_coordinates(&mut x_bytes, &mut y_bytes); - CoseKey { - x_bytes, - y_bytes, - algorithm: ES256_ALGORITHM, - key_type: CoseKey::EC2_KEY_TYPE, - curve: CoseKey::P_256_CURVE, - } - } -} - #[cfg(feature = "ed25519")] impl From for CoseKey { fn from(pk: ed25519_compact::PublicKey) -> Self { @@ -862,56 +902,6 @@ impl From for CoseKey { } } -impl TryFrom for ecdh::PubKey { - type Error = Ctap2StatusCode; - - fn try_from(cose_key: CoseKey) -> Result { - let CoseKey { - x_bytes, - y_bytes, - algorithm, - key_type, - curve, - } = cose_key; - - // Since algorithm can be used for different COSE key types, we check - // whether the current type is correct for ECDH. For an OpenSSH bugfix, - // the algorithm ES256_ALGORITHM is allowed here too. - // https://github.com/google/OpenSK/issues/90 - if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM { - return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); - } - if key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE { - return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); - } - ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes) - .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) - } -} - -impl TryFrom for ecdsa::PubKey { - type Error = Ctap2StatusCode; - - fn try_from(cose_key: CoseKey) -> Result { - let CoseKey { - x_bytes, - y_bytes, - algorithm, - key_type, - curve, - } = cose_key; - - if algorithm != ES256_ALGORITHM - || key_type != CoseKey::EC2_KEY_TYPE - || curve != CoseKey::P_256_CURVE - { - return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); - } - ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes) - .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum PinUvAuthProtocol { @@ -1240,7 +1230,10 @@ pub(super) fn ok_or_missing(value_option: Option) -> Result true, "hmac-secret" => cbor_map! { @@ -1763,10 +1753,7 @@ mod test { #[test] fn test_from_get_assertion_extensions_with_protocol() { - let mut env = TestEnv::default(); - let sk = crypto::ecdh::SecKey::gensk(env.rng()); - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk); + let cose_key = CoseKey::example_ecdh_pubkey(); let cbor_extensions = cbor_map! { "credBlob" => true, "hmac-secret" => cbor_map! { @@ -1938,20 +1925,29 @@ mod test { #[test] fn test_from_into_cose_key_ecdh() { - let mut env = TestEnv::default(); - let sk = crypto::ecdh::SecKey::gensk(env.rng()); - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk.clone()); - let created_pk = ecdh::PubKey::try_from(cose_key); - assert_eq!(created_pk, Ok(pk)); + let cose_key = CoseKey::example_ecdh_pubkey(); + let (x_bytes, y_bytes) = cose_key.clone().try_into_ecdh_coordinates().unwrap(); + let created_pk = EcdhPk::::from_coordinates(&x_bytes, &y_bytes).unwrap(); + let new_cose_key = CoseKey::from_ecdh_public_key(created_pk); + assert_eq!(cose_key, new_cose_key); } #[test] - fn test_into_cose_key_ecdsa() { - let mut env = TestEnv::default(); - let sk = crypto::ecdsa::SecKey::gensk(env.rng()); - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk); + fn test_from_cose_key_ecdsa() { + let x_bytes = [ + 0x74, 0x4A, 0x48, 0xA0, 0xDC, 0x56, 0x9A, 0x42, 0x0B, 0x3F, 0x58, 0xBF, 0xD8, 0xD9, + 0x62, 0xCF, 0x3A, 0xEA, 0xB1, 0x5A, 0x32, 0x03, 0xC1, 0xA4, 0x23, 0x8B, 0x57, 0x75, + 0x74, 0xA4, 0x29, 0x50, + ]; + let y_bytes = [ + 0xCD, 0x93, 0x26, 0x4A, 0xAF, 0x2A, 0xBA, 0xD1, 0x09, 0x3D, 0x2E, 0xD6, 0x8C, 0xC0, + 0x59, 0xB1, 0xD9, 0xAB, 0xD7, 0x81, 0x71, 0x60, 0x35, 0xFE, 0xFF, 0xE8, 0xE1, 0x94, + 0x05, 0x60, 0xA0, 0xBC, + ]; + let created_pk = EcdsaPk::::from_coordinates(&x_bytes, &y_bytes).unwrap(); + let cose_key = CoseKey::from_ecdsa_public_key(created_pk); + assert_eq!(cose_key.x_bytes, x_bytes); + assert_eq!(cose_key.y_bytes, y_bytes); assert_eq!(cose_key.algorithm, ES256_ALGORITHM); } diff --git a/libraries/opensk/src/ctap/large_blobs.rs b/libraries/opensk/src/ctap/large_blobs.rs index 54bc6306..445b6d12 100644 --- a/libraries/opensk/src/ctap/large_blobs.rs +++ b/libraries/opensk/src/ctap/large_blobs.rs @@ -140,12 +140,14 @@ mod test { use super::super::data_formats::PinUvAuthProtocol; use super::super::pin_protocol::authenticate_pin_uv_auth_token; use super::*; + use crate::api::crypto::ecdh::SecretKey as EcdhSecretKey; use crate::env::test::TestEnv; + use crate::env::EcdhSk; #[test] fn test_process_command_get_empty() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -180,7 +182,7 @@ mod test { #[test] fn test_process_command_commit_and_get() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -246,7 +248,7 @@ mod test { #[test] fn test_process_command_commit_unexpected_offset() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -296,7 +298,7 @@ mod test { #[test] fn test_process_command_commit_unexpected_length() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -346,7 +348,7 @@ mod test { #[test] fn test_process_command_commit_end_offset_overflow() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -373,7 +375,7 @@ mod test { #[test] fn test_process_command_commit_unexpected_hash() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, @@ -405,7 +407,7 @@ mod test { fn test_helper_process_command_commit_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let mut client_pin = ClientPin::::new_test( &mut env, diff --git a/libraries/opensk/src/ctap/mod.rs b/libraries/opensk/src/ctap/mod.rs index a10f78f3..a7eb68da 100644 --- a/libraries/opensk/src/ctap/mod.rs +++ b/libraries/opensk/src/ctap/mod.rs @@ -65,11 +65,12 @@ use self::u2f_up::U2fUserPresenceState; use crate::api::attestation_store::{self, Attestation, AttestationStore}; use crate::api::clock::Clock; use crate::api::connection::{HidConnection, SendOrRecvStatus, UsbEndpoint}; +use crate::api::crypto::ecdsa::{SecretKey as _, Signature}; use crate::api::customization::Customization; use crate::api::firmware_protection::FirmwareProtection; use crate::api::upgrade_storage::UpgradeStorage; use crate::api::user_presence::{UserPresence, UserPresenceError}; -use crate::env::Env; +use crate::env::{EcdsaSk, Env}; use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec; @@ -78,7 +79,7 @@ use byteorder::{BigEndian, ByteOrder}; use core::convert::TryFrom; use crypto::hmac::hmac_256; use crypto::sha256::Sha256; -use crypto::{ecdsa, Hash256}; +use crypto::Hash256; use rng256::Rng256; use sk_cbor as cbor; use sk_cbor::cbor_map_options; @@ -921,11 +922,9 @@ impl CtapState { .attestation_store() .get(&id)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - let attestation_key = ecdsa::SecKey::from_bytes(&private_key).unwrap(); + let attestation_key = EcdsaSk::::from_slice(&private_key).unwrap(); ( - attestation_key - .sign_rfc6979::(&signature_data) - .to_asn1_der(), + attestation_key.sign(&signature_data).to_der(), Some(vec![certificate]), ) } @@ -1451,9 +1450,11 @@ mod test { }; use super::pin_protocol::{authenticate_pin_uv_auth_token, PinProtocol}; use super::*; + use crate::api::crypto::ecdh::SecretKey as _; use crate::api::customization; use crate::api::user_presence::UserPresenceResult; use crate::env::test::TestEnv; + use crate::env::EcdhSk; use crate::test_helpers; use cbor::{cbor_array, cbor_array_vec, cbor_map}; @@ -2033,7 +2034,7 @@ mod test { pin_uv_auth_protocol: PinUvAuthProtocol, ) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH]; let client_pin = ClientPin::::new_test( &mut env, @@ -2395,20 +2396,20 @@ mod test { } fn get_assertion_hmac_secret_params( - key_agreement_key: crypto::ecdh::SecKey, + key_agreement_key: EcdhSk, key_agreement_response: ResponseData, credential_id: Option>, pin_uv_auth_protocol: PinUvAuthProtocol, ) -> AuthenticatorGetAssertionParameters { let mut env = TestEnv::default(); - let platform_public_key = key_agreement_key.genpk(); + let platform_public_key = key_agreement_key.public_key(); let public_key = match key_agreement_response { ResponseData::AuthenticatorClientPin(Some(client_pin_response)) => { client_pin_response.key_agreement.unwrap() } _ => panic!("Invalid response type"), }; - let pin_protocol = PinProtocol::new_test(key_agreement_key, [0x91; 32]); + let pin_protocol = PinProtocol::::new_test(key_agreement_key, [0x91; 32]); let shared_secret = pin_protocol .decapsulate(public_key, pin_uv_auth_protocol) .unwrap(); @@ -2417,7 +2418,7 @@ mod test { let salt_enc = shared_secret.as_ref().encrypt(env.rng(), &salt).unwrap(); let salt_auth = shared_secret.authenticate(&salt_enc); let hmac_secret_input = GetAssertionHmacSecretInput { - key_agreement: CoseKey::from(platform_public_key), + key_agreement: CoseKey::from_ecdh_public_key(platform_public_key), salt_enc, salt_auth, pin_uv_auth_protocol, @@ -2449,7 +2450,7 @@ mod test { fn test_helper_process_get_assertion_hmac_secret(pin_uv_auth_protocol: PinUvAuthProtocol) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let mut ctap_state = CtapState::::new(&mut env); let make_extensions = MakeCredentialExtensions { @@ -2505,7 +2506,7 @@ mod test { pin_uv_auth_protocol: PinUvAuthProtocol, ) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let mut ctap_state = CtapState::::new(&mut env); let make_extensions = MakeCredentialExtensions { @@ -2900,7 +2901,7 @@ mod test { pin_uv_auth_protocol: PinUvAuthProtocol, ) { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x88; 32]; let client_pin = ClientPin::::new_test( &mut env, @@ -3504,7 +3505,7 @@ mod test { #[test] fn test_credential_management_timeout() { let mut env = TestEnv::default(); - let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng()); + let key_agreement_key = EcdhSk::::random(env.rng()); let pin_uv_auth_token = [0x55; 32]; let client_pin = ClientPin::::new_test( &mut env, diff --git a/libraries/opensk/src/ctap/pin_protocol.rs b/libraries/opensk/src/ctap/pin_protocol.rs index d8e992d6..12c2e1ce 100644 --- a/libraries/opensk/src/ctap/pin_protocol.rs +++ b/libraries/opensk/src/ctap/pin_protocol.rs @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::api::crypto::ecdh::{ + PublicKey as EcdhPublicKey, SecretKey as EcdhSecretKey, SharedSecret as EcdhSharedSecret, +}; use crate::ctap::client_pin::PIN_TOKEN_LENGTH; use crate::ctap::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt}; use crate::ctap::data_formats::{CoseKey, PinUvAuthProtocol}; use crate::ctap::status_code::Ctap2StatusCode; +use crate::env::{EcdhPk, EcdhSk, Env}; use alloc::boxed::Box; use alloc::vec::Vec; -use core::convert::TryInto; use crypto::hkdf::hkdf_empty_salt_256; #[cfg(test)] use crypto::hmac::hmac_256; @@ -28,17 +31,17 @@ use crypto::Hash256; use rng256::Rng256; /// Implements common functions between existing PIN protocols for handshakes. -pub struct PinProtocol { - key_agreement_key: crypto::ecdh::SecKey, +pub struct PinProtocol { + key_agreement_key: EcdhSk, pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], } -impl PinProtocol { +impl PinProtocol { /// This process is run by the authenticator at power-on. /// /// This function implements "initialize" from the specification. - pub fn new(rng: &mut impl Rng256) -> PinProtocol { - let key_agreement_key = crypto::ecdh::SecKey::gensk(rng); + pub fn new(rng: &mut impl Rng256) -> Self { + let key_agreement_key = EcdhSk::::random(rng); let pin_uv_auth_token = rng.gen_uniform_u8x32(); PinProtocol { key_agreement_key, @@ -48,7 +51,7 @@ impl PinProtocol { /// Generates a fresh public key. pub fn regenerate(&mut self, rng: &mut impl Rng256) { - self.key_agreement_key = crypto::ecdh::SecKey::gensk(rng); + self.key_agreement_key = EcdhSk::::random(rng); } /// Generates a fresh pinUvAuthToken. @@ -58,7 +61,7 @@ impl PinProtocol { /// Returns the authenticator’s public key as a CoseKey structure. pub fn get_public_key(&self) -> CoseKey { - CoseKey::from(self.key_agreement_key.genpk()) + CoseKey::from_ecdh_public_key(self.key_agreement_key.public_key()) } /// Processes the peer's encapsulated CoseKey and returns the shared secret. @@ -67,8 +70,13 @@ impl PinProtocol { peer_cose_key: CoseKey, pin_uv_auth_protocol: PinUvAuthProtocol, ) -> Result, Ctap2StatusCode> { - let pk: crypto::ecdh::PubKey = CoseKey::try_into(peer_cose_key)?; - let handshake = self.key_agreement_key.exchange_x(&pk); + let (x_bytes, y_bytes) = peer_cose_key.try_into_ecdh_coordinates()?; + let pk = EcdhPk::::from_coordinates(&x_bytes, &y_bytes) + .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; + let handshake = self + .key_agreement_key + .diffie_hellman(&pk) + .raw_secret_bytes(); match pin_uv_auth_protocol { PinUvAuthProtocol::V1 => Ok(Box::new(SharedSecretV1::new(handshake))), PinUvAuthProtocol::V2 => Ok(Box::new(SharedSecretV2::new(handshake))), @@ -83,9 +91,9 @@ impl PinProtocol { /// This is used for debugging to inject key material. #[cfg(test)] pub fn new_test( - key_agreement_key: crypto::ecdh::SecKey, + key_agreement_key: EcdhSk, pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], - ) -> PinProtocol { + ) -> Self { PinProtocol { key_agreement_key, pin_uv_auth_token, @@ -235,7 +243,7 @@ mod test { #[test] fn test_pin_protocol_public_key() { let mut env = TestEnv::default(); - let mut pin_protocol = PinProtocol::new(env.rng()); + let mut pin_protocol = PinProtocol::::new(env.rng()); let public_key = pin_protocol.get_public_key(); pin_protocol.regenerate(env.rng()); let new_public_key = pin_protocol.get_public_key(); @@ -245,7 +253,7 @@ mod test { #[test] fn test_pin_protocol_pin_uv_auth_token() { let mut env = TestEnv::default(); - let mut pin_protocol = PinProtocol::new(env.rng()); + let mut pin_protocol = PinProtocol::::new(env.rng()); let token = *pin_protocol.get_pin_uv_auth_token(); pin_protocol.reset_pin_uv_auth_token(env.rng()); let new_token = pin_protocol.get_pin_uv_auth_token(); @@ -328,8 +336,8 @@ mod test { #[test] fn test_decapsulate_symmetric() { let mut env = TestEnv::default(); - let pin_protocol1 = PinProtocol::new(env.rng()); - let pin_protocol2 = PinProtocol::new(env.rng()); + let pin_protocol1 = PinProtocol::::new(env.rng()); + let pin_protocol2 = PinProtocol::::new(env.rng()); for &protocol in &[PinUvAuthProtocol::V1, PinUvAuthProtocol::V2] { let shared_secret1 = pin_protocol1 .decapsulate(pin_protocol2.get_public_key(), protocol) diff --git a/libraries/opensk/src/ctap/response.rs b/libraries/opensk/src/ctap/response.rs index 96a85d79..865b374c 100644 --- a/libraries/opensk/src/ctap/response.rs +++ b/libraries/opensk/src/ctap/response.rs @@ -343,7 +343,6 @@ mod test { use super::super::data_formats::{PackedAttestationStatement, PublicKeyCredentialType}; use super::super::ES256_CRED_PARAM; use super::*; - use crate::env::test::TestEnv; use cbor::{cbor_array, cbor_bytes, cbor_map}; #[test] @@ -506,10 +505,7 @@ mod test { #[test] fn test_used_client_pin_into_cbor() { - let mut env = TestEnv::default(); - let sk = crypto::ecdh::SecKey::gensk(env.rng()); - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk); + let cose_key = CoseKey::example_ecdh_pubkey(); let client_pin_response = AuthenticatorClientPinResponse { key_agreement: Some(cose_key.clone()), pin_uv_auth_token: Some(vec![70]), @@ -550,8 +546,8 @@ mod test { #[test] fn test_used_credential_management_optionals_into_cbor() { - let mut env = TestEnv::default(); - let sk = crypto::ecdh::SecKey::gensk(env.rng()); + let cose_key = CoseKey::example_ecdh_pubkey(); + let rp = PublicKeyCredentialRpEntity { rp_id: String::from("example.com"), rp_name: None, @@ -568,8 +564,6 @@ mod test { key_id: vec![0x1D; 32], transports: None, }; - let pk = sk.genpk(); - let cose_key = CoseKey::from(pk); let cred_management_response = AuthenticatorCredentialManagementResponse { existing_resident_credentials_count: Some(100), diff --git a/libraries/opensk/src/env/mod.rs b/libraries/opensk/src/env/mod.rs index a9e69c18..f1836bfc 100644 --- a/libraries/opensk/src/env/mod.rs +++ b/libraries/opensk/src/env/mod.rs @@ -15,6 +15,9 @@ use crate::api::attestation_store::AttestationStore; use crate::api::clock::Clock; use crate::api::connection::HidConnection; +use crate::api::crypto::ecdh::Ecdh; +use crate::api::crypto::ecdsa::Ecdsa; +use crate::api::crypto::Crypto; use crate::api::customization::Customization; use crate::api::firmware_protection::FirmwareProtection; use crate::api::key_store::KeyStore; @@ -26,6 +29,11 @@ use rng256::Rng256; #[cfg(feature = "std")] pub mod test; +pub type EcdhSk = <<::Crypto as Crypto>::Ecdh as Ecdh>::SecretKey; +pub type EcdhPk = <<::Crypto as Crypto>::Ecdh as Ecdh>::PublicKey; +pub type EcdsaSk = <<::Crypto as Crypto>::Ecdsa as Ecdsa>::SecretKey; +pub type EcdsaPk = <<::Crypto as Crypto>::Ecdsa as Ecdsa>::PublicKey; + /// Describes what CTAP needs to function. pub trait Env { type Rng: Rng256; @@ -39,6 +47,7 @@ pub trait Env { type HidConnection: HidConnection; type AttestationStore: AttestationStore; type Clock: Clock; + type Crypto: Crypto; fn rng(&mut self) -> &mut Self::Rng; fn user_presence(&mut self) -> &mut Self::UserPresence; diff --git a/libraries/opensk/src/env/test/mod.rs b/libraries/opensk/src/env/test/mod.rs index 70795c29..208971aa 100644 --- a/libraries/opensk/src/env/test/mod.rs +++ b/libraries/opensk/src/env/test/mod.rs @@ -16,6 +16,7 @@ use self::upgrade_storage::BufferUpgradeStorage; use crate::api::attestation_store::AttestationStore; use crate::api::clock::Clock; use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus}; +use crate::api::crypto::software_crypto::SoftwareCrypto; use crate::api::customization::DEFAULT_CUSTOMIZATION; use crate::api::firmware_protection::FirmwareProtection; use crate::api::user_presence::{UserPresence, UserPresenceResult}; @@ -217,6 +218,7 @@ impl Env for TestEnv { type Write = TestWrite; type Customization = TestCustomization; type HidConnection = Self; + type Crypto = SoftwareCrypto; fn rng(&mut self) -> &mut Self::Rng { &mut self.rng diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index 334277ec..90b0dafb 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -137,6 +137,8 @@ then cargo +nightly test --release --features std echo "Running CTAP library unit tests (release mode + all features)..." cargo +nightly test --release --features std,debug_ctap,with_ctap1,vendor_hid,ed25519 + echo "Running CTAP library unit tests (release mode + experimental rust crypto)..." + cargo +nightly test --release --features std,debug_ctap,with_ctap1,vendor_hid,ed25519,rust_crypto echo "Running CTAP library unit tests (debug mode)..." cargo +nightly test --features std diff --git a/src/env/tock/mod.rs b/src/env/tock/mod.rs index e6f4bc82..afa8adff 100644 --- a/src/env/tock/mod.rs +++ b/src/env/tock/mod.rs @@ -27,6 +27,7 @@ use opensk::api::attestation_store::AttestationStore; use opensk::api::connection::{ HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus, UsbEndpoint, }; +use opensk::api::crypto::software_crypto::SoftwareCrypto; use opensk::api::customization::{CustomizationImpl, AAGUID_LENGTH, DEFAULT_CUSTOMIZATION}; use opensk::api::firmware_protection::FirmwareProtection; use opensk::api::user_presence::{UserPresence, UserPresenceError, UserPresenceResult}; @@ -249,6 +250,7 @@ impl Env for TockEnv { type Write = Console; type Customization = CustomizationImpl; type HidConnection = TockHidConnection; + type Crypto = SoftwareCrypto; fn rng(&mut self) -> &mut Self::Rng { &mut self.rng