use aws_lc_rs::digest as lc_digest;
use aws_lc_rs::signature::{EcdsaKeyPair, KeyPair as SignKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
pub struct AccountKey {
inner: EcdsaKeyPair,
}
impl AccountKey {
pub fn generate() -> Result<(Self, Vec<u8>), String> {
let key = EcdsaKeyPair::generate(&ECDSA_P256_SHA256_FIXED_SIGNING)
.map_err(|_| "account key generation failed".to_string())?;
let doc = key.to_pkcs8v1()
.map_err(|_| "account key serialization failed".to_string())?;
let der = doc.as_ref().to_vec();
Ok((AccountKey { inner: key }, der))
}
pub fn from_pkcs8(der: &[u8]) -> Result<Self, String> {
let key = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, der)
.map_err(|e| format!("account key parse failed: {e}"))?;
Ok(AccountKey { inner: key })
}
pub fn sign(&self, data: &[u8]) -> Result<[u8; 64], String> {
let rng = aws_lc_rs::rand::SystemRandom::new();
let sig = self.inner.sign(&rng, data)
.map_err(|_| "ECDSA signing failed".to_string())?;
let bytes = sig.as_ref();
if bytes.len() != 64 {
return Err(format!("unexpected ECDSA fixed signature length: {}", bytes.len()));
}
let mut out = [0u8; 64];
out.copy_from_slice(bytes);
Ok(out)
}
pub fn public_key_raw(&self) -> &[u8] {
self.inner.public_key().as_ref()
}
}
pub fn sha256(data: &[u8]) -> [u8; 32] {
let d = lc_digest::digest(&lc_digest::SHA256, data);
let mut out = [0u8; 32];
out.copy_from_slice(d.as_ref());
out
}
pub fn ec_point_xy(raw: &[u8]) -> Result<([u8; 32], [u8; 32]), String> {
if raw.len() != 65 || raw[0] != 0x04 {
return Err(format!(
"unexpected EC public key: {} bytes, first byte 0x{:02x}",
raw.len(),
raw.first().copied().unwrap_or(0)
));
}
let mut x = [0u8; 32];
let mut y = [0u8; 32];
x.copy_from_slice(&raw[1..33]);
y.copy_from_slice(&raw[33..65]);
Ok((x, y))
}
pub fn ec_jwk_json(x: &[u8; 32], y: &[u8; 32]) -> String {
format!(
r#"{{"crv":"P-256","kty":"EC","x":"{}","y":"{}"}}"#,
base64url(x),
base64url(y),
)
}
pub fn key_thumbprint(key: &AccountKey) -> Result<String, String> {
let (x, y) = ec_point_xy(key.public_key_raw())?;
Ok(base64url(&sha256(ec_jwk_json(&x, &y).as_bytes())))
}
pub fn build_jws(
key: &AccountKey,
nonce: &str,
url: &str,
account_url: Option<&str>,
payload: Option<&str>,
) -> Result<String, String> {
let protected = match account_url {
Some(kid) => format!(
r#"{{"alg":"ES256","kid":"{kid}","nonce":"{nonce}","url":"{url}"}}"#,
kid = kid,
nonce = nonce,
url = url,
),
None => {
let (x, y) = ec_point_xy(key.public_key_raw())?;
let jwk = ec_jwk_json(&x, &y);
format!(
r#"{{"alg":"ES256","jwk":{jwk},"nonce":"{nonce}","url":"{url}"}}"#,
jwk = jwk,
nonce = nonce,
url = url,
)
}
};
let protected_b64 = base64url(protected.as_bytes());
let payload_b64 = payload.map(|p| base64url(p.as_bytes())).unwrap_or_default();
let signing_input = format!("{}.{}", protected_b64, payload_b64);
let sig_bytes = key.sign(signing_input.as_bytes())?;
let sig_b64 = base64url(&sig_bytes);
Ok(format!(
r#"{{"protected":"{protected_b64}","payload":"{payload_b64}","signature":"{sig_b64}"}}"#,
protected_b64 = protected_b64,
payload_b64 = payload_b64,
sig_b64 = sig_b64,
))
}
pub use crate::core::x509::{base64_decode_std, base64url, cert_info_from_pem, days_since_epoch, read_cert_info, CertInfo};
#[cfg(any(test, fuzzing))]
pub use crate::core::x509::cert_info_from_der;