/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::ctap2::commands::client_pin::PinUvAuthTokenPermission; usecrate::ctap2::commands::get_info::AuthenticatorInfo; usecrate::errors::AuthenticatorError; usecrate::{ctap2::commands::CommandError, transport::errors::HIDError}; use serde::{
de::{Error as SerdeError, MapAccess, Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
}; use serde_bytes::ByteBuf; use std::convert::TryFrom; use std::fmt;
#[cfg(feature = "crypto_nss")] mod nss; #[cfg(feature = "crypto_nss")] use nss as backend;
#[cfg(feature = "crypto_openssl")] mod openssl; #[cfg(feature = "crypto_openssl")] useself::openssl as backend;
#[cfg(feature = "crypto_dummy")] mod dummy; #[cfg(feature = "crypto_dummy")] use dummy as backend;
use backend::{
decrypt_aes_256_cbc_no_pad, ecdhe_p256_raw, encrypt_aes_256_cbc_no_pad, gen_p256, hmac_sha256,
random_bytes, sha256,
};
/// The output of `PinUvAuthProtocol::encapsulate` is supposed to be used with the same /// PinProtocolImpl. So we stash a copy of the calling PinUvAuthProtocol in the output SharedSecret. /// We need a trick here to tell the compiler that every PinProtocolImpl we define will implement /// Clone. trait ClonablePinProtocolImpl { fn clone_box(&self) -> Box<dyn PinProtocolImpl + Send + Sync>;
}
impl<T> ClonablePinProtocolImpl for T where
T: 'static + PinProtocolImpl + Clone + Send + Sync,
{ fn clone_box(&self) -> Box<dyn PinProtocolImpl + Send + Sync> { Box::new(self.clone())
}
}
/// CTAP 2.1, Section 6.5.4. PIN/UV Auth Protocol Abstract Definition trait PinProtocolImpl: ClonablePinProtocolImpl { fn protocol_id(&self) -> u64; fn initialize(&self); fn encrypt(&self, key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, CryptoError>; fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError>; fn authenticate(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError>; fn kdf(&self, z: &[u8]) -> Result<Vec<u8>, CryptoError>; fn encapsulate(&self, peer_cose_key: &COSEKey) -> Result<SharedSecret, CryptoError> { // [CTAP 2.1] // encapsulate(peerCoseKey) → (coseKey, sharedSecret) | error // 1) Let sharedSecret be the result of calling ecdh(peerCoseKey). Return any // resulting error. // 2) Return (getPublicKey(), sharedSecret) // // ecdh(peerCoseKey) → sharedSecret | error // Parse peerCoseKey as specified for getPublicKey, below, and produce a P-256 // point, Y. If unsuccessful, or if the resulting point is not on the curve, return // error. Calculate xY, the shared point. (I.e. the scalar-multiplication of the // peer's point, Y, with the local private key agreement key.) Let Z be the // 32-byte, big-endian encoding of the x-coordinate of the shared point. Return // kdf(Z).
match peer_cose_key.alg { // There is no COSEAlgorithm for ECDHE with the KDF used here. Section 6.5.6. of CTAP // 2.1 says to use value -25 (= ECDH_ES_HKDF256) even though "this is not the algorithm // actually used".
COSEAlgorithm::ECDH_ES_HKDF256 => (),
other => return Err(CryptoError::UnsupportedAlgorithm(other)),
}
let peer_cose_ec2_key = match peer_cose_key.key {
COSEKeyType::EC2(ref key) => key,
_ => return Err(CryptoError::UnsupportedKeyType),
};
let peer_spki = peer_cose_ec2_key.der_spki()?;
let (shared_point, client_public_sec1) = ecdhe_p256_raw(&peer_spki)?;
let client_cose_ec2_key =
COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, &client_public_sec1)?;
let client_cose_key = COSEKey {
alg: COSEAlgorithm::ECDH_ES_HKDF256,
key: COSEKeyType::EC2(client_cose_ec2_key),
};
fn encrypt(&self, key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> { // [CTAP 2.1] // encrypt(key, demPlaintext) → ciphertext // Return the AES-256-CBC encryption of plaintext using an all-zero IV. (No padding is // performed as the size of plaintext is required to be a multiple of the AES block // length.)
encrypt_aes_256_cbc_no_pad(key, None, plaintext)
}
fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { // [CTAP 2.1] // decrypt(key, demCiphertext) → plaintext | error // If the size of ciphertext is not a multiple of the AES block length, return error. // Otherwise return the AES-256-CBC decryption of ciphertext using an all-zero IV.
decrypt_aes_256_cbc_no_pad(key, None, ciphertext)
}
fn authenticate(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { // [CTAP 2.1] // authenticate(key, message) → signature // Return the first 16 bytes of the result of computing HMAC-SHA-256 with the given // key and message. letmut hmac = hmac_sha256(key, message)?;
hmac.truncate(16);
Ok(hmac)
}
fn encrypt(&self, key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> { // [CTAP 2.1] // encrypt(key, demPlaintext) → ciphertext // 1. Discard the first 32 bytes of key. (This selects the AES-key portion of the // shared secret.) // 2. Let iv be a 16-byte, random bytestring. // 3. Let ct be the AES-256-CBC encryption of demPlaintext using key and iv. (No // padding is performed as the size of demPlaintext is required to be a multiple of // the AES block length.) // 4. Return iv || ct. if key.len() != 64 { return Err(CryptoError::LibraryFailure);
} let key = &key[32..64];
let iv = random_bytes(16)?; letmut ct = encrypt_aes_256_cbc_no_pad(key, Some(&iv), plaintext)?;
letmut out = iv;
out.append(&mut ct);
Ok(out)
}
fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { // decrypt(key, demCiphertext) → plaintext | error // 1. Discard the first 32 bytes of key. (This selects the AES-key portion of the // shared secret.) // 2. If demCiphertext is less than 16 bytes in length, return an error // 3. Split demCiphertext after the 16th byte to produce two subspans, iv and ct. // 4. Return the AES-256-CBC decryption of ct using key and iv. if key.len() < 64 || ciphertext.len() < 16 { return Err(CryptoError::LibraryFailure);
} let key = &key[32..64]; let (iv, ct) = ciphertext.split_at(16);
decrypt_aes_256_cbc_no_pad(key, Some(iv), ct)
}
fn authenticate(&self, key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { // authenticate(key, message) → signature // 1. If key is longer than 32 bytes, discard the excess. (This selects the HMAC-key // portion of the shared secret. When key is the pinUvAuthToken, it is exactly 32 // bytes long and thus this step has no effect.) // 2. Return the result of computing HMAC-SHA-256 on key and message. if key.len() < 32 { return Err(CryptoError::LibraryFailure);
} let key = &key[0..32];
hmac_sha256(key, message)
}
fn kdf(&self, z: &[u8]) -> Result<Vec<u8>, CryptoError> { // kdf(Z) → sharedSecret // return HKDF-SHA-256(salt, Z, L = 32, info = "CTAP2 HMAC key") || // HKDF-SHA-256(salt, Z, L = 32, info = "CTAP2 AES key") // where salt = [0u8; 32]. // // From Section 2 of RFC 5869, we have // HKDF(salt, Z, 32, info) = // HKDF-Expand(HKDF-Extract(salt, Z), info || 0x01) // // And for HKDF-SHA256 both Extract and Expand are instantiated with HMAC-SHA256.
impl Serialize for PinUvAuthParam { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{
serde_bytes::serialize(&self.pin_auth[..], serializer)
}
}
/// A Curve identifier. You probably will never need to alter /// or use this value, as it is set inside the Credential for you. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pubenum Curve { // +---------+-------+----------+------------------------------------+ // | Name | Value | Key Type | Description | // +---------+-------+----------+------------------------------------+ // | P-256 | 1 | EC2 | NIST P-256 also known as secp256r1 | // | P-384 | 2 | EC2 | NIST P-384 also known as secp384r1 | // | P-521 | 3 | EC2 | NIST P-521 also known as secp521r1 | // | X25519 | 4 | OKP | X25519 for use w/ ECDH only | // | X448 | 5 | OKP | X448 for use w/ ECDH only | // | Ed25519 | 6 | OKP | Ed25519 for use w/ EdDSA only | // | Ed448 | 7 | OKP | Ed448 for use w/ EdDSA only | // +---------+-------+----------+------------------------------------+ /// Identifies this curve as SECP256R1 (X9_62_PRIME256V1 in OpenSSL)
SECP256R1 = 1, /// Identifies this curve as SECP384R1
SECP384R1 = 2, /// Identifies this curve as SECP521R1
SECP521R1 = 3, /// Identifieds this as OKP X25519 for use w/ ECDH only
X25519 = 4, /// Identifieds this as OKP X448 for use w/ ECDH only
X448 = 5, /// Identifieds this as OKP Ed25519 for use w/ EdDSA only
Ed25519 = 6, /// Identifieds this as OKP Ed448 for use w/ EdDSA only
Ed448 = 7,
}
impl Serialize for Curve { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{
serializer.serialize_i64(*selfas i64)
}
}
impl TryFrom<i64> for Curve { type Error = CryptoError; fn try_from(i: i64) -> Result<Self, Self::Error> { match i {
i if i == Curve::SECP256R1 as i64 => Ok(Curve::SECP256R1),
i if i == Curve::SECP384R1 as i64 => Ok(Curve::SECP384R1),
i if i == Curve::SECP521R1 as i64 => Ok(Curve::SECP521R1),
i if i == Curve::X25519 as i64 => Ok(Curve::X25519),
i if i == Curve::X448 as i64 => Ok(Curve::X448),
i if i == Curve::Ed25519 as i64 => Ok(Curve::Ed25519),
i if i == Curve::Ed448 as i64 => Ok(Curve::Ed448),
_ => Err(CryptoError::UnknownKeyType),
}
}
} /// A COSE signature algorithm, indicating the type of key and hash type /// that should be used. /// see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms #[rustfmt::skip] #[allow(non_camel_case_types)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pubenum COSEAlgorithm { // /// Identifies this key as ECDSA (recommended SECP256R1) with SHA256 hashing // //#[serde(alias = "ECDSA_SHA256")] // ES256 = -7, // recommends curve SECP256R1 // /// Identifies this key as ECDSA (recommended SECP384R1) with SHA384 hashing // //#[serde(alias = "ECDSA_SHA384")] // ES384 = -35, // recommends curve SECP384R1 // /// Identifies this key as ECDSA (recommended SECP521R1) with SHA512 hashing // //#[serde(alias = "ECDSA_SHA512")] // ES512 = -36, // recommends curve SECP521R1 // /// Identifies this key as RS256 aka RSASSA-PKCS1-v1_5 w/ SHA-256 // RS256 = -257, // /// Identifies this key as RS384 aka RSASSA-PKCS1-v1_5 w/ SHA-384 // RS384 = -258, // /// Identifies this key as RS512 aka RSASSA-PKCS1-v1_5 w/ SHA-512 // RS512 = -259, // /// Identifies this key as PS256 aka RSASSA-PSS w/ SHA-256 // PS256 = -37, // /// Identifies this key as PS384 aka RSASSA-PSS w/ SHA-384 // PS384 = -38, // /// Identifies this key as PS512 aka RSASSA-PSS w/ SHA-512 // PS512 = -39, // /// Identifies this key as EdDSA (likely curve ed25519) // EDDSA = -8, // /// Identifies this as an INSECURE RS1 aka RSASSA-PKCS1-v1_5 using SHA-1. This is not // /// used by validators, but can exist in some windows hello tpm's // INSECURE_RS1 = -65535,
INSECURE_RS1 = -65535, // RSASSA-PKCS1-v1_5 using SHA-1
RS512 = -259, // RSASSA-PKCS1-v1_5 using SHA-512
RS384 = -258, // RSASSA-PKCS1-v1_5 using SHA-384
RS256 = -257, // RSASSA-PKCS1-v1_5 using SHA-256
ES256K = -47, // ECDSA using secp256k1 curve and SHA-256
HSS_LMS = -46, // HSS/LMS hash-based digital signature
SHAKE256 = -45, // SHAKE-256 512-bit Hash Value
SHA512 = -44, // SHA-2 512-bit Hash
SHA384 = -43, // SHA-2 384-bit Hash
RSAES_OAEP_SHA_512 = -42, // RSAES-OAEP w/ SHA-512
RSAES_OAEP_SHA_256 = -41, // RSAES-OAEP w/ SHA-256
RSAES_OAEP_RFC_8017_default = -40, // RSAES-OAEP w/ SHA-1
PS512 = -39, // RSASSA-PSS w/ SHA-512
PS384 = -38, // RSASSA-PSS w/ SHA-384
PS256 = -37, // RSASSA-PSS w/ SHA-256
ES512 = -36, // ECDSA w/ SHA-512
ES384 = -35, // ECDSA w/ SHA-384
ECDH_SS_A256KW = -34, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key
ECDH_SS_A192KW = -33, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key
ECDH_SS_A128KW = -32, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key
ECDH_ES_A256KW = -31, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key
ECDH_ES_A192KW = -30, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key
ECDH_ES_A128KW = -29, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key
ECDH_SS_HKDF512 = -28, // ECDH SS w/ HKDF - generate key directly
ECDH_SS_HKDF256 = -27, // ECDH SS w/ HKDF - generate key directly
ECDH_ES_HKDF512 = -26, // ECDH ES w/ HKDF - generate key directly
ECDH_ES_HKDF256 = -25, // ECDH ES w/ HKDF - generate key directly
SHAKE128 = -18, // SHAKE-128 256-bit Hash Value
SHA512_256 = -17, // SHA-2 512-bit Hash truncated to 256-bits
SHA256 = -16, // SHA-2 256-bit Hash
SHA256_64 = -15, // SHA-2 256-bit Hash truncated to 64-bits
SHA1 = -14, // SHA-1 Hash
Direct_HKDF_AES256 = -13, // Shared secret w/ AES-MAC 256-bit key
Direct_HKDF_AES128 = -12, // Shared secret w/ AES-MAC 128-bit key
Direct_HKDF_SHA512 = -11, // Shared secret w/ HKDF and SHA-512
Direct_HKDF_SHA256 = -10, // Shared secret w/ HKDF and SHA-256
EDDSA = -8, // EdDSA
ES256 = -7, // ECDSA w/ SHA-256
Direct = -6, // Direct use of CEK
A256KW = -5, // AES Key Wrap w/ 256-bit key
A192KW = -4, // AES Key Wrap w/ 192-bit key
A128KW = -3, // AES Key Wrap w/ 128-bit key
A128GCM = 1, // AES-GCM mode w/ 128-bit key, 128-bit tag
A192GCM = 2, // AES-GCM mode w/ 192-bit key, 128-bit tag
A256GCM = 3, // AES-GCM mode w/ 256-bit key, 128-bit tag
HMAC256_64 = 4, // HMAC w/ SHA-256 truncated to 64 bits
HMAC256_256 = 5, // HMAC w/ SHA-256
HMAC384_384 = 6, // HMAC w/ SHA-384
HMAC512_512 = 7, // HMAC w/ SHA-512
AES_CCM_16_64_128 = 10, // AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce
AES_CCM_16_64_256 = 11, // AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce
AES_CCM_64_64_128 = 12, // AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce
AES_CCM_64_64_256 = 13, // AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce
AES_MAC_128_64 = 14, // AES-MAC 128-bit key, 64-bit tag
AES_MAC_256_64 = 15, // AES-MAC 256-bit key, 64-bit tag
ChaCha20_Poly1305 = 24, // ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag
AES_MAC_128_128 = 25, // AES-MAC 128-bit key, 128-bit tag
AES_MAC_256_128 = 26, // AES-MAC 256-bit key, 128-bit tag
AES_CCM_16_128_128 = 30, // AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce
AES_CCM_16_128_256 = 31, // AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce
AES_CCM_64_128_128 = 32, // AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce
AES_CCM_64_128_256 = 33, // AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce
IV_GENERATION = 34, // For doing IV generation for symmetric algorithms.
}
impl Serialize for COSEAlgorithm { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{
serializer.serialize_i64(*selfas i64)
}
}
impl<'de> Deserialize<'de> for COSEAlgorithm { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
D: Deserializer<'de>,
{ struct COSEAlgorithmVisitor;
impl<'de> Visitor<'de> for COSEAlgorithmVisitor { type Value = COSEAlgorithm;
impl TryFrom<i64> for COSEAlgorithm { type Error = CryptoError; fn try_from(i: i64) -> Result<Self, Self::Error> { match i {
i if i == COSEAlgorithm::RS512 as i64 => Ok(COSEAlgorithm::RS512),
i if i == COSEAlgorithm::RS384 as i64 => Ok(COSEAlgorithm::RS384),
i if i == COSEAlgorithm::RS256 as i64 => Ok(COSEAlgorithm::RS256),
i if i == COSEAlgorithm::ES256K as i64 => Ok(COSEAlgorithm::ES256K),
i if i == COSEAlgorithm::HSS_LMS as i64 => Ok(COSEAlgorithm::HSS_LMS),
i if i == COSEAlgorithm::SHAKE256 as i64 => Ok(COSEAlgorithm::SHAKE256),
i if i == COSEAlgorithm::SHA512 as i64 => Ok(COSEAlgorithm::SHA512),
i if i == COSEAlgorithm::SHA384 as i64 => Ok(COSEAlgorithm::SHA384),
i if i == COSEAlgorithm::RSAES_OAEP_SHA_512 as i64 => {
Ok(COSEAlgorithm::RSAES_OAEP_SHA_512)
}
i if i == COSEAlgorithm::RSAES_OAEP_SHA_256 as i64 => {
Ok(COSEAlgorithm::RSAES_OAEP_SHA_256)
}
i if i == COSEAlgorithm::RSAES_OAEP_RFC_8017_default as i64 => {
Ok(COSEAlgorithm::RSAES_OAEP_RFC_8017_default)
}
i if i == COSEAlgorithm::PS512 as i64 => Ok(COSEAlgorithm::PS512),
i if i == COSEAlgorithm::PS384 as i64 => Ok(COSEAlgorithm::PS384),
i if i == COSEAlgorithm::PS256 as i64 => Ok(COSEAlgorithm::PS256),
i if i == COSEAlgorithm::ES512 as i64 => Ok(COSEAlgorithm::ES512),
i if i == COSEAlgorithm::ES384 as i64 => Ok(COSEAlgorithm::ES384),
i if i == COSEAlgorithm::ECDH_SS_A256KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A256KW),
i if i == COSEAlgorithm::ECDH_SS_A192KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A192KW),
i if i == COSEAlgorithm::ECDH_SS_A128KW as i64 => Ok(COSEAlgorithm::ECDH_SS_A128KW),
i if i == COSEAlgorithm::ECDH_ES_A256KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A256KW),
i if i == COSEAlgorithm::ECDH_ES_A192KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A192KW),
i if i == COSEAlgorithm::ECDH_ES_A128KW as i64 => Ok(COSEAlgorithm::ECDH_ES_A128KW),
i if i == COSEAlgorithm::ECDH_SS_HKDF512 as i64 => Ok(COSEAlgorithm::ECDH_SS_HKDF512),
i if i == COSEAlgorithm::ECDH_SS_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_SS_HKDF256),
i if i == COSEAlgorithm::ECDH_ES_HKDF512 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF512),
i if i == COSEAlgorithm::ECDH_ES_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF256),
i if i == COSEAlgorithm::SHAKE128 as i64 => Ok(COSEAlgorithm::SHAKE128),
i if i == COSEAlgorithm::SHA512_256 as i64 => Ok(COSEAlgorithm::SHA512_256),
i if i == COSEAlgorithm::SHA256 as i64 => Ok(COSEAlgorithm::SHA256),
i if i == COSEAlgorithm::SHA256_64 as i64 => Ok(COSEAlgorithm::SHA256_64),
i if i == COSEAlgorithm::SHA1 as i64 => Ok(COSEAlgorithm::SHA1),
i if i == COSEAlgorithm::Direct_HKDF_AES256 as i64 => {
Ok(COSEAlgorithm::Direct_HKDF_AES256)
}
i if i == COSEAlgorithm::Direct_HKDF_AES128 as i64 => {
Ok(COSEAlgorithm::Direct_HKDF_AES128)
}
i if i == COSEAlgorithm::Direct_HKDF_SHA512 as i64 => {
Ok(COSEAlgorithm::Direct_HKDF_SHA512)
}
i if i == COSEAlgorithm::Direct_HKDF_SHA256 as i64 => {
Ok(COSEAlgorithm::Direct_HKDF_SHA256)
}
i if i == COSEAlgorithm::EDDSA as i64 => Ok(COSEAlgorithm::EDDSA),
i if i == COSEAlgorithm::ES256 as i64 => Ok(COSEAlgorithm::ES256),
i if i == COSEAlgorithm::Direct as i64 => Ok(COSEAlgorithm::Direct),
i if i == COSEAlgorithm::A256KW as i64 => Ok(COSEAlgorithm::A256KW),
i if i == COSEAlgorithm::A192KW as i64 => Ok(COSEAlgorithm::A192KW),
i if i == COSEAlgorithm::A128KW as i64 => Ok(COSEAlgorithm::A128KW),
i if i == COSEAlgorithm::A128GCM as i64 => Ok(COSEAlgorithm::A128GCM),
i if i == COSEAlgorithm::A192GCM as i64 => Ok(COSEAlgorithm::A192GCM),
i if i == COSEAlgorithm::A256GCM as i64 => Ok(COSEAlgorithm::A256GCM),
i if i == COSEAlgorithm::HMAC256_64 as i64 => Ok(COSEAlgorithm::HMAC256_64),
i if i == COSEAlgorithm::HMAC256_256 as i64 => Ok(COSEAlgorithm::HMAC256_256),
i if i == COSEAlgorithm::HMAC384_384 as i64 => Ok(COSEAlgorithm::HMAC384_384),
i if i == COSEAlgorithm::HMAC512_512 as i64 => Ok(COSEAlgorithm::HMAC512_512),
i if i == COSEAlgorithm::AES_CCM_16_64_128 as i64 => {
Ok(COSEAlgorithm::AES_CCM_16_64_128)
}
i if i == COSEAlgorithm::AES_CCM_16_64_256 as i64 => {
Ok(COSEAlgorithm::AES_CCM_16_64_256)
}
i if i == COSEAlgorithm::AES_CCM_64_64_128 as i64 => {
Ok(COSEAlgorithm::AES_CCM_64_64_128)
}
i if i == COSEAlgorithm::AES_CCM_64_64_256 as i64 => {
Ok(COSEAlgorithm::AES_CCM_64_64_256)
}
i if i == COSEAlgorithm::AES_MAC_128_64 as i64 => Ok(COSEAlgorithm::AES_MAC_128_64),
i if i == COSEAlgorithm::AES_MAC_256_64 as i64 => Ok(COSEAlgorithm::AES_MAC_256_64),
i if i == COSEAlgorithm::ChaCha20_Poly1305 as i64 => {
Ok(COSEAlgorithm::ChaCha20_Poly1305)
}
i if i == COSEAlgorithm::AES_MAC_128_128 as i64 => Ok(COSEAlgorithm::AES_MAC_128_128),
i if i == COSEAlgorithm::AES_MAC_256_128 as i64 => Ok(COSEAlgorithm::AES_MAC_256_128),
i if i == COSEAlgorithm::AES_CCM_16_128_128 as i64 => {
Ok(COSEAlgorithm::AES_CCM_16_128_128)
}
i if i == COSEAlgorithm::AES_CCM_16_128_256 as i64 => {
Ok(COSEAlgorithm::AES_CCM_16_128_256)
}
i if i == COSEAlgorithm::AES_CCM_64_128_128 as i64 => {
Ok(COSEAlgorithm::AES_CCM_64_128_128)
}
i if i == COSEAlgorithm::AES_CCM_64_128_256 as i64 => {
Ok(COSEAlgorithm::AES_CCM_64_128_256)
}
i if i == COSEAlgorithm::IV_GENERATION as i64 => Ok(COSEAlgorithm::IV_GENERATION),
i if i == COSEAlgorithm::INSECURE_RS1 as i64 => Ok(COSEAlgorithm::INSECURE_RS1),
_ => Err(CryptoError::UnknownAlgorithm),
}
}
}
/// A COSE Elliptic Curve Public Key. This is generally the provided credential /// that an authenticator registers, and is used to authenticate the user. #[derive(Clone, Debug, PartialEq, Eq)] pubstruct COSEEC2Key { /// The curve that this key references. pub curve: Curve, /// The key's public X coordinate. pub x: Vec<u8>, /// The key's public Y coordinate. pub y: Vec<u8>,
}
impl COSEEC2Key { // The SEC 1 uncompressed point format is "0x04 || x coordinate || y coordinate". // See Section 2.3.3 of "SEC 1: Elliptic Curve Cryptography" https://www.secg.org/sec1-v2.pdf. pubfn from_sec1_uncompressed(curve: Curve, key: &[u8]) -> Result<Self, CryptoError> { if !(curve == Curve::SECP256R1 && key.len() == 65) { return Err(CryptoError::UnsupportedCurve(curve));
} if key[0] != 0x04 { return Err(CryptoError::MalformedInput);
} let key = &key[1..]; let (x, y) = key.split_at(key.len() / 2);
Ok(COSEEC2Key {
curve,
x: x.to_vec(),
y: y.to_vec(),
})
}
/// A Octet Key Pair (OKP). /// The other version uses only the x-coordinate as the y-coordinate is /// either to be recomputed or not needed for the key agreement operation ('OKP'). #[derive(Clone, Debug, PartialEq, Eq)] pubstruct COSEOKPKey { /// The curve that this key references. pub curve: Curve, /// The key's public X coordinate. pub x: Vec<u8>,
}
/// A COSE RSA PublicKey. This is a provided credential from a registered authenticator. #[derive(Clone, Debug, PartialEq, Eq)] pubstruct COSERSAKey { /// An RSA modulus pub n: Vec<u8>, /// An RSA exponent pub e: Vec<u8>,
}
impl Serialize for COSEKeyTypeId { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{
serializer.serialize_i64(*selfas i64)
}
}
impl TryFrom<i64> for COSEKeyTypeId { type Error = CryptoError; fn try_from(i: i64) -> Result<Self, Self::Error> { match i {
i if i == COSEKeyTypeId::OKP as i64 => Ok(COSEKeyTypeId::OKP),
i if i == COSEKeyTypeId::EC2 as i64 => Ok(COSEKeyTypeId::EC2),
i if i == COSEKeyTypeId::RSA as i64 => Ok(COSEKeyTypeId::RSA),
_ => Err(CryptoError::UnknownKeyType),
}
}
}
/// The type of Key contained within a COSE value. You should never need /// to alter or change this type. #[allow(non_camel_case_types)] #[derive(Clone, Debug, PartialEq, Eq)] pubenum COSEKeyType { /// Identifies this as an Elliptic Curve EC2 key
EC2(COSEEC2Key), /// Identifies this as an Elliptic Curve octet key pair
OKP(COSEOKPKey), /// Identifies this as an RSA key
RSA(COSERSAKey),
}
/// A COSE Key as provided by the Authenticator. You should never need /// to alter or change these values. #[derive(Clone, Debug, PartialEq, Eq)] pubstruct COSEKey { /// COSE signature algorithm, indicating the type of key and hash type /// that should be used. pub alg: COSEAlgorithm, /// The public key pub key: COSEKeyType,
}
impl COSEKey { /// Generates a new key pair for the specified algorithm. /// Returns an PKCS#8 encoding of the private key, and the public key as a COSEKey. pubfn generate(alg: COSEAlgorithm) -> Result<(Vec<u8>, Self), CryptoError> { if alg != COSEAlgorithm::ES256 && alg != COSEAlgorithm::ECDH_ES_HKDF256 { return Err(CryptoError::UnsupportedAlgorithm(alg));
} let (private, public) = gen_p256()?; let cose_ec2_key = COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, &public)?; let public = COSEKey {
alg,
key: COSEKeyType::EC2(cose_ec2_key),
};
Ok((private, public))
}
whilelet Some(key) = map.next_key()? { // See https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters match key { 1 => { if key_type.is_some() { return Err(SerdeError::duplicate_field("key_type"));
} let value: i64 = map.next_value()?; let val = COSEKeyTypeId::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported key_type {value}"))
})?;
key_type = Some(val);
} 3 => { if alg.is_some() { return Err(SerdeError::duplicate_field("alg"));
} let value: i64 = map.next_value()?; let val = COSEAlgorithm::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported algorithm {value}"))
})?;
alg = Some(val);
}
-1 => match key_type {
None => return Err(SerdeError::missing_field("key_type")),
Some(COSEKeyTypeId::OKP) | Some(COSEKeyTypeId::EC2) => { if curve.is_some() { return Err(SerdeError::duplicate_field("curve"));
} let value: i64 = map.next_value()?; let val = Curve::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported curve {value}"))
})?;
curve = Some(val);
}
Some(COSEKeyTypeId::RSA) => { if n.is_some() { return Err(SerdeError::duplicate_field("n"));
} let value: ByteBuf = map.next_value()?;
n = Some(value.to_vec());
}
},
-2 => match key_type {
None => return Err(SerdeError::missing_field("key_type")),
Some(COSEKeyTypeId::OKP) | Some(COSEKeyTypeId::EC2) => { if x.is_some() { return Err(SerdeError::duplicate_field("x"));
} let value: ByteBuf = map.next_value()?;
x = Some(value.to_vec());
}
Some(COSEKeyTypeId::RSA) => { if e.is_some() { return Err(SerdeError::duplicate_field("e"));
} let value: ByteBuf = map.next_value()?;
e = Some(value.to_vec());
}
},
-3if key_type == Some(COSEKeyTypeId::EC2) => { if y.is_some() { return Err(SerdeError::duplicate_field("y"));
} let value: ByteBuf = map.next_value()?;
y = Some(value.to_vec());
}
other => { return Err(SerdeError::custom(format!("unexpected field: {other}")));
}
};
}
let key_type = key_type.ok_or_else(|| SerdeError::missing_field("key_type (1)"))?; let alg = alg.ok_or_else(|| SerdeError::missing_field("alg (3)"))?;
let res = match key_type {
COSEKeyTypeId::OKP => { let curve = curve.ok_or_else(|| SerdeError::missing_field("curve (-1)"))?; let x = x.ok_or_else(|| SerdeError::missing_field("x (-2)"))?;
COSEKeyType::OKP(COSEOKPKey { curve, x })
}
COSEKeyTypeId::EC2 => { let curve = curve.ok_or_else(|| SerdeError::missing_field("curve (-1)"))?; let x = x.ok_or_else(|| SerdeError::missing_field("x (-2)"))?; let y = y.ok_or_else(|| SerdeError::missing_field("y (-3)"))?;
COSEKeyType::EC2(COSEEC2Key { curve, x, y })
}
COSEKeyTypeId::RSA => { let n = n.ok_or_else(|| SerdeError::missing_field("n (-1)"))?; let e = e.ok_or_else(|| SerdeError::missing_field("e (-2)"))?;
COSEKeyType::RSA(COSERSAKey { e, n })
}
};
Ok(COSEKey { alg, key: res })
}
}
// We will only return MalformedInput here pubfn parse_u2f_der_certificate(data: &[u8]) -> Result<U2FRegisterAnswer, CryptoError> { // So we don't panic below, when accessing individual bytes if data.len() < 4 { return Err(CryptoError::MalformedInput);
} // Check if it is a SEQUENCE if data[0] != 0x30 { return Err(CryptoError::MalformedInput);
}
// This algorithm is taken from mozilla-central/security/nss/lib/mozpkix/lib/pkixder.cpp // The short form of length is a single byte with the high order bit set // to zero. The long form of length is one byte with the high order bit // set, followed by N bytes, where N is encoded in the lowest 7 bits of // the first byte. let end = if (data[1] & 0x80) == 0 { 2 + data[1] as usize
} elseif data[1] == 0x81 { // The next byte specifies the length
if data[2] < 128 { // Not shortest possible encoding // Forbidden by DER-format return Err(CryptoError::MalformedInput);
} 3 + data[2] as usize
} elseif data[1] == 0x82 { // The next 2 bytes specify the length let l = u16::from_be_bytes([data[2], data[3]]); if l < 256 { // Not shortest possible encoding // Forbidden by DER-format return Err(CryptoError::MalformedInput);
} 4 + l as usize
} else { // We don't support lengths larger than 2^16 - 1. return Err(CryptoError::MalformedInput);
};
if data.len() < end { return Err(CryptoError::MalformedInput);
}
#[test] fn test_okp_key_to_cbor() { let okp_key = COSEOKPKey {
curve: Curve::Ed25519,
x: SAMPLE_ED25519_KEY.to_vec(),
}; let cose_key = COSEKey {
alg: COSEAlgorithm::EDDSA,
key: COSEKeyType::OKP(okp_key),
}; let cose_key_cbor = serde_cbor::to_vec(&cose_key).expect("Failed to serialize key"); let actual = serde_cbor::from_slice(&cose_key_cbor).expect("Failed to deserialize key");
assert_eq!(cose_key, actual);
}
#[test] fn test_parse_es256_serialize_key() { // Test values taken from https://github.com/Yubico/python-fido2/blob/master/test/test_cose.py let key_data = decode_hex("A5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C"); let key: COSEKey = from_slice(&key_data).unwrap();
assert_eq!(key.alg, COSEAlgorithm::ES256); iflet COSEKeyType::EC2(ec2key) = &key.key {
assert_eq!(ec2key.curve, Curve::SECP256R1);
assert_eq!(
ec2key.x,
decode_hex("A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1")
);
assert_eq!(
ec2key.y,
decode_hex("FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C")
);
} else {
panic!("Wrong key type!");
}
let serialized = serde_cbor::to_vec(&key).expect("Failed to serialize key");
assert_eq!(key_data, serialized);
}
#[test] #[allow(non_snake_case)] fn test_shared_secret() { // Test values taken from https://github.com/Yubico/python-fido2/blob/main/tests/test_ctap2.py let EC_PRIV =
decode_hex("7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684"); let EC_PUB_X =
decode_hex("44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F"); let EC_PUB_Y =
decode_hex("EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9"); let DEV_PUB_X =
decode_hex("0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168"); let DEV_PUB_Y =
decode_hex("D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47"); let SHARED = decode_hex("c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c"); let TOKEN_ENC = decode_hex("7A9F98E31B77BE90F9C64D12E9635040"); let TOKEN = decode_hex("aff12c6dcfbf9df52f7a09211e8865cd"); let PIN_HASH_ENC = decode_hex("afe8327ce416da8ee3d057589c2ce1a9");
// We are using `test_cose_ec2_p256_ecdh_sha256()` here, because we need a way to hand in // the private key which would be generated on the fly otherwise (ephemeral keys), // to predict the outputs let peer_spki = peer_ec2_key.der_spki().unwrap(); let shared_point = test_ecdh_p256_raw(&peer_spki, &EC_PUB_X, &EC_PUB_Y, &EC_PRIV).unwrap(); let shared_secret = SharedSecret {
pin_protocol: PinUvAuthProtocol(Box::new(PinUvAuth1 {})),
key: sha256(&shared_point).unwrap(),
inputs: PublicInputs {
client: COSEKey {
alg: COSEAlgorithm::ES256,
key: COSEKeyType::EC2(client_ec2_key),
},
peer: COSEKey {
alg: COSEAlgorithm::ES256,
key: COSEKeyType::EC2(peer_ec2_key),
},
},
};
assert_eq!(shared_secret.key, SHARED);
let token_enc = shared_secret.encrypt(&TOKEN).unwrap();
assert_eq!(token_enc, TOKEN_ENC);
let token = shared_secret.decrypt(&TOKEN_ENC).unwrap();
assert_eq!(token, TOKEN);
let pin = Pin::new("1234"); let pin_hash_enc = shared_secret.encrypt(&pin.for_pin_token()).unwrap();
assert_eq!(pin_hash_enc, PIN_HASH_ENC);
}
#[test] fn test_pin_uv_auth2_kdf() { // We don't pull a complete HKDF implementation from the crypto backend, so we need to // check that PinUvAuth2::kdf makes the right sequence of HMAC-SHA256 calls. // // ```python // from cryptography.hazmat.primitives.kdf.hkdf import HKDF // from cryptography.hazmat.primitives import hashes // from cryptography.hazmat.backends import default_backend // // Z = b"\xFF" * 32 // // hmac_key = HKDF( // algorithm=hashes.SHA256(), // length=32, // salt=b"\x00" * 32, // info=b"CTAP2 HMAC key", // ).derive(Z) // // aes_key = HKDF( // algorithm=hashes.SHA256(), // length=32, // salt=b"\x00" * 32, // info=b"CTAP2 AES key", // ).derive(Z) // // print((hmac_key+aes_key).hex()) // ``` let input = decode_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); let expected = decode_hex("570B4ED82AA5DFB49DB79DBEAF4B315D8ABB1A9867B245F3367026987C0D47A17D9A93C39BAEC741D141C6238D8E1846DE323D8EED022CB397D19A73B98945E2"); let output = PinUvAuth2 {}.kdf(&input).unwrap();
assert_eq!(&expected, &output);
}
#[test] fn test_hmac_sha256() { let key = "key"; let message = "The quick brown fox jumps over the lazy dog"; let expected =
decode_hex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
let result = hmac_sha256(key.as_bytes(), message.as_bytes()).expect("HMAC-SHA256 failed");
assert_eq!(result, expected);
let key = "The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog"; let message = "message"; let expected =
decode_hex("5597b93a2843078cbb0c920ae41dfe20f1685e10c67e423c11ab91adfc319d12");
let result = hmac_sha256(key.as_bytes(), message.as_bytes()).expect("HMAC-SHA256 failed");
assert_eq!(result, expected);
}
#[test] fn test_pin_encryption_and_hashing() { let pin = "1234";
// Valid pin_protocols
info.pin_protocols = Some(vec![1, 2]); let pin = PinUvAuthProtocol::try_from(&info).unwrap();
assert_eq!(pin.id(), 1); // The one listed first
info.pin_protocols = None; // No PIN protocols. CTAP1 - not supported
info.versions = vec![AuthenticatorVersion::U2F_V2];
PinUvAuthProtocol::try_from(&info).unwrap_err();
// No PIN protocols. CTAP2.0 - Fallback to 1
info.versions = vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0]; let pin = PinUvAuthProtocol::try_from(&info).unwrap();
assert_eq!(pin.id(), 1);
// No PIN protocols. CTAP2.1 - Fallback to 2
info.versions = vec![AuthenticatorVersion::FIDO_2_1]; let pin = PinUvAuthProtocol::try_from(&info).unwrap();
assert_eq!(pin.id(), 2);
// No PIN protocols. CTAP2.1_PRE - Fallback to 2
info.versions = vec![
AuthenticatorVersion::FIDO_2_0,
AuthenticatorVersion::FIDO_2_1_PRE,
]; let pin = PinUvAuthProtocol::try_from(&info).unwrap();
assert_eq!(pin.id(), 2);
}
#[test] #[cfg(feature = "crypto_nss")] fn test_sign() { let (good_private, good_public) =
COSEKey::generate(COSEAlgorithm::ES256).expect("could not generate a key pair"); let good_spki = match good_public.key {
COSEKeyType::EC2(ref x) => x.der_spki().expect("could not serialize public key"),
_ => unreachable!(),
};
let good_data = vec![1, 2, 3, 4, 5, 6, 7, 8]; let good_signature =
ecdsa_p256_sha256_sign_raw(&good_private, &good_data).expect("could not sign"); let good_signature2 =
ecdsa_p256_sha256_sign_raw(&good_private, &good_data).expect("could not sign");
// Signing is randomized
assert_ne!(good_signature, good_signature2);
// Good signature verifies
assert!(test_ecdsa_p256_sha256_verify_raw(&good_spki, &good_signature, &good_data).is_ok());
// Wrong data does not verify let other_data = vec![0, 0, 0, 0, 5, 6, 7, 8];
assert!(
test_ecdsa_p256_sha256_verify_raw(&good_spki, &good_signature, &other_data).is_err()
);
// Wrong signature does not verify let other_signature =
ecdsa_p256_sha256_sign_raw(&good_private, &other_data).expect("could not sign");
assert!(
test_ecdsa_p256_sha256_verify_raw(&good_spki, &other_signature, &good_data).is_err()
);
// Wrong key does not verify let (_, other_public) =
COSEKey::generate(COSEAlgorithm::ES256).expect("could not generate a key pair"); let other_spki = match other_public.key {
COSEKeyType::EC2(ref x) => x.der_spki().expect("could not serialize public key"),
_ => unreachable!(),
};
assert!(
test_ecdsa_p256_sha256_verify_raw(&other_spki, &good_signature, &good_data).is_err()
);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet am 2026-06-17)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.