1use base64::{engine::general_purpose, Engine};
6use serde_json::from_slice;
7
8use crate::{errors::AtomicResult, urls, Resource, Storelike, Value};
9
10#[derive(Clone, Debug, PartialEq)]
12pub enum ForAgent {
13 AgentSubject(String),
15 Sudo,
18 Public,
21}
22
23impl std::fmt::Display for ForAgent {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 ForAgent::AgentSubject(subject) => write!(f, "{}", subject),
27 ForAgent::Sudo => write!(f, "{}", urls::SUDO_AGENT),
28 ForAgent::Public => write!(f, "{}", urls::PUBLIC_AGENT),
29 }
30 }
31}
32
33impl<T: Into<String>> From<T> for ForAgent {
35 fn from(subject: T) -> Self {
36 let subject = subject.into();
37 if subject == urls::SUDO_AGENT {
38 ForAgent::Sudo
39 } else if subject == urls::PUBLIC_AGENT {
40 ForAgent::Public
41 } else {
42 ForAgent::AgentSubject(subject)
43 }
44 }
45}
46
47#[derive(Clone, Debug)]
50pub struct Agent {
51 pub private_key: Option<String>,
53 pub public_key: String,
55 pub subject: String,
57 pub created_at: i64,
58 pub name: Option<String>,
59}
60
61impl Agent {
62 pub fn to_resource(&self) -> AtomicResult<Resource> {
65 let mut resource = Resource::new(self.subject.clone());
66 resource.set_class(urls::AGENT);
67 resource.set_subject(self.subject.clone());
68 if let Some(name) = &self.name {
69 resource.set_unsafe(crate::urls::NAME.into(), Value::String(name.into()));
70 }
71 resource.set_unsafe(
72 crate::urls::PUBLIC_KEY.into(),
73 Value::String(self.public_key.clone()),
74 );
75 resource.push(crate::urls::READ, urls::PUBLIC_AGENT.into(), true)?;
77 resource.set_unsafe(
78 crate::urls::CREATED_AT.into(),
79 Value::Timestamp(self.created_at),
80 );
81 Ok(resource)
82 }
83
84 pub fn new(name: Option<&str>, store: &impl Storelike) -> AtomicResult<Agent> {
86 let keypair = generate_keypair()?;
87
88 Ok(Agent::new_from_private_key(name, store, &keypair.private))
89 }
90
91 pub fn new_from_private_key(
94 name: Option<&str>,
95 store: &impl Storelike,
96 private_key: &str,
97 ) -> Agent {
98 let keypair = generate_public_key(private_key);
99
100 Agent {
101 private_key: Some(keypair.private),
102 public_key: keypair.public.clone(),
103 subject: format!("{}/agents/{}", store.get_server_url(), keypair.public),
104 name: name.map(|x| x.to_owned()),
105 created_at: crate::utils::now(),
106 }
107 }
108
109 pub fn new_from_public_key(store: &impl Storelike, public_key: &str) -> AtomicResult<Agent> {
112 verify_public_key(public_key)?;
113
114 Ok(Agent {
115 private_key: None,
116 public_key: public_key.into(),
117 subject: format!("{}/agents/{}", store.get_server_url(), public_key),
118 name: None,
119 created_at: crate::utils::now(),
120 })
121 }
122
123 pub fn from_secret(secret_b64: &str) -> AtomicResult<Agent> {
124 let agent_bytes = decode_base64(secret_b64)?;
125 let parsed: serde_json::Value = from_slice(&agent_bytes)?;
126 let private_key = parsed["privateKey"].as_str().ok_or("Invalid private key")?;
127 let subject = parsed["subject"].as_str().ok_or("Invalid subject")?;
128 let agent = Agent {
129 private_key: Some(private_key.into()),
130 public_key: generate_public_key(private_key).public,
131 subject: subject.into(),
132 name: None,
133 created_at: crate::utils::now(),
134 };
135 Ok(agent)
136 }
137
138 pub fn from_private_key_and_subject(private_key: &str, subject: &str) -> AtomicResult<Agent> {
139 let keypair = generate_public_key(private_key);
140
141 Ok(Agent {
142 private_key: Some(keypair.private),
143 public_key: keypair.public.clone(),
144 subject: subject.into(),
145 name: None,
146 created_at: crate::utils::now(),
147 })
148 }
149}
150
151pub struct Pair {
153 pub private: String,
154 pub public: String,
155}
156
157fn generate_keypair() -> AtomicResult<Pair> {
159 use ring::signature::KeyPair;
160 let rng = ring::rand::SystemRandom::new();
161 const SEED_LEN: usize = 32;
162 let seed: [u8; SEED_LEN] = ring::rand::generate(&rng)
163 .map_err(|e| format!("Error generating random seed: {}", e))?
164 .expose();
165 let key_pair = ring::signature::Ed25519KeyPair::from_seed_unchecked(&seed)
166 .map_err(|e| format!("Error generating keypair: {}", e))
167 .unwrap();
168 Ok(Pair {
169 private: encode_base64(&seed),
170 public: encode_base64(key_pair.public_key().as_ref()),
171 })
172}
173
174pub fn generate_public_key(private_key: &str) -> Pair {
176 use ring::signature::KeyPair;
177 let private_key_bytes = decode_base64(private_key).unwrap();
178 let key_pair = ring::signature::Ed25519KeyPair::from_seed_unchecked(private_key_bytes.as_ref())
179 .map_err(|e| format!("Error generating keypair: {e}"))
180 .unwrap();
181 Pair {
182 private: encode_base64(&private_key_bytes),
183 public: encode_base64(key_pair.public_key().as_ref()),
184 }
185}
186
187pub fn decode_base64(string: &str) -> AtomicResult<Vec<u8>> {
188 let vec = general_purpose::STANDARD
189 .decode(string)
190 .map_err(|e| format!("Invalid key. Not valid Base64. {}", e))?;
191 Ok(vec)
192}
193
194pub fn encode_base64(bytes: &[u8]) -> String {
195 general_purpose::STANDARD.encode(bytes)
196}
197
198pub fn verify_public_key(public_key: &str) -> AtomicResult<()> {
201 let pubkey_bin = decode_base64(public_key)
202 .map_err(|e| format!("Invalid public key. Not valid Base64. {}", e))?;
203 if pubkey_bin.len() != 32 {
204 return Err(format!(
205 "Invalid public key, should be 32 bytes long instead of {}. Key: {}",
206 pubkey_bin.len(),
207 public_key
208 )
209 .into());
210 }
211 Ok(())
212}
213
214#[cfg(test)]
215mod test {
216 #[cfg(test)]
217 use super::*;
218
219 #[test]
220 fn keypair() {
221 let pair = generate_keypair().unwrap();
222 let regenerated_pair = generate_public_key(&pair.private);
223 assert_eq!(pair.public, regenerated_pair.public);
224 }
225
226 #[test]
227 fn generate_from_private_key() {
228 let private_key = "CapMWIhFUT+w7ANv9oCPqrHrwZpkP2JhzF9JnyT6WcI=";
229 let public_key = "7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=";
230 let regenerated_pair = generate_public_key(private_key);
231 assert_eq!(public_key, regenerated_pair.public);
232 }
233
234 #[test]
235 fn verifies_public_keys() {
236 let valid_public_key = "7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwqm+h8U=";
237 let invalid_length = "7LsjMW5gOfDdJzK/atgjQ1t20J/rw8MjVg6xwm+h8U";
238 let invalid_char = "7LsjMW5gOfDdJzK/atgjQ1t20^/rw8MjVg6xwqm+h8U=";
239 verify_public_key(valid_public_key).unwrap();
240 verify_public_key(invalid_length).unwrap_err();
241 verify_public_key(invalid_char).unwrap_err();
242 }
243
244 #[test]
245 fn creates_from_secret() {
246 let secret = "eyJjbGllbnQiOnt9LCJzdWJqZWN0IjoiaHR0cDovL2xvY2FsaG9zdDo5ODgzL2FnZW50cy9ScVB3cGdIditQSzdQbnovZFZhYjhobUhqWW52VEwxWXJsVmE2TDlHOVpnPSIsInByaXZhdGVLZXkiOiJTTXl4UmdGN1FoaUM3QzUwNnFYU1VLZkUrU0tBdENkTkZ1NVhlVGp6YWRBPSIsInB1YmxpY0tleSI6IlJxUHdwZ0h2K1BLN1Buei9kVmFiOGhtSGpZbnZUTDFZcmxWYTZMOUc5Wmc9In0=";
247 let agent = Agent::from_secret(secret).unwrap();
248 assert_eq!(
249 agent.private_key.unwrap(),
250 "SMyxRgF7QhiC7C506qXSUKfE+SKAtCdNFu5XeTjzadA="
251 );
252 assert_eq!(
253 agent.subject,
254 "http://localhost:9883/agents/RqPwpgHv+PK7Pnz/dVab8hmHjYnvTL1YrlVa6L9G9Zg="
255 );
256 }
257}