1use super::{Ap2Error, Result, AP2_CONTEXT, VC_CONTEXT};
4use crate::ap2::did::DidResolver;
5use chrono::{DateTime, Utc};
6use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use sha2::{Digest, Sha256};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct VerifiableCredential {
15 #[serde(rename = "@context")]
16 pub context: Vec<String>,
17 pub id: String,
18 #[serde(rename = "type")]
19 pub types: Vec<String>,
20 pub issuer: String,
21 pub issuance_date: DateTime<Utc>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub expiration_date: Option<DateTime<Utc>>,
24 pub credential_subject: CredentialSubject,
25 pub proof: Proof,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct CredentialSubject {
31 pub id: String,
32 #[serde(flatten)]
33 pub claims: Value,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Proof {
40 #[serde(rename = "type")]
41 pub proof_type: String,
42 pub created: DateTime<Utc>,
43 pub proof_purpose: ProofPurpose,
44 pub verification_method: String,
45 pub jws: String, }
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub enum ProofPurpose {
52 AssertionMethod,
53 Authentication,
54 KeyAgreement,
55 CapabilityInvocation,
56 CapabilityDelegation,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct VerificationMethod {
63 pub id: String,
64 #[serde(rename = "type")]
65 pub method_type: String,
66 pub controller: String,
67 pub public_key_multibase: String,
68}
69
70impl VerifiableCredential {
71 pub fn new(issuer: String, subject: CredentialSubject, private_key: &[u8]) -> Result<Self> {
73 let now = Utc::now();
74 let id = format!("urn:uuid:{}", uuid::Uuid::new_v4());
75
76 let mut credential = Self {
77 context: vec![VC_CONTEXT.to_string(), AP2_CONTEXT.to_string()],
78 id,
79 types: vec!["VerifiableCredential".to_string()],
80 issuer: issuer.clone(),
81 issuance_date: now,
82 expiration_date: Some(now + chrono::Duration::days(365)),
83 credential_subject: subject,
84 proof: Proof {
85 proof_type: "Ed25519Signature2020".to_string(),
86 created: now,
87 proof_purpose: ProofPurpose::AssertionMethod,
88 verification_method: format!("{}#key-1", issuer),
89 jws: String::new(), },
91 };
92
93 credential.sign(private_key)?;
95
96 Ok(credential)
97 }
98
99 fn sign(&mut self, private_key: &[u8]) -> Result<()> {
101 let key_bytes: [u8; 32] = private_key
103 .try_into()
104 .map_err(|_| Ap2Error::CryptographicError("Invalid private key length".to_string()))?;
105 let signing_key = SigningKey::from_bytes(&key_bytes);
106
107 let mut signing_input = self.clone();
109 signing_input.proof.jws = String::new();
110
111 let canonical = serde_json::to_string(&signing_input)
112 .map_err(|e| Ap2Error::SerializationError(e.to_string()))?;
113
114 let mut hasher = Sha256::new();
116 hasher.update(canonical.as_bytes());
117 let hash = hasher.finalize();
118
119 let signature = signing_key.sign(&hash);
121
122 self.proof.jws = base64_url::encode(&signature.to_bytes());
124
125 Ok(())
126 }
127
128 pub fn verify(&self, did_resolver: &DidResolver) -> Result<bool> {
130 if let Some(exp) = self.expiration_date {
132 if Utc::now() > exp {
133 return Err(Ap2Error::Expired);
134 }
135 }
136
137 let did_doc = did_resolver.resolve(&self.issuer)?;
139 let public_key = did_doc
140 .verification_method
141 .first()
142 .ok_or_else(|| {
143 Ap2Error::DidResolutionFailed("No verification method found".to_string())
144 })?
145 .public_key_multibase
146 .clone();
147
148 let public_key_bytes = base64_url::decode(&public_key)
150 .map_err(|e| Ap2Error::CryptographicError(format!("Invalid public key: {}", e)))?;
151
152 let verifying_key_bytes: [u8; 32] = public_key_bytes
153 .try_into()
154 .map_err(|_| Ap2Error::CryptographicError("Invalid public key length".to_string()))?;
155 let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)
156 .map_err(|e| Ap2Error::CryptographicError(e.to_string()))?;
157
158 let mut verification_input = self.clone();
160 verification_input.proof.jws = String::new();
161
162 let canonical = serde_json::to_string(&verification_input)
163 .map_err(|e| Ap2Error::SerializationError(e.to_string()))?;
164
165 let mut hasher = Sha256::new();
167 hasher.update(canonical.as_bytes());
168 let hash = hasher.finalize();
169
170 let signature_bytes = base64_url::decode(&self.proof.jws)
172 .map_err(|e| Ap2Error::CryptographicError(format!("Invalid signature: {}", e)))?;
173
174 let signature = Signature::from_bytes(
175 signature_bytes
176 .as_slice()
177 .try_into()
178 .map_err(|_| Ap2Error::CryptographicError("Invalid signature length".to_string()))?,
179 );
180
181 verifying_key
183 .verify(&hash, &signature)
184 .map_err(|e| Ap2Error::SignatureVerificationFailed(e.to_string()))?;
185
186 Ok(true)
187 }
188
189 pub fn add_type(&mut self, credential_type: String) {
191 if !self.types.contains(&credential_type) {
192 self.types.push(credential_type);
193 }
194 }
195
196 pub fn with_expiration(mut self, expiration: DateTime<Utc>) -> Self {
198 self.expiration_date = Some(expiration);
199 self
200 }
201
202 pub fn is_expired(&self) -> bool {
204 self.expiration_date
205 .map_or(false, |exp| Utc::now() > exp)
206 }
207
208 pub fn get_claim(&self, key: &str) -> Option<&Value> {
210 self.credential_subject.claims.get(key)
211 }
212}
213
214pub struct CredentialBuilder {
216 issuer: String,
217 subject_id: String,
218 claims: serde_json::Map<String, Value>,
219 types: Vec<String>,
220 expiration: Option<DateTime<Utc>>,
221}
222
223impl CredentialBuilder {
224 pub fn new(issuer: String, subject_id: String) -> Self {
225 Self {
226 issuer,
227 subject_id,
228 claims: serde_json::Map::new(),
229 types: vec!["VerifiableCredential".to_string()],
230 expiration: None,
231 }
232 }
233
234 pub fn add_claim(mut self, key: String, value: Value) -> Self {
235 self.claims.insert(key, value);
236 self
237 }
238
239 pub fn add_type(mut self, credential_type: String) -> Self {
240 if !self.types.contains(&credential_type) {
241 self.types.push(credential_type);
242 }
243 self
244 }
245
246 pub fn with_expiration(mut self, expiration: DateTime<Utc>) -> Self {
247 self.expiration = Some(expiration);
248 self
249 }
250
251 pub fn build(self, private_key: &[u8]) -> Result<VerifiableCredential> {
252 let subject = CredentialSubject {
253 id: self.subject_id,
254 claims: Value::Object(self.claims),
255 };
256
257 let mut credential = VerifiableCredential::new(self.issuer, subject, private_key)?;
258 credential.types = self.types;
259 if let Some(exp) = self.expiration {
260 credential.expiration_date = Some(exp);
261 }
262
263 credential.sign(private_key)?;
265
266 Ok(credential)
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 fn generate_keypair() -> (SigningKey, VerifyingKey) {
275 let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
276 let verifying_key = signing_key.verifying_key();
277 (signing_key, verifying_key)
278 }
279
280 #[test]
281 fn test_credential_creation() {
282 let (signing_key, _) = generate_keypair();
283 let issuer = "did:example:issuer".to_string();
284 let subject = CredentialSubject {
285 id: "did:example:subject".to_string(),
286 claims: serde_json::json!({"name": "Test Agent"}),
287 };
288
289 let credential = VerifiableCredential::new(issuer, subject, signing_key.as_bytes());
290 assert!(credential.is_ok());
291
292 let vc = credential.unwrap();
293 assert_eq!(vc.types, vec!["VerifiableCredential"]);
294 assert!(!vc.proof.jws.is_empty());
295 }
296
297 #[test]
298 fn test_credential_builder() {
299 let (signing_key, _) = generate_keypair();
300
301 let credential = CredentialBuilder::new(
302 "did:example:issuer".to_string(),
303 "did:example:subject".to_string(),
304 )
305 .add_claim("role".to_string(), serde_json::json!("agent"))
306 .add_type("AgentCredential".to_string())
307 .build(signing_key.as_bytes());
308
309 assert!(credential.is_ok());
310 let vc = credential.unwrap();
311 assert!(vc.types.contains(&"AgentCredential".to_string()));
312 }
313
314 #[test]
315 fn test_credential_expiration() {
316 let (signing_key, _) = generate_keypair();
317 let past = Utc::now() - chrono::Duration::days(1);
318
319 let subject = CredentialSubject {
320 id: "did:example:subject".to_string(),
321 claims: serde_json::json!({}),
322 };
323
324 let mut credential =
325 VerifiableCredential::new("did:example:issuer".to_string(), subject, signing_key.as_bytes())
326 .unwrap();
327 credential.expiration_date = Some(past);
328
329 assert!(credential.is_expired());
330 }
331}