use std::time::{SystemTime, UNIX_EPOCH};
use crate::{
base::CanonicalRequest,
profile::Platform,
signature::{
NonceError, SignatureInputError, SignatureParams, generate_nonce, signature_field,
validate_nonce,
},
};
pub trait Signer {
type Error;
fn platform(&self) -> Platform;
fn sign(&self, client_data_hash: &[u8; 32]) -> Result<Vec<u8>, Self::Error>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignedHeaders {
pub integrity_token: String,
pub signature_input: String,
pub signature: String,
pub request_binding: String,
}
impl SignedHeaders {
#[must_use]
pub fn headers(&self) -> [(&'static str, &str); 3] {
[
(
crate::profile::INTEGRITY_TOKEN_HEADER,
&self.integrity_token,
),
(
crate::profile::SIGNATURE_INPUT_HEADER,
&self.signature_input,
),
(crate::profile::SIGNATURE_HEADER, &self.signature),
]
}
}
#[derive(Debug, thiserror::Error)]
pub enum SignError<E> {
#[error("failed to generate a nonce")]
Randomness(#[source] getrandom::Error),
#[error(transparent)]
Nonce(#[from] NonceError),
#[error(transparent)]
Params(#[from] SignatureInputError),
#[error("the signer failed")]
Signer(#[source] E),
}
pub fn sign_request<S: Signer>(
request: &CanonicalRequest<'_>,
integrity_token: &str,
signer: &S,
) -> Result<SignedHeaders, SignError<S::Error>> {
let created = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs());
let nonce = generate_nonce().map_err(SignError::Randomness)?;
sign_request_at(
request,
integrity_token,
i64::try_from(created).unwrap_or(i64::MAX),
&nonce,
signer,
)
}
pub fn sign_request_at<S: Signer>(
request: &CanonicalRequest<'_>,
integrity_token: &str,
created: i64,
nonce: &str,
signer: &S,
) -> Result<SignedHeaders, SignError<S::Error>> {
validate_nonce(nonce)?;
let params = SignatureParams::new(created, nonce, signer.platform().alg())?;
let base = request.signature_base(¶ms, integrity_token);
let signature = signer
.sign(&base.client_data_hash())
.map_err(SignError::Signer)?;
Ok(SignedHeaders {
integrity_token: integrity_token.to_owned(),
signature_input: params.to_field(),
signature: signature_field(&signature),
request_binding: base.request_binding(),
})
}