use std::error::Error;
use std::time::{Duration, SystemTime};
use blake2::digest::{
FixedOutput, Mac,
generic_array::{GenericArray, typenum},
};
use bytes::{BufMut, Bytes, BytesMut};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
pub fn check_exp(token: &[u8]) -> Result<bool, Box<dyn Error>> {
let exp = if token.len() > 4 {
let exp_as_bytes: [u8; 4] = token[0..4].try_into()?;
u32::from_be_bytes(exp_as_bytes)
} else {
return Err("no exp on token".into());
};
let now = u32::try_from(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?;
Ok(exp >= now)
}
pub fn get_exp(token: &[u8]) -> Result<u32, Box<dyn Error>> {
let exp = if token.len() > 4 {
let exp_as_bytes: [u8; 4] = token[0..4].try_into()?;
u32::from_be_bytes(exp_as_bytes)
} else {
return Err("no exp on token".into());
};
Ok(exp)
}
pub fn generate_signed(
claims: &[u8],
from_now_secs: u32,
secret_key: [u8; 32],
) -> Result<Bytes, Box<dyn Error>> {
match SystemTime::now().checked_add(Duration::from_secs(u64::from(from_now_secs))) {
Some(future_time) => {
let exp_as_bytes = u32::try_from(
future_time
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?
.to_be_bytes();
let singing_key: SigningKey = SigningKey::from_bytes(&secret_key);
let mut buf = BytesMut::new();
buf.put(&exp_as_bytes[..]);
buf.put(claims);
let signature = singing_key.sign(&buf[..]);
let mut out = BytesMut::new();
out.put(&exp_as_bytes[..]);
out.put(&signature.to_bytes()[..]);
out.put(claims);
Ok(out.into())
}
None => Err("date is out of range".into()),
}
}
pub fn extract_signed_no_check<'a>(token: &'a [u8]) -> Result<&'a [u8], Box<dyn Error>> {
if token.len() >= 4 + 64 {
let claims: &'a [u8] = &token[4 + 64..];
Ok(claims)
} else {
Err("no signature for token".into())
}
}
pub fn verify_signed<'a>(
token: &'a [u8],
public_key: [u8; 32],
) -> Result<&'a [u8], Box<dyn Error>> {
let (exp, exp_as_bytes) = if token.len() > 4 {
let exp_as_bytes: [u8; 4] = token[0..4].try_into()?;
(u32::from_be_bytes(exp_as_bytes), exp_as_bytes)
} else {
return Err("no exp on token".into());
};
let now = u32::try_from(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?;
if exp < now {
return Err("token is expired".into());
}
if token.len() >= 4 + 64 {
let claims: &'a [u8] = &token[4 + 64..];
let mut buf = BytesMut::new();
buf.put(&exp_as_bytes[..]);
buf.put(claims);
let signature_bytes: [u8; 64] = token[4..4 + 64].try_into()?;
let signature = Signature::from_bytes(&signature_bytes);
let verifying_key = VerifyingKey::from_bytes(&public_key)?;
verifying_key.verify(&buf, &signature)?;
Ok(claims)
} else {
Err("no signature for token".into())
}
}
#[inline]
pub fn generate_hmac(
claims: &[u8],
from_now_secs: u32,
key: [u8; 32],
) -> Result<Bytes, Box<dyn Error>> {
match SystemTime::now().checked_add(Duration::from_secs(u64::from(from_now_secs))) {
Some(future_time) => {
let exp_as_bytes = u32::try_from(
future_time
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?
.to_be_bytes();
let hmac = blake2::Blake2sMac256::new_from_slice(key.as_ref())?
.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 => Err("date is out of range".into()),
}
}
pub fn extract_hmac_no_check<'a>(token: &'a [u8]) -> Result<&'a [u8], Box<dyn Error>> {
if token.len() >= 4 + 32 {
let claims: &'a [u8] = &token[4 + 32..];
Ok(claims)
} else {
Err("no signature for token".into())
}
}
#[inline]
pub fn verify_hmac<'a>(token: &'a [u8], key: [u8; 32]) -> Result<&'a [u8], Box<dyn Error>> {
let (exp, exp_as_bytes) = if token.len() > 4 {
let exp_as_bytes: [u8; 4] = token[0..4].try_into()?;
(u32::from_be_bytes(exp_as_bytes), exp_as_bytes)
} else {
return Err("no exp on token".into());
};
let now = u32::try_from(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?;
if exp < now {
return Err("token is expired".into());
}
if token.len() >= 4 + 32 {
let claims: &'a [u8] = &token[4 + 32..];
let comp = blake2::Blake2sMac256::new_from_slice(&key)?
.chain_update(exp_as_bytes)
.chain_update(claims)
.finalize_fixed();
let hmac = GenericArray::<u8, typenum::U32>::from_slice(&token[4..4 + 32]);
if &comp == hmac {
Ok(claims)
} else {
Err("invalid token".into())
}
} else {
Err("no token hmac".into())
}
}
pub fn verify_client_signature(
token: &[u8],
verifying_key_bytes: [u8; 32],
) -> Result<(), Box<dyn Error>> {
let client_exp_as_bytes = token[token.len() - (4 + 64)..token.len() - 64].try_into()?;
let client_exp = u32::from_be_bytes(client_exp_as_bytes);
let now = u32::try_from(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs(),
)?;
if client_exp < now {
return Err("client exp exceeded".into());
}
let signature_bytes: [u8; 64] = token[token.len() - 64..].try_into()?;
let signature = Signature::from_bytes(&signature_bytes);
let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)?;
verifying_key.verify(&token[..token.len() - 64], &signature)?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use chacha20poly1305::aead::OsRng;
const HMAC_KEY: [u8; 32] = [0u8; 32];
#[test]
fn hmac() -> Result<(), Box<dyn Error>> {
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() -> Result<(), Box<dyn Error>> {
let mut rng = OsRng;
let signing_key = SigningKey::generate(&mut rng);
let verifying_key = signing_key.verifying_key();
let token = generate_signed(b"test", 60 * 60 * 24, signing_key.to_bytes())?;
let payload = verify_signed(&token[..], verifying_key.to_bytes())?;
assert_eq!(payload, b"test");
Ok(())
}
}