summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/crypto')
-rw-r--r--third_party/rust/authenticator/src/crypto/der.rs185
-rw-r--r--third_party/rust/authenticator/src/crypto/dummy.rs57
-rw-r--r--third_party/rust/authenticator/src/crypto/mod.rs1638
-rw-r--r--third_party/rust/authenticator/src/crypto/nss.rs481
-rw-r--r--third_party/rust/authenticator/src/crypto/openssl.rs183
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!()
+}