1use std::sync::Arc;
4
5use async_trait::async_trait;
6use base64::Engine;
7use sha2::{Digest, Sha256};
8
9use dpp_domain::ports::identity_port::IdentityPort;
10use dpp_domain::{DppError, PassportId, SignedCredential};
11
12use dpp_crypto::jws::signer;
13use dpp_crypto::jws::verifier::{
14 extract_key_by_fingerprint, extract_kid_from_jws, extract_primary_public_key, verify_jws,
15};
16use dpp_crypto::keystore::KeyStore;
17
18use crate::did_builder::build_did_document;
19use crate::passport_credential::build_passport_credential;
20
21pub struct LocalIdentityService {
29 store: Arc<KeyStore>,
30 key_id: String,
31 base_url: String,
32}
33
34impl LocalIdentityService {
35 pub fn new(store: Arc<KeyStore>, key_id: String, base_url: String) -> Self {
36 Self {
37 store,
38 key_id,
39 base_url,
40 }
41 }
42}
43
44#[async_trait]
45impl IdentityPort for LocalIdentityService {
46 async fn sign_passport(
47 &self,
48 passport_id: PassportId,
49 payload: &serde_json::Value,
50 ) -> Result<SignedCredential, DppError> {
51 let canonical = dpp_crypto::jws::canonical::canonicalize(payload)
52 .map_err(|e| DppError::Signing(e.to_string()))?;
53 let payload_hash = hex::encode(Sha256::digest(&canonical));
54
55 let jws = signer::sign(&self.store, &self.key_id, payload)
56 .map_err(|e| DppError::Signing(e.to_string()))?;
57
58 let did_doc = build_did_document(&self.store, &self.base_url, &self.key_id)
59 .map_err(|e| DppError::Signing(e.to_string()))?;
60
61 let issuer_did = did_doc["id"].as_str().unwrap_or_default().to_string();
62
63 let passport_vc = build_passport_credential(issuer_did.clone(), passport_id, payload_hash);
64
65 Ok(SignedCredential {
66 credential: passport_vc,
67 jws,
68 issuer_did,
69 })
70 }
71
72 async fn verify_signature(
73 &self,
74 jws: &str,
75 payload: &serde_json::Value,
76 ) -> Result<bool, DppError> {
77 let did_doc = build_did_document(&self.store, &self.base_url, &self.key_id)
78 .map_err(|e| DppError::Signing(e.to_string()))?;
79
80 let pub_key_b64 = extract_kid_from_jws(jws)
81 .and_then(|kid| extract_key_by_fingerprint(&did_doc, &kid))
82 .or_else(|| extract_primary_public_key(&did_doc))
83 .ok_or_else(|| DppError::Signing("no matching public key in DID document".into()))?;
84
85 if !verify_jws(jws, &pub_key_b64).map_err(|e| DppError::Signing(e.to_string()))? {
86 return Ok(false);
87 }
88
89 let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD;
90 let signed_payload = match jws.split('.').nth(1).and_then(|p| b64.decode(p).ok()) {
91 Some(bytes) => bytes,
92 None => return Ok(false),
93 };
94 let expected = dpp_crypto::jws::canonical::canonicalize(payload)
95 .map_err(|e| DppError::Signing(e.to_string()))?;
96 Ok(signed_payload == expected)
97 }
98
99 async fn own_did_document(&self) -> Result<serde_json::Value, DppError> {
100 build_did_document(&self.store, &self.base_url, &self.key_id)
101 .map_err(|e| DppError::Signing(e.to_string()))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use dpp_crypto::keystore::KeyStore;
109 use dpp_domain::PassportId;
110 use std::sync::Arc;
111
112 fn service() -> LocalIdentityService {
113 let path = std::env::temp_dir().join(format!("ls-test-{}.json", uuid::Uuid::new_v4()));
114 let store = KeyStore::open(&path, "test-pass").expect("open store");
115 store.generate_key("root").expect("generate key");
116 LocalIdentityService::new(
117 Arc::new(store),
118 "root".to_owned(),
119 "https://node.example.com".to_owned(),
120 )
121 }
122
123 #[tokio::test]
124 async fn signs_then_verifies_round_trip() {
125 let svc = service();
126 let payload = serde_json::json!({ "productName": "Widget", "sector": "battery" });
127 let signed = svc
128 .sign_passport(PassportId::new(), &payload)
129 .await
130 .expect("sign");
131
132 assert!(!signed.jws.is_empty());
133 assert!(signed.issuer_did.starts_with("did:web:"));
134 assert_eq!(signed.credential.issuer, signed.issuer_did);
135
136 let ok = svc
137 .verify_signature(&signed.jws, &payload)
138 .await
139 .expect("verify");
140 assert!(ok, "freshly signed payload must verify");
141 }
142
143 #[tokio::test]
144 async fn verify_rejects_tampered_payload() {
145 let svc = service();
146 let payload = serde_json::json!({ "value": 1 });
147 let signed = svc
148 .sign_passport(PassportId::new(), &payload)
149 .await
150 .expect("sign");
151
152 let tampered = serde_json::json!({ "value": 2 });
153 let ok = svc
154 .verify_signature(&signed.jws, &tampered)
155 .await
156 .expect("verify");
157 assert!(!ok, "a tampered payload must not verify");
158 }
159}