use ed25519_dalek::{Signer, Verifier};
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand::rngs::OsRng;
use sha2::Sha256;
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
use crate::identity::Identity;
type HmacSha256 = Hmac<Sha256>;
#[derive(Clone)]
pub struct SessionKey {
key: [u8; 32],
}
impl SessionKey {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.key
}
}
pub struct HandshakeInit {
pub sender_pub: [u8; 32],
pub ephemeral_pub: [u8; 32],
pub nonce: [u8; 32],
pub signature: [u8; 64],
}
pub struct HandshakeResponse {
pub sender_pub: [u8; 32],
pub ephemeral_pub: [u8; 32],
pub nonce: [u8; 32],
pub signature: [u8; 64],
}
pub struct HandshakeConfirm {
pub hmac: [u8; 32],
}
pub struct Handshaker {
identity: HandshakeIdentity,
ephemeral_secret: Option<StaticSecret>,
ephemeral_pub: Option<[u8; 32]>,
session_key: Option<SessionKey>,
transcript: Vec<u8>,
}
struct HandshakeIdentity {
signing_key_bytes: [u8; 32],
verifying_key_bytes: [u8; 32],
}
impl Handshaker {
pub fn new(identity: &Identity) -> Self {
Self {
identity: HandshakeIdentity {
signing_key_bytes: identity.signing_key().to_bytes(),
verifying_key_bytes: identity.public_key_bytes(),
},
ephemeral_secret: None,
ephemeral_pub: None,
session_key: None,
transcript: Vec::new(),
}
}
pub fn initiate(&mut self) -> HandshakeInit {
let ephemeral_secret = StaticSecret::random_from_rng(OsRng);
let ephemeral_pub = X25519PublicKey::from(&ephemeral_secret);
let ephemeral_pub_bytes = ephemeral_pub.to_bytes();
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut OsRng, &mut nonce);
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&ephemeral_pub_bytes);
msg.extend_from_slice(&nonce);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&self.identity.signing_key_bytes);
let signature = signing_key.sign(&msg);
self.ephemeral_secret = Some(ephemeral_secret);
self.ephemeral_pub = Some(ephemeral_pub_bytes);
let init = HandshakeInit {
sender_pub: self.identity.verifying_key_bytes,
ephemeral_pub: ephemeral_pub_bytes,
nonce,
signature: signature.to_bytes(),
};
self.transcript.extend_from_slice(&init.sender_pub);
self.transcript.extend_from_slice(&init.ephemeral_pub);
self.transcript.extend_from_slice(&init.nonce);
init
}
pub fn respond(&mut self, init: &HandshakeInit) -> Result<HandshakeResponse, HandshakeError> {
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&init.sender_pub)
.map_err(|_| HandshakeError::InvalidPublicKey)?;
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&init.ephemeral_pub);
msg.extend_from_slice(&init.nonce);
let signature = ed25519_dalek::Signature::from_bytes(&init.signature);
verifying_key
.verify(&msg, &signature)
.map_err(|_| HandshakeError::InvalidSignature)?;
let ephemeral_secret = StaticSecret::random_from_rng(OsRng);
let ephemeral_pub = X25519PublicKey::from(&ephemeral_secret);
let ephemeral_pub_bytes = ephemeral_pub.to_bytes();
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut OsRng, &mut nonce);
let mut sign_msg = Vec::with_capacity(64);
sign_msg.extend_from_slice(&ephemeral_pub_bytes);
sign_msg.extend_from_slice(&nonce);
let signing_key = ed25519_dalek::SigningKey::from_bytes(&self.identity.signing_key_bytes);
let signature = signing_key.sign(&sign_msg);
let their_ephemeral = X25519PublicKey::from(init.ephemeral_pub);
let shared_secret = ephemeral_secret.diffie_hellman(&their_ephemeral);
self.transcript.extend_from_slice(&init.sender_pub);
self.transcript.extend_from_slice(&init.ephemeral_pub);
self.transcript.extend_from_slice(&init.nonce);
self.transcript
.extend_from_slice(&self.identity.verifying_key_bytes);
self.transcript.extend_from_slice(&ephemeral_pub_bytes);
self.transcript.extend_from_slice(&nonce);
let mut salt = Vec::with_capacity(64);
salt.extend_from_slice(&init.nonce);
salt.extend_from_slice(&nonce);
let hk = Hkdf::<Sha256>::new(Some(&salt), shared_secret.as_bytes());
let mut key = [0u8; 32];
hk.expand(b"pim-session-v1", &mut key)
.expect("32 bytes is valid for HKDF-SHA256");
self.session_key = Some(SessionKey { key });
self.ephemeral_pub = Some(ephemeral_pub_bytes);
Ok(HandshakeResponse {
sender_pub: self.identity.verifying_key_bytes,
ephemeral_pub: ephemeral_pub_bytes,
nonce,
signature: signature.to_bytes(),
})
}
pub fn finalize_initiator(
&mut self,
response: &HandshakeResponse,
) -> Result<(), HandshakeError> {
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&response.sender_pub)
.map_err(|_| HandshakeError::InvalidPublicKey)?;
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&response.ephemeral_pub);
msg.extend_from_slice(&response.nonce);
let signature = ed25519_dalek::Signature::from_bytes(&response.signature);
verifying_key
.verify(&msg, &signature)
.map_err(|_| HandshakeError::InvalidSignature)?;
let ephemeral_secret = self
.ephemeral_secret
.take()
.ok_or(HandshakeError::InvalidState)?;
let their_ephemeral = X25519PublicKey::from(response.ephemeral_pub);
let shared_secret = ephemeral_secret.diffie_hellman(&their_ephemeral);
self.transcript.extend_from_slice(&response.sender_pub);
self.transcript.extend_from_slice(&response.ephemeral_pub);
self.transcript.extend_from_slice(&response.nonce);
let init_nonce = &self.transcript[64..96];
let mut salt = Vec::with_capacity(64);
salt.extend_from_slice(init_nonce);
salt.extend_from_slice(&response.nonce);
let hk = Hkdf::<Sha256>::new(Some(&salt), shared_secret.as_bytes());
let mut key = [0u8; 32];
hk.expand(b"pim-session-v1", &mut key)
.expect("32 bytes is valid for HKDF-SHA256");
self.session_key = Some(SessionKey { key });
Ok(())
}
pub fn make_confirm(&self) -> Result<HandshakeConfirm, HandshakeError> {
let session_key = self
.session_key
.as_ref()
.ok_or(HandshakeError::InvalidState)?;
let mut mac =
HmacSha256::new_from_slice(session_key.as_bytes()).expect("HMAC accepts any key size");
mac.update(&self.transcript);
let result = mac.finalize().into_bytes();
let mut hmac = [0u8; 32];
hmac.copy_from_slice(&result);
Ok(HandshakeConfirm { hmac })
}
pub fn verify_confirm(&self, confirm: &HandshakeConfirm) -> Result<(), HandshakeError> {
let session_key = self
.session_key
.as_ref()
.ok_or(HandshakeError::InvalidState)?;
let mut mac =
HmacSha256::new_from_slice(session_key.as_bytes()).expect("HMAC accepts any key size");
mac.update(&self.transcript);
mac.verify_slice(&confirm.hmac)
.map_err(|_| HandshakeError::ConfirmMismatch)
}
pub fn session_key(&self) -> Option<&SessionKey> {
self.session_key.as_ref()
}
}
#[derive(Debug, thiserror::Error)]
pub enum HandshakeError {
#[error("invalid public key")]
InvalidPublicKey,
#[error("invalid signature")]
InvalidSignature,
#[error("handshake confirm mismatch")]
ConfirmMismatch,
#[error("invalid handshake state")]
InvalidState,
}
#[cfg(test)]
mod tests;