use sha2::{Digest, Sha256};
use crate::{
errors::CryptoError,
exchange::pair::{self, KEMPair},
signatures::keypair::{self, PublicKeyBytes, SignerPair, VerifierPair, ViewOperations},
};
const PREKEY_LABEL: &[u8] = b"pq-msg/v2 prekey";
pub const SIGNED_PREKEY_BYTES: usize = pair::PUBLIC_KEY_BYTES + keypair::SIGNATURE_BYTES;
pub type PrekeyId = [u8; 32];
#[derive(Clone)]
pub struct SignedPrekey {
kem_pub_key: [u8; pair::PUBLIC_KEY_BYTES],
signature: keypair::SignatureBytes,
}
impl SignedPrekey {
pub fn new(owner: &mut SignerPair, prekey: &KEMPair) -> Result<Self, CryptoError> {
let kem_pub_key = prekey.pub_key_bytes();
let signature = owner.sign(&signed_data(&kem_pub_key))?;
Ok(Self {
kem_pub_key,
signature,
})
}
pub fn verify(&self, identity: &PublicKeyBytes) -> bool {
VerifierPair::new(identity)
.is_ok_and(|v| v.verify(&signed_data(&self.kem_pub_key), &self.signature))
}
pub fn id(&self) -> PrekeyId {
Sha256::digest(self.kem_pub_key).into()
}
pub fn kem_pub_key(&self) -> &[u8; pair::PUBLIC_KEY_BYTES] {
&self.kem_pub_key
}
pub fn to_bytes(&self) -> [u8; SIGNED_PREKEY_BYTES] {
let mut bytes = [0u8; SIGNED_PREKEY_BYTES];
bytes[..pair::PUBLIC_KEY_BYTES].copy_from_slice(&self.kem_pub_key);
bytes[pair::PUBLIC_KEY_BYTES..].copy_from_slice(&self.signature);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
if bytes.len() != SIGNED_PREKEY_BYTES {
return Err(CryptoError::IncongruentLength(
SIGNED_PREKEY_BYTES,
bytes.len(),
));
}
let (kem_pub_key, signature) = bytes.split_at(pair::PUBLIC_KEY_BYTES);
Ok(Self {
kem_pub_key: kem_pub_key.try_into()?,
signature: signature.try_into()?,
})
}
}
fn signed_data(kem_pub_key: &[u8; pair::PUBLIC_KEY_BYTES]) -> Vec<u8> {
[PREKEY_LABEL, &kem_pub_key[..]].concat()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signed_prekey() {
let mut owner = SignerPair::create();
let prekey = KEMPair::create();
let signed = SignedPrekey::new(&mut owner, &prekey).unwrap();
assert!(signed.verify(owner.pub_key_bytes()));
assert!(!signed.verify(SignerPair::create().pub_key_bytes()));
let mut undecodable = *owner.pub_key_bytes();
undecodable[0] ^= 0xFF;
assert!(!signed.verify(&undecodable));
assert_eq!(signed.kem_pub_key(), &prekey.pub_key_bytes());
let restored = SignedPrekey::from_bytes(&signed.to_bytes()).unwrap();
assert!(restored.verify(owner.pub_key_bytes()));
assert_eq!(restored.id(), signed.id());
assert_ne!(signed.id(), {
let other = KEMPair::create();
SignedPrekey::new(&mut owner, &other).unwrap().id()
});
}
#[test]
fn test_swapped_kem_key_rejected() {
let mut owner = SignerPair::create();
let signed = SignedPrekey::new(&mut owner, &KEMPair::create()).unwrap();
let mut bytes = signed.to_bytes();
bytes[..pair::PUBLIC_KEY_BYTES].copy_from_slice(&KEMPair::create().pub_key_bytes());
let swapped = SignedPrekey::from_bytes(&bytes).unwrap();
assert!(!swapped.verify(owner.pub_key_bytes()));
assert!(matches!(
SignedPrekey::from_bytes(&bytes[1..]),
Err(CryptoError::IncongruentLength(SIGNED_PREKEY_BYTES, _))
));
}
#[test]
fn test_validly_signed_invalid_kem_key_rejected() {
let mut owner = SignerPair::create();
let kem_pub_key = [0xFF; pair::PUBLIC_KEY_BYTES];
let bad = SignedPrekey {
kem_pub_key,
signature: owner.sign(&signed_data(&kem_pub_key)).unwrap(),
};
assert!(bad.verify(owner.pub_key_bytes()));
let result = crate::messaging::MessageSession::new_initiator(
SignerPair::create(),
[0u8; 24],
&bad,
owner.pub_key_bytes(),
);
assert!(matches!(result, Err(CryptoError::InvalidKey)));
}
}