use jsonwebtoken::{DecodingKey, EncodingKey};
use crate::access_token::IssueError;
use crate::KeySet;
pub struct SigningKey {
inner: EncodingKey,
kid: String,
}
impl SigningKey {
pub fn from_ed25519_pem(pem: &[u8], kid: impl Into<String>) -> Result<Self, IssueError> {
let inner =
EncodingKey::from_ed_pem(pem).map_err(|e| IssueError::KeyParse(e.to_string()))?;
Ok(Self {
inner,
kid: kid.into(),
})
}
pub fn kid(&self) -> &str {
&self.kid
}
#[allow(clippy::expect_used)]
pub fn test_pair() -> (Self, KeySet) {
const TEST_PRIVATE_KEY_PEM: &[u8] = b"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIG+00IvEd4uv6IWtGFVUEBVdqnXiuI/ESQHu6rmcDvAs
-----END PRIVATE KEY-----
";
const TEST_PUBLIC_KEY_PEM: &[u8] = b"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAh//e6j3It3xhjghg8Kpn2pM0jMCH/cvemGu4vv7D1Q4=
-----END PUBLIC KEY-----
";
const TEST_KID: &str = "k4.test.0";
let signer = Self::from_ed25519_pem(TEST_PRIVATE_KEY_PEM, TEST_KID)
.expect("checked-in test PEM should always parse");
let mut key_set = KeySet::new();
let dec = DecodingKey::from_ed_pem(TEST_PUBLIC_KEY_PEM)
.expect("checked-in test PEM should always parse");
key_set.insert(TEST_KID, dec);
(signer, key_set)
}
#[allow(dead_code)] pub(crate) fn encoding(&self) -> &EncodingKey {
&self.inner
}
}
pub fn ed25519_public_from_pem(pem: &[u8]) -> Result<[u8; 32], IssueError> {
let pem_str = std::str::from_utf8(pem)
.map_err(|e| IssueError::KeyParse(format!("PEM utf8: {e}")))?;
let secret = ed25519_compact::SecretKey::from_pem(pem_str)
.map_err(|e| IssueError::KeyParse(format!("ed25519 pem decode: {e}")))?;
Ok(*secret.public_key())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_from_pem_matches_test_pair_public_key() {
const TEST_PRIVATE_KEY_PEM: &[u8] = b"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIG+00IvEd4uv6IWtGFVUEBVdqnXiuI/ESQHu6rmcDvAs
-----END PRIVATE KEY-----
";
const TEST_PUBLIC_KEY_SPKI_B64: &str =
"MCowBQYDK2VwAyEAh//e6j3It3xhjghg8Kpn2pM0jMCH/cvemGu4vv7D1Q4=";
use base64::Engine as _;
let spki = base64::engine::general_purpose::STANDARD
.decode(TEST_PUBLIC_KEY_SPKI_B64)
.expect("test SPKI must decode");
let expected: [u8; 32] = spki[12..].try_into().expect("SPKI carries 32-byte pk");
let derived = ed25519_public_from_pem(TEST_PRIVATE_KEY_PEM)
.expect("checked-in test PEM must derive");
assert_eq!(
derived, expected,
"PEM-derived public key must match the test_pair fixture",
);
}
#[test]
fn public_from_pem_rejects_non_pem() {
let err = ed25519_public_from_pem(b"not a pem at all").expect_err("garbage must reject");
match err {
IssueError::KeyParse(_) => {}
other => panic!("expected KeyParse, got {other:?}"),
}
}
}