use crate::Credential;
use base64::{Engine as _, engine::general_purpose};
use http::header::{AUTHORIZATION, DATE};
use http::request::Parts;
use log::debug;
use reqsign_core::Result;
use reqsign_core::time::Timestamp;
use reqsign_core::{Context, SignRequest, SigningRequest};
use rsa::pkcs1v15::SigningKey;
use rsa::sha2::Sha256;
use rsa::signature::{SignatureEncoding, Signer};
use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey};
use std::fmt::Write;
use std::time::Duration;
#[derive(Debug)]
pub struct RequestSigner {}
impl RequestSigner {
pub fn new() -> Self {
Self {}
}
}
impl Default for RequestSigner {
fn default() -> Self {
Self::new()
}
}
impl SignRequest for RequestSigner {
type Credential = Credential;
async fn sign_request(
&self,
ctx: &Context,
req: &mut Parts,
credential: Option<&Self::Credential>,
_expires_in: Option<Duration>,
) -> Result<()> {
let Some(cred) = credential else {
return Ok(());
};
let now = Timestamp::now();
let mut signing_req = SigningRequest::build(req)?;
let string_to_sign = {
let mut f = String::new();
writeln!(f, "date: {}", now.format_http_date())?;
writeln!(
f,
"(request-target): {} {}",
signing_req.method.as_str().to_lowercase(),
signing_req.path
)?;
write!(f, "host: {}", signing_req.authority)?;
f
};
debug!("string to sign: {}", &string_to_sign);
let private_key_content = ctx.file_read_as_string(&cred.key_file).await?;
let private_key = RsaPrivateKey::from_pkcs8_pem(&private_key_content).map_err(|e| {
reqsign_core::Error::credential_invalid(format!("Failed to read private key: {e}"))
})?;
let signing_key = SigningKey::<Sha256>::new(private_key);
let signature = signing_key
.try_sign(string_to_sign.as_bytes())
.map_err(|e| reqsign_core::Error::unexpected(format!("Failed to sign: {e}")))?;
let encoded_signature = general_purpose::STANDARD.encode(signature.to_bytes());
signing_req
.headers
.insert(DATE, now.format_http_date().parse()?);
let mut auth_value = String::new();
write!(auth_value, "Signature version=\"1\",")?;
write!(auth_value, "headers=\"date (request-target) host\",")?;
write!(
auth_value,
"keyId=\"{}/{}/{}\",",
cred.tenancy, cred.user, cred.fingerprint
)?;
write!(auth_value, "algorithm=\"rsa-sha256\",")?;
write!(auth_value, "signature=\"{encoded_signature}\"")?;
signing_req
.headers
.insert(AUTHORIZATION, auth_value.parse()?);
signing_req.apply(req)
}
}