#![doc(html_root_url = "https://docs.rs/cpace/0.1.0")]
use std::convert::TryFrom;
use curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
};
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::Sha512;
#[derive(Copy, Clone)]
pub struct Key(pub [u8; 64]);
#[derive(Copy, Clone)]
pub struct InitMessage(pub [u8; 48]);
#[derive(Copy, Clone)]
pub struct ResponseMessage(pub [u8; 32]);
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}-byte initiator id is too long.")]
InitiatorIdTooLong(usize),
#[error("{0}-byte responder id is too long.")]
ResponderIdTooLong(usize),
#[error("{0}-byte associated data is too long.")]
AssociatedDataTooLong(usize),
#[error("Invalid point encoding.")]
InvalidPoint,
}
#[derive(Copy, Clone)]
pub struct Context<'ctx> {
pub initiator_id: &'ctx str,
pub responder_id: &'ctx str,
pub associated_data: &'ctx [u8],
}
const CONTEXT_LABEL: &[u8; 10] = b"cpace-r255";
impl<'ctx> Context<'ctx> {
fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
let len = u16::try_from(CONTEXT_LABEL.len()).unwrap();
bytes.extend_from_slice(&len.to_be_bytes());
bytes.extend_from_slice(CONTEXT_LABEL);
let len = u16::try_from(self.initiator_id.len())
.map_err(|_| Error::InitiatorIdTooLong(self.initiator_id.len()))?;
bytes.extend_from_slice(&len.to_be_bytes());
bytes.extend_from_slice(self.initiator_id.as_bytes());
let len = u16::try_from(self.responder_id.len())
.map_err(|_| Error::ResponderIdTooLong(self.responder_id.len()))?;
bytes.extend_from_slice(&len.to_be_bytes());
bytes.extend_from_slice(self.responder_id.as_bytes());
let len = u16::try_from(self.associated_data.len())
.map_err(|_| Error::AssociatedDataTooLong(self.associated_data.len()))?;
bytes.extend_from_slice(&len.to_be_bytes());
bytes.extend_from_slice(self.associated_data);
Ok(bytes)
}
}
fn secret_generator(password: &str, salt: &[u8], context_bytes: &[u8]) -> RistrettoPoint {
let mut output = [0; 64];
Hkdf::<Sha512>::new(Some(salt), password.as_bytes())
.expand(context_bytes, &mut output)
.expect("64 bytes is less than max output size");
RistrettoPoint::from_uniform_bytes(&output)
}
fn transcript(init_msg: InitMessage, rsp_msg: ResponseMessage) -> [u8; 16 + 32 + 32] {
let mut bytes = [0; 16 + 32 + 32];
bytes[0..48].copy_from_slice(&init_msg.0[..]);
bytes[48..].copy_from_slice(&rsp_msg.0[..]);
bytes
}
#[allow(non_snake_case)]
pub fn init<R: RngCore + CryptoRng>(
password: &str,
context: Context,
mut rng: R,
) -> Result<(InitMessage, AwaitingResponse), Error> {
let mut msg_bytes = [0u8; 48];
let (salt, point) = msg_bytes.split_at_mut(16);
rng.fill_bytes(salt);
let H = secret_generator(password, salt, &context.serialize()?);
let a = Scalar::random(&mut rng);
let A = a * H;
point.copy_from_slice(A.compress().as_bytes());
let init_msg = InitMessage(msg_bytes);
Ok((init_msg, AwaitingResponse { a, init_msg }))
}
#[allow(non_snake_case)]
pub fn respond<R: RngCore + CryptoRng>(
init_msg: InitMessage,
password: &str,
context: Context,
mut rng: R,
) -> Result<(Key, ResponseMessage), Error> {
let (salt, A_bytes) = init_msg.0[..].split_at(16);
let H = secret_generator(password, salt, &context.serialize()?);
let b = Scalar::random(&mut rng);
let B = b * H;
let rsp_msg = ResponseMessage(B.compress().to_bytes());
let A = CompressedRistretto::from_slice(A_bytes)
.decompress()
.ok_or(Error::InvalidPoint)?;
let (key_bytes, _) = Hkdf::<Sha512>::extract(
Some(&transcript(init_msg, rsp_msg)[..]),
(b * A).compress().as_bytes(),
);
let key = {
let mut bytes = [0; 64];
bytes.copy_from_slice(&key_bytes[..]);
Key(bytes)
};
Ok((key, rsp_msg))
}
pub struct AwaitingResponse {
a: Scalar,
init_msg: InitMessage,
}
impl AwaitingResponse {
#[allow(non_snake_case)]
pub fn recv(self, rsp_msg: ResponseMessage) -> Result<Key, Error> {
let B = CompressedRistretto(rsp_msg.0)
.decompress()
.ok_or(Error::InvalidPoint)?;
let (key_bytes, _) = Hkdf::<Sha512>::extract(
Some(&transcript(self.init_msg, rsp_msg)[..]),
(self.a * B).compress().as_bytes(),
);
let key = {
let mut bytes = [0; 64];
bytes.copy_from_slice(&key_bytes[..]);
Key(bytes)
};
Ok(key)
}
}