|
|
#![feature(split_array)]
|
|
|
use aes::{
|
|
|
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
|
|
|
Block,
|
|
|
};
|
|
|
use ctap2_proto::{
|
|
|
authenticator::client_pin::auth_protocol,
|
|
|
prelude::{
|
|
|
client_pin::{
|
|
|
self,
|
|
|
auth_protocol::{platform, BLOCK_SIZE},
|
|
|
},
|
|
|
Sha256Hash,
|
|
|
},
|
|
|
};
|
|
|
use hmac::{Hmac, Mac};
|
|
|
use p256::{ecdh::EphemeralSecret, EncodedPoint, PublicKey};
|
|
|
use rand::rngs::OsRng;
|
|
|
use sha2::{Digest, Sha256};
|
|
|
|
|
|
const IV: [u8; BLOCK_SIZE] = [0; BLOCK_SIZE];
|
|
|
|
|
|
pub struct Session {
|
|
|
platform_key_agreement_key: cosey::PublicKey,
|
|
|
shared_secret: Sha256Hash,
|
|
|
}
|
|
|
|
|
|
impl Session {
|
|
|
fn ecdh(
|
|
|
private_key_agreement_key: EphemeralSecret,
|
|
|
peer_cose_key: cosey::PublicKey,
|
|
|
) -> Result<[u8; 32], client_pin::Error> {
|
|
|
// 1. 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.
|
|
|
let cosey::PublicKey::EcdhEsHkdf256Key(peer_public_key) = peer_cose_key else {
|
|
|
return Err(client_pin::Error::InvalidParameter);
|
|
|
};
|
|
|
|
|
|
// Magical SEC1 incantation
|
|
|
let mut encoded = 0x04u8.to_be_bytes().to_vec();
|
|
|
encoded.extend_from_slice(&peer_public_key.x);
|
|
|
encoded.extend_from_slice(&peer_public_key.y);
|
|
|
|
|
|
let peer_public_key = PublicKey::from_sec1_bytes(&encoded).unwrap();
|
|
|
|
|
|
// 2. Calculate xY, the shared point. (I.e. the scalar-multiplication of the
|
|
|
// peer’s point, Y, with the local private key agreement key.)
|
|
|
// 3. Let Z be the 32-byte, big-endian encoding of the x-coordinate of the
|
|
|
// shared point.
|
|
|
let z = private_key_agreement_key
|
|
|
.diffie_hellman(&peer_public_key)
|
|
|
.raw_secret_bytes()
|
|
|
.as_slice()
|
|
|
.to_owned();
|
|
|
|
|
|
// 4. Return kdf(Z).
|
|
|
Ok(Self::kdf(z.as_slice()))
|
|
|
}
|
|
|
|
|
|
/// Return SHA-256(Z)
|
|
|
fn kdf(z: &[u8]) -> Sha256Hash {
|
|
|
Sha256::digest(z).into()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl platform::Session<{ auth_protocol::Version::One }> for Session {
|
|
|
type Error = client_pin::Error;
|
|
|
|
|
|
fn initialize(peer_cose_key: cosey::PublicKey) -> Result<Self, Self::Error> {
|
|
|
let platform_key_agreement_key = EphemeralSecret::random(&mut OsRng);
|
|
|
let public_key = platform_key_agreement_key.public_key();
|
|
|
|
|
|
let public_key = EncodedPoint::from(&public_key);
|
|
|
|
|
|
let public_key = cosey::P256PublicKey {
|
|
|
x: cosey::Bytes::from_slice(public_key.x().unwrap().as_slice()).unwrap(),
|
|
|
y: cosey::Bytes::from_slice(public_key.y().unwrap().as_slice()).unwrap(),
|
|
|
};
|
|
|
|
|
|
let shared_secret = Self::ecdh(platform_key_agreement_key, peer_cose_key)?;
|
|
|
Ok(Self {
|
|
|
platform_key_agreement_key: public_key.into(),
|
|
|
shared_secret,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
fn platform_key_agreement_key(&self) -> &cosey::PublicKey {
|
|
|
&self.platform_key_agreement_key
|
|
|
}
|
|
|
|
|
|
fn encrypt<const N: usize>(
|
|
|
&self,
|
|
|
plaintext: &[[u8; BLOCK_SIZE]; N],
|
|
|
) -> Result<[[u8; BLOCK_SIZE]; N], Self::Error> {
|
|
|
// Return the AES-256-CBC encryption of demPlaintext using an all-zero
|
|
|
// IV. (No padding is performed as the size of demPlaintext is required
|
|
|
// to be a multiple of the AES block length.)
|
|
|
|
|
|
let mut ciphertext = plaintext.map(Block::from);
|
|
|
|
|
|
cbc::Encryptor::<aes::Aes256>::new(&self.shared_secret.into(), &IV.into())
|
|
|
.encrypt_blocks_mut(&mut ciphertext);
|
|
|
|
|
|
let ciphertext = ciphertext.map(|block| block.into());
|
|
|
|
|
|
Ok(ciphertext)
|
|
|
}
|
|
|
|
|
|
fn decrypt<const N: usize>(&self, ciphertext: &[[u8; 16]; N]) -> [[u8; 16]; N] {
|
|
|
// If the size of demCiphertext is not a multiple of the AES block length,
|
|
|
// return error. Otherwise return the AES-256-CBC decryption of demCiphertext
|
|
|
// using an all-zero IV.
|
|
|
|
|
|
let mut plaintext = ciphertext.map(Block::from);
|
|
|
|
|
|
cbc::Decryptor::<aes::Aes256>::new(&self.shared_secret.into(), &IV.into())
|
|
|
.decrypt_blocks_mut(&mut plaintext);
|
|
|
|
|
|
let plaintext = plaintext.map(|block| block.into());
|
|
|
|
|
|
plaintext
|
|
|
}
|
|
|
|
|
|
fn authenticate(&self, message: &[u8]) -> Result<[u8; 16], Self::Error> {
|
|
|
// Return the first 16 bytes of the result of computing HMAC-SHA-256 with the
|
|
|
// given key and message.
|
|
|
|
|
|
let mut mac = Hmac::<Sha256>::new_from_slice(&self.shared_secret)
|
|
|
.expect("HMAC can take key of any size");
|
|
|
|
|
|
mac.update(message);
|
|
|
|
|
|
let result = mac
|
|
|
.finalize()
|
|
|
.into_bytes()
|
|
|
.as_slice()
|
|
|
.split_array_ref()
|
|
|
.0
|
|
|
.to_owned();
|
|
|
|
|
|
Ok(result)
|
|
|
}
|
|
|
}
|