diff options
Diffstat (limited to 'third_party/rust/authenticator/src/crypto')
-rw-r--r-- | third_party/rust/authenticator/src/crypto/der.rs | 185 | ||||
-rw-r--r-- | third_party/rust/authenticator/src/crypto/dummy.rs | 57 | ||||
-rw-r--r-- | third_party/rust/authenticator/src/crypto/mod.rs | 1638 | ||||
-rw-r--r-- | third_party/rust/authenticator/src/crypto/nss.rs | 481 | ||||
-rw-r--r-- | third_party/rust/authenticator/src/crypto/openssl.rs | 183 |
5 files changed, 2544 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/crypto/der.rs b/third_party/rust/authenticator/src/crypto/der.rs new file mode 100644 index 0000000000..39c5e0b676 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/der.rs @@ -0,0 +1,185 @@ +use super::CryptoError; + +pub const TAG_INTEGER: u8 = 0x02; +pub const TAG_BIT_STRING: u8 = 0x03; +#[cfg(all(test, feature = "crypto_nss"))] +pub const TAG_OCTET_STRING: u8 = 0x04; +pub const TAG_NULL: u8 = 0x05; +pub const TAG_OBJECT_ID: u8 = 0x06; +pub const TAG_SEQUENCE: u8 = 0x30; + +// Object identifiers in DER tag-length-value form +pub const OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[ + /* RFC 5480 (id-ecPublicKey) */ + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, +]; +pub const OID_SECP256R1_BYTES: &[u8] = &[ + /* RFC 5480 (secp256r1) */ + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, +]; +pub const OID_ED25519_BYTES: &[u8] = &[/* RFC 8410 (id-ed25519) */ 0x2b, 0x65, 0x70]; +pub const OID_RS256_BYTES: &[u8] = &[ + /* RFC 4055 (sha256WithRSAEncryption) */ + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, +]; + +pub type Result<T> = std::result::Result<T, CryptoError>; + +const MAX_TAG_AND_LENGTH_BYTES: usize = 4; +fn write_tag_and_length(out: &mut Vec<u8>, tag: u8, len: usize) -> Result<()> { + if len > 0xFFFF { + return Err(CryptoError::LibraryFailure); + } + out.push(tag); + if len > 0xFF { + out.push(0x82); + out.push((len >> 8) as u8); + out.push(len as u8); + } else if len > 0x7F { + out.push(0x81); + out.push(len as u8); + } else { + out.push(len as u8); + } + Ok(()) +} + +pub fn integer(val: &[u8]) -> Result<Vec<u8>> { + if val.is_empty() { + return Err(CryptoError::MalformedInput); + } + // trim leading zeros, leaving a single zero if the input is the zero vector. + let mut val = val; + while val.len() > 1 && val[0] == 0 { + val = &val[1..]; + } + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + 1 + val.len()); + if val[0] & 0x80 != 0 { + // needs zero prefix + write_tag_and_length(&mut out, TAG_INTEGER, 1 + val.len())?; + out.push(0x00); + out.extend_from_slice(val); + } else { + write_tag_and_length(&mut out, TAG_INTEGER, val.len())?; + out.extend_from_slice(val); + } + Ok(out) +} + +pub fn bit_string(val: &[u8]) -> Result<Vec<u8>> { + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + 1 + val.len()); + write_tag_and_length(&mut out, TAG_BIT_STRING, 1 + val.len())?; + out.push(0x00); // trailing bits aren't supported + out.extend_from_slice(val); + Ok(out) +} + +pub fn null() -> Result<Vec<u8>> { + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES); + write_tag_and_length(&mut out, TAG_NULL, 0)?; + Ok(out) +} + +pub fn object_id(val: &[u8]) -> Result<Vec<u8>> { + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + val.len()); + write_tag_and_length(&mut out, TAG_OBJECT_ID, val.len())?; + out.extend_from_slice(val); + Ok(out) +} + +pub fn sequence(items: &[&[u8]]) -> Result<Vec<u8>> { + let len = items.iter().map(|i| i.len()).sum(); + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + len); + write_tag_and_length(&mut out, TAG_SEQUENCE, len)?; + for item in items { + out.extend_from_slice(item); + } + Ok(out) +} + +#[cfg(all(test, feature = "crypto_nss"))] +pub fn octet_string(val: &[u8]) -> Result<Vec<u8>> { + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + val.len()); + write_tag_and_length(&mut out, TAG_OCTET_STRING, val.len())?; + out.extend_from_slice(val); + Ok(out) +} + +#[cfg(all(test, feature = "crypto_nss"))] +pub fn context_specific_explicit_tag(tag: u8, content: &[u8]) -> Result<Vec<u8>> { + let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + content.len()); + write_tag_and_length(&mut out, 0xa0 + tag, content.len())?; + out.extend_from_slice(content); + Ok(out) +} + +// Given "tag || len || value || rest" where tag and len are of length one, len is in [0, 127], +// and value is of length len, returns (value, rest) +#[cfg(all(test, feature = "crypto_nss"))] +fn expect_tag_with_short_len(tag: u8, z: &[u8]) -> Result<(&[u8], &[u8])> { + if z.is_empty() { + return Err(CryptoError::MalformedInput); + } + let (h, z) = z.split_at(1); + if h[0] != tag || z.is_empty() { + return Err(CryptoError::MalformedInput); + } + let (h, z) = z.split_at(1); + if h[0] >= 0x80 || h[0] as usize > z.len() { + return Err(CryptoError::MalformedInput); + } + Ok(z.split_at(h[0] as usize)) +} + +// Given a DER encoded RFC 3279 Ecdsa-Sig-Value, +// Ecdsa-Sig-Value ::= SEQUENCE { +// r INTEGER, +// s INTEGER }, +// with r and s < 2^256, returns a 64 byte array containing +// r and s encoded as 32 byte zero-padded big endian unsigned +// integers +#[cfg(all(test, feature = "crypto_nss"))] +pub fn read_p256_sig(z: &[u8]) -> Result<Vec<u8>> { + // Strip the tag and length. + let (z, rest) = expect_tag_with_short_len(TAG_SEQUENCE, z)?; + + // The input should not have any trailing data. + if !rest.is_empty() { + return Err(CryptoError::MalformedInput); + } + + let read_u256 = |z| -> Result<(&[u8], &[u8])> { + let (r, z) = expect_tag_with_short_len(TAG_INTEGER, z)?; + // We're expecting r < 2^256, so no more than 33 bytes as a signed integer. + if r.is_empty() || r.len() > 33 { + return Err(CryptoError::MalformedInput); + } + // If it is 33 bytes the leading byte must be zero. + if r.len() == 33 && r[0] != 0 { + return Err(CryptoError::MalformedInput); + } + // Ensure r is no more than 32 bytes. + if r.len() == 33 { + Ok((&r[1..], z)) + } else { + Ok((r, z)) + } + }; + + let (r, z) = read_u256(z)?; + let (s, z) = read_u256(z)?; + + // We should have consumed the entire buffer + if !z.is_empty() { + return Err(CryptoError::MalformedInput); + } + + // Left pad each integer with zeros to length 32 and concatenate the results + let mut out = vec![0u8; 64]; + { + let (r_out, s_out) = out.split_at_mut(32); + r_out[32 - r.len()..].copy_from_slice(r); + s_out[32 - s.len()..].copy_from_slice(s); + } + Ok(out) +} diff --git a/third_party/rust/authenticator/src/crypto/dummy.rs b/third_party/rust/authenticator/src/crypto/dummy.rs new file mode 100644 index 0000000000..9d16856958 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/dummy.rs @@ -0,0 +1,57 @@ +use super::CryptoError; + +/* +This is a dummy implementation for CI, to avoid having to install NSS or openSSL in the CI-pipeline +*/ + +pub type Result<T> = std::result::Result<T, CryptoError>; + +pub fn ecdhe_p256_raw(_peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> { + unimplemented!() +} + +pub fn encrypt_aes_256_cbc_no_pad( + _key: &[u8], + _iv: Option<&[u8]>, + _data: &[u8], +) -> Result<Vec<u8>> { + unimplemented!() +} + +pub fn decrypt_aes_256_cbc_no_pad( + _key: &[u8], + _iv: Option<&[u8]>, + _data: &[u8], +) -> Result<Vec<u8>> { + unimplemented!() +} + +pub fn hmac_sha256(_key: &[u8], _data: &[u8]) -> Result<Vec<u8>> { + unimplemented!() +} + +pub fn sha256(_data: &[u8]) -> Result<Vec<u8>> { + unimplemented!() +} + +pub fn random_bytes(_count: usize) -> Result<Vec<u8>> { + unimplemented!() +} + +pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> { + unimplemented!() +} + +pub fn ecdsa_p256_sha256_sign_raw(_private: &[u8], _data: &[u8]) -> Result<Vec<u8>> { + unimplemented!() +} + +#[allow(dead_code)] +#[cfg(test)] +pub fn test_ecdsa_p256_sha256_verify_raw( + _public: &[u8], + _signature: &[u8], + _data: &[u8], +) -> Result<()> { + unimplemented!() +} diff --git a/third_party/rust/authenticator/src/crypto/mod.rs b/third_party/rust/authenticator/src/crypto/mod.rs new file mode 100644 index 0000000000..fd74030ed7 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/mod.rs @@ -0,0 +1,1638 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ctap2::commands::client_pin::PinUvAuthTokenPermission; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::errors::AuthenticatorError; +use crate::{ctap2::commands::CommandError, transport::errors::HIDError}; +use serde::{ + de::{Error as SerdeError, MapAccess, Unexpected, Visitor}, + ser::SerializeMap, + 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")] +use self::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, +}; + +mod der; + +pub use backend::ecdsa_p256_sha256_sign_raw; + +pub struct PinUvAuthProtocol(Box<dyn PinProtocolImpl + Send + Sync>); +impl PinUvAuthProtocol { + pub fn id(&self) -> u64 { + self.0.protocol_id() + } + pub fn encapsulate(&self, peer_cose_key: &COSEKey) -> Result<SharedSecret, CryptoError> { + self.0.encapsulate(peer_cose_key) + } +} + +/// 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()) + } +} + +impl Clone for PinUvAuthProtocol { + fn clone(&self) -> Self { + PinUvAuthProtocol(self.0.as_ref().clone_box()) + } +} + +/// 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), + }; + + let shared_secret = SharedSecret { + pin_protocol: PinUvAuthProtocol(self.clone_box()), + key: self.kdf(&shared_point)?, + inputs: PublicInputs { + peer: peer_cose_key.clone(), + client: client_cose_key, + }, + }; + + Ok(shared_secret) + } +} + +impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol { + type Error = CommandError; + + fn try_from(info: &AuthenticatorInfo) -> Result<Self, Self::Error> { + // CTAP 2.1, Section 6.5.5.4 + // "If there are multiple mutually supported protocols, and the platform + // has no preference, it SHOULD select the one listed first in + // pinUvAuthProtocols." + if let Some(pin_protocols) = &info.pin_protocols { + for proto_id in pin_protocols.iter() { + match proto_id { + 1 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))), + 2 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))), + _ => continue, + } + } + } else { + match info.max_supported_version() { + crate::ctap2::commands::get_info::AuthenticatorVersion::U2F_V2 => { + return Err(CommandError::UnsupportedPinProtocol) + } + crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_0 => { + return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))) + } + crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1_PRE + | crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1 => { + return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))) + } + } + } + Err(CommandError::UnsupportedPinProtocol) + } +} + +impl fmt::Debug for PinUvAuthProtocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PinUvAuthProtocol") + .field("id", &self.id()) + .finish() + } +} + +/// CTAP 2.1, Section 6.5.6. +#[derive(Copy, Clone)] +pub struct PinUvAuth1; + +impl PinProtocolImpl for PinUvAuth1 { + fn protocol_id(&self) -> u64 { + 1 + } + + fn initialize(&self) {} + + 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. + let mut hmac = hmac_sha256(key, message)?; + hmac.truncate(16); + Ok(hmac) + } + + fn kdf(&self, z: &[u8]) -> Result<Vec<u8>, CryptoError> { + // kdf(Z) → sharedSecret + // Return SHA-256(Z) + sha256(z) + } +} + +/// CTAP 2.1, Section 6.5.7. +#[derive(Copy, Clone)] +pub struct PinUvAuth2; + +impl PinProtocolImpl for PinUvAuth2 { + fn protocol_id(&self) -> u64 { + 2 + } + + fn initialize(&self) {} + + 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)?; + let mut ct = encrypt_aes_256_cbc_no_pad(key, Some(&iv), plaintext)?; + + let mut 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. + + let prk = hmac_sha256(&[0u8; 32], z)?; + let mut shared_secret = hmac_sha256(&prk, "CTAP2 HMAC key\x01".as_bytes())?; + shared_secret.append(&mut hmac_sha256(&prk, "CTAP2 AES key\x01".as_bytes())?); + Ok(shared_secret) + } +} + +#[derive(Clone, Debug)] +struct PublicInputs { + client: COSEKey, + peer: COSEKey, +} + +#[derive(Clone, Debug)] +pub struct SharedSecret { + pub pin_protocol: PinUvAuthProtocol, + key: Vec<u8>, + inputs: PublicInputs, +} + +impl SharedSecret { + pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> { + self.pin_protocol.0.encrypt(&self.key, plaintext) + } + pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { + self.pin_protocol.0.decrypt(&self.key, ciphertext) + } + pub fn decrypt_pin_token( + &self, + permissions: PinUvAuthTokenPermission, + encrypted_pin_token: &[u8], + ) -> Result<PinUvAuthToken, CryptoError> { + let pin_token = self.decrypt(encrypted_pin_token)?; + Ok(PinUvAuthToken { + pin_protocol: self.pin_protocol.clone(), + pin_token, + permissions, + }) + } + pub fn authenticate(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> { + self.pin_protocol.0.authenticate(&self.key, message) + } + pub fn client_input(&self) -> &COSEKey { + &self.inputs.client + } + pub fn peer_input(&self) -> &COSEKey { + &self.inputs.peer + } +} + +#[derive(Clone, Debug)] +pub struct PinUvAuthToken { + pub pin_protocol: PinUvAuthProtocol, + pin_token: Vec<u8>, + pub permissions: PinUvAuthTokenPermission, +} + +impl PinUvAuthToken { + pub fn derive(self, message: &[u8]) -> Result<PinUvAuthParam, CryptoError> { + let pin_auth = self.pin_protocol.0.authenticate(&self.pin_token, message)?; + Ok(PinUvAuthParam { + pin_auth, + pin_protocol: self.pin_protocol, + permissions: self.permissions, + }) + } +} + +#[derive(Clone, Debug)] +pub struct PinUvAuthParam { + pin_auth: Vec<u8>, + pub pin_protocol: PinUvAuthProtocol, + #[allow(dead_code)] // Not yet used + permissions: PinUvAuthTokenPermission, +} + +impl PinUvAuthParam { + pub(crate) fn create_empty() -> Self { + let pin_protocol = PinUvAuthProtocol(Box::new(PinUvAuth1 {})); + Self { + pin_auth: vec![], + pin_protocol, + permissions: PinUvAuthTokenPermission::empty(), + } + } +} + +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)] +pub enum 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(*self as 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)] +pub enum 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(*self as 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; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a signed integer") + } + + fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> + where + E: SerdeError, + { + COSEAlgorithm::try_from(v).map_err(|_| { + SerdeError::invalid_value(Unexpected::Signed(v), &"valid COSEAlgorithm") + }) + } + } + + deserializer.deserialize_any(COSEAlgorithmVisitor) + } +} + +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)] +pub struct 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. + pub fn 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(), + }) + } + + pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> { + if self.curve != Curve::SECP256R1 { + return Err(CryptoError::UnsupportedCurve(self.curve)); + } + + // SubjectPublicKeyInfo + der::sequence(&[ + // algorithm: AlgorithmIdentifier + &der::sequence(&[ + // algorithm + &der::object_id(der::OID_EC_PUBLIC_KEY_BYTES)?, + // parameters + &der::object_id(der::OID_SECP256R1_BYTES)?, + ])?, + // subjectPublicKey + &der::bit_string( + // SEC 1 uncompressed format + &[&[0x04], self.x.as_slice(), self.y.as_slice()].concat(), + )?, + ]) + } +} + +/// 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)] +pub struct COSEOKPKey { + /// The curve that this key references. + pub curve: Curve, + /// The key's public X coordinate. + pub x: Vec<u8>, +} + +impl COSEOKPKey { + pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> { + if self.curve != Curve::Ed25519 { + return Err(CryptoError::UnsupportedCurve(self.curve)); + } + + // SubjectPublicKeyInfo + der::sequence(&[ + // algorithm: AlgorithmIdentifier + &der::sequence(&[ + // algorithm + &der::object_id(der::OID_ED25519_BYTES)?, + // parameters + // (absent as per RFC 8410) + ])?, + // subjectPublicKey + &der::bit_string( + // RFC 8410 + self.x.as_slice(), + )?, + ]) + } +} + +/// A COSE RSA PublicKey. This is a provided credential from a registered authenticator. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct COSERSAKey { + /// An RSA modulus + pub n: Vec<u8>, + /// An RSA exponent + pub e: Vec<u8>, +} + +impl COSERSAKey { + pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> { + // SubjectPublicKeyInfo + der::sequence(&[ + // algorithm: AlgorithmIdentifier + &der::sequence(&[ + // algorithm + &der::object_id(der::OID_RS256_BYTES)?, + // parameters + &der::null()?, + ])?, + // subjectPublicKey + &der::bit_string( + // RFC 4055 RSAPublicKey + &der::sequence(&[&der::integer(&self.n)?, &der::integer(&self.e)?])?, + )?, + ]) + } +} + +// https://tools.ietf.org/html/rfc8152#section-13 +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum COSEKeyTypeId { + // Reserved is invalid + // Reserved = 0, + /// Octet Key Pair + OKP = 1, + /// Elliptic Curve Keys w/ x- and y-coordinate + EC2 = 2, + /// RSA + RSA = 3, +} + +impl Serialize for COSEKeyTypeId { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_i64(*self as 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)] +pub enum 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)] +pub struct 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. + pub fn 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)) + } + + pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> { + match &self.key { + COSEKeyType::EC2(ec2_key) => ec2_key.der_spki(), + COSEKeyType::OKP(okp_key) => okp_key.der_spki(), + COSEKeyType::RSA(rsa_key) => rsa_key.der_spki(), + } + } +} + +impl<'de> Deserialize<'de> for COSEKey { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct COSEKeyVisitor; + + impl<'de> Visitor<'de> for COSEKeyVisitor { + type Value = COSEKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error> + where + M: MapAccess<'de>, + { + let mut key_type: Option<COSEKeyTypeId> = None; + let mut alg: Option<COSEAlgorithm> = None; + // OKP / EC2 + let mut curve: Option<Curve> = None; + let mut x: Option<Vec<u8>> = None; + let mut y: Option<Vec<u8>> = None; + + // RSA specific + let mut n: Option<Vec<u8>> = None; + let mut e: Option<Vec<u8>> = None; + + while let 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()); + } + }, + -3 if 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 }) + } + } + + deserializer.deserialize_bytes(COSEKeyVisitor) + } +} + +impl Serialize for COSEKey { + fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> + where + S: Serializer, + { + let map_len = match &self.key { + COSEKeyType::OKP(_) => 4, + COSEKeyType::EC2(_) => 5, + COSEKeyType::RSA(_) => 4, + }; + let mut map = serializer.serialize_map(Some(map_len))?; + match &self.key { + COSEKeyType::OKP(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::OKP)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &key.curve)?; + map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.x))?; + } + COSEKeyType::EC2(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::EC2)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &key.curve)?; + map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.x))?; + map.serialize_entry(&-3, &serde_bytes::Bytes::new(&key.y))?; + } + COSEKeyType::RSA(key) => { + map.serialize_entry(&1, &COSEKeyTypeId::RSA)?; + map.serialize_entry(&3, &self.alg)?; + map.serialize_entry(&-1, &serde_bytes::Bytes::new(&key.n))?; + map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.e))?; + } + } + + map.end() + } +} + +/// Errors that can be returned from COSE functions. +#[derive(Debug, Clone, Serialize)] +pub enum CryptoError { + // DecodingFailure, + LibraryFailure, + MalformedInput, + // MissingHeader, + // UnexpectedHeaderValue, + // UnexpectedTag, + // UnexpectedType, + // Unimplemented, + // VerificationFailed, + // SigningFailed, + // InvalidArgument, + UnknownKeyType, + UnknownSignatureScheme, + UnknownAlgorithm, + WrongSaltLength, + UnsupportedAlgorithm(COSEAlgorithm), + UnsupportedCurve(Curve), + UnsupportedKeyType, + Backend(String), +} + +impl From<CryptoError> for CommandError { + fn from(e: CryptoError) -> Self { + CommandError::Crypto(e) + } +} + +impl From<CryptoError> for AuthenticatorError { + fn from(e: CryptoError) -> Self { + AuthenticatorError::HIDError(HIDError::Command(CommandError::Crypto(e))) + } +} + +pub struct U2FRegisterAnswer<'a> { + pub certificate: &'a [u8], + pub signature: &'a [u8], +} + +// We will only return MalformedInput here +pub fn 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 + } else if 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 + } else if 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); + } + + Ok(U2FRegisterAnswer { + certificate: &data[0..end], + signature: &data[end..], + }) +} + +#[cfg(all(test, not(feature = "crypto_dummy")))] +mod test { + use std::convert::TryFrom; + + #[cfg(feature = "crypto_nss")] + use super::backend::{ecdsa_p256_sha256_sign_raw, test_ecdsa_p256_sha256_verify_raw}; + use super::{ + backend::hmac_sha256, backend::sha256, backend::test_ecdh_p256_raw, COSEAlgorithm, COSEKey, + Curve, PinProtocolImpl, PinUvAuth1, PinUvAuth2, PinUvAuthProtocol, PublicInputs, + SharedSecret, + }; + use crate::crypto::{COSEEC2Key, COSEKeyType, COSEOKPKey, COSERSAKey}; + use crate::ctap2::attestation::AAGuid; + use crate::ctap2::commands::client_pin::Pin; + use crate::ctap2::commands::get_info::{ + tests::AAGUID_RAW, AuthenticatorOptions, AuthenticatorVersion, + }; + use crate::util::decode_hex; + use crate::AuthenticatorInfo; + use serde_cbor::de::from_slice; + + // Extracted from RFC 8410 Example 10.1 + const SAMPLE_ED25519_KEY: &[u8] = &[ + 0x19, 0xbf, 0x44, 0x09, 0x69, 0x84, 0xcd, 0xfe, 0x85, 0x41, 0xba, 0xc1, 0x67, 0xdc, 0x3b, + 0x96, 0xc8, 0x50, 0x86, 0xaa, 0x30, 0xb6, 0xb6, 0xcb, 0x0c, 0x5c, 0x38, 0xad, 0x70, 0x31, + 0x66, 0xe1, + ]; + + const SAMPLE_P256_X: &[u8] = &[ + 0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75, 0x67, + 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33, 0x05, 0xe3, + 0x1a, 0x80, + ]; + const SAMPLE_P256_Y: &[u8] = &[ + 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, 0xac, 0xf9, 0xd8, 0xe1, + 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, 0x2c, 0x9a, 0xad, 0x6d, 0xfa, + 0x8b, 0x27, + ]; + + const SAMPLE_RSA_MODULUS: &[u8] = &[ + 0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1, + 0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27, + 0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f, + 0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b, + 0x1b, 0x8d, 0xb7, 0x8d, 0x2d, 0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1, + 0x84, 0xa1, 0x40, 0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68, + 0xbd, 0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a, 0x1d, + 0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e, 0xbe, 0x12, 0xbd, + 0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1, + 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30, 0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9, + 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2, + 0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4, + 0xcb, 0xac, 0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49, + 0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a, 0x99, 0x41, + 0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20, + 0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, + 0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed, 0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a, + 0xa5, + ]; + + #[test] + fn test_rsa_key_to_der_spki() { + // $ ascii2der | xxd -i + // SEQUENCE { + // SEQUENCE { + // # sha256WithRSAEncryption + // OBJECT_IDENTIFIER { 1.2.840.113549.1.1.11 } + // NULL {} + // } + // BIT_STRING { + // `00` + // SEQUENCE { + // INTEGER { `00d4d253ed7a69b184c9fb70300c51b18f896cb1316d87bee1c7f7b04fe727a7b77c552037a8ac40f4bc59c4928f135b5ea71805ccd79cfb886cf1bc6b1b8db78d2daacbeedbab493677e5d184a1403ff6f7986caa24483044dc68bd9e7437af271290740d9e3ca53a1db85492d46d1ff939b81d8a5ebe12bde29cf25a485d712c71726dd2cb37b1e62f7643daca44307b28e7e4eca9c91a5fe5510325607c5a69124d50fdb2b86e13b292da0e31c9f19cde1763e4cbacd5ee8406de672db8d2e14bbb49ea45d4a17f46f2d60c059d1d1a9941205e1aa4cc2144588bcd98e43d5320fcfc7b9f4335fb383723d076e33d4f899b89328189ed58c08018835baf5aa5` } + // INTEGER { 65537 } + // } + // } + // } + let expected: &[u8] = &[ + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, + 0x02, 0x82, 0x01, 0x01, 0x00, 0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, + 0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1, 0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, + 0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27, 0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, + 0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f, 0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, + 0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d, + 0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1, 0x84, 0xa1, 0x40, 0x3f, + 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68, 0xbd, 0x9e, 0x74, + 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a, 0x1d, 0xb8, 0x54, + 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e, 0xbe, 0x12, 0xbd, 0xe2, + 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1, + 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30, 0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, + 0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, + 0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, + 0x17, 0x63, 0xe4, 0xcb, 0xac, 0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, + 0xe1, 0x4b, 0xbb, 0x49, 0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, + 0x9d, 0x1d, 0x1a, 0x99, 0x41, 0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, + 0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, + 0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed, + 0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a, 0xa5, 0x02, 0x03, 0x01, 0x00, 0x01, + ]; + let rsa_key = COSERSAKey { + e: vec![1, 0, 1], + n: SAMPLE_RSA_MODULUS.to_vec(), + }; + let cose_key: COSEKey = COSEKey { + alg: COSEAlgorithm::RS256, + key: COSEKeyType::RSA(rsa_key), + }; + let actual = cose_key.der_spki().expect("Failed to serialize to SPKI"); + assert_eq!(expected, &actual); + } + + #[test] + fn test_rsa_key_to_cbor() { + let key = COSERSAKey { + e: vec![1, 0, 1], + n: SAMPLE_RSA_MODULUS.to_vec(), + }; + let cose_key: COSEKey = COSEKey { + alg: COSEAlgorithm::RS256, + key: COSEKeyType::RSA(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_ec2_key_to_der_spki() { + // $ ascii2der | xxd -i + // SEQUENCE { + // SEQUENCE { + // # ecPublicKey + // OBJECT_IDENTIFIER { 1.2.840.10045.2.1 } + // # secp256r1 + // OBJECT_IDENTIFIER { 1.2.840.10045.3.1.7 } + // } + // BIT_STRING { `00` `04fc9ed36f7c1aa915ce3ea177f07567f07f16f9479d95ad8ed4971d3305e31a8050b733af8c0b0ee1da8de0acf9d8e13282f063b7b30d73d4d32c9aad6dfa8b27` } + // } + let expected = [ + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xfc, + 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75, 0x67, + 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33, 0x05, + 0xe3, 0x1a, 0x80, 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, + 0xac, 0xf9, 0xd8, 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, + 0x2c, 0x9a, 0xad, 0x6d, 0xfa, 0x8b, 0x27, + ]; + let ec2_key = COSEEC2Key { + curve: Curve::SECP256R1, + x: SAMPLE_P256_X.to_vec(), + y: SAMPLE_P256_Y.to_vec(), + }; + let cose_key = COSEKey { + alg: COSEAlgorithm::EDDSA, + key: COSEKeyType::EC2(ec2_key), + }; + let actual = cose_key.der_spki().expect("Failed to serialize key"); + assert_eq!(actual, expected); + } + + #[test] + fn test_ec2_key_to_cbor() { + let ec2_key = COSEEC2Key { + curve: Curve::SECP256R1, + x: SAMPLE_P256_X.to_vec(), + y: SAMPLE_P256_Y.to_vec(), + }; + let cose_key = COSEKey { + alg: COSEAlgorithm::EDDSA, + key: COSEKeyType::EC2(ec2_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_okp_key_to_der_spki() { + // RFC 8410 Example 10.1 + let expected = [ + 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0x19, 0xbf, + 0x44, 0x09, 0x69, 0x84, 0xcd, 0xfe, 0x85, 0x41, 0xba, 0xc1, 0x67, 0xdc, 0x3b, 0x96, + 0xc8, 0x50, 0x86, 0xaa, 0x30, 0xb6, 0xb6, 0xcb, 0x0c, 0x5c, 0x38, 0xad, 0x70, 0x31, + 0x66, 0xe1, + ]; + 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 actual = cose_key.der_spki().expect("Failed to serialize key"); + assert_eq!(actual, expected); + } + + #[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); + if let 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"); + + let client_ec2_key = COSEEC2Key { + curve: Curve::SECP256R1, + x: EC_PUB_X.clone(), + y: EC_PUB_Y.clone(), + }; + + let peer_ec2_key = COSEEC2Key { + curve: Curve::SECP256R1, + x: DEV_PUB_X, + y: DEV_PUB_Y, + }; + + // 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"; + + let shared_secret = vec![ + 0x82, 0xE3, 0xD8, 0x41, 0xE2, 0x5C, 0x5C, 0x13, 0x46, 0x2C, 0x12, 0x3C, 0xC3, 0xD3, + 0x98, 0x78, 0x65, 0xBA, 0x3D, 0x20, 0x46, 0x74, 0xFB, 0xED, 0xD4, 0x7E, 0xF5, 0xAB, + 0xAB, 0x8D, 0x13, 0x72, + ]; + let expected_new_pin_enc = vec![ + 0x70, 0x66, 0x4B, 0xB5, 0x81, 0xE2, 0x57, 0x45, 0x1A, 0x3A, 0xB9, 0x1B, 0xF1, 0xAA, + 0xD8, 0xE4, 0x5F, 0x6C, 0xE9, 0xB5, 0xC3, 0xB0, 0xF3, 0x2B, 0x5E, 0xCD, 0x62, 0xD0, + 0xBA, 0x3B, 0x60, 0x5F, 0xD9, 0x18, 0x31, 0x66, 0xF6, 0xC5, 0xFA, 0xF3, 0xE4, 0xDA, + 0x24, 0x81, 0x50, 0x2C, 0xD0, 0xCE, 0xE0, 0x15, 0x8B, 0x35, 0x1F, 0xC3, 0x92, 0x08, + 0xA7, 0x7C, 0xB2, 0x74, 0x4B, 0xD4, 0x3C, 0xF9, + ]; + let expected_pin_auth = vec![ + 0x8E, 0x7F, 0x01, 0x69, 0x97, 0xF3, 0xB0, 0xA2, 0x7B, 0xA4, 0x34, 0x7A, 0x0E, 0x49, + 0xFD, 0xF5, + ]; + + let mut input = vec![0x00; 64]; + { + let pin_bytes = pin.as_bytes(); + let (head, _) = input.split_at_mut(pin_bytes.len()); + head.copy_from_slice(pin_bytes); + } + + let new_pin_enc = PinUvAuth1 {} + .encrypt(&shared_secret, &input) + .expect("Failed to encrypt pin"); + assert_eq!(new_pin_enc, expected_new_pin_enc); + + let pin_auth = PinUvAuth1 {} + .authenticate(&shared_secret, &new_pin_enc) + .expect("HMAC-SHA256 failed"); + assert_eq!(pin_auth[0..16], expected_pin_auth); + } + + #[test] + fn test_pin_protocol() { + let mut info = AuthenticatorInfo { + versions: vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0], + extensions: vec![], + aaguid: AAGuid(AAGUID_RAW), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(false), + user_presence: true, + ..Default::default() + }, + max_msg_size: Some(1200), + pin_protocols: None, + ..Default::default() + }; + + // 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 + + // Invalid pin_protocols + info.pin_protocols = Some(vec![0, 10]); + PinUvAuthProtocol::try_from(&info).unwrap_err(); + + 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() + ); + } +} diff --git a/third_party/rust/authenticator/src/crypto/nss.rs b/third_party/rust/authenticator/src/crypto/nss.rs new file mode 100644 index 0000000000..56c23db5dd --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/nss.rs @@ -0,0 +1,481 @@ +use super::CryptoError; +use nss_gk_api::p11::{ + PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp, + PK11_Encrypt, PK11_ExportDERPrivateKeyInfo, PK11_GenerateKeyPairWithOpFlags, + PK11_GenerateRandom, PK11_HashBuf, PK11_ImportDERPrivateKeyInfoAndReturnKey, PK11_ImportSymKey, + PK11_PubDeriveWithKDF, PK11_SignWithMechanism, PrivateKey, PublicKey, + SECKEY_DecodeDERSubjectPublicKeyInfo, SECKEY_ExtractPublicKey, SECOidTag, Slot, + SubjectPublicKeyInfo, AES_BLOCK_SIZE, PK11_ATTR_EXTRACTABLE, PK11_ATTR_INSENSITIVE, + PK11_ATTR_SESSION, SHA256_LENGTH, +}; +use nss_gk_api::{IntoResult, SECItem, SECItemBorrowed, ScopedSECItem, PR_FALSE}; +use pkcs11_bindings::{ + CKA_DERIVE, CKA_ENCRYPT, CKA_SIGN, CKD_NULL, CKF_DERIVE, CKM_AES_CBC, CKM_ECDH1_DERIVE, + CKM_ECDSA_SHA256, CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC, +}; +use std::convert::TryFrom; +use std::os::raw::{c_int, c_uint}; +use std::ptr; + +use super::der; + +#[cfg(test)] +use nss_gk_api::p11::PK11_VerifyWithMechanism; + +impl From<nss_gk_api::Error> for CryptoError { + fn from(e: nss_gk_api::Error) -> Self { + CryptoError::Backend(format!("{e}")) + } +} + +pub type Result<T> = std::result::Result<T, CryptoError>; + +fn nss_public_key_from_der_spki(spki: &[u8]) -> Result<PublicKey> { + // TODO: replace this with an nss-gk-api function + // https://github.com/mozilla/nss-gk-api/issues/7 + let mut spki_item = SECItemBorrowed::wrap(spki); + let spki_item_ptr: *mut SECItem = spki_item.as_mut(); + let nss_spki = unsafe { + SubjectPublicKeyInfo::from_ptr(SECKEY_DecodeDERSubjectPublicKeyInfo(spki_item_ptr))? + }; + let public_key = unsafe { PublicKey::from_ptr(SECKEY_ExtractPublicKey(*nss_spki))? }; + Ok(public_key) +} + +/// ECDH using NSS types. Computes the x coordinate of scalar multiplication of `peer_public` by +/// `client_private`. +fn ecdh_nss_raw(client_private: PrivateKey, peer_public: PublicKey) -> Result<Vec<u8>> { + let ecdh_x_coord = unsafe { + PK11_PubDeriveWithKDF( + *client_private, + *peer_public, + PR_FALSE, + std::ptr::null_mut(), + std::ptr::null_mut(), + CKM_ECDH1_DERIVE, + CKM_SHA512_HMAC, // unused + CKA_DERIVE, // unused + 0, + CKD_NULL, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + .into_result()? + }; + let ecdh_x_coord_bytes = ecdh_x_coord.as_bytes()?; + Ok(ecdh_x_coord_bytes.to_vec()) +} + +fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> { + // Hard-coding the P256 OID here is easier than extracting a group name from peer_public and + // comparing it with P256. We'll fail in `PK11_GenerateKeyPairWithOpFlags` if peer_public is on + // the wrong curve. + let oid_bytes = der::object_id(der::OID_SECP256R1_BYTES)?; + let mut oid = SECItemBorrowed::wrap(&oid_bytes); + let oid_ptr: *mut SECItem = oid.as_mut(); + + let slot = Slot::internal()?; + + let mut client_public_ptr = ptr::null_mut(); + + // We have to be careful with error handling between the `PK11_GenerateKeyPairWithOpFlags` and + // `PublicKey::from_ptr` calls here, so I've wrapped them in the same unsafe block as a + // warning. TODO(jms) Replace this once there is a safer alternative. + // https://github.com/mozilla/nss-gk-api/issues/1 + unsafe { + let client_private = + // Type of `param` argument depends on mechanism. For EC keygen it is + // `SECKEYECParams *` which is a typedef for `SECItem *`. + PK11_GenerateKeyPairWithOpFlags( + *slot, + CKM_EC_KEY_PAIR_GEN, + oid_ptr.cast(), + &mut client_public_ptr, + PK11_ATTR_EXTRACTABLE | PK11_ATTR_INSENSITIVE | PK11_ATTR_SESSION, + CKF_DERIVE, + CKF_DERIVE, + ptr::null_mut(), + ) + .into_result()?; + + let client_public = PublicKey::from_ptr(client_public_ptr)?; + + Ok((client_private, client_public)) + } +} + +/// This returns a PKCS#8 ECPrivateKey and an uncompressed SEC1 public key. +pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> { + nss_gk_api::init(); + + let (client_private, client_public) = generate_p256_nss()?; + + let pkcs8_priv = unsafe { + let pkcs8_priv_item: ScopedSECItem = + PK11_ExportDERPrivateKeyInfo(*client_private, ptr::null_mut()).into_result()?; + pkcs8_priv_item.into_vec() + }; + + let sec1_pub = client_public.key_data()?; + + Ok((pkcs8_priv, sec1_pub)) +} + +pub fn ecdsa_p256_sha256_sign_raw(private: &[u8], data: &[u8]) -> Result<Vec<u8>> { + nss_gk_api::init(); + + let slot = Slot::internal()?; + + let imported_private: PrivateKey = unsafe { + let mut imported_private_ptr = ptr::null_mut(); + PK11_ImportDERPrivateKeyInfoAndReturnKey( + *slot, + SECItemBorrowed::wrap(private).as_mut(), + ptr::null_mut(), + ptr::null_mut(), + PR_FALSE, + PR_FALSE, + 255, /* todo: expose KU_ flags in nss-gk-api */ + &mut imported_private_ptr, + ptr::null_mut(), + ); + imported_private_ptr.into_result()? + }; + + let signature_buf = vec![0; 64]; + unsafe { + PK11_SignWithMechanism( + *imported_private, + CKM_ECDSA_SHA256, + ptr::null_mut(), + SECItemBorrowed::wrap(&signature_buf).as_mut(), + SECItemBorrowed::wrap(data).as_mut(), + ) + .into_result()?; + } + + let (r, s) = signature_buf.split_at(32); + der::sequence(&[&der::integer(r)?, &der::integer(s)?]) +} + +/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates +/// an ephemeral P256 key pair. Returns +/// 1) the x coordinate of the shared point, and +/// 2) the uncompressed SEC 1 encoding of the ephemeral public key. +pub fn ecdhe_p256_raw(peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> { + nss_gk_api::init(); + + let peer_public = nss_public_key_from_der_spki(peer_spki)?; + + let (client_private, client_public) = generate_p256_nss()?; + + let shared_point = ecdh_nss_raw(client_private, peer_public)?; + + Ok((shared_point, client_public.key_data()?)) +} + +/// AES-256-CBC encryption for data that is a multiple of the AES block size (16 bytes) in length. +/// Uses the zero IV if `iv` is None. +pub fn encrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> { + nss_gk_api::init(); + + if key.len() != 32 { + return Err(CryptoError::LibraryFailure); + } + + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + if iv.len() != AES_BLOCK_SIZE { + return Err(CryptoError::LibraryFailure); + } + + let in_len = match c_uint::try_from(data.len()) { + Ok(in_len) => in_len, + _ => return Err(CryptoError::LibraryFailure), + }; + + if data.len() % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + let slot = Slot::internal()?; + + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_AES_CBC, + PK11Origin::PK11_OriginUnwrap, + CKA_ENCRYPT, + SECItemBorrowed::wrap(key).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + + let mut params = SECItemBorrowed::wrap(iv); + let params_ptr: *mut SECItem = params.as_mut(); + let mut out_len: c_uint = 0; + let mut out = vec![0; data.len()]; + unsafe { + PK11_Encrypt( + *sym_key, + CKM_AES_CBC, + params_ptr, + out.as_mut_ptr(), + &mut out_len, + in_len, + data.as_ptr(), + in_len, + ) + .into_result()? + } + // CKM_AES_CBC should have output length equal to input length. + debug_assert_eq!(out_len, in_len); + + Ok(out) +} + +/// AES-256-CBC decryption for data that is a multiple of the AES block size (16 bytes) in length. +/// Uses the zero IV if `iv` is None. +pub fn decrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> { + nss_gk_api::init(); + + if key.len() != 32 { + return Err(CryptoError::LibraryFailure); + } + + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + if iv.len() != AES_BLOCK_SIZE { + return Err(CryptoError::LibraryFailure); + } + + let in_len = match c_uint::try_from(data.len()) { + Ok(in_len) => in_len, + _ => return Err(CryptoError::LibraryFailure), + }; + + if data.len() % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + let slot = Slot::internal()?; + + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_AES_CBC, + PK11Origin::PK11_OriginUnwrap, + CKA_ENCRYPT, + SECItemBorrowed::wrap(key).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + + let mut params = SECItemBorrowed::wrap(iv); + let params_ptr: *mut SECItem = params.as_mut(); + let mut out_len: c_uint = 0; + let mut out = vec![0; data.len()]; + unsafe { + PK11_Decrypt( + *sym_key, + CKM_AES_CBC, + params_ptr, + out.as_mut_ptr(), + &mut out_len, + in_len, + data.as_ptr(), + in_len, + ) + .into_result()? + } + // CKM_AES_CBC should have output length equal to input length. + debug_assert_eq!(out_len, in_len); + + Ok(out) +} + +/// Textbook HMAC-SHA256 +pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Vec<u8>> { + nss_gk_api::init(); + + let data_len = match u32::try_from(data.len()) { + Ok(data_len) => data_len, + _ => return Err(CryptoError::LibraryFailure), + }; + + let slot = Slot::internal()?; + let sym_key = unsafe { + PK11_ImportSymKey( + *slot, + CKM_SHA256_HMAC, + PK11Origin::PK11_OriginUnwrap, + CKA_SIGN, + SECItemBorrowed::wrap(key).as_mut(), + ptr::null_mut(), + ) + .into_result()? + }; + let param = SECItemBorrowed::make_empty(); + let context = unsafe { + PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, *sym_key, param.as_ref()) + .into_result()? + }; + unsafe { PK11_DigestOp(*context, data.as_ptr(), data_len).into_result()? }; + let mut digest = vec![0u8; SHA256_LENGTH]; + let mut digest_len = 0u32; + unsafe { + PK11_DigestFinal( + *context, + digest.as_mut_ptr(), + &mut digest_len, + digest.len() as u32, + ) + .into_result()? + } + assert_eq!(digest_len as usize, SHA256_LENGTH); + Ok(digest) +} + +/// Textbook SHA256 +pub fn sha256(data: &[u8]) -> Result<Vec<u8>> { + nss_gk_api::init(); + + let data_len: i32 = match i32::try_from(data.len()) { + Ok(data_len) => data_len, + _ => return Err(CryptoError::LibraryFailure), + }; + let mut digest = vec![0u8; SHA256_LENGTH]; + unsafe { + PK11_HashBuf( + SECOidTag::SEC_OID_SHA256, + digest.as_mut_ptr(), + data.as_ptr(), + data_len, + ) + .into_result()? + }; + Ok(digest) +} + +pub fn random_bytes(count: usize) -> Result<Vec<u8>> { + nss_gk_api::init(); + + let count_cint: c_int = match c_int::try_from(count) { + Ok(c) => c, + _ => return Err(CryptoError::LibraryFailure), + }; + + let mut out = vec![0u8; count]; + unsafe { PK11_GenerateRandom(out.as_mut_ptr(), count_cint).into_result()? }; + Ok(out) +} + +#[cfg(test)] +pub fn test_ecdh_p256_raw( + peer_spki: &[u8], + client_public_x: &[u8], + client_public_y: &[u8], + client_private: &[u8], +) -> Result<Vec<u8>> { + nss_gk_api::init(); + + let peer_public = nss_public_key_from_der_spki(peer_spki)?; + + // NSS has no mechanism to import a raw elliptic curve coordinate as a private key. + // We need to encode it in an RFC 5208 PrivateKeyInfo: + // + // PrivateKeyInfo ::= SEQUENCE { + // version Version, + // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + // privateKey PrivateKey, + // attributes [0] IMPLICIT Attributes OPTIONAL } + // + // Version ::= INTEGER + // PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier + // PrivateKey ::= OCTET STRING + // Attributes ::= SET OF Attribute + // + // The privateKey field will contain an RFC 5915 ECPrivateKey: + // ECPrivateKey ::= SEQUENCE { + // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + // privateKey OCTET STRING, + // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + // publicKey [1] BIT STRING OPTIONAL + // } + + // PrivateKeyInfo + let priv_key_info = der::sequence(&[ + // version + &der::integer(&[0x00])?, + // privateKeyAlgorithm + &der::sequence(&[ + &der::object_id(der::OID_EC_PUBLIC_KEY_BYTES)?, + &der::object_id(der::OID_SECP256R1_BYTES)?, + ])?, + // privateKey + &der::octet_string( + // ECPrivateKey + &der::sequence(&[ + // version + &der::integer(&[0x01])?, + // privateKey + &der::octet_string(client_private)?, + // publicKey + &der::context_specific_explicit_tag( + 1, // publicKey + &der::bit_string(&[&[0x04], client_public_x, client_public_y].concat())?, + )?, + ])?, + )?, + ])?; + + // Now we can import the private key. + let slot = Slot::internal()?; + let mut priv_key_info_item = SECItemBorrowed::wrap(&priv_key_info); + let priv_key_info_item_ptr: *mut SECItem = priv_key_info_item.as_mut(); + let mut client_private_ptr = ptr::null_mut(); + unsafe { + PK11_ImportDERPrivateKeyInfoAndReturnKey( + *slot, + priv_key_info_item_ptr, + ptr::null_mut(), + ptr::null_mut(), + PR_FALSE, + PR_FALSE, + 255, /* todo: expose KU_ flags in nss-gk-api */ + &mut client_private_ptr, + ptr::null_mut(), + ) + }; + let client_private = unsafe { PrivateKey::from_ptr(client_private_ptr) }?; + + let shared_point = ecdh_nss_raw(client_private, peer_public)?; + + Ok(shared_point) +} + +#[cfg(test)] +pub fn test_ecdsa_p256_sha256_verify_raw( + public: &[u8], + signature: &[u8], + data: &[u8], +) -> Result<()> { + nss_gk_api::init(); + + let signature = der::read_p256_sig(signature)?; + let public = nss_public_key_from_der_spki(public)?; + unsafe { + PK11_VerifyWithMechanism( + *public, + CKM_ECDSA_SHA256, + ptr::null_mut(), + SECItemBorrowed::wrap(&signature).as_mut(), + SECItemBorrowed::wrap(data).as_mut(), + ptr::null_mut(), + ) + .into_result()? + } + Ok(()) +} diff --git a/third_party/rust/authenticator/src/crypto/openssl.rs b/third_party/rust/authenticator/src/crypto/openssl.rs new file mode 100644 index 0000000000..065ea201a5 --- /dev/null +++ b/third_party/rust/authenticator/src/crypto/openssl.rs @@ -0,0 +1,183 @@ +use super::CryptoError; +use openssl::bn::BigNumContext; +use openssl::derive::Deriver; +use openssl::ec::{EcGroup, EcKey, PointConversionForm}; +use openssl::error::ErrorStack; +use openssl::hash::{hash, MessageDigest}; +use openssl::nid::Nid; +use openssl::pkey::{PKey, Private, Public}; +use openssl::rand::rand_bytes; +use openssl::sign::Signer; +use openssl::symm::{Cipher, Crypter, Mode}; +use std::os::raw::c_int; + +#[cfg(test)] +use openssl::ec::EcPoint; + +#[cfg(test)] +use openssl::bn::BigNum; + +const AES_BLOCK_SIZE: usize = 16; + +impl From<ErrorStack> for CryptoError { + fn from(e: ErrorStack) -> Self { + CryptoError::Backend(format!("{e}")) + } +} + +impl From<&ErrorStack> for CryptoError { + fn from(e: &ErrorStack) -> Self { + CryptoError::Backend(format!("{e}")) + } +} + +pub type Result<T> = std::result::Result<T, CryptoError>; + +/// ECDH using OpenSSL types. Computes the x coordinate of scalar multiplication of `peer_public` +/// by `client_private`. +fn ecdh_openssl_raw(client_private: EcKey<Private>, peer_public: EcKey<Public>) -> Result<Vec<u8>> { + let client_pkey = PKey::from_ec_key(client_private)?; + let peer_pkey = PKey::from_ec_key(peer_public)?; + let mut deriver = Deriver::new(&client_pkey)?; + deriver.set_peer(&peer_pkey)?; + let shared_point = deriver.derive_to_vec()?; + Ok(shared_point) +} + +/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates +/// an ephemeral P256 key pair. Returns +/// 1) the x coordinate of the shared point, and +/// 2) the uncompressed SEC 1 encoding of the ephemeral public key. +pub fn ecdhe_p256_raw(peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> { + let peer_public = EcKey::public_key_from_der(peer_spki)?; + + // Hard-coding the P256 group here is easier than extracting a group name from peer_public and + // comparing it with P256. We'll fail in key derivation if peer_public is on the wrong curve. + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + + let mut bn_ctx = BigNumContext::new()?; + let client_private = EcKey::generate(&group)?; + let client_public_sec1 = client_private.public_key().to_bytes( + &group, + PointConversionForm::UNCOMPRESSED, + &mut bn_ctx, + )?; + + let shared_point = ecdh_openssl_raw(client_private, peer_public)?; + + Ok((shared_point, client_public_sec1)) +} + +/// AES-256-CBC encryption for data that is a multiple of the AES block size (16 bytes) in length. +/// Uses the zero IV if `iv` is None. +pub fn encrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> { + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + let mut encrypter = Crypter::new(Cipher::aes_256_cbc(), Mode::Encrypt, key, Some(iv))?; + encrypter.pad(false); + + let in_len = data.len(); + if in_len % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + // OpenSSL would panic if we didn't allocate an extra block here. + let mut out = vec![0; in_len + AES_BLOCK_SIZE]; + let mut out_len = 0; + out_len += encrypter.update(data, out.as_mut_slice())?; + out_len += encrypter.finalize(out.as_mut_slice())?; + debug_assert_eq!(in_len, out_len); + + out.truncate(out_len); + Ok(out) +} + +/// AES-256-CBC decryption for data that is a multiple of the AES block size (16 bytes) in length. +/// Uses the zero IV if `iv` is None. +pub fn decrypt_aes_256_cbc_no_pad(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>> { + let iv = iv.unwrap_or(&[0u8; AES_BLOCK_SIZE]); + + let mut encrypter = Crypter::new(Cipher::aes_256_cbc(), Mode::Decrypt, key, Some(iv))?; + encrypter.pad(false); + + let in_len = data.len(); + if in_len % AES_BLOCK_SIZE != 0 { + return Err(CryptoError::LibraryFailure); + } + + // OpenSSL would panic if we didn't allocate an extra block here. + let mut out = vec![0; in_len + AES_BLOCK_SIZE]; + let mut out_len = 0; + out_len += encrypter.update(data, out.as_mut_slice())?; + out_len += encrypter.finalize(out.as_mut_slice())?; + debug_assert_eq!(in_len, out_len); + + out.truncate(out_len); + Ok(out) +} + +/// Textbook HMAC-SHA256 +pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<Vec<u8>> { + let key = PKey::hmac(key)?; + let mut signer = Signer::new(MessageDigest::sha256(), &key)?; + signer.update(data)?; + Ok(signer.sign_to_vec()?) +} + +pub fn sha256(data: &[u8]) -> Result<Vec<u8>> { + let digest = hash(MessageDigest::sha256(), data)?; + Ok(digest.as_ref().to_vec()) +} + +pub fn random_bytes(count: usize) -> Result<Vec<u8>> { + if count > c_int::MAX as usize { + return Err(CryptoError::LibraryFailure); + } + let mut out = vec![0u8; count]; + rand_bytes(&mut out)?; + Ok(out) +} + +#[cfg(test)] +pub fn test_ecdh_p256_raw( + peer_spki: &[u8], + client_public_x: &[u8], + client_public_y: &[u8], + client_private: &[u8], +) -> Result<Vec<u8>> { + let peer_public = EcKey::public_key_from_der(peer_spki)?; + let group = peer_public.group(); + + let mut client_pub_sec1 = vec![]; + client_pub_sec1.push(0x04); // SEC 1 encoded uncompressed point + client_pub_sec1.extend_from_slice(&client_public_x); + client_pub_sec1.extend_from_slice(&client_public_y); + + let mut ctx = BigNumContext::new()?; + let client_pub_point = EcPoint::from_bytes(&group, &client_pub_sec1, &mut ctx)?; + let client_priv_bignum = BigNum::from_slice(client_private)?; + let client_private = + EcKey::from_private_components(&group, &client_priv_bignum, &client_pub_point)?; + + let shared_point = ecdh_openssl_raw(client_private, peer_public)?; + + Ok(shared_point) +} + +pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> { + unimplemented!() +} + +pub fn ecdsa_p256_sha256_sign_raw(_private: &[u8], _data: &[u8]) -> Result<Vec<u8>> { + unimplemented!() +} + +#[allow(dead_code)] +#[cfg(test)] +pub fn test_ecdsa_p256_sha256_verify_raw( + _public: &[u8], + _signature: &[u8], + _data: &[u8], +) -> Result<()> { + unimplemented!() +} |