Add support for ed25519 keys (#335)

This commit is contained in:
Egor Duda
2022-05-12 22:56:33 +03:00
parent f95ae1f5ab
commit 983bc5c5b2
6 changed files with 325 additions and 47 deletions

View File

@@ -25,6 +25,7 @@ serde_json = { version = "=1.0.69", default-features = false, features = ["alloc
embedded-time = "0.12.1" embedded-time = "0.12.1"
arbitrary = { version = "0.4.7", features = ["derive"], optional = true } arbitrary = { version = "0.4.7", features = ["derive"], optional = true }
rand = { version = "0.8.4", optional = true } rand = { version = "0.8.4", optional = true }
ed25519-dalek = { version = "1", default-features = false, features = ["u32_backend", "rand"], optional = true }
[features] [features]
debug_allocations = ["lang_items/debug_allocations"] debug_allocations = ["lang_items/debug_allocations"]
@@ -36,6 +37,7 @@ with_ctap1 = ["crypto/with_ctap1"]
with_nfc = ["libtock_drivers/with_nfc"] with_nfc = ["libtock_drivers/with_nfc"]
vendor_hid = ["libtock_drivers/vendor_hid"] vendor_hid = ["libtock_drivers/vendor_hid"]
fuzz = ["arbitrary", "std"] fuzz = ["arbitrary", "std"]
with_ed25519 = ["ed25519-dalek"]
[dev-dependencies] [dev-dependencies]
enum-iterator = "0.6.0" enum-iterator = "0.6.0"

View File

@@ -442,8 +442,6 @@ class OpenSKInstaller:
f"link-arg=-T{props.app_ldscript}", f"link-arg=-T{props.app_ldscript}",
"-C", "-C",
"relocation-model=static", "relocation-model=static",
"-D",
"warnings",
f"--remap-path-prefix={os.getcwd()}=", f"--remap-path-prefix={os.getcwd()}=",
"-C", "-C",
"link-arg=-icf=all", "link-arg=-icf=all",
@@ -1091,6 +1089,14 @@ if __name__ == "__main__":
help=("When set, the output of elf2tab is appended to this file."), help=("When set, the output of elf2tab is appended to this file."),
) )
main_parser.add_argument(
"--with_ed25519",
action="append_const",
const="with_ed25519",
dest="features",
help=("Enable Ed25519 support"),
)
main_parser.set_defaults(features=["with_ctap1"]) main_parser.set_defaults(features=["with_ctap1"])
# Start parsing to know if we're going to list things or not. # Start parsing to know if we're going to list things or not.

View File

@@ -14,7 +14,7 @@
use crate::ctap::data_formats::{ use crate::ctap::data_formats::{
extract_array, extract_byte_string, CoseKey, PublicKeyCredentialSource, extract_array, extract_byte_string, CoseKey, PublicKeyCredentialSource,
PublicKeyCredentialType, SignatureAlgorithm, PublicKeyCredentialType, SignatureAlgorithm, ES256_ALGORITHM, EDDSA_ALGORITHM,
}; };
use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::storage; use crate::ctap::storage;
@@ -30,6 +30,8 @@ use crypto::sha256::Sha256;
use rng256::Rng256; use rng256::Rng256;
use sk_cbor as cbor; use sk_cbor as cbor;
use sk_cbor::{cbor_array, cbor_bytes, cbor_int}; use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
#[cfg(feature = "with_ed25519")]
use ed25519_dalek::Signer;
// Legacy credential IDs consist of // Legacy credential IDs consist of
// - 16 bytes: initialization vector for AES-256, // - 16 bytes: initialization vector for AES-256,
@@ -42,6 +44,16 @@ pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113;
// See encrypt_key_handle v1 documentation. // See encrypt_key_handle v1 documentation.
pub const MAX_CREDENTIAL_ID_SIZE: usize = 113; pub const MAX_CREDENTIAL_ID_SIZE: usize = 113;
const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01;
#[cfg(feature = "with_ed25519")]
const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02;
#[cfg(test)]
#[cfg(feature = "with_ed25519")]
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x03;
#[cfg(test)]
#[cfg(not(feature = "with_ed25519"))]
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x02;
/// Wraps the AES256-CBC encryption to match what we need in CTAP. /// Wraps the AES256-CBC encryption to match what we need in CTAP.
pub fn aes256_cbc_encrypt( pub fn aes256_cbc_encrypt(
rng: &mut dyn Rng256, rng: &mut dyn Rng256,
@@ -89,11 +101,37 @@ pub fn aes256_cbc_decrypt(
} }
/// An asymmetric private key that can sign messages. /// An asymmetric private key that can sign messages.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Debug)]
pub enum PrivateKey { pub enum PrivateKey {
Ecdsa(ecdsa::SecKey), Ecdsa(ecdsa::SecKey),
#[cfg(feature = "with_ed25519")]
Ed25519(ed25519_dalek::Keypair),
} }
impl Clone for PrivateKey {
fn clone(&self) -> Self {
match self {
Self::Ecdsa(sk) => Self::Ecdsa (sk.clone ()),
#[cfg(feature = "with_ed25519")]
Self::Ed25519(keypair) => Self::Ed25519 (ed25519_dalek::Keypair::from_bytes (&keypair.to_bytes()).unwrap()),
}
}
}
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&Self::Ecdsa(ref a), &Self::Ecdsa(ref b)) => a == b,
#[cfg(feature = "with_ed25519")]
(&Self::Ed25519(ref a), &Self::Ed25519(ref b)) => a.to_bytes() == b.to_bytes(),
#[cfg(feature = "with_ed25519")]
_ => false,
}
}
}
impl Eq for PrivateKey {}
impl PrivateKey { impl PrivateKey {
/// Creates a new private key for the given algorithm. /// Creates a new private key for the given algorithm.
/// ///
@@ -103,6 +141,11 @@ impl PrivateKey {
pub fn new(rng: &mut impl Rng256, alg: SignatureAlgorithm) -> Self { pub fn new(rng: &mut impl Rng256, alg: SignatureAlgorithm) -> Self {
match alg { match alg {
SignatureAlgorithm::ES256 => PrivateKey::Ecdsa(crypto::ecdsa::SecKey::gensk(rng)), SignatureAlgorithm::ES256 => PrivateKey::Ecdsa(crypto::ecdsa::SecKey::gensk(rng)),
#[cfg(feature = "with_ed25519")]
SignatureAlgorithm::EDDSA => {
let bytes = rng.gen_uniform_u8x32();
Self::new_ed25519_from_bytes(&bytes).unwrap()
},
SignatureAlgorithm::Unknown => unreachable!(), SignatureAlgorithm::Unknown => unreachable!(),
} }
} }
@@ -117,10 +160,25 @@ impl PrivateKey {
ecdsa::SecKey::from_bytes(array_ref!(bytes, 0, 32)).map(PrivateKey::from) ecdsa::SecKey::from_bytes(array_ref!(bytes, 0, 32)).map(PrivateKey::from)
} }
#[cfg(feature = "with_ed25519")]
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 32 {
return None;
}
if let Ok(secret) = ed25519_dalek::SecretKey::from_bytes(bytes) {
let public = ed25519_dalek::PublicKey::from (&secret);
Some(Self::Ed25519(ed25519_dalek::Keypair{secret, public}))
} else {
None
}
}
/// Returns the corresponding public key. /// Returns the corresponding public key.
pub fn get_pub_key(&self) -> CoseKey { pub fn get_pub_key(&self) -> CoseKey {
match self { match self {
PrivateKey::Ecdsa(ecdsa_key) => CoseKey::from(ecdsa_key.genpk()), PrivateKey::Ecdsa(ecdsa_key) => CoseKey::from(ecdsa_key.genpk()),
#[cfg(feature = "with_ed25519")]
PrivateKey::Ed25519(ed25519_keypair) => CoseKey::from(ed25519_keypair.public),
} }
} }
@@ -128,6 +186,8 @@ impl PrivateKey {
pub fn sign_and_encode(&self, message: &[u8]) -> Vec<u8> { pub fn sign_and_encode(&self, message: &[u8]) -> Vec<u8> {
match self { match self {
PrivateKey::Ecdsa(ecdsa_key) => ecdsa_key.sign_rfc6979::<Sha256>(message).to_asn1_der(), PrivateKey::Ecdsa(ecdsa_key) => ecdsa_key.sign_rfc6979::<Sha256>(message).to_asn1_der(),
#[cfg(feature = "with_ed25519")]
PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.try_sign(message).unwrap().to_bytes().to_vec(),
} }
} }
@@ -135,6 +195,8 @@ impl PrivateKey {
pub fn signature_algorithm(&self) -> SignatureAlgorithm { pub fn signature_algorithm(&self) -> SignatureAlgorithm {
match self { match self {
PrivateKey::Ecdsa(_) => SignatureAlgorithm::ES256, PrivateKey::Ecdsa(_) => SignatureAlgorithm::ES256,
#[cfg(feature = "with_ed25519")]
PrivateKey::Ed25519(_) => SignatureAlgorithm::EDDSA,
} }
} }
@@ -146,6 +208,8 @@ impl PrivateKey {
ecdsa_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); ecdsa_key.to_bytes(array_mut_ref!(key_bytes, 0, 32));
key_bytes key_bytes
} }
#[cfg(feature = "with_ed25519")]
PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.secret.to_bytes().to_vec(),
} }
} }
} }
@@ -171,6 +235,9 @@ impl TryFrom<cbor::Value> for PrivateKey {
match SignatureAlgorithm::try_from(array.pop().unwrap())? { match SignatureAlgorithm::try_from(array.pop().unwrap())? {
SignatureAlgorithm::ES256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes) SignatureAlgorithm::ES256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
#[cfg(feature = "with_ed25519")]
SignatureAlgorithm::EDDSA => PrivateKey::new_ed25519_from_bytes(&key_bytes)
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
} }
} }
@@ -190,12 +257,19 @@ impl From<ecdsa::SecKey> for PrivateKey {
/// Also, by limiting ourselves to private key and RP ID hash, we are compatible with U2F for /// Also, by limiting ourselves to private key and RP ID hash, we are compatible with U2F for
/// ECDSA private keys. /// ECDSA private keys.
/// ///
/// This is v1, and we write the following data for ECDSA (algorithm -7): /// For v1 we write the following data for ECDSA (algorithm -7):
/// - 1 byte : version number /// - 1 byte : version number
/// - 16 bytes: initialization vector for AES-256, /// - 16 bytes: initialization vector for AES-256,
/// - 32 bytes: ECDSA private key for the credential, /// - 32 bytes: ECDSA private key for the credential,
/// - 32 bytes: relying party ID hashed with SHA256, /// - 32 bytes: relying party ID hashed with SHA256,
/// - 32 bytes: HMAC-SHA256 over everything else. /// - 32 bytes: HMAC-SHA256 over everything else.
///
/// For v2 we write the following data for EdDSA over curve Ed25519 (algorithm -8, curve 6):
/// - 1 byte : version number
/// - 16 bytes: initialization vector for AES-256,
/// - 32 bytes: Ed25519 private key for the credential,
/// - 32 bytes: relying party ID hashed with SHA256,
/// - 32 bytes: HMAC-SHA256 over everything else.
pub fn encrypt_key_handle( pub fn encrypt_key_handle(
env: &mut impl Env, env: &mut impl Env,
private_key: &PrivateKey, private_key: &PrivateKey,
@@ -204,17 +278,22 @@ pub fn encrypt_key_handle(
let master_keys = storage::master_keys(env)?; let master_keys = storage::master_keys(env)?;
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
let mut encrypted_id = match private_key {
PrivateKey::Ecdsa(ecdsa_key) => {
let mut plaintext = [0; 64]; let mut plaintext = [0; 64];
let version;
match private_key {
PrivateKey::Ecdsa(ecdsa_key) => {
ecdsa_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); ecdsa_key.to_bytes(array_mut_ref!(plaintext, 0, 32));
plaintext[32..64].copy_from_slice(application); version = ECDSA_CREDENTIAL_ID_VERSION;
let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; }
// Version number #[cfg(feature = "with_ed25519")]
encrypted_id.insert(0, 0x01); PrivateKey::Ed25519(keypair) => {
encrypted_id plaintext[0..32].copy_from_slice(&keypair.secret.to_bytes());
version = ED25519_CREDENTIAL_ID_VERSION;
} }
}; };
plaintext[32..64].copy_from_slice(application);
let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?;
encrypted_id.insert(0, version);
let id_hmac = hmac_256::<Sha256>(&master_keys.hmac, &encrypted_id[..]); let id_hmac = hmac_256::<Sha256>(&master_keys.hmac, &encrypted_id[..]);
encrypted_id.extend(&id_hmac); encrypted_id.extend(&id_hmac);
@@ -231,6 +310,7 @@ pub fn encrypt_key_handle(
/// This functions reads: /// This functions reads:
/// - legacy credentials (no version number), /// - legacy credentials (no version number),
/// - v1 (ECDSA) /// - v1 (ECDSA)
/// - v2 (EdDSA over curve Ed25519)
pub fn decrypt_credential_source( pub fn decrypt_credential_source(
env: &mut impl Env, env: &mut impl Env,
credential_id: Vec<u8>, credential_id: Vec<u8>,
@@ -249,12 +329,17 @@ pub fn decrypt_credential_source(
return Ok(None); return Ok(None);
} }
let algorithm;
let payload = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE { let payload = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE {
algorithm = ES256_ALGORITHM;
&credential_id[..hmac_message_size] &credential_id[..hmac_message_size]
} else { } else {
// Version number check // Version number check
if credential_id[0] != 1 { match credential_id[0] {
return Ok(None); ECDSA_CREDENTIAL_ID_VERSION => algorithm = ES256_ALGORITHM,
#[cfg(feature = "with_ed25519")]
ED25519_CREDENTIAL_ID_VERSION => algorithm = EDDSA_ALGORITHM,
_ => return Ok(None),
} }
&credential_id[1..hmac_message_size] &credential_id[1..hmac_message_size]
}; };
@@ -269,7 +354,15 @@ pub fn decrypt_credential_source(
if rp_id_hash != &decrypted_id[32..] { if rp_id_hash != &decrypted_id[32..] {
return Ok(None); return Ok(None);
} }
let sk_option = PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]); let sk_option;
match algorithm {
ES256_ALGORITHM => sk_option = PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]),
#[cfg(feature = "with_ed25519")]
EDDSA_ALGORITHM => sk_option = PrivateKey::new_ed25519_from_bytes(&decrypted_id[..32]),
#[cfg(not(feature = "with_ed25519"))]
EDDSA_ALGORITHM => return Ok(None),
_ => return Ok(None),
}
Ok(sk_option.map(|sk| PublicKeyCredentialSource { Ok(sk_option.map(|sk| PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey, key_type: PublicKeyCredentialType::PublicKey,
@@ -368,6 +461,18 @@ mod test {
); );
} }
#[test]
#[cfg(feature = "with_ed25519")]
fn test_new_ed25519_from_bytes() {
let mut env = TestEnv::new();
let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::EDDSA);
let key_bytes = private_key.to_bytes();
assert_eq!(
PrivateKey::new_ed25519_from_bytes(&key_bytes),
Some(private_key)
);
}
#[test] #[test]
fn test_new_ecdsa_from_bytes_wrong_length() { fn test_new_ecdsa_from_bytes_wrong_length() {
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None); assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None);
@@ -376,6 +481,15 @@ mod test {
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None); assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None);
} }
#[test]
#[cfg(feature = "with_ed25519")]
fn test_new_ed25519_from_bytes_wrong_length() {
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None);
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None);
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None);
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None);
}
#[test] #[test]
fn test_private_key_get_pub_key() { fn test_private_key_get_pub_key() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
@@ -395,22 +509,41 @@ mod test {
assert_eq!(private_key.sign_and_encode(&message), signature); assert_eq!(private_key.sign_and_encode(&message), signature);
} }
#[test] fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) {
fn test_private_key_signature_algorithm() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
let algorithm = SignatureAlgorithm::ES256; let private_key = PrivateKey::new(env.rng(), signature_algorithm);
let private_key = PrivateKey::new(env.rng(), algorithm); assert_eq!(private_key.signature_algorithm(), signature_algorithm);
assert_eq!(private_key.signature_algorithm(), algorithm);
} }
#[test] #[test]
fn test_private_key_from_to_cbor() { fn test_ecdsa_private_key_signature_algorithm() {
test_private_key_signature_algorithm(SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_ed25519_private_key_signature_algorithm() {
test_private_key_signature_algorithm(SignatureAlgorithm::EDDSA);
}
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let private_key = PrivateKey::new(env.rng(), signature_algorithm);
let cbor = cbor::Value::from(private_key.clone()); let cbor = cbor::Value::from(private_key.clone());
assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),); assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),);
} }
#[test]
fn test_ecdsa_private_key_from_to_cbor() {
test_private_key_from_to_cbor(SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_ed25519_private_key_from_to_cbor() {
test_private_key_from_to_cbor(SignatureAlgorithm::EDDSA);
}
#[test] #[test]
fn test_private_key_from_bad_cbor() { fn test_private_key_from_bad_cbor() {
let cbor = cbor_array![ let cbor = cbor_array![
@@ -424,6 +557,20 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
); );
#[cfg(feature = "with_ed25519")]
{
let cbor = cbor_array![
cbor_int!(SignatureAlgorithm::EDDSA as i64),
cbor_bytes!(vec![0x88; 32]),
// The array is too long.
cbor_int!(0),
];
assert_eq!(
PrivateKey::try_from(cbor),
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
);
}
let cbor = cbor_array![ let cbor = cbor_array![
// This algorithms doesn't exist. // This algorithms doesn't exist.
cbor_int!(-1), cbor_int!(-1),
@@ -435,11 +582,10 @@ mod test {
); );
} }
#[test] fn test_encrypt_decrypt_credential(signature_algorithm: SignatureAlgorithm) {
fn test_encrypt_decrypt_credential() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
storage::init(&mut env).ok().unwrap(); storage::init(&mut env).ok().unwrap();
let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let private_key = PrivateKey::new(env.rng(), signature_algorithm);
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
@@ -450,6 +596,17 @@ mod test {
assert_eq!(private_key, decrypted_source.private_key); assert_eq!(private_key, decrypted_source.private_key);
} }
#[test]
fn test_encrypt_decrypt_ecdsa_credential() {
test_encrypt_decrypt_credential(SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_encrypt_decrypt_ed25519_credential() {
test_encrypt_decrypt_credential(SignatureAlgorithm::EDDSA);
}
#[test] #[test]
fn test_encrypt_decrypt_bad_version() { fn test_encrypt_decrypt_bad_version() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
@@ -459,7 +616,7 @@ mod test {
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let mut encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); let mut encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
// Version 2 does not exist yet. // Version 2 does not exist yet.
encrypted_id[0] = 0x02; encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
// Override the HMAC to pass the check. // Override the HMAC to pass the check.
encrypted_id.truncate(&encrypted_id.len() - 32); encrypted_id.truncate(&encrypted_id.len() - 32);
let master_keys = storage::master_keys(&mut env).unwrap(); let master_keys = storage::master_keys(&mut env).unwrap();
@@ -472,11 +629,10 @@ mod test {
); );
} }
#[test] fn test_encrypt_decrypt_bad_hmac(signature_algorithm: SignatureAlgorithm) {
fn test_encrypt_decrypt_bad_hmac() {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
storage::init(&mut env).ok().unwrap(); storage::init(&mut env).ok().unwrap();
let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let private_key = PrivateKey::new(env.rng(), signature_algorithm);
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
@@ -491,10 +647,20 @@ mod test {
} }
#[test] #[test]
fn test_decrypt_credential_missing_blocks() { fn test_ecdsa_encrypt_decrypt_bad_hmac() {
test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_ed25519_encrypt_decrypt_bad_hmac() {
test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::EDDSA);
}
fn test_decrypt_credential_missing_blocks(signature_algorithm: SignatureAlgorithm) {
let mut env = TestEnv::new(); let mut env = TestEnv::new();
storage::init(&mut env).ok().unwrap(); storage::init(&mut env).ok().unwrap();
let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let private_key = PrivateKey::new(env.rng(), signature_algorithm);
let rp_id_hash = [0x55; 32]; let rp_id_hash = [0x55; 32];
let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
@@ -507,6 +673,17 @@ mod test {
} }
} }
#[test]
fn test_ecdsa_decrypt_credential_missing_blocks() {
test_decrypt_credential_missing_blocks(SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_ed25519_decrypt_credential_missing_blocks() {
test_decrypt_credential_missing_blocks(SignatureAlgorithm::EDDSA);
}
/// This is a copy of the function that genereated deprecated key handles. /// This is a copy of the function that genereated deprecated key handles.
fn legacy_encrypt_key_handle( fn legacy_encrypt_key_handle(
env: &mut impl Env, env: &mut impl Env,

View File

@@ -27,7 +27,8 @@ use sk_cbor as cbor;
use sk_cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map}; use sk_cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map};
// Used as the identifier for ECDSA in assertion signatures and COSE. // Used as the identifier for ECDSA in assertion signatures and COSE.
const ES256_ALGORITHM: i64 = -7; pub const ES256_ALGORITHM: i64 = -7;
pub const EDDSA_ALGORITHM: i64 = -8;
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@@ -503,6 +504,8 @@ impl From<PackedAttestationStatement> for cbor::Value {
#[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum SignatureAlgorithm { pub enum SignatureAlgorithm {
ES256 = ES256_ALGORITHM as isize, ES256 = ES256_ALGORITHM as isize,
#[cfg(feature = "with_ed25519")]
EDDSA = EDDSA_ALGORITHM as isize,
// This is the default for all numbers not covered above. // This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors. // Unknown types should be ignored, instead of returning errors.
Unknown = 0, Unknown = 0,
@@ -518,6 +521,8 @@ impl From<i64> for SignatureAlgorithm {
fn from(int: i64) -> Self { fn from(int: i64) -> Self {
match int { match int {
ES256_ALGORITHM => SignatureAlgorithm::ES256, ES256_ALGORITHM => SignatureAlgorithm::ES256,
#[cfg(feature = "with_ed25519")]
EDDSA_ALGORITHM => SignatureAlgorithm::EDDSA,
_ => SignatureAlgorithm::Unknown, _ => SignatureAlgorithm::Unknown,
} }
} }
@@ -720,6 +725,8 @@ pub struct CoseKey {
x_bytes: [u8; ecdh::NBYTES], x_bytes: [u8; ecdh::NBYTES],
y_bytes: [u8; ecdh::NBYTES], y_bytes: [u8; ecdh::NBYTES],
algorithm: i64, algorithm: i64,
key_type: i64,
curve: i64,
} }
impl CoseKey { impl CoseKey {
@@ -729,8 +736,12 @@ impl CoseKey {
const ECDH_ALGORITHM: i64 = -25; const ECDH_ALGORITHM: i64 = -25;
// The parameter behind map key 1. // The parameter behind map key 1.
const EC2_KEY_TYPE: i64 = 2; const EC2_KEY_TYPE: i64 = 2;
#[cfg(feature = "with_ed25519")]
const OKP_KEY_TYPE: i64 = 1;
// The parameter behind map key -1. // The parameter behind map key -1.
const P_256_CURVE: i64 = 1; const P_256_CURVE: i64 = 1;
#[cfg(feature = "with_ed25519")]
const ED25519_CURVE: i64 = 6;
} }
// This conversion accepts both ECDH and ECDSA. // This conversion accepts both ECDH and ECDSA.
@@ -776,6 +787,8 @@ impl TryFrom<cbor::Value> for CoseKey {
x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES], x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES],
y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES], y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES],
algorithm, algorithm,
key_type,
curve,
}) })
} }
} }
@@ -786,12 +799,14 @@ impl From<CoseKey> for cbor::Value {
x_bytes, x_bytes,
y_bytes, y_bytes,
algorithm, algorithm,
key_type,
curve,
} = cose_key; } = cose_key;
cbor_map! { cbor_map! {
1 => CoseKey::EC2_KEY_TYPE, 1 => key_type,
3 => algorithm, 3 => algorithm,
-1 => CoseKey::P_256_CURVE, -1 => curve,
-2 => x_bytes, -2 => x_bytes,
-3 => y_bytes, -3 => y_bytes,
} }
@@ -807,6 +822,8 @@ impl From<ecdh::PubKey> for CoseKey {
x_bytes, x_bytes,
y_bytes, y_bytes,
algorithm: CoseKey::ECDH_ALGORITHM, algorithm: CoseKey::ECDH_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
} }
} }
} }
@@ -820,6 +837,21 @@ impl From<ecdsa::PubKey> for CoseKey {
x_bytes, x_bytes,
y_bytes, y_bytes,
algorithm: ES256_ALGORITHM, algorithm: ES256_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
}
}
}
#[cfg(feature = "with_ed25519")]
impl From<ed25519_dalek::PublicKey> for CoseKey {
fn from(pk: ed25519_dalek::PublicKey) -> Self {
CoseKey {
x_bytes: pk.to_bytes(),
y_bytes: [0u8; 32],
key_type: CoseKey::OKP_KEY_TYPE,
curve: CoseKey::ED25519_CURVE,
algorithm: EDDSA_ALGORITHM,
} }
} }
} }
@@ -832,6 +864,8 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
x_bytes, x_bytes,
y_bytes, y_bytes,
algorithm, algorithm,
key_type,
curve,
} = cose_key; } = cose_key;
// Since algorithm can be used for different COSE key types, we check // Since algorithm can be used for different COSE key types, we check
@@ -841,6 +875,9 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM { if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_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) ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
} }
@@ -854,9 +891,11 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
x_bytes, x_bytes,
y_bytes, y_bytes,
algorithm, algorithm,
key_type,
curve,
} = cose_key; } = cose_key;
if algorithm != ES256_ALGORITHM { if algorithm != ES256_ALGORITHM || key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
} }
ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes) ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes)
@@ -904,7 +943,11 @@ impl TryFrom<CoseSignature> for ecdsa::Signature {
match cose_signature.algorithm { match cose_signature.algorithm {
SignatureAlgorithm::ES256 => ecdsa::Signature::from_bytes(&cose_signature.bytes) SignatureAlgorithm::ES256 => ecdsa::Signature::from_bytes(&cose_signature.bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER), .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), #[cfg(feature = "with_ed25519")]
SignatureAlgorithm::EDDSA =>
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
SignatureAlgorithm::Unknown =>
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
} }
} }
} }
@@ -1564,6 +1607,13 @@ mod test {
let signature_algorithm = SignatureAlgorithm::from(alg_int); let signature_algorithm = SignatureAlgorithm::from(alg_int);
assert_eq!(signature_algorithm, SignatureAlgorithm::ES256); assert_eq!(signature_algorithm, SignatureAlgorithm::ES256);
#[cfg(feature = "with_ed25519")]
{
let alg_int = SignatureAlgorithm::EDDSA as i64;
let signature_algorithm = SignatureAlgorithm::from(alg_int);
assert_eq!(signature_algorithm, SignatureAlgorithm::EDDSA);
}
let unknown_alg_int = -1; let unknown_alg_int = -1;
let unknown_algorithm = SignatureAlgorithm::from(unknown_alg_int); let unknown_algorithm = SignatureAlgorithm::from(unknown_alg_int);
assert_eq!(unknown_algorithm, SignatureAlgorithm::Unknown); assert_eq!(unknown_algorithm, SignatureAlgorithm::Unknown);
@@ -1578,6 +1628,16 @@ mod test {
let created_cbor: cbor::Value = signature_algorithm.unwrap().into(); let created_cbor: cbor::Value = signature_algorithm.unwrap().into();
assert_eq!(created_cbor, cbor_signature_algorithm); assert_eq!(created_cbor, cbor_signature_algorithm);
#[cfg(feature = "with_ed25519")]
{
let cbor_signature_algorithm: cbor::Value = cbor_int!(EDDSA_ALGORITHM);
let signature_algorithm = SignatureAlgorithm::try_from(cbor_signature_algorithm.clone());
let expected_signature_algorithm = SignatureAlgorithm::EDDSA;
assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
let created_cbor: cbor::Value = signature_algorithm.unwrap().into();
assert_eq!(created_cbor, cbor_signature_algorithm);
}
let cbor_unknown_algorithm: cbor::Value = cbor_int!(-1); let cbor_unknown_algorithm: cbor::Value = cbor_int!(-1);
let unknown_algorithm = SignatureAlgorithm::try_from(cbor_unknown_algorithm); let unknown_algorithm = SignatureAlgorithm::try_from(cbor_unknown_algorithm);
let expected_unknown_algorithm = SignatureAlgorithm::Unknown; let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
@@ -1641,23 +1701,33 @@ mod test {
} }
} }
#[test] fn test_from_into_public_key_credential_parameter(alg_int: i64, signature_algorithm: SignatureAlgorithm) {
fn test_from_into_public_key_credential_parameter() {
let cbor_credential_parameter = cbor_map! { let cbor_credential_parameter = cbor_map! {
"alg" => ES256_ALGORITHM, "alg" => alg_int,
"type" => "public-key", "type" => "public-key",
}; };
let credential_parameter = let credential_parameter =
PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone()); PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone());
let expected_credential_parameter = PublicKeyCredentialParameter { let expected_credential_parameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey, cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256, alg: signature_algorithm,
}; };
assert_eq!(credential_parameter, Ok(expected_credential_parameter)); assert_eq!(credential_parameter, Ok(expected_credential_parameter));
let created_cbor: cbor::Value = credential_parameter.unwrap().into(); let created_cbor: cbor::Value = credential_parameter.unwrap().into();
assert_eq!(created_cbor, cbor_credential_parameter); assert_eq!(created_cbor, cbor_credential_parameter);
} }
#[test]
fn test_from_into_ecdsa_public_key_credential_parameter() {
test_from_into_public_key_credential_parameter(ES256_ALGORITHM, SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_from_into_ed25519_public_key_credential_parameter() {
test_from_into_public_key_credential_parameter(EDDSA_ALGORITHM, SignatureAlgorithm::EDDSA);
}
#[test] #[test]
fn test_from_into_public_key_credential_descriptor() { fn test_from_into_public_key_credential_descriptor() {
let cbor_credential_descriptor = cbor_map! { let cbor_credential_descriptor = cbor_map! {

View File

@@ -117,6 +117,30 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
alg: SignatureAlgorithm::ES256, alg: SignatureAlgorithm::ES256,
}; };
#[cfg(feature = "with_ed25519")]
pub const EDDSA_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::EDDSA,
};
fn get_supported_cred_params() -> Vec<PublicKeyCredentialParameter> {
let mut ret_val = vec!();
ret_val.push(ES256_CRED_PARAM);
#[cfg(feature = "with_ed25519")]
ret_val.push(EDDSA_CRED_PARAM);
ret_val
}
fn get_preferred_cred_param (params: &Vec<PublicKeyCredentialParameter>) -> Option<&PublicKeyCredentialParameter> {
let supported_cred_params = get_supported_cred_params();
for param in params {
if supported_cred_params.contains(param) {
return Some(param);
}
}
return None;
}
/// Transports supported by OpenSK. /// Transports supported by OpenSK.
/// ///
/// An OpenSK library user annotates incoming data with this data type. /// An OpenSK library user annotates incoming data with this data type.
@@ -604,10 +628,9 @@ impl CtapState {
self.pin_uv_auth_precheck(env, &pin_uv_auth_param, pin_uv_auth_protocol, channel)?; self.pin_uv_auth_precheck(env, &pin_uv_auth_param, pin_uv_auth_protocol, channel)?;
// When more algorithms are supported, iterate and pick the first match. // When more algorithms are supported, iterate and pick the first match.
if !pub_key_cred_params.contains(&ES256_CRED_PARAM) { let cred_param = get_preferred_cred_param(&pub_key_cred_params)
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); .ok_or(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)?;
} let algorithm = cred_param.alg;
let algorithm = SignatureAlgorithm::ES256;
let rp_id = rp.rp_id; let rp_id = rp.rp_id;
let ep_att = if let Some(enterprise_attestation) = enterprise_attestation { let ep_att = if let Some(enterprise_attestation) = enterprise_attestation {
@@ -1146,7 +1169,7 @@ impl CtapState {
.map(|c| c as u64), .map(|c| c as u64),
max_credential_id_length: Some(MAX_CREDENTIAL_ID_SIZE as u64), max_credential_id_length: Some(MAX_CREDENTIAL_ID_SIZE as u64),
transports: Some(vec![AuthenticatorTransport::Usb]), transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![ES256_CRED_PARAM]), algorithms: Some(get_supported_cred_params()),
max_serialized_large_blob_array: Some( max_serialized_large_blob_array: Some(
env.customization().max_large_blob_array_size() as u64, env.customization().max_large_blob_array_size() as u64,
), ),
@@ -1461,7 +1484,7 @@ mod test {
0x07 => env.customization().max_credential_count_in_list().map(|c| c as u64), 0x07 => env.customization().max_credential_count_in_list().map(|c| c as u64),
0x08 => MAX_CREDENTIAL_ID_SIZE as u64, 0x08 => MAX_CREDENTIAL_ID_SIZE as u64,
0x09 => cbor_array!["usb"], 0x09 => cbor_array!["usb"],
0x0A => cbor_array![ES256_CRED_PARAM], 0x0A => cbor_array_vec!(get_supported_cred_params()),
0x0B => env.customization().max_large_blob_array_size() as u64, 0x0B => env.customization().max_large_blob_array_size() as u64,
0x0C => false, 0x0C => false,
0x0D => storage::min_pin_length(&mut env).unwrap() as u64, 0x0D => storage::min_pin_length(&mut env).unwrap() as u64,