use hpke::{
Deserializable as _, OpModeS, Serializable as _, aead::AeadCtxS, kem::X25519HkdfSha256,
};
use crate::{
Curve25519PublicKey,
hpke::{
Aead, BidirectionalCreationResult, CreateResponseContext, Error, EstablishedHpkeChannel,
InitialMessage, InitialResponse, Kdf, Kem, MATRIX_QR_LOGIN_INFO_PREFIX, Role,
UnidirectionalHkpeChannel,
},
};
pub struct SenderCreationResult {
pub channel: UnidirectionalSenderChannel,
pub message: InitialMessage,
}
#[derive(Debug)]
pub struct HpkeSenderChannel {
application_info_prefix: String,
}
impl HpkeSenderChannel {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::with_info(MATRIX_QR_LOGIN_INFO_PREFIX)
}
pub fn with_info(info: &str) -> Self {
Self { application_info_prefix: info.to_owned() }
}
pub fn establish_channel(
self,
their_public_key: Curve25519PublicKey,
initial_plaintext: &[u8],
aad: &[u8],
) -> Result<SenderCreationResult, Error> {
let Self { application_info_prefix } = self;
let their_key = convert_public_key(their_public_key);
#[allow(clippy::expect_used)]
let (encapsulated_key, mut context) =
hpke::setup_sender(&OpModeS::Base, &their_key, application_info_prefix.as_bytes())?;
let ciphertext = context.seal(initial_plaintext, aad)?;
let encapsulated_key = convert_encapsulated_key(encapsulated_key);
let our_public_key = encapsulated_key;
let channel = UnidirectionalSenderChannel(UnidirectionalHkpeChannel {
sender_context: context,
application_info_prefix,
our_public_key,
their_public_key,
});
Ok(SenderCreationResult {
channel,
message: InitialMessage { encapsulated_key, ciphertext },
})
}
}
fn convert_public_key(
public_key: Curve25519PublicKey,
) -> <X25519HkdfSha256 as hpke::Kem>::PublicKey {
#[allow(clippy::expect_used)]
<X25519HkdfSha256 as hpke::Kem>::PublicKey::from_bytes(public_key.as_bytes())
.expect("Converting the Dalek public key to the HPKE public key should always work")
}
fn convert_encapsulated_key(
encapsulated_key: <X25519HkdfSha256 as hpke::Kem>::EncappedKey,
) -> Curve25519PublicKey {
let encapsulated_key = encapsulated_key.to_bytes();
#[allow(clippy::expect_used)]
Curve25519PublicKey::from_slice(encapsulated_key.as_slice())
.expect("Converting from the HPKE public key to the Dalek public key should always work")
}
pub struct UnidirectionalSenderChannel(UnidirectionalHkpeChannel<AeadCtxS<Aead, Kdf, Kem>>);
impl std::fmt::Debug for UnidirectionalSenderChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UnidirectionalSenderChannel")
.field("application_info_prefix", &self.0.application_info_prefix)
.field("our_public_key", &self.0.our_public_key)
.field("their_public_key", &self.0.their_public_key)
.finish_non_exhaustive()
}
}
impl UnidirectionalSenderChannel {
pub fn establish_bidirectional_channel(
self,
message: &InitialResponse,
aad: &[u8],
) -> Result<BidirectionalCreationResult<Vec<u8>>, Error> {
let Self(UnidirectionalHkpeChannel {
sender_context,
application_info_prefix,
our_public_key,
their_public_key,
}) = self;
let mut response_context = sender_context.create_response_context(
&application_info_prefix,
our_public_key,
&message.base_response_nonce,
);
let plaintext =
response_context.open(&message.ciphertext, aad).map_err(|_| Error::Decryption)?;
let role = Role::Sender { sender_context, response_context };
let check_code =
role.check_code(&application_info_prefix, our_public_key, their_public_key);
Ok(BidirectionalCreationResult {
channel: EstablishedHpkeChannel { our_public_key, their_public_key, role, check_code },
message: plaintext,
})
}
}
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
use super::*;
use crate::hpke::HpkeRecipientChannel;
#[test]
fn snapshot_debug() {
let alice = HpkeSenderChannel::new();
assert_debug_snapshot!(alice);
}
#[test]
fn snapshot_debug_unidirectional_channel() {
let key = Curve25519PublicKey::from_bytes([0; 32]);
let alice = HpkeSenderChannel::new();
let bob = HpkeRecipientChannel::new();
let SenderCreationResult { channel: mut alice, .. } = alice
.establish_channel(bob.public_key(), b"", &[])
.expect("We should be able to create the initial sender channel");
alice.0.our_public_key = key;
alice.0.their_public_key = key;
assert_debug_snapshot!(alice);
}
}