1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
2use chrono::Utc;
3use serde::{Deserialize, Serialize};
4use serde_json::{json, Value};
5use sha2::{Digest, Sha256};
6
7use anp::authentication::{validate_device_manifest, validate_did_document_binding};
8
9use crate::document::{
10 ed25519_public_multibase, key_metadata, public_keys_equal, service_json, sign_root_document,
11 ManagedPrivateKey,
12};
13use crate::input::{canonicalize_kid, validate_fragment};
14use crate::keystore::{SealIfAbsent, SecretRef};
15use crate::registry::{
16 list_update_journals, read_identity, remove_update_journal, write_identity,
17 write_update_journal, IdentityRecord, KeyMetadata, UpdateJournal, UpdateJournalKind,
18};
19use crate::store::StoreRuntime;
20use crate::store_lock::StoreWriteGuard;
21use crate::{DidError, DidIdentity, DidResult, KeyOrigin, KeyRole, KeyState, ServiceSpec};
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
24#[serde(rename_all = "snake_case")]
25pub enum PublicationState {
26 Prepared,
27 PublicationInFlight,
28 PublicationUncertain,
29 Published,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(deny_unknown_fields)]
34pub struct RequestSigningRotation {
35 pub old_kid: String,
36 pub new_fragment: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(deny_unknown_fields)]
41pub struct RequestSigningPublicKeySpec {
42 pub kid: String,
43 pub public_key_multibase: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
47#[serde(tag = "operation", rename_all = "snake_case")]
48pub enum RequestSigningMutationSpec {
49 Add { key: RequestSigningPublicKeySpec },
50 Remove { kid: String },
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(deny_unknown_fields)]
55pub struct DevicePublicKeySpec {
56 pub kid: String,
57 pub public_key_multibase: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(deny_unknown_fields)]
62pub struct DeviceAddSpec {
63 pub device_id: String,
64 pub signing_key: DevicePublicKeySpec,
65 pub e2ee_key: DevicePublicKeySpec,
66 pub profiles: Vec<String>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70#[serde(tag = "operation", rename_all = "snake_case")]
71pub enum DeviceMutationSpec {
72 Add { device: DeviceAddSpec },
73 Remove { device_id: String },
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77#[serde(deny_unknown_fields)]
78pub struct DocumentUpdateSpec {
79 #[serde(default)]
80 pub request_signing_rotation: Option<RequestSigningRotation>,
81 #[serde(default)]
82 pub request_signing_mutations: Vec<RequestSigningMutationSpec>,
83 #[serde(default)]
84 pub device_mutations: Vec<DeviceMutationSpec>,
85 pub services: Option<Vec<ServiceSpec>>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89#[serde(deny_unknown_fields)]
90pub struct PreparedUpdate {
91 pub revision_id: String,
92 pub candidate_document: Value,
93 pub candidate_digest: String,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
97#[serde(rename_all = "snake_case")]
98pub enum ReconcileOutcome {
99 RemoteOld,
100 Committed,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104#[serde(deny_unknown_fields)]
105pub struct PendingRevisionSummary {
106 pub revision_id: String,
107 pub parent_revision: u64,
108 pub candidate_document: Value,
109 pub candidate_digest: String,
110 pub candidate_kids: Vec<String>,
111 pub state: PublicationState,
112 pub generation: u64,
113 pub created_at: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
117#[serde(deny_unknown_fields)]
118pub(crate) struct PendingRevisionRecord {
119 pub(crate) revision_id: String,
120 pub(crate) parent_revision: u64,
121 pub(crate) candidate_document: Value,
122 pub(crate) candidate_digest: String,
123 pub(crate) candidate_kids: Vec<String>,
124 pub(crate) pending_secret_refs: Vec<SecretRef>,
125 pub(crate) new_key_metadata: Vec<KeyMetadata>,
126 pub(crate) retired_kids: Vec<String>,
127 pub(crate) state: PublicationState,
128 pub(crate) generation: u64,
129 pub(crate) created_at: String,
130}
131
132impl PendingRevisionRecord {
133 fn summary(&self) -> PendingRevisionSummary {
134 PendingRevisionSummary {
135 revision_id: self.revision_id.clone(),
136 parent_revision: self.parent_revision,
137 candidate_document: self.candidate_document.clone(),
138 candidate_digest: self.candidate_digest.clone(),
139 candidate_kids: self.candidate_kids.clone(),
140 state: self.state,
141 generation: self.generation,
142 created_at: self.created_at.clone(),
143 }
144 }
145}
146
147impl DidIdentity {
148 pub fn pending_revision(&self) -> Option<PendingRevisionSummary> {
149 self.record()
150 .pending_revision
151 .as_ref()
152 .map(PendingRevisionRecord::summary)
153 }
154
155 pub fn prepare_update(&mut self, spec: DocumentUpdateSpec) -> DidResult<PreparedUpdate> {
156 self.prepare_update_inner(spec, None)
157 }
158
159 pub fn begin_publication(&mut self, revision_id: &str) -> DidResult<()> {
160 self.transition_publication(
161 revision_id,
162 PublicationState::Prepared,
163 PublicationState::PublicationInFlight,
164 )
165 }
166
167 pub fn mark_publication_uncertain(&mut self, revision_id: &str) -> DidResult<()> {
168 self.transition_publication(
169 revision_id,
170 PublicationState::PublicationInFlight,
171 PublicationState::PublicationUncertain,
172 )
173 }
174
175 pub fn mark_published(&mut self, revision_id: &str) -> DidResult<()> {
176 self.transition_publication(
177 revision_id,
178 PublicationState::PublicationInFlight,
179 PublicationState::Published,
180 )
181 }
182
183 pub fn commit_update(&mut self, revision_id: &str) -> DidResult<()> {
184 self.commit_pending(revision_id, false)
185 }
186
187 pub fn abort_update(&mut self, revision_id: &str) -> DidResult<()> {
188 self.abort_update_inner(revision_id, None)
189 }
190
191 pub fn reconcile_update(
192 &mut self,
193 revision_id: &str,
194 observed_remote_document: &Value,
195 ) -> DidResult<ReconcileOutcome> {
196 let pending = required_pending(self.record(), revision_id)?;
197 if pending.state != PublicationState::PublicationUncertain {
198 return Err(DidError::InvalidPublicationState);
199 }
200 if observed_remote_document.get("id").and_then(Value::as_str) != Some(self.did())
201 || !validate_did_document_binding(observed_remote_document, true)
202 {
203 return Err(DidError::InvalidIdentity);
204 }
205 let observed_digest = canonical_digest(observed_remote_document)?;
206 if observed_digest == pending.candidate_digest {
207 self.commit_pending(revision_id, true)?;
208 return Ok(ReconcileOutcome::Committed);
209 }
210 if observed_digest == canonical_digest(self.document())? {
211 self.transition_publication(
212 revision_id,
213 PublicationState::PublicationUncertain,
214 PublicationState::Prepared,
215 )?;
216 return Ok(ReconcileOutcome::RemoteOld);
217 }
218 Err(DidError::Conflict)
219 }
220
221 pub fn end_retirement(&mut self, kid: &str) -> DidResult<()> {
222 let kid = canonicalize_kid(self.did(), kid)?;
223 let guard = self.runtime().acquire_write()?;
224 let mut record = self.current_record_for_mutation()?;
225 reject_uncertain_mutation(&record)?;
226 let metadata = record
227 .keys
228 .iter_mut()
229 .find(|metadata| metadata.kid == kid)
230 .ok_or(DidError::KeyNotFound)?;
231 if metadata.role == KeyRole::RootControl {
232 return Err(DidError::UnsupportedOperation);
233 }
234 if metadata.state != KeyState::Retired {
235 return Err(DidError::KeyNotUsable);
236 }
237 metadata.state = KeyState::Revoked;
238 persist_record(self.runtime(), &guard, &mut record)?;
239 drop(guard);
240 self.replace_record(record);
241 Ok(())
242 }
243
244 pub fn delete_revoked_key(&mut self, kid: &str) -> DidResult<()> {
245 let kid = canonicalize_kid(self.did(), kid)?;
246 let guard = self.runtime().acquire_write()?;
247 let mut record = self.current_record_for_mutation()?;
248 reject_uncertain_mutation(&record)?;
249 let metadata_index = record
250 .keys
251 .iter()
252 .position(|metadata| metadata.kid == kid)
253 .ok_or(DidError::KeyNotFound)?;
254 let metadata = &record.keys[metadata_index];
255 if metadata.origin == KeyOrigin::External {
256 return Err(DidError::ExternalKeyOperation);
257 }
258 if metadata.role == KeyRole::RootControl {
259 return Err(DidError::UnsupportedOperation);
260 }
261 if metadata.state != KeyState::Revoked {
262 return Err(DidError::KeyNotUsable);
263 }
264 if metadata.material_erased {
265 return Err(DidError::KeyMaterialErased);
266 }
267 let secret_ref = SecretRef {
268 identity_id: self.identity_id().to_string(),
269 key_id: metadata.kid.clone(),
270 role: metadata.role,
271 version: metadata.version,
272 };
273 self.runtime().key_store().delete(&guard, &secret_ref)?;
274 record.keys[metadata_index].material_erased = true;
275 persist_record(self.runtime(), &guard, &mut record)?;
276 drop(guard);
277 self.replace_record(record);
278 Ok(())
279 }
280
281 fn prepare_update_inner(
282 &mut self,
283 spec: DocumentUpdateSpec,
284 failure: Option<LifecycleFailurePoint>,
285 ) -> DidResult<PreparedUpdate> {
286 if spec.request_signing_rotation.is_none()
287 && spec.request_signing_mutations.is_empty()
288 && spec.device_mutations.is_empty()
289 && spec.services.is_none()
290 {
291 return Err(DidError::UnsupportedOperation);
292 }
293 for service in spec.services.as_deref().unwrap_or_default() {
294 service.validate()?;
295 }
296 let guard = self.runtime().acquire_write()?;
297 let mut record = self.current_record_for_mutation()?;
298 if let Some(pending) = &record.pending_revision {
299 return Err(if pending.state == PublicationState::PublicationUncertain {
300 DidError::InvalidPublicationState
301 } else {
302 DidError::PendingRevisionExists
303 });
304 }
305 let rotation = spec
306 .request_signing_rotation
307 .as_ref()
308 .map(|rotation| prepare_rotation(&record, rotation))
309 .transpose()?;
310 let changes = build_candidate_document(
311 self,
312 &record,
313 rotation.as_ref(),
314 &spec.request_signing_mutations,
315 &spec.device_mutations,
316 spec.services.as_deref(),
317 )?;
318 let candidate = changes.document;
319 let created_at = Utc::now().to_rfc3339();
320 let mut new_metadata = changes
321 .added_keys
322 .iter()
323 .map(|(kid, role)| {
324 key_metadata(&candidate, kid, *role, KeyOrigin::External, &created_at)
325 })
326 .collect::<DidResult<Vec<_>>>()?;
327 let mut secret_refs = Vec::new();
328 if let Some(rotation) = &rotation {
329 let metadata = key_metadata(
330 &candidate,
331 &rotation.new_kid,
332 KeyRole::RequestSigning,
333 KeyOrigin::Managed,
334 &created_at,
335 )?;
336 secret_refs.push(SecretRef {
337 identity_id: record.identity_id.clone(),
338 key_id: rotation.new_kid.clone(),
339 role: KeyRole::RequestSigning,
340 version: metadata.version,
341 });
342 new_metadata.push(metadata);
343 }
344 let revision_id = random_revision_id();
345 let update_journal = UpdateJournal::new(
346 revision_id.clone(),
347 record.identity_id.clone(),
348 secret_refs.clone(),
349 UpdateJournalKind::Prepare,
350 created_at.clone(),
351 );
352 write_update_journal(self.runtime().root(), &guard, &update_journal)?;
353 maybe_fail(failure, LifecycleFailurePoint::PrepareJournalPersisted)?;
354 if let (Some(rotation), Some(secret_ref)) = (&rotation, secret_refs.first()) {
355 let sealed = self.runtime().key_store().seal_if_absent(
356 &guard,
357 self.runtime().root_key(),
358 secret_ref.clone(),
359 rotation.new_key.secret_bytes(),
360 )?;
361 if sealed == SealIfAbsent::AlreadyExists {
362 let stored = self
363 .runtime()
364 .key_store()
365 .open(self.runtime().root_key(), secret_ref)?;
366 let actual =
367 crate::document::public_key_from_secret(KeyRole::RequestSigning, &stored)?;
368 if !public_keys_equal(&actual, &rotation.new_key.public_key()) {
369 return Err(DidError::InvalidIdentity);
370 }
371 }
372 }
373 maybe_fail(failure, LifecycleFailurePoint::PendingSecretPersisted)?;
374
375 let candidate_digest = canonical_digest(&candidate)?;
376 let mut candidate_kids = changes
377 .added_keys
378 .into_iter()
379 .map(|(kid, _)| kid)
380 .collect::<Vec<_>>();
381 if let Some(rotation) = &rotation {
382 candidate_kids.push(rotation.new_kid.clone());
383 }
384 let mut retired_kids = changes.retired_kids;
385 if let Some(rotation) = &rotation {
386 retired_kids.push(rotation.old_kid.clone());
387 }
388 let pending = PendingRevisionRecord {
389 revision_id: revision_id.clone(),
390 parent_revision: record.revision,
391 candidate_document: candidate.clone(),
392 candidate_digest: candidate_digest.clone(),
393 candidate_kids,
394 pending_secret_refs: secret_refs,
395 new_key_metadata: new_metadata,
396 retired_kids,
397 state: PublicationState::Prepared,
398 generation: 1,
399 created_at,
400 };
401 record.pending_revision = Some(pending);
402 persist_record(self.runtime(), &guard, &mut record)?;
403 maybe_fail(failure, LifecycleFailurePoint::PendingIdentityPersisted)?;
404 remove_update_journal(self.runtime().root(), &guard, &revision_id)?;
405 drop(guard);
406 self.replace_record(record);
407 Ok(PreparedUpdate {
408 revision_id,
409 candidate_document: candidate,
410 candidate_digest,
411 })
412 }
413
414 fn transition_publication(
415 &mut self,
416 revision_id: &str,
417 expected: PublicationState,
418 next: PublicationState,
419 ) -> DidResult<()> {
420 let guard = self.runtime().acquire_write()?;
421 let mut record = self.current_record_for_mutation()?;
422 let pending = required_pending_mut(&mut record, revision_id)?;
423 if pending.state != expected {
424 return Err(DidError::InvalidPublicationState);
425 }
426 pending.state = next;
427 pending.generation = pending
428 .generation
429 .checked_add(1)
430 .ok_or(DidError::Conflict)?;
431 persist_record(self.runtime(), &guard, &mut record)?;
432 drop(guard);
433 self.replace_record(record);
434 Ok(())
435 }
436
437 fn commit_pending(&mut self, revision_id: &str, from_reconcile: bool) -> DidResult<()> {
438 let guard = self.runtime().acquire_write()?;
439 let mut record = self.current_record_for_mutation()?;
440 let pending = required_pending(&record, revision_id)?.clone();
441 let allowed = pending.state == PublicationState::Published
442 || (from_reconcile && pending.state == PublicationState::PublicationUncertain);
443 if !allowed || pending.parent_revision != record.revision {
444 return Err(DidError::InvalidPublicationState);
445 }
446 for retired_kid in &pending.retired_kids {
447 if let Some(replaced) = record
448 .keys
449 .iter_mut()
450 .find(|metadata| metadata.kid == *retired_kid)
451 {
452 replaced.state = KeyState::Retired;
453 }
454 }
455 record.keys.extend(pending.new_key_metadata);
456 record.document = pending.candidate_document;
457 record.revision = record.revision.checked_add(1).ok_or(DidError::Conflict)?;
458 let registry_version = record
459 .checkpoint
460 .as_ref()
461 .map(|checkpoint| checkpoint.registry_version)
462 .unwrap_or_default()
463 .checked_add(1)
464 .ok_or(DidError::Conflict)?;
465 record.checkpoint = Some(crate::DocumentCheckpoint {
466 document_version: record.revision,
467 registry_version,
468 document_digest: crate::document::document_digest(&record.document)?,
469 });
470 record.pending_revision = None;
471 persist_record(self.runtime(), &guard, &mut record)?;
472 drop(guard);
473 self.replace_record(record);
474 Ok(())
475 }
476
477 fn abort_update_inner(
478 &mut self,
479 revision_id: &str,
480 failure: Option<LifecycleFailurePoint>,
481 ) -> DidResult<()> {
482 let guard = self.runtime().acquire_write()?;
483 let mut record = self.current_record_for_mutation()?;
484 let pending = required_pending(&record, revision_id)?.clone();
485 if pending.state != PublicationState::Prepared {
486 return Err(DidError::InvalidPublicationState);
487 }
488 let journal = UpdateJournal::new(
489 pending.revision_id.clone(),
490 record.identity_id.clone(),
491 pending.pending_secret_refs.clone(),
492 UpdateJournalKind::Cleanup,
493 Utc::now().to_rfc3339(),
494 );
495 write_update_journal(self.runtime().root(), &guard, &journal)?;
496 maybe_fail(failure, LifecycleFailurePoint::CleanupJournalPersisted)?;
497 record.pending_revision = None;
498 persist_record(self.runtime(), &guard, &mut record)?;
499 maybe_fail(failure, LifecycleFailurePoint::PendingIdentityCleared)?;
500 for secret_ref in &pending.pending_secret_refs {
501 self.runtime().key_store().delete(&guard, secret_ref)?;
502 }
503 maybe_fail(failure, LifecycleFailurePoint::PendingSecretsDeleted)?;
504 remove_update_journal(self.runtime().root(), &guard, revision_id)?;
505 drop(guard);
506 self.replace_record(record);
507 Ok(())
508 }
509
510 fn current_record_for_mutation(&self) -> DidResult<IdentityRecord> {
511 let record = read_identity(self.runtime().root(), self.identity_id())?;
512 if record.generation != self.record().generation
513 || record.state != crate::IdentityState::Active
514 {
515 return Err(DidError::Conflict);
516 }
517 Ok(record)
518 }
519}
520
521pub(crate) fn recover_update_journals(
522 runtime: &StoreRuntime,
523 guard: &StoreWriteGuard,
524) -> DidResult<()> {
525 for journal in list_update_journals(runtime.root())? {
526 let mut record = read_identity(runtime.root(), &journal.identity_id)?;
527 match journal.kind {
528 UpdateJournalKind::Prepare => {
529 let committed = record
530 .pending_revision
531 .as_ref()
532 .is_some_and(|pending| pending.revision_id == journal.revision_id);
533 if !committed {
534 for secret_ref in &journal.secret_refs {
535 runtime.key_store().delete(guard, secret_ref)?;
536 }
537 }
538 }
539 UpdateJournalKind::Cleanup => {
540 if record
541 .pending_revision
542 .as_ref()
543 .is_some_and(|pending| pending.revision_id == journal.revision_id)
544 {
545 record.pending_revision = None;
546 persist_record(runtime, guard, &mut record)?;
547 }
548 for secret_ref in &journal.secret_refs {
549 runtime.key_store().delete(guard, secret_ref)?;
550 }
551 }
552 }
553 remove_update_journal(runtime.root(), guard, &journal.revision_id)?;
554 }
555 Ok(())
556}
557
558struct PreparedRotation {
559 old_kid: String,
560 new_kid: String,
561 new_key: ManagedPrivateKey,
562}
563
564struct CandidateChanges {
565 document: Value,
566 added_keys: Vec<(String, KeyRole)>,
567 retired_kids: Vec<String>,
568}
569
570struct DeviceChanges {
571 added_keys: Vec<(String, KeyRole)>,
572 retired_kids: Vec<String>,
573}
574
575fn prepare_rotation(
576 record: &IdentityRecord,
577 rotation: &RequestSigningRotation,
578) -> DidResult<PreparedRotation> {
579 let old_kid = canonicalize_kid(&record.did, &rotation.old_kid)?;
580 let old_metadata = record
581 .keys
582 .iter()
583 .find(|metadata| metadata.kid == old_kid)
584 .ok_or(DidError::KeyNotFound)?;
585 if old_metadata.origin == KeyOrigin::External {
586 return Err(DidError::ExternalKeyOperation);
587 }
588 if old_metadata.role == KeyRole::RootControl {
589 return Err(DidError::UnsupportedOperation);
590 }
591 if old_metadata.role != KeyRole::RequestSigning {
592 return Err(DidError::KeyRoleViolation);
593 }
594 if old_metadata.state != KeyState::Active {
595 return Err(DidError::KeyNotUsable);
596 }
597 validate_fragment(&rotation.new_fragment)?;
598 let new_kid = canonicalize_kid(&record.did, &rotation.new_fragment)?;
599 if record.keys.iter().any(|metadata| metadata.kid == new_kid) {
600 return Err(DidError::DuplicateKid);
601 }
602 Ok(PreparedRotation {
603 old_kid,
604 new_kid,
605 new_key: ManagedPrivateKey::generate(
606 rotation.new_fragment.clone(),
607 KeyRole::RequestSigning,
608 ),
609 })
610}
611
612fn build_candidate_document(
613 identity: &DidIdentity,
614 record: &IdentityRecord,
615 rotation: Option<&PreparedRotation>,
616 request_signing_mutations: &[RequestSigningMutationSpec],
617 device_mutations: &[DeviceMutationSpec],
618 services: Option<&[ServiceSpec]>,
619) -> DidResult<CandidateChanges> {
620 let mut document = record.document.clone();
621 let object = document.as_object_mut().ok_or(DidError::InvalidIdentity)?;
622 let proof_domain = object
623 .get("proof")
624 .and_then(|proof| proof.get("domain"))
625 .and_then(Value::as_str)
626 .map(ToOwned::to_owned);
627 object.remove("proof");
628 if let Some(rotation) = rotation {
629 let methods = object
630 .get_mut("verificationMethod")
631 .and_then(Value::as_array_mut)
632 .ok_or(DidError::InvalidIdentity)?;
633 methods.push(json!({
634 "id": rotation.new_kid,
635 "type": "Multikey",
636 "controller": record.did,
637 "publicKeyMultibase": ed25519_public_multibase(&rotation.new_key.public_key())?,
638 }));
639 let authentication = object
640 .get_mut("authentication")
641 .and_then(Value::as_array_mut)
642 .ok_or(DidError::InvalidIdentity)?;
643 authentication.retain(|entry| entry.as_str() != Some(&rotation.old_kid));
644 authentication.push(Value::String(rotation.new_kid.clone()));
645 }
646 let request_signing_changes =
647 apply_request_signing_mutations(object, record, request_signing_mutations)?;
648 let device_changes = apply_device_mutations(object, record, device_mutations)?;
649 if let Some(services) = services {
650 let mut output = object
651 .get("service")
652 .and_then(Value::as_array)
653 .into_iter()
654 .flatten()
655 .filter(|service| {
656 service.get("type").and_then(Value::as_str) == Some("AgentDescription")
657 })
658 .cloned()
659 .collect::<Vec<_>>();
660 for service in services {
661 let mut value = service_json(service);
662 if let Some(id) = value.get("id").and_then(Value::as_str) {
663 if id.starts_with('#') {
664 value["id"] = Value::String(format!("{}{}", record.did, id));
665 }
666 }
667 output.push(value);
668 }
669 object.insert("service".to_string(), Value::Array(output));
670 }
671 let root = record
672 .keys
673 .iter()
674 .find(|metadata| metadata.role == KeyRole::RootControl)
675 .ok_or(DidError::InvalidIdentity)?;
676 let root_secret = identity.load_managed_secret(root)?;
677 let signed = sign_root_document(&document, &root.kid, &root_secret, proof_domain)?;
678 if !validate_did_document_binding(&signed, true) {
679 return Err(DidError::InvalidIdentity);
680 }
681 validate_device_manifest(&signed).map_err(|_| DidError::InvalidExtension)?;
682 Ok(CandidateChanges {
683 document: signed,
684 added_keys: request_signing_changes
685 .added_keys
686 .into_iter()
687 .chain(device_changes.added_keys)
688 .collect(),
689 retired_kids: request_signing_changes
690 .retired_kids
691 .into_iter()
692 .chain(device_changes.retired_kids)
693 .collect(),
694 })
695}
696
697fn apply_request_signing_mutations(
698 object: &mut serde_json::Map<String, Value>,
699 record: &IdentityRecord,
700 mutations: &[RequestSigningMutationSpec],
701) -> DidResult<DeviceChanges> {
702 let mut added_keys = Vec::new();
703 let mut retired_kids = Vec::new();
704 for mutation in mutations {
705 match mutation {
706 RequestSigningMutationSpec::Add { key } => {
707 let kid = canonicalize_kid(&record.did, &key.kid)?;
708 if record.keys.iter().any(|metadata| metadata.kid == kid)
709 || method_exists(object, &kid)
710 {
711 return Err(DidError::DuplicateKid);
712 }
713 validate_request_signing_public_key(&kid, &key.public_key_multibase)?;
714 methods_mut(object)?.push(json!({
715 "id": kid,
716 "type": "Multikey",
717 "controller": record.did,
718 "publicKeyMultibase": key.public_key_multibase,
719 }));
720 relationship_mut(object, "authentication")?.push(Value::String(kid.clone()));
721 added_keys.push((kid, KeyRole::RequestSigning));
722 }
723 RequestSigningMutationSpec::Remove { kid } => {
724 let kid = canonicalize_kid(&record.did, kid)?;
725 let metadata = record
726 .keys
727 .iter()
728 .find(|metadata| metadata.kid == kid)
729 .ok_or(DidError::KeyNotFound)?;
730 if metadata.role != KeyRole::RequestSigning {
731 return Err(DidError::KeyRoleViolation);
732 }
733 if metadata.origin != KeyOrigin::External {
734 return Err(DidError::UnsupportedOperation);
735 }
736 methods_mut(object)?.retain(|method| {
737 method.get("id").and_then(Value::as_str) != Some(kid.as_str())
738 });
739 relationship_mut(object, "authentication")?
740 .retain(|entry| entry.as_str() != Some(kid.as_str()));
741 retired_kids.push(kid);
742 }
743 }
744 }
745 Ok(DeviceChanges {
746 added_keys,
747 retired_kids,
748 })
749}
750
751fn validate_request_signing_public_key(kid: &str, multibase: &str) -> DidResult<()> {
752 let method = json!({
753 "id": kid,
754 "type": "Multikey",
755 "publicKeyMultibase": multibase,
756 });
757 match anp::authentication::extract_public_key(&method)
758 .map_err(|_| DidError::InvalidPublicKey)?
759 {
760 anp::PublicKeyMaterial::Ed25519(_) => Ok(()),
761 _ => Err(DidError::InvalidPublicKey),
762 }
763}
764
765fn apply_device_mutations(
766 object: &mut serde_json::Map<String, Value>,
767 record: &IdentityRecord,
768 mutations: &[DeviceMutationSpec],
769) -> DidResult<DeviceChanges> {
770 let mut manifest = object
771 .get("deviceManifest")
772 .cloned()
773 .map(serde_json::from_value::<anp::authentication::DeviceManifest>)
774 .transpose()
775 .map_err(|_| DidError::InvalidExtension)?;
776 let mut added_keys = Vec::new();
777 let mut retired_kids = Vec::new();
778 for mutation in mutations {
779 match mutation {
780 DeviceMutationSpec::Add { device } => {
781 validate_device_add(device)?;
782 let signing_kid = canonicalize_kid(&record.did, &device.signing_key.kid)?;
783 let e2ee_kid = canonicalize_kid(&record.did, &device.e2ee_key.kid)?;
784 if signing_kid == e2ee_kid
785 || record
786 .keys
787 .iter()
788 .any(|key| key.kid == signing_kid || key.kid == e2ee_kid)
789 || method_exists(object, &signing_kid)
790 || method_exists(object, &e2ee_kid)
791 || manifest.as_ref().is_some_and(|manifest| {
792 manifest
793 .devices
794 .iter()
795 .any(|entry| entry.device_id == device.device_id)
796 })
797 {
798 return Err(DidError::DuplicateKid);
799 }
800 validate_device_public_key(
801 &signing_kid,
802 &device.signing_key.public_key_multibase,
803 KeyRole::DeviceSigning,
804 )?;
805 validate_device_public_key(
806 &e2ee_kid,
807 &device.e2ee_key.public_key_multibase,
808 KeyRole::E2eeAgreement,
809 )?;
810 methods_mut(object)?.extend([
811 json!({
812 "id": signing_kid,
813 "type": "Multikey",
814 "controller": record.did,
815 "publicKeyMultibase": device.signing_key.public_key_multibase,
816 }),
817 json!({
818 "id": e2ee_kid,
819 "type": "X25519KeyAgreementKey2019",
820 "controller": record.did,
821 "publicKeyMultibase": device.e2ee_key.public_key_multibase,
822 }),
823 ]);
824 relationship_mut(object, "authentication")?
825 .push(Value::String(signing_kid.clone()));
826 relationship_mut(object, "assertionMethod")?
827 .push(Value::String(signing_kid.clone()));
828 relationship_mut(object, "keyAgreement")?.push(Value::String(e2ee_kid.clone()));
829 manifest
830 .get_or_insert_with(|| anp::authentication::DeviceManifest {
831 manifest_type: anp::authentication::DEVICE_MANIFEST_TYPE.to_string(),
832 devices: Vec::new(),
833 })
834 .devices
835 .push(anp::authentication::DeviceManifestEntry {
836 device_id: device.device_id.clone(),
837 signing_key_id: signing_kid.clone(),
838 e2ee_key_id: e2ee_kid.clone(),
839 profiles: device.profiles.clone(),
840 });
841 added_keys.extend([
842 (signing_kid, KeyRole::DeviceSigning),
843 (e2ee_kid, KeyRole::E2eeAgreement),
844 ]);
845 }
846 DeviceMutationSpec::Remove { device_id } => {
847 if device_id.trim().is_empty() || device_id.trim() != device_id {
848 return Err(DidError::InvalidExtension);
849 }
850 if record
851 .local_authorization
852 .as_ref()
853 .is_some_and(|local| local.device_id == *device_id)
854 {
855 return Err(DidError::UnsupportedOperation);
856 }
857 let current = manifest.as_mut().ok_or(DidError::KeyNotFound)?;
858 let index = current
859 .devices
860 .iter()
861 .position(|entry| entry.device_id == *device_id)
862 .ok_or(DidError::KeyNotFound)?;
863 let removed = current.devices.remove(index);
864 methods_mut(object)?.retain(|method| {
865 method.get("id").and_then(Value::as_str)
866 != Some(removed.signing_key_id.as_str())
867 && method.get("id").and_then(Value::as_str)
868 != Some(removed.e2ee_key_id.as_str())
869 });
870 for relationship in ["authentication", "assertionMethod"] {
871 relationship_mut(object, relationship)?
872 .retain(|entry| entry.as_str() != Some(removed.signing_key_id.as_str()));
873 }
874 relationship_mut(object, "keyAgreement")?
875 .retain(|entry| entry.as_str() != Some(removed.e2ee_key_id.as_str()));
876 retired_kids.extend([removed.signing_key_id, removed.e2ee_key_id]);
877 }
878 }
879 }
880 match manifest {
881 Some(manifest) if manifest.devices.is_empty() => {
882 object.remove("deviceManifest");
883 }
884 Some(manifest) => {
885 object.insert(
886 "deviceManifest".to_string(),
887 serde_json::to_value(manifest).map_err(|_| DidError::InvalidExtension)?,
888 );
889 }
890 None => {}
891 }
892 Ok(DeviceChanges {
893 added_keys,
894 retired_kids,
895 })
896}
897
898fn validate_device_add(device: &DeviceAddSpec) -> DidResult<()> {
899 if device.device_id.trim().is_empty()
900 || device.device_id.trim() != device.device_id
901 || device.profiles.is_empty()
902 || device
903 .profiles
904 .iter()
905 .any(|profile| profile.trim().is_empty())
906 {
907 return Err(DidError::InvalidExtension);
908 }
909 Ok(())
910}
911
912fn validate_device_public_key(kid: &str, multibase: &str, role: KeyRole) -> DidResult<()> {
913 let method = match role {
914 KeyRole::DeviceSigning => json!({
915 "id": kid,
916 "type": "Multikey",
917 "publicKeyMultibase": multibase,
918 }),
919 KeyRole::E2eeAgreement => json!({
920 "id": kid,
921 "type": "X25519KeyAgreementKey2019",
922 "publicKeyMultibase": multibase,
923 }),
924 _ => return Err(DidError::KeyRoleViolation),
925 };
926 let public =
927 anp::authentication::extract_public_key(&method).map_err(|_| DidError::InvalidPublicKey)?;
928 if matches!(
929 (role, public),
930 (KeyRole::DeviceSigning, anp::PublicKeyMaterial::Ed25519(_))
931 | (KeyRole::E2eeAgreement, anp::PublicKeyMaterial::X25519(_))
932 ) {
933 Ok(())
934 } else {
935 Err(DidError::InvalidPublicKey)
936 }
937}
938
939fn methods_mut(object: &mut serde_json::Map<String, Value>) -> DidResult<&mut Vec<Value>> {
940 object
941 .get_mut("verificationMethod")
942 .and_then(Value::as_array_mut)
943 .ok_or(DidError::InvalidIdentity)
944}
945
946fn relationship_mut<'a>(
947 object: &'a mut serde_json::Map<String, Value>,
948 name: &str,
949) -> DidResult<&'a mut Vec<Value>> {
950 object
951 .get_mut(name)
952 .and_then(Value::as_array_mut)
953 .ok_or(DidError::InvalidIdentity)
954}
955
956fn method_exists(object: &serde_json::Map<String, Value>, kid: &str) -> bool {
957 object
958 .get("verificationMethod")
959 .and_then(Value::as_array)
960 .is_some_and(|methods| {
961 methods
962 .iter()
963 .any(|method| method.get("id").and_then(Value::as_str) == Some(kid))
964 })
965}
966
967fn required_pending<'a>(
968 record: &'a IdentityRecord,
969 revision_id: &str,
970) -> DidResult<&'a PendingRevisionRecord> {
971 record
972 .pending_revision
973 .as_ref()
974 .filter(|pending| pending.revision_id == revision_id)
975 .ok_or(DidError::PendingRevisionNotFound)
976}
977
978fn reject_uncertain_mutation(record: &IdentityRecord) -> DidResult<()> {
979 if record
980 .pending_revision
981 .as_ref()
982 .is_some_and(|pending| pending.state == PublicationState::PublicationUncertain)
983 {
984 return Err(DidError::InvalidPublicationState);
985 }
986 Ok(())
987}
988
989fn required_pending_mut<'a>(
990 record: &'a mut IdentityRecord,
991 revision_id: &str,
992) -> DidResult<&'a mut PendingRevisionRecord> {
993 record
994 .pending_revision
995 .as_mut()
996 .filter(|pending| pending.revision_id == revision_id)
997 .ok_or(DidError::PendingRevisionNotFound)
998}
999
1000fn persist_record(
1001 runtime: &StoreRuntime,
1002 guard: &StoreWriteGuard,
1003 record: &mut IdentityRecord,
1004) -> DidResult<()> {
1005 record.generation = record.generation.checked_add(1).ok_or(DidError::Conflict)?;
1006 write_identity(runtime.root(), guard, record)
1007}
1008
1009fn canonical_digest(document: &Value) -> DidResult<String> {
1010 let canonical =
1011 serde_json_canonicalizer::to_vec(document).map_err(|_| DidError::InvalidIdentity)?;
1012 Ok(URL_SAFE_NO_PAD.encode(Sha256::digest(canonical)))
1013}
1014
1015fn random_revision_id() -> String {
1016 use rand::RngCore;
1017
1018 let mut bytes = [0_u8; 16];
1019 rand::rngs::OsRng.fill_bytes(&mut bytes);
1020 URL_SAFE_NO_PAD.encode(bytes)
1021}
1022
1023#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1024enum LifecycleFailurePoint {
1025 PrepareJournalPersisted,
1026 PendingSecretPersisted,
1027 PendingIdentityPersisted,
1028 CleanupJournalPersisted,
1029 PendingIdentityCleared,
1030 PendingSecretsDeleted,
1031}
1032
1033fn maybe_fail(
1034 configured: Option<LifecycleFailurePoint>,
1035 current: LifecycleFailurePoint,
1036) -> DidResult<()> {
1037 if configured == Some(current) {
1038 return Err(DidError::Io("injected lifecycle failure".to_string()));
1039 }
1040 Ok(())
1041}
1042
1043#[cfg(test)]
1044mod tests;