alef_component_runtime/
keys.rs1use base64::Engine as _;
8use ed25519_dalek::VerifyingKey;
9use ed25519_dalek::pkcs8::DecodePublicKey as _;
10
11use crate::ComponentError;
12
13pub fn decode_public_key(value: &str) -> Result<VerifyingKey, ComponentError> {
21 if value.trim_start().starts_with("-----BEGIN") {
22 return VerifyingKey::from_public_key_pem(value).map_err(|_| ComponentError::InvalidPublicKey);
23 }
24 let trimmed = value.trim();
25 let bytes = base64::engine::general_purpose::STANDARD
26 .decode(trimmed)
27 .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(trimmed))
28 .map_err(|_| ComponentError::InvalidPublicKey)?;
29 if let Ok(raw) = <[u8; 32]>::try_from(bytes.as_slice()) {
30 return VerifyingKey::from_bytes(&raw).map_err(|_| ComponentError::InvalidPublicKey);
31 }
32 VerifyingKey::from_public_key_der(&bytes).map_err(|_| ComponentError::InvalidPublicKey)
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use ed25519_dalek::SigningKey;
39
40 const FIXTURE_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA68ACmVmPwMQ4mvLWXAuT7IezNeigy/lauMP08bFC6lU=\n-----END PUBLIC KEY-----\n";
44 const FIXTURE_RAW: [u8; 32] = [
45 0xeb, 0xc0, 0x02, 0x99, 0x59, 0x8f, 0xc0, 0xc4, 0x38, 0x9a, 0xf2, 0xd6, 0x5c, 0x0b, 0x93, 0xec, 0x87, 0xb3,
46 0x35, 0xe8, 0xa0, 0xcb, 0xf9, 0x5a, 0xb8, 0xc3, 0xf4, 0xf1, 0xb1, 0x42, 0xea, 0x55,
47 ];
48
49 fn key() -> VerifyingKey {
50 SigningKey::from_bytes(&[9; 32]).verifying_key()
51 }
52
53 #[test]
54 fn decodes_standard_padded_base64() {
55 let encoded = base64::engine::general_purpose::STANDARD.encode(key().to_bytes());
56 assert_eq!(decode_public_key(&encoded).unwrap(), key());
57 }
58
59 #[test]
60 fn decodes_unpadded_base64() {
61 let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(key().to_bytes());
62 assert_eq!(decode_public_key(&encoded).unwrap(), key());
63 }
64
65 #[test]
66 fn decodes_pem() {
67 let expected = VerifyingKey::from_bytes(&FIXTURE_RAW).unwrap();
68 assert_eq!(decode_public_key(FIXTURE_PEM).unwrap(), expected);
69 }
70
71 #[test]
72 fn rejects_garbage() {
73 assert!(matches!(
74 decode_public_key("not-base64"),
75 Err(ComponentError::InvalidPublicKey)
76 ));
77 }
78}