#![forbid(unsafe_code)]
use std::time::SystemTime;
use base64::Engine as _;
use ring::rand::SystemRandom;
use ring::signature::{RsaKeyPair, RSA_PKCS1_SHA256};
use crate::{Error, Result};
pub(crate) fn build_signing_header(
service_name: &str,
host: &str,
port: u16,
now: SystemTime,
) -> String {
let date = httpdate::fmt_http_date(now);
format!("date: {date}\n(request-target): {service_name}\nhost: {host}:{port}")
}
pub(crate) fn sign_signing_header(private_key_pkcs8_pem: &str, header: &str) -> Result<String> {
let der = pkcs8_pem_to_der(private_key_pkcs8_pem)?;
let key_pair = RsaKeyPair::from_pkcs8(&der).map_err(|_| Error::IamTokenProofOfPossession)?;
let rng = SystemRandom::new();
let mut signature = vec![0u8; key_pair.public().modulus_len()];
key_pair
.sign(&RSA_PKCS1_SHA256, &rng, header.as_bytes(), &mut signature)
.map_err(|_| Error::IamTokenProofOfPossession)?;
Ok(base64::engine::general_purpose::STANDARD.encode(&signature))
}
fn pkcs8_pem_to_der(pem: &str) -> Result<Vec<u8>> {
let mut reader = std::io::BufReader::new(pem.as_bytes());
loop {
match rustls_pemfile::read_one(&mut reader).map_err(|_| Error::IamTokenProofOfPossession)? {
Some(rustls_pemfile::Item::Pkcs8Key(der)) => {
return Ok(der.secret_pkcs8_der().to_vec())
}
Some(_) => continue,
None => return Err(Error::IamTokenProofOfPossession),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ring::signature::{UnparsedPublicKey, RSA_PKCS1_2048_8192_SHA256};
const TEST_PKCS8_PEM: &str = include_str!("../tests/fixtures/tls/iam_pop_test_key_pkcs8.pem");
#[test]
fn header_matches_reference_format() {
let now = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_784_271_600);
let header = build_signing_header("adb.example.db.example.com", "10.0.0.5", 1522, now);
assert_eq!(
header,
"date: Fri, 17 Jul 2026 07:00:00 GMT\n\
(request-target): adb.example.db.example.com\n\
host: 10.0.0.5:1522"
);
}
#[test]
fn signature_verifies_against_the_public_key() {
let now = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_784_271_600);
let header = build_signing_header("svc", "h", 1522, now);
let sig_b64 = sign_signing_header(TEST_PKCS8_PEM, &header).expect("sign");
let sig = base64::engine::general_purpose::STANDARD
.decode(sig_b64.as_bytes())
.expect("base64");
let der = pkcs8_pem_to_der(TEST_PKCS8_PEM).expect("der");
let key_pair = RsaKeyPair::from_pkcs8(&der).expect("keypair");
let public =
UnparsedPublicKey::new(&RSA_PKCS1_2048_8192_SHA256, key_pair.public().as_ref());
public
.verify(header.as_bytes(), &sig)
.expect("proof-of-possession signature must verify");
assert!(
public.verify(b"date: tampered", &sig).is_err(),
"signature must not verify a different header"
);
}
#[test]
fn rejects_non_pkcs8_key_material_without_leaking() {
let err = sign_signing_header("unusable-key-material-xyzzy\n", "hdr").unwrap_err();
assert!(matches!(err, Error::IamTokenProofOfPossession));
assert!(!format!("{err}").contains("xyzzy"));
}
}