use cipher::{Array, common::Generate};
use hpke::{Deserializable as _, Serializable, aead::AeadCtxR, kem::X25519HkdfSha256};
use crate::{
Curve25519PublicKey,
hpke::{
Aead, BidirectionalCreationResult, CreateResponseContext, Error, EstablishedHpkeChannel,
InitialMessage, InitialResponse, Kdf, Kem, MATRIX_QR_LOGIN_INFO_PREFIX, RecipientContext,
Role, UnidirectionalHkpeChannel, response_context::AeadKeySize,
},
};
#[derive(Debug)]
pub struct RecipientCreationResult {
pub channel: UnidirectionalRecipientChannel,
pub message: Vec<u8>,
}
pub struct HpkeRecipientChannel {
secret_key: <X25519HkdfSha256 as hpke::Kem>::PrivateKey,
public_key: Curve25519PublicKey,
application_info_prefix: String,
}
impl std::fmt::Debug for HpkeRecipientChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HpkeRecipientChannel")
.field("our_public_key", &self.public_key)
.field("application_info_prefix", &self.application_info_prefix)
.finish_non_exhaustive()
}
}
impl HpkeRecipientChannel {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::with_info(MATRIX_QR_LOGIN_INFO_PREFIX)
}
pub fn with_info(info: &str) -> Self {
let (secret_key, public_key) = <X25519HkdfSha256 as hpke::Kem>::gen_keypair();
let public_key = convert_public_key(public_key);
Self { secret_key, public_key, application_info_prefix: info.to_owned() }
}
pub fn establish_channel(
self,
message: &InitialMessage,
aad: &[u8],
) -> Result<RecipientCreationResult, Error> {
let Self { secret_key, public_key, application_info_prefix } = self;
let InitialMessage { encapsulated_key, ciphertext } = message;
let their_public_key = *encapsulated_key;
let our_public_key = public_key;
let encapsulated_key = convert_encapsulated_key(encapsulated_key);
let mut sender_context: RecipientContext = hpke::setup_receiver(
&hpke::OpModeR::Base,
&secret_key,
&encapsulated_key,
application_info_prefix.as_bytes(),
)
.map_err(|_| Error::Decryption)?;
let message = sender_context.open(ciphertext, aad).map_err(|_| Error::Decryption)?;
let channel = UnidirectionalRecipientChannel(UnidirectionalHkpeChannel {
application_info_prefix,
sender_context,
their_public_key,
our_public_key,
});
Ok(RecipientCreationResult { channel, message })
}
pub fn public_key(&self) -> Curve25519PublicKey {
self.public_key
}
}
fn convert_public_key(
public_key: <X25519HkdfSha256 as hpke::Kem>::PublicKey,
) -> Curve25519PublicKey {
#[allow(clippy::expect_used)]
Curve25519PublicKey::from_bytes(public_key.to_bytes().0)
}
fn convert_encapsulated_key(
public_key: &Curve25519PublicKey,
) -> <X25519HkdfSha256 as hpke::Kem>::EncappedKey {
#[allow(clippy::expect_used)]
<X25519HkdfSha256 as hpke::Kem>::EncappedKey::from_bytes(public_key.as_bytes())
.expect("Converting to the HPKE EncappedKey type should never fail")
}
pub struct UnidirectionalRecipientChannel(UnidirectionalHkpeChannel<AeadCtxR<Aead, Kdf, Kem>>);
impl std::fmt::Debug for UnidirectionalRecipientChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UnidirectionalRecipientChannel")
.field("application_info_prefix", &self.0.application_info_prefix)
.field("their_public_key", &self.0.their_public_key)
.field("our_public_key", &self.0.our_public_key)
.finish_non_exhaustive()
}
}
impl UnidirectionalRecipientChannel {
pub fn establish_bidirectional_channel(
self,
plaintext: &[u8],
aad: &[u8],
) -> BidirectionalCreationResult<InitialResponse> {
let Self(UnidirectionalHkpeChannel {
sender_context,
their_public_key,
our_public_key,
application_info_prefix,
}) = self;
let base_response_nonce = Array::<u8, AeadKeySize>::generate();
let mut response_context = sender_context.create_response_context(
&application_info_prefix,
their_public_key,
&base_response_nonce,
);
#[allow(clippy::expect_used)]
let ciphertext = response_context
.seal(plaintext, aad)
.expect("We should be able to seal the initial response");
let role = Role::Recipient { sender_context, response_context };
let check_code =
role.check_code(&application_info_prefix, our_public_key, their_public_key);
let channel = EstablishedHpkeChannel { our_public_key, their_public_key, role, check_code };
BidirectionalCreationResult {
channel,
message: InitialResponse {
ciphertext,
base_response_nonce: base_response_nonce.into(),
},
}
}
}
#[cfg(test)]
mod tests {
use hpke::Kem as _;
use insta::assert_debug_snapshot;
use rand::{SeedableRng, rngs::StdRng};
use super::*;
use crate::hpke::{HpkeSenderChannel, SenderCreationResult};
#[test]
fn snapshot_debug() {
let key = <X25519HkdfSha256 as hpke::Kem>::PrivateKey::from_bytes(&[0; 32]).unwrap();
let mut bob = HpkeRecipientChannel::new();
bob.public_key = Curve25519PublicKey::from_bytes([0; 32]);
bob.secret_key = key;
assert_debug_snapshot!(bob);
}
#[test]
fn snapshot_debug_unidirectional_channel() {
let mut rng = StdRng::seed_from_u64(0);
let (secret_key, public_key) = X25519HkdfSha256::gen_keypair_with_rng(&mut rng);
let public_key = convert_public_key(public_key);
let alice = HpkeSenderChannel::new();
let mut bob = HpkeRecipientChannel::new();
bob.secret_key = secret_key;
bob.public_key = public_key;
assert_debug_snapshot!(bob);
let SenderCreationResult { message, .. } = alice
.establish_channel(bob.public_key(), b"", &[])
.expect("We should be able to create the sender channel");
let RecipientCreationResult { channel: mut bob, .. } =
bob.establish_channel(&message, &[]).unwrap();
let key = Curve25519PublicKey::from_bytes([0; 32]);
bob.0.our_public_key = key;
bob.0.their_public_key = key;
assert_debug_snapshot!(bob);
}
}