typesec_integrations/did/
keystore.rs1use std::collections::{HashMap, HashSet};
5
6use super::crypto::{hex_decode, hex_encode, sha256_tagged};
7use super::document::{DidDocument, VerificationMethod};
8use super::error::DidError;
9use super::identifier::Did;
10
11pub trait DidKeyStore: Send + Sync {
13 fn sign(&self, signer: &Did, message: &[u8]) -> Result<String, DidError>;
15
16 fn verify(
18 &self,
19 method: &VerificationMethod,
20 message: &[u8],
21 signature: &str,
22 ) -> Result<(), DidError>;
23
24 fn encrypt_for(
29 &self,
30 sender: &Did,
31 recipient_public_key: &[u8],
32 plaintext: &[u8],
33 nonce: &[u8],
34 associated_data: &[u8],
35 ) -> Result<String, DidError>;
36
37 fn decrypt_for(
42 &self,
43 recipient: &Did,
44 sender_public_key: &[u8],
45 nonce: &[u8],
46 ciphertext_hex: &str,
47 associated_data: &[u8],
48 ) -> Result<Vec<u8>, DidError>;
49}
50
51#[derive(Clone)]
59pub struct Ed25519DidKey {
60 signing: ed25519_dalek::SigningKey,
61 agreement: x25519_dalek::StaticSecret,
62}
63
64impl Ed25519DidKey {
65 pub fn generate() -> Result<Self, DidError> {
67 let mut signing_seed = [0u8; 32];
68 let mut agreement_seed = [0u8; 32];
69 getrandom::getrandom(&mut signing_seed).map_err(|e| DidError::KeyGen(e.to_string()))?;
70 getrandom::getrandom(&mut agreement_seed).map_err(|e| DidError::KeyGen(e.to_string()))?;
71 Ok(Self::from_seeds(signing_seed, agreement_seed))
72 }
73
74 pub fn from_seed(seed: impl AsRef<[u8]>) -> Self {
79 let signing_seed = sha256_tagged(b"typesec-ed25519-signing", seed.as_ref());
80 let agreement_seed = sha256_tagged(b"typesec-x25519-agreement", seed.as_ref());
81 Self::from_seeds(signing_seed, agreement_seed)
82 }
83
84 fn from_seeds(signing_seed: [u8; 32], agreement_seed: [u8; 32]) -> Self {
85 Self {
86 signing: ed25519_dalek::SigningKey::from_bytes(&signing_seed),
87 agreement: x25519_dalek::StaticSecret::from(agreement_seed),
88 }
89 }
90
91 pub fn signing_public(&self) -> [u8; 32] {
93 self.signing.verifying_key().to_bytes()
94 }
95
96 pub fn agreement_public(&self) -> [u8; 32] {
98 x25519_dalek::PublicKey::from(&self.agreement).to_bytes()
99 }
100
101 pub fn document(&self, did: Did) -> DidDocument {
103 DidDocument::with_signing_and_agreement_keys(
104 did,
105 self.signing_public(),
106 self.agreement_public(),
107 )
108 }
109}
110
111impl std::fmt::Debug for Ed25519DidKey {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.debug_struct("Ed25519DidKey")
114 .field("signing_public", &hex_encode(&self.signing_public()))
115 .field("agreement_public", &hex_encode(&self.agreement_public()))
116 .finish_non_exhaustive()
117 }
118}
119
120#[derive(Debug, Default, Clone)]
123pub struct Ed25519DidKeyStore {
124 keys: HashMap<Did, Vec<Ed25519DidKeyRecord>>,
125 retired_methods: HashSet<String>,
126}
127
128#[derive(Debug, Clone)]
129struct Ed25519DidKeyRecord {
130 version: u64,
131 key: Ed25519DidKey,
132 retired: bool,
133}
134
135impl Ed25519DidKeyStore {
136 pub fn new() -> Self {
138 Self::default()
139 }
140
141 pub fn with_key(mut self, did: Did, key: Ed25519DidKey) -> Self {
143 self.keys.insert(
144 did,
145 vec![Ed25519DidKeyRecord {
146 version: 1,
147 key,
148 retired: false,
149 }],
150 );
151 self
152 }
153
154 pub fn rotate_key(&mut self, did: &Did, key: Ed25519DidKey) -> Result<u64, DidError> {
159 let records = self
160 .keys
161 .get_mut(did)
162 .ok_or_else(|| DidError::MissingPrivateKey(did.to_string()))?;
163 let next_version = records
164 .iter()
165 .map(|record| record.version)
166 .max()
167 .unwrap_or(0)
168 + 1;
169 records.push(Ed25519DidKeyRecord {
170 version: next_version,
171 key,
172 retired: false,
173 });
174 Ok(next_version)
175 }
176
177 pub fn retire_key(&mut self, did: &Did, version: u64) -> Result<(), DidError> {
182 if self.active_key_version(did)? == version {
183 return Err(DidError::CannotRetireActiveKey {
184 did: did.to_string(),
185 version,
186 });
187 }
188
189 let records = self
190 .keys
191 .get_mut(did)
192 .ok_or_else(|| DidError::MissingPrivateKey(did.to_string()))?;
193 let record = records
194 .iter_mut()
195 .find(|record| record.version == version)
196 .ok_or_else(|| DidError::MissingKeyVersion {
197 did: did.to_string(),
198 version,
199 })?;
200 record.retired = true;
201 self.retired_methods
202 .insert(Self::signing_method_id(did, version));
203 self.retired_methods
204 .insert(Self::agreement_method_id(did, version));
205 Ok(())
206 }
207
208 pub fn active_key_version(&self, did: &Did) -> Result<u64, DidError> {
210 Ok(self.active_record(did)?.version)
211 }
212
213 pub fn document(&self, did: &Did) -> Result<DidDocument, DidError> {
215 let records = self
216 .keys
217 .get(did)
218 .ok_or_else(|| DidError::MissingPrivateKey(did.to_string()))?;
219 let active_version = self.active_key_version(did)?;
220 let mut verification_method = Vec::new();
221 let mut authentication = Vec::new();
222 let mut key_agreement = Vec::new();
223
224 for record in records.iter().filter(|record| !record.retired) {
225 let status = if record.version == active_version {
226 "active"
227 } else {
228 "previous"
229 };
230 let signing_id = Self::signing_method_id(did, record.version);
231 let agreement_id = Self::agreement_method_id(did, record.version);
232 verification_method.push(VerificationMethod {
233 id: signing_id.clone(),
234 method_type: "Ed25519VerificationKey2020".to_owned(),
235 controller: did.clone(),
236 public_key_hex: hex_encode(&record.key.signing_public()),
237 key_version: Some(record.version),
238 key_status: Some(status.to_owned()),
239 });
240 verification_method.push(VerificationMethod {
241 id: agreement_id.clone(),
242 method_type: "X25519KeyAgreementKey2020".to_owned(),
243 controller: did.clone(),
244 public_key_hex: hex_encode(&record.key.agreement_public()),
245 key_version: Some(record.version),
246 key_status: Some(status.to_owned()),
247 });
248
249 if record.version == active_version {
250 authentication.insert(0, signing_id);
251 key_agreement.insert(0, agreement_id);
252 } else {
253 authentication.push(signing_id);
254 key_agreement.push(agreement_id);
255 }
256 }
257
258 Ok(DidDocument {
259 id: did.clone(),
260 verification_method,
261 authentication,
262 key_agreement,
263 service: Vec::new(),
264 })
265 }
266
267 fn active_record(&self, did: &Did) -> Result<&Ed25519DidKeyRecord, DidError> {
268 self.keys
269 .get(did)
270 .and_then(|records| {
271 records
272 .iter()
273 .filter(|record| !record.retired)
274 .max_by_key(|record| record.version)
275 })
276 .ok_or_else(|| DidError::MissingPrivateKey(did.to_string()))
277 }
278
279 fn signing_method_id(did: &Did, version: u64) -> String {
280 if version == 1 {
281 format!("{did}#key-1")
282 } else {
283 format!("{did}#key-signing-v{version}")
284 }
285 }
286
287 fn agreement_method_id(did: &Did, version: u64) -> String {
288 if version == 1 {
289 format!("{did}#key-2")
290 } else {
291 format!("{did}#key-agreement-v{version}")
292 }
293 }
294
295 fn aead_key(shared_secret: &[u8; 32]) -> chacha20poly1305::Key {
296 let digest = sha256_tagged(b"typesec-did-aead", shared_secret);
297 chacha20poly1305::Key::from(digest)
298 }
299}
300
301impl DidKeyStore for Ed25519DidKeyStore {
302 fn sign(&self, signer: &Did, message: &[u8]) -> Result<String, DidError> {
303 use ed25519_dalek::Signer;
304 let record = self.active_record(signer)?;
305 Ok(hex_encode(&record.key.signing.sign(message).to_bytes()))
306 }
307
308 fn verify(
309 &self,
310 method: &VerificationMethod,
311 message: &[u8],
312 signature: &str,
313 ) -> Result<(), DidError> {
314 use ed25519_dalek::Verifier;
315 if self.retired_methods.contains(&method.id) {
316 return Err(DidError::RetiredKey(method.id.clone()));
317 }
318 let public: [u8; 32] = method
319 .public_key()?
320 .try_into()
321 .map_err(|_| DidError::InvalidKey("ed25519 public key must be 32 bytes".into()))?;
322 let verifying = ed25519_dalek::VerifyingKey::from_bytes(&public)
323 .map_err(|e| DidError::InvalidKey(e.to_string()))?;
324 let signature_bytes: [u8; 64] = hex_decode(signature)?
325 .try_into()
326 .map_err(|_| DidError::InvalidSignature)?;
327 verifying
328 .verify(
329 message,
330 &ed25519_dalek::Signature::from_bytes(&signature_bytes),
331 )
332 .map_err(|_| DidError::InvalidSignature)
333 }
334
335 fn encrypt_for(
336 &self,
337 sender: &Did,
338 recipient_public_key: &[u8],
339 plaintext: &[u8],
340 nonce: &[u8],
341 associated_data: &[u8],
342 ) -> Result<String, DidError> {
343 use chacha20poly1305::KeyInit;
344 use chacha20poly1305::aead::{Aead, Payload};
345 let sender_key = &self.active_record(sender)?.key;
346 let recipient: [u8; 32] = recipient_public_key
347 .try_into()
348 .map_err(|_| DidError::InvalidKey("x25519 public key must be 32 bytes".into()))?;
349 let shared = sender_key
350 .agreement
351 .diffie_hellman(&x25519_dalek::PublicKey::from(recipient));
352 let nonce: [u8; 12] = nonce.try_into().map_err(|_| DidError::InvalidNonce)?;
353 let cipher = chacha20poly1305::ChaCha20Poly1305::new(&Self::aead_key(shared.as_bytes()));
354 let ciphertext = cipher
355 .encrypt(
356 &chacha20poly1305::Nonce::from(nonce),
357 Payload {
358 msg: plaintext,
359 aad: associated_data,
360 },
361 )
362 .map_err(|_| DidError::EncryptionFailed)?;
363 Ok(hex_encode(&ciphertext))
364 }
365
366 fn decrypt_for(
367 &self,
368 recipient: &Did,
369 sender_public_key: &[u8],
370 nonce: &[u8],
371 ciphertext_hex: &str,
372 associated_data: &[u8],
373 ) -> Result<Vec<u8>, DidError> {
374 use chacha20poly1305::KeyInit;
375 use chacha20poly1305::aead::{Aead, Payload};
376 let sender: [u8; 32] = sender_public_key
377 .try_into()
378 .map_err(|_| DidError::InvalidKey("x25519 public key must be 32 bytes".into()))?;
379 let nonce: [u8; 12] = nonce.try_into().map_err(|_| DidError::InvalidNonce)?;
380 let ciphertext = hex_decode(ciphertext_hex)?;
381 let records = self
382 .keys
383 .get(recipient)
384 .ok_or_else(|| DidError::MissingPrivateKey(recipient.to_string()))?;
385
386 for record in records.iter().filter(|record| !record.retired) {
387 let shared = record
388 .key
389 .agreement
390 .diffie_hellman(&x25519_dalek::PublicKey::from(sender));
391 let cipher =
392 chacha20poly1305::ChaCha20Poly1305::new(&Self::aead_key(shared.as_bytes()));
393 if let Ok(plaintext) = cipher.decrypt(
394 &chacha20poly1305::Nonce::from(nonce),
395 Payload {
396 msg: ciphertext.as_slice(),
397 aad: associated_data,
398 },
399 ) {
400 return Ok(plaintext);
401 }
402 }
403
404 Err(DidError::DecryptionFailed)
405 }
406}