dpp_crypto/identity/
local_service.rs1use 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 crate::jws::signer;
13use crate::jws::verifier::{
14 extract_key_by_fingerprint, extract_kid_from_jws, extract_primary_public_key, verify_jws,
15};
16use crate::keystore::KeyStore;
17
18use super::did_builder::build_did_document;
19use super::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 = crate::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 = crate::jws::canonical::canonicalize(payload)
95 .map_err(|e| DppError::Signing(e.to_string()))?;
96 Ok(signed_payload == expected)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::keystore::KeyStore;
104 use dpp_domain::PassportId;
105 use std::sync::Arc;
106
107 fn service() -> LocalIdentityService {
108 let path = std::env::temp_dir().join(format!("ls-test-{}.json", uuid::Uuid::new_v4()));
109 let store = KeyStore::open(&path, "test-pass").expect("open store");
110 store.generate_key("root").expect("generate key");
111 LocalIdentityService::new(
112 Arc::new(store),
113 "root".to_owned(),
114 "https://node.example.com".to_owned(),
115 )
116 }
117
118 #[tokio::test]
119 async fn signs_then_verifies_round_trip() {
120 let svc = service();
121 let payload = serde_json::json!({ "productName": "Widget", "sector": "battery" });
122 let signed = svc
123 .sign_passport(PassportId::new(), &payload)
124 .await
125 .expect("sign");
126
127 assert!(!signed.jws.is_empty());
128 assert!(signed.issuer_did.starts_with("did:web:"));
129 assert_eq!(signed.credential.issuer, signed.issuer_did);
130
131 let ok = svc
132 .verify_signature(&signed.jws, &payload)
133 .await
134 .expect("verify");
135 assert!(ok, "freshly signed payload must verify");
136 }
137
138 #[tokio::test]
139 async fn verify_rejects_tampered_payload() {
140 let svc = service();
141 let payload = serde_json::json!({ "value": 1 });
142 let signed = svc
143 .sign_passport(PassportId::new(), &payload)
144 .await
145 .expect("sign");
146
147 let tampered = serde_json::json!({ "value": 2 });
148 let ok = svc
149 .verify_signature(&signed.jws, &tampered)
150 .await
151 .expect("verify");
152 assert!(!ok, "a tampered payload must not verify");
153 }
154}