1use super::{KeyPair, SeaError};
20use serde_json::Value as JsonValue;
21
22pub async fn certify(
31 authority: &KeyPair,
32 certificants: &[String],
33 policies: Option<&JsonValue>,
34) -> Result<JsonValue, SeaError> {
35 let mut cert = serde_json::json!({
36 "c": certificants,
37 });
38
39 if let Some(pol) = policies {
40 if let Some(obj) = pol.as_object() {
41 for key in ["e", "r", "w", "rb", "wb"] {
42 if let Some(v) = obj.get(key) {
43 cert[key] = v.clone();
44 }
45 }
46 }
47 }
48
49 super::sign::sign(&cert, authority).await
50}
51
52pub fn verify_certificate(
57 signed_cert: &JsonValue,
58 authority_pubkey: &str,
59) -> Result<JsonValue, SeaError> {
60 let payload = super::verify::verify_sync(signed_cert, authority_pubkey)?;
61
62 if let Some(expiry) = payload.get("e").and_then(|e| e.as_f64()) {
63 let now = web_time::SystemTime::now()
64 .duration_since(web_time::UNIX_EPOCH)
65 .unwrap_or_default()
66 .as_millis() as f64;
67 if expiry < now {
68 return Err(SeaError::VerificationFailed);
69 }
70 }
71
72 Ok(payload)
73}
74
75pub fn is_certified(payload: &JsonValue, pubkey: &str) -> bool {
77 payload
78 .get("c")
79 .and_then(|c| c.as_array())
80 .map(|arr| arr.iter().any(|v| v.as_str() == Some(pubkey)))
81 .unwrap_or(false)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::sea::generate_pair;
88 use serde_json::json;
89
90 #[tokio::test]
91 async fn test_certify_basic() {
92 let authority = generate_pair().await.unwrap();
93 let alice = generate_pair().await.unwrap();
94 let cert = certify(&authority, std::slice::from_ref(&alice.pub_key), None)
95 .await
96 .unwrap();
97 let payload = verify_certificate(&cert, &authority.pub_key).unwrap();
98 assert!(is_certified(&payload, &alice.pub_key));
99 }
100
101 #[tokio::test]
102 async fn test_certify_with_policies() {
103 let authority = generate_pair().await.unwrap();
104 let alice = generate_pair().await.unwrap();
105 let policies = json!({"w": "skills/", "r": ".*"});
106 let cert = certify(
107 &authority,
108 std::slice::from_ref(&alice.pub_key),
109 Some(&policies),
110 )
111 .await
112 .unwrap();
113 let payload = verify_certificate(&cert, &authority.pub_key).unwrap();
114 assert_eq!(payload["w"].as_str(), Some("skills/"));
115 assert_eq!(payload["r"].as_str(), Some(".*"));
116 }
117
118 #[tokio::test]
119 async fn test_certify_expired() {
120 let authority = generate_pair().await.unwrap();
121 let alice = generate_pair().await.unwrap();
122 let policies = json!({"e": 1000.0_f64}); let cert = certify(&authority, &[alice.pub_key], Some(&policies))
124 .await
125 .unwrap();
126 assert!(verify_certificate(&cert, &authority.pub_key).is_err());
127 }
128
129 #[tokio::test]
130 async fn test_certify_wrong_authority() {
131 let authority = generate_pair().await.unwrap();
132 let imposter = generate_pair().await.unwrap();
133 let alice = generate_pair().await.unwrap();
134 let cert = certify(&authority, &[alice.pub_key], None).await.unwrap();
135 assert!(verify_certificate(&cert, &imposter.pub_key).is_err());
136 }
137
138 #[tokio::test]
139 async fn test_certify_multiple_certificants() {
140 let authority = generate_pair().await.unwrap();
141 let alice = generate_pair().await.unwrap();
142 let bob = generate_pair().await.unwrap();
143 let cert = certify(
144 &authority,
145 &[alice.pub_key.clone(), bob.pub_key.clone()],
146 None,
147 )
148 .await
149 .unwrap();
150 let payload = verify_certificate(&cert, &authority.pub_key).unwrap();
151 assert!(is_certified(&payload, &alice.pub_key));
152 assert!(is_certified(&payload, &bob.pub_key));
153 assert!(!is_certified(&payload, "unknown"));
154 }
155
156 #[test]
157 fn test_is_certified_missing_c_field() {
158 let payload = json!({"other": "data"});
159 assert!(!is_certified(&payload, "anykey"));
160 }
161
162 #[test]
163 fn test_is_certified_empty_array() {
164 let payload = json!({"c": []});
165 assert!(!is_certified(&payload, "anykey"));
166 }
167}