use purecrypto::ec::{BoxedEcdsaPrivateKey, CurveId};
use purecrypto::hash::{Sha256, sha256};
use super::json;
use crate::error::{Error, Result};
pub fn b64url(data: &[u8]) -> String {
const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32;
out.push(A[((n >> 18) & 0x3f) as usize] as char);
out.push(A[((n >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(A[((n >> 6) & 0x3f) as usize] as char);
}
if chunk.len() > 2 {
out.push(A[(n & 0x3f) as usize] as char);
}
}
out
}
pub struct AccountKey {
key: BoxedEcdsaPrivateKey,
}
impl AccountKey {
pub fn new(key: BoxedEcdsaPrivateKey) -> AccountKey {
AccountKey { key }
}
pub fn generate() -> AccountKey {
let mut rng = purecrypto::rng::OsRng;
AccountKey {
key: BoxedEcdsaPrivateKey::generate(CurveId::P256, &mut rng),
}
}
pub fn private_key(&self) -> &BoxedEcdsaPrivateKey {
&self.key
}
pub fn jwk_json(&self) -> String {
let (x, y) = self.xy();
format!(
r#"{{"crv":"P-256","kty":"EC","x":"{}","y":"{}"}}"#,
b64url(&x),
b64url(&y)
)
}
pub fn thumbprint(&self) -> [u8; 32] {
sha256(self.jwk_json().as_bytes())
}
pub fn key_authorization(&self, token: &str) -> String {
format!("{token}.{}", b64url(&self.thumbprint()))
}
fn xy(&self) -> (Vec<u8>, Vec<u8>) {
let sec1 = self.key.public_key().to_sec1(); (sec1[1..33].to_vec(), sec1[33..65].to_vec())
}
pub fn sign(&self, url: &str, nonce: &str, auth: &KeyId, payload: &str) -> Result<String> {
let auth_field = match auth {
KeyId::Jwk => format!(r#""jwk":{}"#, self.jwk_json()),
KeyId::Kid(kid) => format!(r#""kid":"{}""#, json::escape(kid)),
};
let protected = format!(
r#"{{"alg":"ES256","nonce":"{}","url":"{}",{}}}"#,
json::escape(nonce),
json::escape(url),
auth_field
);
let protected_b64 = b64url(protected.as_bytes());
let payload_b64 = b64url(payload.as_bytes());
let signing_input = format!("{protected_b64}.{payload_b64}");
let sig = self
.key
.sign::<Sha256>(signing_input.as_bytes())
.map_err(|e| Error::Tls(format!("acme jws sign: {e:?}")))?
.to_bytes(CurveId::P256);
Ok(json::obj(&[
("protected", format!(r#""{protected_b64}""#)),
("payload", format!(r#""{payload_b64}""#)),
("signature", format!(r#""{}""#, b64url(&sig))),
]))
}
}
pub enum KeyId {
Jwk,
Kid(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn b64url_vectors() {
assert_eq!(b64url(b""), "");
assert_eq!(b64url(b"f"), "Zg");
assert_eq!(b64url(b"fo"), "Zm8");
assert_eq!(b64url(b"foo"), "Zm9v");
assert_eq!(b64url(b"foobar"), "Zm9vYmFy");
assert_eq!(b64url(&[0xfb, 0xff]), "-_8");
}
#[test]
fn jwk_and_thumbprint_are_stable() {
let k = AccountKey::generate();
let j1 = k.jwk_json();
assert!(j1.starts_with(r#"{"crv":"P-256","kty":"EC","x":"#));
assert_eq!(k.thumbprint(), k.thumbprint());
let ka = k.key_authorization("tok");
let parts: Vec<&str> = ka.split('.').collect();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0], "tok");
assert_eq!(parts[1].len(), 43);
}
#[test]
fn jws_is_well_formed() {
let k = AccountKey::generate();
let body = k
.sign("https://acme.test/x", "nonce123", &KeyId::Jwk, r#"{"a":1}"#)
.unwrap();
let v = json::parse(&body).unwrap();
let prot_b64 = v.str_at("protected").unwrap();
let sig = v.str_at("signature").unwrap();
assert_eq!(sig.len(), 86);
let prot = String::from_utf8(b64url_decode(prot_b64)).unwrap();
let ph = json::parse(&prot).unwrap();
assert_eq!(ph.str_at("alg"), Some("ES256"));
assert_eq!(ph.str_at("nonce"), Some("nonce123"));
assert_eq!(ph.str_at("url"), Some("https://acme.test/x"));
assert_eq!(ph.get("jwk").unwrap().str_at("crv"), Some("P-256"));
let g = k
.sign("https://acme.test/y", "n2", &KeyId::Kid("acc".into()), "")
.unwrap();
assert_eq!(json::parse(&g).unwrap().str_at("payload"), Some(""));
}
fn b64url_decode(s: &str) -> Vec<u8> {
fn v(c: u8) -> u8 {
match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'-' => 62,
_ => 63, }
}
let mut out = Vec::new();
let mut acc = 0u32;
let mut bits = 0;
for &c in s.as_bytes() {
acc = (acc << 6) | v(c) as u32;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((acc >> bits) as u8);
}
}
out
}
}