1use anda_core::BoxError;
2use ic_auth_types::ByteBufB64;
3use ic_cose_types::cose::{CborSerializable, CoseKey, ed25519::VerifyingKey, get_cose_key_public};
4use std::str::FromStr;
5
6pub mod agents;
7pub mod handler;
8pub mod payload;
9pub mod space;
10pub mod types;
11
12pub fn parse_ed25519_pubkeys(input: &str) -> Result<Vec<VerifyingKey>, BoxError> {
13 if input.is_empty() {
14 return Ok(vec![]);
15 }
16
17 input
18 .split(',')
19 .map(|item| match parse_ed25519_pubkey(item.trim()) {
20 Some(key) => Ok(key),
21 None => Err("invalid ED25519_PUBKEYS entry".into()),
22 })
23 .collect::<Result<Vec<_>, _>>()
24}
25
26fn parse_ed25519_pubkey(input: &str) -> Option<VerifyingKey> {
27 let data = ByteBufB64::from_str(input).ok()?;
28
29 if data.len() == 32 {
30 let mut bytes = [0u8; 32];
31 bytes.copy_from_slice(&data);
32 return VerifyingKey::from_bytes(&bytes).ok();
33 }
34
35 let cose_key = CoseKey::from_slice(data.as_slice()).ok()?;
36 let public_key = get_cose_key_public(cose_key).ok()?;
37 let bytes: [u8; 32] = public_key.try_into().ok()?;
38 VerifyingKey::from_bytes(&bytes).ok()
39}
40
41#[cfg(test)]
42mod tests {
43 use super::parse_ed25519_pubkeys;
44 use coset::{
45 CoseKeyBuilder, Label,
46 cbor::value::Value,
47 iana::{self},
48 };
49 use ic_auth_types::ByteBufB64;
50 use ic_cose_types::cose::CborSerializable;
51
52 fn ed25519_basepoint_bytes() -> [u8; 32] {
53 let mut bytes = [0x66; 32];
54 bytes[0] = 0x58;
55 bytes
56 }
57
58 #[test]
59 fn parse_ed25519_pubkeys_allows_empty_input() {
60 let keys = parse_ed25519_pubkeys("").unwrap();
61
62 assert!(keys.is_empty());
63 }
64
65 #[test]
66 fn parse_ed25519_pubkeys_accepts_raw_keys_and_trims_items() {
67 let key_bytes = ed25519_basepoint_bytes();
68 let encoded = ByteBufB64(key_bytes.to_vec()).to_string();
69 let keys = parse_ed25519_pubkeys(&format!(" {encoded} , {encoded} ")).unwrap();
70
71 assert_eq!(keys.len(), 2);
72 assert_eq!(keys[0].to_bytes(), key_bytes);
73 assert_eq!(keys[1].to_bytes(), key_bytes);
74 }
75
76 #[test]
77 fn parse_ed25519_pubkeys_accepts_cose_key_entries() {
78 let key_bytes = ed25519_basepoint_bytes();
79 let mut cose_key = CoseKeyBuilder::new_okp_key().build();
80 cose_key.params.push((
81 Label::Int(iana::OkpKeyParameter::X as i64),
82 Value::Bytes(key_bytes.to_vec()),
83 ));
84 let encoded = ByteBufB64(cose_key.to_vec().unwrap()).to_string();
85
86 let keys = parse_ed25519_pubkeys(&encoded).unwrap();
87
88 assert_eq!(keys.len(), 1);
89 assert_eq!(keys[0].to_bytes(), key_bytes);
90 }
91
92 #[test]
93 fn parse_ed25519_pubkeys_rejects_invalid_entries() {
94 let short_key = ByteBufB64(vec![1, 2, 3]).to_string();
95
96 assert!(parse_ed25519_pubkeys("not base64").is_err());
97 assert!(parse_ed25519_pubkeys(&short_key).is_err());
98 assert!(parse_ed25519_pubkeys(" ").is_err());
99 }
100}