use std::time::{Duration, SystemTime};
use crate::algorithm::Signer as AlgorithmSigner;
use crate::base64::URLSafeBase64Encode;
use crate::error::BadTimedSignature;
use crate::timestamp;
use crate::traits::GetSigner;
use crate::{AsSigner, Separator, Signer, TimestampSigner};
pub struct TimestampSignerImpl<TSigner>(TSigner);
impl<TSigner> TimestampSignerImpl<TSigner>
where
TSigner: Signer + GetSigner,
{
pub(crate) fn with_signer(signer: TSigner) -> Self {
Self(signer)
}
pub(crate) fn split<'a>(
&'a self,
value: &'a str,
) -> Result<(&'a str, &'a str), BadTimedSignature<'a>> {
self.0
.separator()
.split(value)
.map_err(|_| BadTimedSignature::TimestampMissing { value })
}
}
impl<TSigner> TimestampSigner for TimestampSignerImpl<TSigner>
where
TSigner: Signer + GetSigner,
{
fn separator(&self) -> Separator {
self.0.separator()
}
fn sign_with_timestamp<S: AsRef<str>>(&self, value: S, timestamp: SystemTime) -> String {
let value = value.as_ref();
let encoded_timestamp = timestamp::encode(timestamp);
let separator = self.0.separator().0;
let signature = self
.0
.get_signer()
.input_chained(value.as_bytes())
.input_chained(&[separator as u8])
.input_chained(encoded_timestamp.as_slice())
.sign();
let mut output = String::with_capacity(
value.len() + 1 + encoded_timestamp.length() + 1 + self.0.signature_output_size(),
);
output.push_str(value);
output.push(separator);
output.push_str(encoded_timestamp.as_str());
output.push(separator);
signature.base64_encode_str(&mut output);
output
}
fn sign<S: AsRef<str>>(&self, value: S) -> String {
self.sign_with_timestamp(value, SystemTime::now())
}
fn unsign<'a>(&'a self, value: &'a str) -> Result<UnsignedValue, BadTimedSignature<'a>> {
let value = self.0.unsign(value)?;
let (value, timestamp) = self.split(value)?;
let timestamp = timestamp::decode(timestamp)?;
Ok(UnsignedValue { timestamp, value })
}
}
impl<TSigner> AsSigner for TimestampSignerImpl<TSigner>
where
TSigner: Signer,
{
type Signer = TSigner;
fn as_signer(&self) -> &Self::Signer {
&self.0
}
}
pub struct UnsignedValue<'a> {
value: &'a str,
timestamp: SystemTime,
}
impl<'a> UnsignedValue<'a> {
pub fn value(&self) -> &'a str {
&self.value
}
pub fn timestamp(&self) -> SystemTime {
self.timestamp
}
pub fn value_if_not_expired(self, max_age: Duration) -> Result<&'a str, BadTimedSignature<'a>> {
match self.timestamp.elapsed() {
Ok(duration) if duration > max_age => Err(BadTimedSignature::TimestampExpired {
timestamp: self.timestamp,
value: self.value,
max_age,
}),
Ok(_) | Err(_) => Ok(self.value),
}
}
}
#[cfg(test)]
mod tests {
use crate::{default_builder, IntoTimestampSigner, TimestampSigner};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[test]
fn test_sign() {
let signer = default_builder("hello").build().into_timestamp_signer();
let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622);
let signed = signer.sign_with_timestamp("hello world", timestamp);
assert_eq!(signed, "hello world.XP57dg.uBK_KvrfABr48ZHk6IrBINjpqp8");
let unsigned = signer.unsign(&signed).unwrap();
assert_eq!(unsigned.value(), "hello world");
assert_eq!(unsigned.timestamp(), timestamp);
}
#[test]
fn test_sign_expired() {
let signer = default_builder("hello").build().into_timestamp_signer();
let timestamp = SystemTime::now() - Duration::from_secs(60);
let signed = signer.sign_with_timestamp("hello world", timestamp);
let unsigned = signer.unsign(&signed).unwrap();
assert!(unsigned
.value_if_not_expired(Duration::from_secs(30))
.is_err());
}
#[test]
fn test_sign_not_expired() {
let signer = default_builder("hello").build().into_timestamp_signer();
let timestamp = SystemTime::now() - Duration::from_secs(60);
let signed = signer.sign_with_timestamp("hello world", timestamp);
let unsigned = signer.unsign(&signed).unwrap();
assert!(unsigned
.value_if_not_expired(Duration::from_secs(90))
.is_ok());
}
}
#[cfg(all(test, feature = "nightly"))]
mod bench {
use crate::*;
use std::time::{Duration, UNIX_EPOCH};
extern crate test;
use test::Bencher;
#[bench]
fn bench_sign(bench: &mut Bencher) {
let signer = default_builder("hello").build().into_timestamp_signer();
let timestamp = UNIX_EPOCH + Duration::from_secs(1560181622);
bench.iter(|| signer.sign_with_timestamp("hello world", timestamp))
}
#[bench]
fn bench_unsign(bench: &mut Bencher) {
let signer = default_builder("hello").build().into_timestamp_signer();
bench.iter(|| signer.unsign("hello world.D-AM9g.T7AHtE1DsJn4dzUb-oeOwpWWoX8"))
}
}