use std::time::SystemTime;
use anyhow::bail;
#[cfg(feature = "core")]
use blake2::digest::{FixedOutput, Mac};
#[cfg(feature = "core")]
use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "core")]
use std::time::Duration;
#[cfg(feature = "core")]
use crate::{SIG_LEN, ZEROED_KEY};
use crate::{EXP_LEN, MAC_LEN};
#[cfg(feature = "core")]
use ed25519_dalek::{Signature, VerifyingKey};
pub fn check_exp(token: &[u8]) -> anyhow::Result<bool> {
let exp = if token.len() > 8 {
let exp_as_bytes: [u8; EXP_LEN] = token[0..EXP_LEN].try_into()?;
u64::from_be_bytes(exp_as_bytes)
} else {
bail!("no exp on token");
};
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
Ok(exp >= now)
}
pub fn get_exp(token: &[u8]) -> anyhow::Result<u64> {
let exp = if token.len() > EXP_LEN {
let exp_as_bytes: [u8; EXP_LEN] = token[0..EXP_LEN].try_into()?;
u64::from_be_bytes(exp_as_bytes)
} else {
bail!("no exp on token");
};
Ok(exp)
}
#[cfg(feature = "core")]
pub fn generate_hmac(claims: &[u8], from_now_secs: u32, key: &[u8; 32]) -> anyhow::Result<Bytes> {
match SystemTime::now().checked_add(Duration::from_secs(u64::from(from_now_secs))) {
Some(future_time) => {
let exp_as_bytes: [u8; EXP_LEN] = future_time
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs()
.to_be_bytes();
let hmac = blake2::Blake2sMac256::new_from_slice(key)?
.chain_update(exp_as_bytes)
.chain_update(claims)
.finalize_fixed();
let mut buf = BytesMut::new();
buf.put(&exp_as_bytes[..]);
buf.put(&hmac[..]);
buf.put(claims);
Ok(buf.into())
}
None => bail!("date is out of range"),
}
}
pub fn extract_hmac_no_check<'a>(token: &'a [u8]) -> anyhow::Result<&'a [u8]> {
if token.len() >= EXP_LEN + MAC_LEN {
let claims: &'a [u8] = &token[EXP_LEN + MAC_LEN..];
Ok(claims)
} else {
bail!("no signature for token")
}
}
#[cfg(feature = "core")]
pub fn verify_hmac<'a>(token: &'a [u8], key: &[u8; 32]) -> anyhow::Result<&'a [u8]> {
let (exp, exp_as_bytes) = if token.len() > EXP_LEN {
let exp_as_bytes: [u8; EXP_LEN] = token[0..EXP_LEN].try_into()?;
(u64::from_be_bytes(exp_as_bytes), exp_as_bytes)
} else {
bail!("no exp on token");
};
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
if exp < now {
bail!("token is expired");
}
if token.len() >= EXP_LEN + MAC_LEN {
let claims: &'a [u8] = &token[EXP_LEN + MAC_LEN..];
let comp = blake2::Blake2sMac256::new_from_slice(key)?
.chain_update(exp_as_bytes)
.chain_update(claims)
.finalize_fixed();
if comp.as_slice() == &token[EXP_LEN..EXP_LEN + MAC_LEN] {
Ok(claims)
} else {
bail!("invalid token")
}
} else {
bail!("no token hmac")
}
}
#[cfg(feature = "core")]
pub fn verify_client_signature(token: &[u8], public_key: &[u8; 32]) -> anyhow::Result<()> {
if public_key == &ZEROED_KEY {
bail!("public key cannot be all 0s");
}
let client_exp_as_bytes =
token[token.len() - (EXP_LEN + SIG_LEN)..token.len() - SIG_LEN].try_into()?;
let client_exp = u64::from_be_bytes(client_exp_as_bytes);
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
if client_exp < now {
bail!("client exp exceeded");
}
let signature_bytes: [u8; SIG_LEN] = token[token.len() - SIG_LEN..].try_into()?;
let signature = Signature::from_bytes(&signature_bytes);
let verifying_key = VerifyingKey::from_bytes(public_key)?;
verifying_key.verify_strict(&token[..token.len() - SIG_LEN], &signature)?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use chacha20poly1305::aead::OsRng;
use ed25519_dalek::{Signer, SigningKey};
const HMAC_KEY: [u8; 32] = [0u8; 32];
#[test]
fn hmac() -> anyhow::Result<()> {
let token = generate_hmac(b"test", 60 * 60 * 24, &HMAC_KEY)?;
let payload = verify_hmac(&token[..], &HMAC_KEY)?;
assert_eq!(payload, b"test");
Ok(())
}
#[test]
fn signed() -> anyhow::Result<()> {
let signing_key: SigningKey = SigningKey::generate(&mut OsRng);
let verifying_key = signing_key.verifying_key();
let mut token: BytesMut = generate_hmac(b"test", 60 * 60 * 24, &HMAC_KEY)?.into();
let exp = SystemTime::now()
.checked_add(Duration::from_secs(5))
.expect("time to work")
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs();
token.put_u64(exp);
let signature = signing_key.sign(&token[..]);
token.put(&signature.to_bytes()[..]);
verify_client_signature(&token, verifying_key.as_bytes())
}
}