use crate::{errors::AuthError, groups::*, utils::*};
use alloc::vec::Vec;
use bigint::{BoxedUint, ConcatenatingMul, Odd, Resize, modular::BoxedMontyForm};
use core::marker::PhantomData;
use digest::{Digest, Output};
use subtle::ConstantTimeEq;
#[deprecated(since = "0.7.0", note = "too small to be secure; use a larger group")]
#[allow(deprecated)]
pub type ClientG1024<D> = Client<G1024, D>;
#[deprecated(since = "0.7.0", note = "too small to be secure; use a larger group")]
#[allow(deprecated)]
pub type ClientG1536<D> = Client<G1536, D>;
pub type ClientG2048<D> = Client<G2048, D>;
pub type ClientG3072<D> = Client<G3072, D>;
pub type ClientG4096<D> = Client<G4096, D>;
#[cfg_attr(feature = "getrandom", doc = "```")]
#[cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
#[derive(Debug)]
pub struct Client<G: Group, D: Digest> {
g: BoxedMontyForm,
username_in_x: bool,
d: PhantomData<(G, D)>,
}
impl<G: Group, D: Digest> Client<G, D> {
#[must_use]
pub fn new() -> Self {
Self::new_with_options(true)
}
#[must_use]
pub fn new_with_options(username_in_x: bool) -> Self {
Self {
g: G::generator(),
username_in_x,
d: PhantomData,
}
}
#[must_use]
pub fn compute_g_x(&self, x: &BoxedUint) -> BoxedUint {
self.g.pow(x).retrieve()
}
#[must_use]
pub fn compute_public_ephemeral(&self, a: &[u8]) -> Vec<u8> {
self.compute_g_x(&BoxedUint::from_be_slice_vartime(a))
.to_be_bytes_trimmed_vartime()
.into()
}
#[must_use]
pub fn compute_identity_hash(username: &[u8], password: &[u8]) -> Output<D> {
let mut d = D::new();
d.update(username);
d.update(b":");
d.update(password);
d.finalize()
}
#[must_use]
pub fn compute_x(identity_hash: &[u8], salt: &[u8]) -> BoxedUint {
let mut x = D::new();
x.update(salt);
x.update(identity_hash);
BoxedUint::from_be_slice_vartime(&x.finalize())
}
#[must_use]
pub fn compute_premaster_secret(
&self,
b_pub: &BoxedUint,
k: &BoxedUint,
x: &BoxedUint,
a: &BoxedUint,
u: &BoxedUint,
) -> BoxedUint {
let b_pub = monty_form(b_pub, self.g.params());
let k = monty_form(k, self.g.params());
let base = b_pub - k * self.g.pow(x);
let exp = a.concatenating_add(u.concatenating_mul(x));
base.pow(&exp).retrieve()
}
#[must_use]
pub fn compute_verifier(&self, username: &[u8], password: &[u8], salt: &[u8]) -> Vec<u8> {
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
let x = Self::compute_x(identity_hash.as_slice(), salt);
self.compute_g_x(&x).to_be_bytes_trimmed_vartime().into()
}
pub fn process_reply(
&self,
a: &[u8],
username: &[u8],
password: &[u8],
salt: &[u8],
b_pub_bytes: &[u8],
) -> Result<ClientVerifier<D>, AuthError> {
let a = BoxedUint::from_be_slice_vartime(a);
let a_pub_bytes = self.compute_g_x(&a).to_be_bytes_trimmed_vartime();
let b_pub = BoxedUint::from_be_slice_vartime(b_pub_bytes);
self.validate_b_pub(&b_pub)?;
let u = compute_u::<D>(&a_pub_bytes, b_pub_bytes);
let k = compute_k::<D>(&self.g);
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
let x = Self::compute_x(identity_hash.as_slice(), salt);
let premaster_secret = self
.compute_premaster_secret(&b_pub, &k, &x, &a, &u)
.to_be_bytes_trimmed_vartime();
let session_key = compute_hash::<D>(&premaster_secret);
let m1 = compute_m1_rfc5054::<D>(
&self.g,
false,
username,
salt,
&a_pub_bytes,
b_pub_bytes,
session_key.as_slice(),
);
let m2 = compute_m2::<D>(&a_pub_bytes, &m1, session_key.as_slice());
Ok(ClientVerifier {
m1,
m2,
key: premaster_secret.to_vec(),
session_key: session_key.to_vec(),
})
}
#[deprecated(since = "0.7.0", note = "please use `Client::process_reply` (RFC5054)")]
#[allow(deprecated)]
pub fn process_reply_legacy(
&self,
a: &[u8],
username: &[u8],
password: &[u8],
salt: &[u8],
b_pub_bytes: &[u8],
) -> Result<LegacyClientVerifier<D>, AuthError> {
let a = BoxedUint::from_be_slice_vartime(a);
let a_pub_bytes = self.compute_g_x(&a).to_be_bytes_trimmed_vartime();
let b_pub = BoxedUint::from_be_slice_vartime(b_pub_bytes);
self.validate_b_pub(&b_pub)?;
let u = compute_u::<D>(&a_pub_bytes, b_pub_bytes);
let k = compute_k::<D>(&self.g);
let identity_hash = Self::compute_identity_hash(self.identity_username(username), password);
let x = Self::compute_x(identity_hash.as_slice(), salt);
let key = self
.compute_premaster_secret(&b_pub, &k, &x, &a, &u)
.to_be_bytes_trimmed_vartime()
.to_vec();
let m1 = compute_m1_legacy::<D>(&a_pub_bytes, b_pub_bytes, &key);
let m2 = compute_m2::<D>(&a_pub_bytes, &m1, &key);
Ok(LegacyClientVerifier { m1, m2, key })
}
fn identity_username<'a>(&self, username: &'a [u8]) -> &'a [u8] {
if self.username_in_x { username } else { &[] }
}
fn n(&self) -> &Odd<BoxedUint> {
self.g.params().modulus()
}
fn validate_b_pub(&self, b_pub: &BoxedUint) -> Result<(), AuthError> {
let n = self.n().as_nz_ref();
if (b_pub.resize(n.bits_precision()) % n).is_zero().into() {
return Err(AuthError::IllegalParameter { name: "b_pub" });
}
Ok(())
}
}
impl<G: Group, D: Digest> Default for Client<G, D> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ClientVerifier<D: Digest> {
m1: Output<D>,
m2: Output<D>,
key: Vec<u8>,
session_key: Vec<u8>,
}
impl<D: Digest> ClientVerifier<D> {
pub fn key(&self) -> &[u8] {
&self.key
}
pub fn proof(&self) -> &[u8] {
self.m1.as_slice()
}
pub fn verify_server(&self, reply: &[u8]) -> Result<&[u8], AuthError> {
if self.m2.ct_eq(reply).unwrap_u8() == 1 {
Ok(self.session_key.as_slice())
} else {
Err(AuthError::BadRecordMac { peer: "server" })
}
}
}
#[deprecated(since = "0.7.0", note = "please switch to `ClientVerifierRfc5054`")]
#[derive(Debug)]
pub struct LegacyClientVerifier<D: Digest> {
m1: Output<D>,
m2: Output<D>,
key: Vec<u8>,
}
#[allow(deprecated)]
impl<D: Digest> LegacyClientVerifier<D> {
pub fn key(&self) -> &[u8] {
&self.key
}
pub fn proof(&self) -> &[u8] {
self.m1.as_slice()
}
pub fn verify_server(&self, reply: &[u8]) -> Result<(), AuthError> {
if self.m2.ct_eq(reply).unwrap_u8() == 1 {
Ok(())
} else {
Err(AuthError::BadRecordMac { peer: "server" })
}
}
}