Merge pull request #478 from egor-duda/ed25519
Support ed25519 crypto algorithm
This commit is contained in:
@@ -25,6 +25,7 @@ serde_json = { version = "=1.0.69", default-features = false, features = ["alloc
|
||||
embedded-time = "0.12.1"
|
||||
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 }
|
||||
|
||||
[features]
|
||||
debug_allocations = ["lang_items/debug_allocations"]
|
||||
@@ -36,6 +37,7 @@ with_ctap1 = ["crypto/with_ctap1"]
|
||||
with_nfc = ["libtock_drivers/with_nfc"]
|
||||
vendor_hid = ["libtock_drivers/vendor_hid"]
|
||||
fuzz = ["arbitrary", "std"]
|
||||
ed25519 = ["ed25519-compact"]
|
||||
|
||||
[dev-dependencies]
|
||||
enum-iterator = "0.6.0"
|
||||
|
||||
11
deploy.py
11
deploy.py
@@ -1091,6 +1091,17 @@ if __name__ == "__main__":
|
||||
help=("When set, the output of elf2tab is appended to this file."),
|
||||
)
|
||||
|
||||
main_parser.add_argument(
|
||||
"--ed25519",
|
||||
action="append_const",
|
||||
const="ed25519",
|
||||
dest="features",
|
||||
help=("Adds support for credentials that use EdDSA algorithm over "
|
||||
"curve Ed25519. "
|
||||
"Current implementation is not side-channel resilient due to use "
|
||||
"of variable-time arithmetic for computations over secret key."),
|
||||
)
|
||||
|
||||
main_parser.set_defaults(features=["with_ctap1"])
|
||||
|
||||
# Start parsing to know if we're going to list things or not.
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
use crate::ctap::data_formats::EDDSA_ALGORITHM;
|
||||
use crate::ctap::data_formats::{
|
||||
extract_array, extract_byte_string, CoseKey, PublicKeyCredentialSource,
|
||||
PublicKeyCredentialType, SignatureAlgorithm,
|
||||
PublicKeyCredentialType, SignatureAlgorithm, ES256_ALGORITHM,
|
||||
};
|
||||
use crate::ctap::status_code::Ctap2StatusCode;
|
||||
use crate::ctap::storage;
|
||||
@@ -42,6 +44,12 @@ pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113;
|
||||
// See encrypt_key_handle v1 documentation.
|
||||
pub const MAX_CREDENTIAL_ID_SIZE: usize = 113;
|
||||
|
||||
const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01;
|
||||
#[allow(dead_code)]
|
||||
const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02;
|
||||
#[cfg(test)]
|
||||
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;
|
||||
|
||||
/// Wraps the AES256-CBC encryption to match what we need in CTAP.
|
||||
pub fn aes256_cbc_encrypt(
|
||||
rng: &mut dyn Rng256,
|
||||
@@ -94,6 +102,8 @@ pub fn aes256_cbc_decrypt(
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub enum PrivateKey {
|
||||
Ecdsa(ecdsa::SecKey),
|
||||
#[cfg(feature = "ed25519")]
|
||||
Ed25519(ed25519_compact::SecretKey),
|
||||
}
|
||||
|
||||
impl PrivateKey {
|
||||
@@ -105,6 +115,11 @@ impl PrivateKey {
|
||||
pub fn new(rng: &mut impl Rng256, alg: SignatureAlgorithm) -> Self {
|
||||
match alg {
|
||||
SignatureAlgorithm::ES256 => PrivateKey::Ecdsa(crypto::ecdsa::SecKey::gensk(rng)),
|
||||
#[cfg(feature = "ed25519")]
|
||||
SignatureAlgorithm::EDDSA => {
|
||||
let bytes = rng.gen_uniform_u8x32();
|
||||
Self::new_ed25519_from_bytes(&bytes).unwrap()
|
||||
}
|
||||
SignatureAlgorithm::Unknown => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -119,10 +134,21 @@ impl PrivateKey {
|
||||
ecdsa::SecKey::from_bytes(array_ref!(bytes, 0, 32)).map(PrivateKey::from)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||
if bytes.len() != 32 {
|
||||
return None;
|
||||
}
|
||||
let seed = ed25519_compact::Seed::from_slice(bytes).unwrap();
|
||||
Some(Self::Ed25519(ed25519_compact::KeyPair::from_seed(seed).sk))
|
||||
}
|
||||
|
||||
/// Returns the corresponding public key.
|
||||
pub fn get_pub_key(&self) -> CoseKey {
|
||||
match self {
|
||||
PrivateKey::Ecdsa(ecdsa_key) => CoseKey::from(ecdsa_key.genpk()),
|
||||
#[cfg(feature = "ed25519")]
|
||||
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +156,8 @@ impl PrivateKey {
|
||||
pub fn sign_and_encode(&self, message: &[u8]) -> Vec<u8> {
|
||||
match self {
|
||||
PrivateKey::Ecdsa(ecdsa_key) => ecdsa_key.sign_rfc6979::<Sha256>(message).to_asn1_der(),
|
||||
#[cfg(feature = "ed25519")]
|
||||
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +165,8 @@ impl PrivateKey {
|
||||
pub fn signature_algorithm(&self) -> SignatureAlgorithm {
|
||||
match self {
|
||||
PrivateKey::Ecdsa(_) => SignatureAlgorithm::ES256,
|
||||
#[cfg(feature = "ed25519")]
|
||||
PrivateKey::Ed25519(_) => SignatureAlgorithm::EDDSA,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +178,8 @@ impl PrivateKey {
|
||||
ecdsa_key.to_bytes(array_mut_ref!(key_bytes, 0, 32));
|
||||
key_bytes
|
||||
}
|
||||
#[cfg(feature = "ed25519")]
|
||||
PrivateKey::Ed25519(ed25519_key) => ed25519_key.seed().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +205,9 @@ impl TryFrom<cbor::Value> for PrivateKey {
|
||||
match SignatureAlgorithm::try_from(array.pop().unwrap())? {
|
||||
SignatureAlgorithm::ES256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
|
||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||
#[cfg(feature = "ed25519")]
|
||||
SignatureAlgorithm::EDDSA => PrivateKey::new_ed25519_from_bytes(&key_bytes)
|
||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||
}
|
||||
}
|
||||
@@ -192,12 +227,19 @@ impl From<ecdsa::SecKey> for PrivateKey {
|
||||
/// Also, by limiting ourselves to private key and RP ID hash, we are compatible with U2F for
|
||||
/// 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
|
||||
/// - 16 bytes: initialization vector for AES-256,
|
||||
/// - 32 bytes: ECDSA private key for the credential,
|
||||
/// - 32 bytes: relying party ID hashed with SHA256,
|
||||
/// - 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(
|
||||
env: &mut impl Env,
|
||||
private_key: &PrivateKey,
|
||||
@@ -206,17 +248,22 @@ pub fn encrypt_key_handle(
|
||||
let master_keys = storage::master_keys(env)?;
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
|
||||
|
||||
let mut encrypted_id = match private_key {
|
||||
let mut plaintext = [0; 64];
|
||||
let version = match private_key {
|
||||
PrivateKey::Ecdsa(ecdsa_key) => {
|
||||
let mut plaintext = [0; 64];
|
||||
ecdsa_key.to_bytes(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)?;
|
||||
// Version number
|
||||
encrypted_id.insert(0, 0x01);
|
||||
encrypted_id
|
||||
ECDSA_CREDENTIAL_ID_VERSION
|
||||
}
|
||||
#[cfg(feature = "ed25519")]
|
||||
PrivateKey::Ed25519(ed25519_key) => {
|
||||
let sk_bytes = *ed25519_key.seed();
|
||||
plaintext[0..32].copy_from_slice(&sk_bytes);
|
||||
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[..]);
|
||||
encrypted_id.extend(&id_hmac);
|
||||
@@ -233,6 +280,7 @@ pub fn encrypt_key_handle(
|
||||
/// This functions reads:
|
||||
/// - legacy credentials (no version number),
|
||||
/// - v1 (ECDSA)
|
||||
/// - v2 (EdDSA over curve Ed25519)
|
||||
pub fn decrypt_credential_source(
|
||||
env: &mut impl Env,
|
||||
credential_id: Vec<u8>,
|
||||
@@ -251,14 +299,17 @@ pub fn decrypt_credential_source(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let payload = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE {
|
||||
&credential_id[..hmac_message_size]
|
||||
let (payload, algorithm) = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE {
|
||||
(&credential_id[..hmac_message_size], ES256_ALGORITHM)
|
||||
} else {
|
||||
// Version number check
|
||||
if credential_id[0] != 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
&credential_id[1..hmac_message_size]
|
||||
let algorithm = match credential_id[0] {
|
||||
ECDSA_CREDENTIAL_ID_VERSION => ES256_ALGORITHM,
|
||||
#[cfg(feature = "ed25519")]
|
||||
ED25519_CREDENTIAL_ID_VERSION => EDDSA_ALGORITHM,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
(&credential_id[1..hmac_message_size], algorithm)
|
||||
};
|
||||
if payload.len() != 80 {
|
||||
// We shouldn't have HMAC'ed anything of different length. The check is cheap though.
|
||||
@@ -271,7 +322,12 @@ pub fn decrypt_credential_source(
|
||||
if rp_id_hash != &decrypted_id[32..] {
|
||||
return Ok(None);
|
||||
}
|
||||
let sk_option = PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]);
|
||||
let sk_option = match algorithm {
|
||||
ES256_ALGORITHM => PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]),
|
||||
#[cfg(feature = "ed25519")]
|
||||
EDDSA_ALGORITHM => PrivateKey::new_ed25519_from_bytes(&decrypted_id[..32]),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(sk_option.map(|sk| PublicKeyCredentialSource {
|
||||
key_type: PublicKeyCredentialType::PublicKey,
|
||||
@@ -370,6 +426,18 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "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]
|
||||
fn test_new_ecdsa_from_bytes_wrong_length() {
|
||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None);
|
||||
@@ -378,6 +446,15 @@ mod test {
|
||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "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]
|
||||
fn test_private_key_get_pub_key() {
|
||||
let mut env = TestEnv::new();
|
||||
@@ -397,26 +474,44 @@ mod test {
|
||||
assert_eq!(private_key.sign_and_encode(&message), signature);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_key_signature_algorithm() {
|
||||
fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) {
|
||||
let mut env = TestEnv::new();
|
||||
let algorithm = SignatureAlgorithm::ES256;
|
||||
let private_key = PrivateKey::new(env.rng(), algorithm);
|
||||
assert_eq!(private_key.signature_algorithm(), algorithm);
|
||||
let private_key = PrivateKey::new(env.rng(), signature_algorithm);
|
||||
assert_eq!(private_key.signature_algorithm(), signature_algorithm);
|
||||
}
|
||||
|
||||
#[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 = "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 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());
|
||||
assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_key_from_bad_cbor() {
|
||||
fn test_ecdsa_private_key_from_to_cbor() {
|
||||
test_private_key_from_to_cbor(SignatureAlgorithm::ES256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ed25519")]
|
||||
fn test_ed25519_private_key_from_to_cbor() {
|
||||
test_private_key_from_to_cbor(SignatureAlgorithm::EDDSA);
|
||||
}
|
||||
|
||||
fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
|
||||
let cbor = cbor_array![
|
||||
cbor_int!(SignatureAlgorithm::ES256 as i64),
|
||||
cbor_int!(signature_algorithm as i64),
|
||||
cbor_bytes!(vec![0x88; 32]),
|
||||
// The array is too long.
|
||||
cbor_int!(0),
|
||||
@@ -425,7 +520,21 @@ mod test {
|
||||
PrivateKey::try_from(cbor),
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdsa_private_key_from_bad_cbor() {
|
||||
test_private_key_from_bad_cbor(SignatureAlgorithm::ES256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ed25519")]
|
||||
fn test_ed25519_private_key_from_bad_cbor() {
|
||||
test_private_key_from_bad_cbor(SignatureAlgorithm::EDDSA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_key_from_bad_cbor_unsupported_algo() {
|
||||
let cbor = cbor_array![
|
||||
// This algorithms doesn't exist.
|
||||
cbor_int!(-1),
|
||||
@@ -437,11 +546,10 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_credential() {
|
||||
fn test_encrypt_decrypt_credential(signature_algorithm: SignatureAlgorithm) {
|
||||
let mut env = TestEnv::new();
|
||||
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 encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
|
||||
@@ -452,6 +560,17 @@ mod test {
|
||||
assert_eq!(private_key, decrypted_source.private_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_ecdsa_credential() {
|
||||
test_encrypt_decrypt_credential(SignatureAlgorithm::ES256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ed25519")]
|
||||
fn test_encrypt_decrypt_ed25519_credential() {
|
||||
test_encrypt_decrypt_credential(SignatureAlgorithm::EDDSA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_bad_version() {
|
||||
let mut env = TestEnv::new();
|
||||
@@ -460,8 +579,7 @@ mod test {
|
||||
|
||||
let rp_id_hash = [0x55; 32];
|
||||
let mut encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
|
||||
// Version 2 does not exist yet.
|
||||
encrypted_id[0] = 0x02;
|
||||
encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
|
||||
// Override the HMAC to pass the check.
|
||||
encrypted_id.truncate(&encrypted_id.len() - 32);
|
||||
let master_keys = storage::master_keys(&mut env).unwrap();
|
||||
@@ -474,11 +592,10 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_bad_hmac() {
|
||||
fn test_encrypt_decrypt_bad_hmac(signature_algorithm: SignatureAlgorithm) {
|
||||
let mut env = TestEnv::new();
|
||||
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 encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
|
||||
@@ -493,10 +610,20 @@ mod 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 = "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();
|
||||
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 encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap();
|
||||
@@ -509,6 +636,17 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdsa_decrypt_credential_missing_blocks() {
|
||||
test_decrypt_credential_missing_blocks(SignatureAlgorithm::ES256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "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.
|
||||
fn legacy_encrypt_key_handle(
|
||||
env: &mut impl Env,
|
||||
|
||||
@@ -27,7 +27,9 @@ use sk_cbor as cbor;
|
||||
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.
|
||||
const ES256_ALGORITHM: i64 = -7;
|
||||
pub const ES256_ALGORITHM: i64 = -7;
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub const EDDSA_ALGORITHM: i64 = -8;
|
||||
|
||||
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -503,6 +505,8 @@ impl From<PackedAttestationStatement> for cbor::Value {
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
pub enum SignatureAlgorithm {
|
||||
ES256 = ES256_ALGORITHM as isize,
|
||||
#[cfg(feature = "ed25519")]
|
||||
EDDSA = EDDSA_ALGORITHM as isize,
|
||||
// This is the default for all numbers not covered above.
|
||||
// Unknown types should be ignored, instead of returning errors.
|
||||
Unknown = 0,
|
||||
@@ -518,6 +522,8 @@ impl From<i64> for SignatureAlgorithm {
|
||||
fn from(int: i64) -> Self {
|
||||
match int {
|
||||
ES256_ALGORITHM => SignatureAlgorithm::ES256,
|
||||
#[cfg(feature = "ed25519")]
|
||||
EDDSA_ALGORITHM => SignatureAlgorithm::EDDSA,
|
||||
_ => SignatureAlgorithm::Unknown,
|
||||
}
|
||||
}
|
||||
@@ -721,6 +727,8 @@ pub struct CoseKey {
|
||||
x_bytes: [u8; ecdh::NBYTES],
|
||||
y_bytes: [u8; ecdh::NBYTES],
|
||||
algorithm: i64,
|
||||
key_type: i64,
|
||||
curve: i64,
|
||||
}
|
||||
|
||||
impl CoseKey {
|
||||
@@ -730,8 +738,12 @@ impl CoseKey {
|
||||
const ECDH_ALGORITHM: i64 = -25;
|
||||
// The parameter behind map key 1.
|
||||
const EC2_KEY_TYPE: i64 = 2;
|
||||
#[cfg(feature = "ed25519")]
|
||||
const OKP_KEY_TYPE: i64 = 1;
|
||||
// The parameter behind map key -1.
|
||||
const P_256_CURVE: i64 = 1;
|
||||
#[cfg(feature = "ed25519")]
|
||||
const ED25519_CURVE: i64 = 6;
|
||||
}
|
||||
|
||||
// This conversion accepts both ECDH and ECDSA.
|
||||
@@ -777,6 +789,8 @@ impl TryFrom<cbor::Value> for CoseKey {
|
||||
x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES],
|
||||
y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES],
|
||||
algorithm,
|
||||
key_type,
|
||||
curve,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -787,12 +801,14 @@ impl From<CoseKey> for cbor::Value {
|
||||
x_bytes,
|
||||
y_bytes,
|
||||
algorithm,
|
||||
key_type,
|
||||
curve,
|
||||
} = cose_key;
|
||||
|
||||
cbor_map! {
|
||||
1 => CoseKey::EC2_KEY_TYPE,
|
||||
1 => key_type,
|
||||
3 => algorithm,
|
||||
-1 => CoseKey::P_256_CURVE,
|
||||
-1 => curve,
|
||||
-2 => x_bytes,
|
||||
-3 => y_bytes,
|
||||
}
|
||||
@@ -808,6 +824,8 @@ impl From<ecdh::PubKey> for CoseKey {
|
||||
x_bytes,
|
||||
y_bytes,
|
||||
algorithm: CoseKey::ECDH_ALGORITHM,
|
||||
key_type: CoseKey::EC2_KEY_TYPE,
|
||||
curve: CoseKey::P_256_CURVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -821,6 +839,21 @@ impl From<ecdsa::PubKey> for CoseKey {
|
||||
x_bytes,
|
||||
y_bytes,
|
||||
algorithm: ES256_ALGORITHM,
|
||||
key_type: CoseKey::EC2_KEY_TYPE,
|
||||
curve: CoseKey::P_256_CURVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
impl From<ed25519_compact::PublicKey> for CoseKey {
|
||||
fn from(pk: ed25519_compact::PublicKey) -> Self {
|
||||
CoseKey {
|
||||
x_bytes: *pk,
|
||||
y_bytes: [0u8; 32],
|
||||
key_type: CoseKey::OKP_KEY_TYPE,
|
||||
curve: CoseKey::ED25519_CURVE,
|
||||
algorithm: EDDSA_ALGORITHM,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -833,6 +866,8 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
|
||||
x_bytes,
|
||||
y_bytes,
|
||||
algorithm,
|
||||
key_type,
|
||||
curve,
|
||||
} = cose_key;
|
||||
|
||||
// Since algorithm can be used for different COSE key types, we check
|
||||
@@ -842,6 +877,9 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
|
||||
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)
|
||||
}
|
||||
@@ -855,9 +893,14 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
|
||||
x_bytes,
|
||||
y_bytes,
|
||||
algorithm,
|
||||
key_type,
|
||||
curve,
|
||||
} = 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);
|
||||
}
|
||||
ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes)
|
||||
@@ -905,6 +948,8 @@ impl TryFrom<CoseSignature> for ecdsa::Signature {
|
||||
match cose_signature.algorithm {
|
||||
SignatureAlgorithm::ES256 => ecdsa::Signature::from_bytes(&cose_signature.bytes)
|
||||
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
||||
#[cfg(feature = "ed25519")]
|
||||
SignatureAlgorithm::EDDSA => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
|
||||
SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
|
||||
}
|
||||
}
|
||||
@@ -1565,6 +1610,13 @@ mod test {
|
||||
let signature_algorithm = SignatureAlgorithm::from(alg_int);
|
||||
assert_eq!(signature_algorithm, SignatureAlgorithm::ES256);
|
||||
|
||||
#[cfg(feature = "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_algorithm = SignatureAlgorithm::from(unknown_alg_int);
|
||||
assert_eq!(unknown_algorithm, SignatureAlgorithm::Unknown);
|
||||
@@ -1579,6 +1631,17 @@ mod test {
|
||||
let created_cbor: cbor::Value = signature_algorithm.unwrap().into();
|
||||
assert_eq!(created_cbor, cbor_signature_algorithm);
|
||||
|
||||
#[cfg(feature = "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 unknown_algorithm = SignatureAlgorithm::try_from(cbor_unknown_algorithm);
|
||||
let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
|
||||
@@ -1642,23 +1705,36 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_into_public_key_credential_parameter() {
|
||||
fn test_from_into_public_key_credential_parameter(
|
||||
alg_int: i64,
|
||||
signature_algorithm: SignatureAlgorithm,
|
||||
) {
|
||||
let cbor_credential_parameter = cbor_map! {
|
||||
"alg" => ES256_ALGORITHM,
|
||||
"alg" => alg_int,
|
||||
"type" => "public-key",
|
||||
};
|
||||
let credential_parameter =
|
||||
PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone());
|
||||
let expected_credential_parameter = PublicKeyCredentialParameter {
|
||||
cred_type: PublicKeyCredentialType::PublicKey,
|
||||
alg: SignatureAlgorithm::ES256,
|
||||
alg: signature_algorithm,
|
||||
};
|
||||
assert_eq!(credential_parameter, Ok(expected_credential_parameter));
|
||||
let created_cbor: cbor::Value = credential_parameter.unwrap().into();
|
||||
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 = "ed25519")]
|
||||
fn test_from_into_ed25519_public_key_credential_parameter() {
|
||||
test_from_into_public_key_credential_parameter(EDDSA_ALGORITHM, SignatureAlgorithm::EDDSA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_into_public_key_credential_descriptor() {
|
||||
let cbor_credential_descriptor = cbor_map! {
|
||||
|
||||
@@ -117,6 +117,26 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
|
||||
alg: SignatureAlgorithm::ES256,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub const EDDSA_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
|
||||
cred_type: PublicKeyCredentialType::PublicKey,
|
||||
alg: SignatureAlgorithm::EDDSA,
|
||||
};
|
||||
|
||||
const SUPPORTED_CRED_PARAMS: &[PublicKeyCredentialParameter] = &[
|
||||
ES256_CRED_PARAM,
|
||||
#[cfg(feature = "ed25519")]
|
||||
EDDSA_CRED_PARAM,
|
||||
];
|
||||
|
||||
fn get_preferred_cred_param(
|
||||
params: &[PublicKeyCredentialParameter],
|
||||
) -> Option<&PublicKeyCredentialParameter> {
|
||||
params
|
||||
.iter()
|
||||
.find(|¶m| SUPPORTED_CRED_PARAMS.contains(param))
|
||||
}
|
||||
|
||||
/// Transports supported by OpenSK.
|
||||
///
|
||||
/// An OpenSK library user annotates incoming data with this data type.
|
||||
@@ -604,10 +624,9 @@ impl CtapState {
|
||||
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.
|
||||
if !pub_key_cred_params.contains(&ES256_CRED_PARAM) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||
}
|
||||
let algorithm = SignatureAlgorithm::ES256;
|
||||
let cred_param = get_preferred_cred_param(&pub_key_cred_params)
|
||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)?;
|
||||
let algorithm = cred_param.alg;
|
||||
|
||||
let rp_id = rp.rp_id;
|
||||
let ep_att = if let Some(enterprise_attestation) = enterprise_attestation {
|
||||
@@ -1146,7 +1165,7 @@ impl CtapState {
|
||||
.map(|c| c as u64),
|
||||
max_credential_id_length: Some(MAX_CREDENTIAL_ID_SIZE as u64),
|
||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||
algorithms: Some(vec![ES256_CRED_PARAM]),
|
||||
algorithms: Some(SUPPORTED_CRED_PARAMS.to_vec()),
|
||||
max_serialized_large_blob_array: Some(
|
||||
env.customization().max_large_blob_array_size() as u64,
|
||||
),
|
||||
@@ -1461,7 +1480,7 @@ mod test {
|
||||
0x07 => env.customization().max_credential_count_in_list().map(|c| c as u64),
|
||||
0x08 => MAX_CREDENTIAL_ID_SIZE as u64,
|
||||
0x09 => cbor_array!["usb"],
|
||||
0x0A => cbor_array![ES256_CRED_PARAM],
|
||||
0x0A => cbor_array_vec!(SUPPORTED_CRED_PARAMS.to_vec()),
|
||||
0x0B => env.customization().max_large_blob_array_size() as u64,
|
||||
0x0C => false,
|
||||
0x0D => storage::min_pin_length(&mut env).unwrap() as u64,
|
||||
|
||||
Reference in New Issue
Block a user