Skip to main content

anp_identity/
lifecycle.rs

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;