use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
type HmacSha256 = Hmac<Sha256>;
pub fn object_path(vault_id: &str, namespace_id: &str, object_id: &str) -> String {
format!("/v1/vault/{vault_id}/ns/{namespace_id}/obj/{object_id}")
}
pub fn namespace_objects_path(vault_id: &str, namespace_id: &str) -> String {
format!("/v1/vault/{vault_id}/ns/{namespace_id}/objects")
}
pub fn body_sha256(body: &[u8]) -> Vec<u8> {
Sha256::digest(body).to_vec()
}
fn canonical_string(method: &str, path: &str, timestamp: i64, body_hash: &[u8]) -> String {
format!("{method}\n{path}\n{timestamp}\n{}", hex::encode(body_hash))
}
pub fn sign_request(
credential: &[u8],
method: &str,
path: &str,
timestamp: i64,
body: &[u8],
) -> String {
sign_with_body_hash(credential, method, path, timestamp, &body_sha256(body))
}
pub fn sign_with_body_hash(
credential: &[u8],
method: &str,
path: &str,
timestamp: i64,
body_hash: &[u8],
) -> String {
let mut mac = HmacSha256::new_from_slice(credential).expect("HMAC accepts a key of any length");
mac.update(canonical_string(method, path, timestamp, body_hash).as_bytes());
hex::encode(mac.finalize().into_bytes())
}
pub fn verify_request(
credential: &[u8],
method: &str,
path: &str,
timestamp: i64,
body: &[u8],
signature_hex: &str,
) -> bool {
verify_with_body_hash(
credential,
method,
path,
timestamp,
&body_sha256(body),
signature_hex,
)
}
pub fn verify_with_body_hash(
credential: &[u8],
method: &str,
path: &str,
timestamp: i64,
body_hash: &[u8],
signature_hex: &str,
) -> bool {
let Ok(signature_bytes) = hex::decode(signature_hex) else {
return false;
};
let mut mac = HmacSha256::new_from_slice(credential).expect("HMAC accepts a key of any length");
mac.update(canonical_string(method, path, timestamp, body_hash).as_bytes());
mac.verify_slice(&signature_bytes).is_ok()
}
pub fn is_valid_hex_id(id: &str) -> bool {
id.len() == 64
&& id
.chars()
.all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c))
}