use base64::Engine as _;
use ed25519_dalek::VerifyingKey;
use ed25519_dalek::pkcs8::DecodePublicKey as _;
use crate::ComponentError;
pub fn decode_public_key(value: &str) -> Result<VerifyingKey, ComponentError> {
if value.trim_start().starts_with("-----BEGIN") {
return VerifyingKey::from_public_key_pem(value).map_err(|_| ComponentError::InvalidPublicKey);
}
let trimmed = value.trim();
let bytes = base64::engine::general_purpose::STANDARD
.decode(trimmed)
.or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(trimmed))
.map_err(|_| ComponentError::InvalidPublicKey)?;
if let Ok(raw) = <[u8; 32]>::try_from(bytes.as_slice()) {
return VerifyingKey::from_bytes(&raw).map_err(|_| ComponentError::InvalidPublicKey);
}
VerifyingKey::from_public_key_der(&bytes).map_err(|_| ComponentError::InvalidPublicKey)
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
const FIXTURE_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA68ACmVmPwMQ4mvLWXAuT7IezNeigy/lauMP08bFC6lU=\n-----END PUBLIC KEY-----\n";
const FIXTURE_RAW: [u8; 32] = [
0xeb, 0xc0, 0x02, 0x99, 0x59, 0x8f, 0xc0, 0xc4, 0x38, 0x9a, 0xf2, 0xd6, 0x5c, 0x0b, 0x93, 0xec, 0x87, 0xb3,
0x35, 0xe8, 0xa0, 0xcb, 0xf9, 0x5a, 0xb8, 0xc3, 0xf4, 0xf1, 0xb1, 0x42, 0xea, 0x55,
];
fn key() -> VerifyingKey {
SigningKey::from_bytes(&[9; 32]).verifying_key()
}
#[test]
fn decodes_standard_padded_base64() {
let encoded = base64::engine::general_purpose::STANDARD.encode(key().to_bytes());
assert_eq!(decode_public_key(&encoded).unwrap(), key());
}
#[test]
fn decodes_unpadded_base64() {
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(key().to_bytes());
assert_eq!(decode_public_key(&encoded).unwrap(), key());
}
#[test]
fn decodes_pem() {
let expected = VerifyingKey::from_bytes(&FIXTURE_RAW).unwrap();
assert_eq!(decode_public_key(FIXTURE_PEM).unwrap(), expected);
}
#[test]
fn rejects_garbage() {
assert!(matches!(
decode_public_key("not-base64"),
Err(ComponentError::InvalidPublicKey)
));
}
}