Skip to main content

anp_identity/
crypto_ops.rs

1use std::collections::BTreeMap;
2use std::fmt;
3
4use anp::authentication::{
5    complete_http_signature_headers, complete_legacy_did_wba_auth_header, find_verification_method,
6    prepare_http_signature_headers, prepare_legacy_did_wba_auth_header,
7};
8use anp::proof::{
9    complete_object_proof, complete_rfc9421_origin_proof, complete_w3c_proof, prepare_object_proof,
10    prepare_rfc9421_origin_proof, prepare_w3c_proof, ProofGenerationOptions, Rfc9421OriginProof,
11    Rfc9421OriginProofGenerationOptions,
12};
13use zeroize::{Zeroize, Zeroizing};
14
15use crate::secret::SecretBytes;
16use crate::{DidError, DidIdentity, DidResult, KeyMetadata, KeyRole, KeyState};
17
18pub struct SharedSecret {
19    bytes: Zeroizing<[u8; 32]>,
20}
21
22impl SharedSecret {
23    pub fn as_bytes(&self) -> &[u8; 32] {
24        &self.bytes
25    }
26}
27
28impl fmt::Debug for SharedSecret {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        formatter
31            .debug_struct("SharedSecret")
32            .field("value", &"[REDACTED]")
33            .finish()
34    }
35}
36
37impl DidIdentity {
38    pub fn sign_pending_root_object_proof(
39        &self,
40        kid: &str,
41        document: &serde_json::Value,
42        issuer_did: &str,
43        created: Option<String>,
44    ) -> DidResult<serde_json::Value> {
45        let current = crate::registry::read_identity(self.runtime().root(), self.identity_id())?;
46        if current.generation != self.record().generation {
47            return Err(DidError::Conflict);
48        }
49        if current.state != crate::IdentityState::Active
50            || current.root_capability != crate::RootCapabilityState::Pending
51            || current.pending_root_transfer.is_none()
52        {
53            return Err(DidError::RootCapabilityUnavailable);
54        }
55        let kid = crate::input::canonicalize_kid(&current.did, kid)?;
56        let metadata = current
57            .keys
58            .iter()
59            .find(|metadata| metadata.kid == kid)
60            .ok_or(DidError::KeyNotFound)?;
61        if metadata.role != KeyRole::RootControl
62            || metadata.origin != crate::KeyOrigin::Managed
63            || metadata.state != KeyState::Pending
64            || metadata.material_erased
65        {
66            return Err(DidError::RootCapabilityUnavailable);
67        }
68        let method = find_verification_method(self.document(), &metadata.kid)
69            .ok_or(DidError::InvalidIdentity)?;
70        let public = anp::authentication::extract_public_key(&method)
71            .map_err(|_| DidError::InvalidPublicKey)?;
72        let prepared = prepare_object_proof(document, &public, &metadata.kid, issuer_did, created)
73            .map_err(|_| DidError::Crypto)?;
74        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
75        complete_object_proof(prepared, &signature).map_err(|_| DidError::Crypto)
76    }
77
78    pub fn sign_pending_enrollment(
79        &self,
80        enrollment_id: &str,
81        kid: &str,
82        message: &[u8],
83    ) -> DidResult<Vec<u8>> {
84        let record = self.current_pending_enrollment(enrollment_id)?;
85        let local = record
86            .local_authorization
87            .as_ref()
88            .ok_or(DidError::InvalidIdentity)?;
89        let kid = crate::input::canonicalize_kid(&record.did, kid)?;
90        if kid != local.signing_kid {
91            return Err(DidError::KeyRoleViolation);
92        }
93        let metadata = record
94            .keys
95            .iter()
96            .find(|metadata| metadata.kid == kid)
97            .ok_or(DidError::KeyNotFound)?;
98        if metadata.role != KeyRole::DeviceSigning
99            || metadata.origin != crate::KeyOrigin::Managed
100            || metadata.state != KeyState::Pending
101            || metadata.material_erased
102        {
103            return Err(DidError::KeyNotUsable);
104        }
105        self.sign_prepared(metadata, message)
106    }
107
108    pub fn ecdh_pending_enrollment(
109        &self,
110        enrollment_id: &str,
111        kid: &str,
112        peer_public: &[u8],
113    ) -> DidResult<SharedSecret> {
114        let record = self.current_pending_enrollment(enrollment_id)?;
115        let local = record
116            .local_authorization
117            .as_ref()
118            .ok_or(DidError::InvalidIdentity)?;
119        let kid = crate::input::canonicalize_kid(&record.did, kid)?;
120        if kid != local.e2ee_kid {
121            return Err(DidError::KeyRoleViolation);
122        }
123        let metadata = record
124            .keys
125            .iter()
126            .find(|metadata| metadata.kid == kid)
127            .ok_or(DidError::KeyNotFound)?;
128        if metadata.role != KeyRole::E2eeAgreement
129            || metadata.origin != crate::KeyOrigin::Managed
130            || metadata.state != KeyState::Pending
131            || metadata.material_erased
132        {
133            return Err(DidError::KeyNotUsable);
134        }
135        let peer: [u8; 32] = peer_public
136            .try_into()
137            .map_err(|_| DidError::InvalidPeerKey)?;
138        let secret = self.load_managed_secret(metadata)?;
139        let private = x25519_private_key(&secret)?;
140        let shared = private.diffie_hellman(&x25519_dalek::PublicKey::from(peer));
141        let bytes = Zeroizing::new(shared.to_bytes());
142        if bytes.iter().all(|byte| *byte == 0) {
143            return Err(DidError::InvalidPeerKey);
144        }
145        Ok(SharedSecret { bytes })
146    }
147
148    pub fn sign(&self, kid: &str, message: &[u8]) -> DidResult<Vec<u8>> {
149        self.require_operational()?;
150        let metadata = self.managed_key_metadata(kid)?;
151        require_active(metadata)?;
152        if !matches!(
153            metadata.role,
154            KeyRole::DeviceSigning | KeyRole::RequestSigning | KeyRole::E2eeSigning
155        ) {
156            return Err(DidError::KeyRoleViolation);
157        }
158        let secret = self.load_managed_secret(metadata)?;
159        let signing_key = ed25519_signing_key(&secret)?;
160        use ed25519_dalek::Signer;
161        Ok(signing_key.sign(message).to_bytes().to_vec())
162    }
163
164    pub fn sign_device_assertion(&self, kid: &str, message: &[u8]) -> DidResult<Vec<u8>> {
165        self.require_operational()?;
166        let metadata = self.managed_key_metadata(kid)?;
167        require_active(metadata)?;
168        if metadata.role != KeyRole::DeviceSigning {
169            return Err(DidError::KeyRoleViolation);
170        }
171        self.sign_prepared(metadata, message)
172    }
173
174    pub fn sign_object_proof(
175        &self,
176        kid: &str,
177        document: &serde_json::Value,
178        issuer_did: &str,
179        created: Option<String>,
180    ) -> DidResult<serde_json::Value> {
181        let metadata = self.device_signing_metadata(kid)?;
182        let method = find_verification_method(self.document(), &metadata.kid)
183            .ok_or(DidError::InvalidIdentity)?;
184        let public = anp::authentication::extract_public_key(&method)
185            .map_err(|_| DidError::InvalidPublicKey)?;
186        let prepared = prepare_object_proof(document, &public, &metadata.kid, issuer_did, created)
187            .map_err(|_| DidError::Crypto)?;
188        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
189        complete_object_proof(prepared, &signature).map_err(|_| DidError::Crypto)
190    }
191
192    pub fn sign_document_proof(
193        &self,
194        document: &serde_json::Value,
195        verification_method: &str,
196        options: ProofGenerationOptions,
197    ) -> DidResult<serde_json::Value> {
198        self.require_operational()?;
199        if self.root_capability() != crate::RootCapabilityState::Active {
200            return Err(DidError::RootCapabilityUnavailable);
201        }
202        let metadata = self.managed_key_metadata(verification_method)?;
203        require_active(metadata)?;
204        if metadata.role != KeyRole::RootControl {
205            return Err(DidError::KeyRoleViolation);
206        }
207        let method = find_verification_method(self.document(), &metadata.kid)
208            .ok_or(DidError::InvalidIdentity)?;
209        let public = anp::authentication::extract_public_key(&method)
210            .map_err(|_| DidError::InvalidPublicKey)?;
211        let prepared = prepare_w3c_proof(document, &public, &metadata.kid, options)
212            .map_err(|_| DidError::Crypto)?;
213        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
214        complete_w3c_proof(prepared, &signature).map_err(|_| DidError::Crypto)
215    }
216
217    pub fn sign_origin_proof(
218        &self,
219        method: &str,
220        meta: &serde_json::Value,
221        body: &serde_json::Value,
222        kid: &str,
223        options: Rfc9421OriginProofGenerationOptions,
224    ) -> DidResult<Rfc9421OriginProof> {
225        let metadata = self.request_signing_metadata(kid)?;
226        let verification_method = find_verification_method(self.document(), &metadata.kid)
227            .ok_or(DidError::InvalidIdentity)?;
228        let public = anp::authentication::extract_public_key(&verification_method)
229            .map_err(|_| DidError::InvalidPublicKey)?;
230        let prepared =
231            prepare_rfc9421_origin_proof(method, meta, body, &public, &metadata.kid, options)
232                .map_err(|_| DidError::Crypto)?;
233        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
234        complete_rfc9421_origin_proof(prepared, &signature).map_err(|_| DidError::Crypto)
235    }
236
237    pub fn verify(&self, kid: &str, message: &[u8], signature: &[u8]) -> DidResult<()> {
238        let metadata = self.key_metadata(kid)?;
239        if metadata.state == KeyState::Revoked {
240            return Err(DidError::KeyNotUsable);
241        }
242        if metadata.role == KeyRole::E2eeAgreement {
243            return Err(DidError::KeyRoleViolation);
244        }
245        let method = find_verification_method(self.document(), &metadata.kid)
246            .ok_or(DidError::InvalidIdentity)?;
247        let public_key = anp::authentication::extract_public_key(&method)
248            .map_err(|_| DidError::InvalidPublicKey)?;
249        public_key
250            .verify_message(message, signature)
251            .map_err(|_| DidError::VerificationFailed)
252    }
253
254    pub fn ecdh(&self, kid: &str, peer_public: &[u8]) -> DidResult<SharedSecret> {
255        self.require_operational()?;
256        let metadata = self.managed_key_metadata(kid)?;
257        require_active(metadata)?;
258        if metadata.role != KeyRole::E2eeAgreement {
259            return Err(DidError::KeyRoleViolation);
260        }
261        let peer: [u8; 32] = peer_public
262            .try_into()
263            .map_err(|_| DidError::InvalidPeerKey)?;
264        let secret = self.load_managed_secret(metadata)?;
265        let private = x25519_private_key(&secret)?;
266        let shared = private.diffie_hellman(&x25519_dalek::PublicKey::from(peer));
267        let bytes = Zeroizing::new(shared.to_bytes());
268        if bytes.iter().all(|byte| *byte == 0) {
269            return Err(DidError::InvalidPeerKey);
270        }
271        Ok(SharedSecret { bytes })
272    }
273
274    pub fn legacy_did_wba_header(
275        &self,
276        kid: &str,
277        service_domain: &str,
278        version: &str,
279    ) -> DidResult<String> {
280        let metadata = self.request_signing_metadata(kid)?;
281        let prepared = prepare_legacy_did_wba_auth_header(
282            self.document(),
283            service_domain,
284            version,
285            &metadata.kid,
286        )
287        .map_err(|_| DidError::InvalidIdentity)?;
288        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
289        complete_legacy_did_wba_auth_header(prepared, &signature).map_err(|_| DidError::Crypto)
290    }
291
292    pub fn http_signature_headers(
293        &self,
294        kid: &str,
295        request_url: &str,
296        request_method: &str,
297        headers: Option<&BTreeMap<String, String>>,
298        body: Option<&[u8]>,
299    ) -> DidResult<BTreeMap<String, String>> {
300        self.http_signature_headers_with_options(
301            kid,
302            request_url,
303            request_method,
304            headers,
305            body,
306            crate::HttpSignatureOptions::default(),
307        )
308    }
309
310    pub fn http_signature_headers_with_options(
311        &self,
312        kid: &str,
313        request_url: &str,
314        request_method: &str,
315        headers: Option<&BTreeMap<String, String>>,
316        body: Option<&[u8]>,
317        mut options: crate::HttpSignatureOptions,
318    ) -> DidResult<BTreeMap<String, String>> {
319        let metadata = self.request_signing_metadata(kid)?;
320        options.keyid = Some(metadata.kid.clone());
321        let prepared = prepare_http_signature_headers(
322            self.document(),
323            request_url,
324            request_method,
325            headers,
326            body,
327            options,
328        )
329        .map_err(|_| DidError::InvalidIdentity)?;
330        let signature = self.sign_prepared(metadata, prepared.signing_input())?;
331        complete_http_signature_headers(prepared, &signature).map_err(|_| DidError::Crypto)
332    }
333
334    fn request_signing_metadata(&self, kid: &str) -> DidResult<&KeyMetadata> {
335        self.require_operational()?;
336        let metadata = self.managed_key_metadata(kid)?;
337        require_active(metadata)?;
338        if !matches!(
339            metadata.role,
340            KeyRole::DeviceSigning | KeyRole::RequestSigning
341        ) {
342            return Err(DidError::KeyRoleViolation);
343        }
344        Ok(metadata)
345    }
346
347    fn device_signing_metadata(&self, kid: &str) -> DidResult<&KeyMetadata> {
348        self.require_operational()?;
349        let metadata = self.managed_key_metadata(kid)?;
350        require_active(metadata)?;
351        if metadata.role != KeyRole::DeviceSigning {
352            return Err(DidError::KeyRoleViolation);
353        }
354        Ok(metadata)
355    }
356
357    fn sign_prepared(&self, metadata: &KeyMetadata, message: &[u8]) -> DidResult<Vec<u8>> {
358        let secret = self.load_managed_secret(metadata)?;
359        let signing_key = ed25519_signing_key(&secret)?;
360        use ed25519_dalek::Signer;
361        Ok(signing_key.sign(message).to_bytes().to_vec())
362    }
363
364    fn require_operational(&self) -> DidResult<()> {
365        let current = crate::registry::read_identity(self.runtime().root(), self.identity_id())?;
366        if current.generation != self.record().generation {
367            return Err(DidError::Conflict);
368        }
369        if current.state != crate::IdentityState::Active {
370            return Err(DidError::KeyNotUsable);
371        }
372        Ok(())
373    }
374
375    fn current_pending_enrollment(
376        &self,
377        enrollment_id: &str,
378    ) -> DidResult<crate::registry::IdentityRecord> {
379        let current = crate::registry::read_identity(self.runtime().root(), self.identity_id())?;
380        if current.generation != self.record().generation {
381            return Err(DidError::Conflict);
382        }
383        if current.state != crate::IdentityState::Enrolling
384            || current
385                .pending_enrollment
386                .as_ref()
387                .is_none_or(|pending| pending.enrollment_id != enrollment_id)
388        {
389            return Err(DidError::KeyNotUsable);
390        }
391        Ok(current)
392    }
393}
394
395fn require_active(metadata: &KeyMetadata) -> DidResult<()> {
396    if metadata.state != KeyState::Active {
397        return Err(DidError::KeyNotUsable);
398    }
399    Ok(())
400}
401
402fn ed25519_signing_key(secret: &SecretBytes) -> DidResult<ed25519_dalek::SigningKey> {
403    if secret.expose().len() != 32 {
404        return Err(DidError::InvalidIdentity);
405    }
406    let mut bytes = Zeroizing::new([0_u8; 32]);
407    bytes.copy_from_slice(secret.expose());
408    Ok(ed25519_dalek::SigningKey::from_bytes(&bytes))
409}
410
411fn x25519_private_key(secret: &SecretBytes) -> DidResult<x25519_dalek::StaticSecret> {
412    if secret.expose().len() != 32 {
413        return Err(DidError::InvalidIdentity);
414    }
415    let mut bytes = [0_u8; 32];
416    bytes.copy_from_slice(secret.expose());
417    let private = x25519_dalek::StaticSecret::from(bytes);
418    bytes.zeroize();
419    Ok(private)
420}
421
422#[cfg(test)]
423mod tests {
424    use anp::authentication::{verify_auth_header_signature, verify_http_message_signature};
425
426    use super::*;
427    use crate::{Capabilities, DidCreateSpec, DidProfile, DidStore, ManagedKeySpec};
428
429    #[test]
430    fn crypto_ops_enforce_roles_and_interoperate_with_anp_verifiers() {
431        let alice_root = tempfile::tempdir().unwrap();
432        let bob_root = tempfile::tempdir().unwrap();
433        let mut alice_store =
434            DidStore::initialize_injected(alice_root.path(), "host", [21_u8; 32]).unwrap();
435        let mut bob_store =
436            DidStore::initialize_injected(bob_root.path(), "host", [22_u8; 32]).unwrap();
437        let alice = alice_store.create_identity(spec("alice")).unwrap();
438        let bob = bob_store.create_identity(spec("bob")).unwrap();
439        let message = b"message-to-sign";
440
441        let signature = alice.sign("#request", message).unwrap();
442        alice.verify("#request", message, &signature).unwrap();
443        let device_signature = alice.sign_device_assertion("#device", message).unwrap();
444        alice.verify("#device", message, &device_signature).unwrap();
445        assert_eq!(
446            alice.sign_device_assertion("#request", message),
447            Err(DidError::KeyRoleViolation)
448        );
449        assert_eq!(
450            alice.sign_device_assertion("#e2ee-signing", message),
451            Err(DidError::KeyRoleViolation)
452        );
453        let object = serde_json::json!({"operation": "device.assertion"});
454        let signed_object = alice
455            .sign_object_proof("#device", &object, alice.did(), None)
456            .unwrap();
457        anp::proof::verify_object_proof(&signed_object, alice.did(), alice.document()).unwrap();
458        assert_eq!(
459            alice.sign_object_proof("#request", &object, alice.did(), None),
460            Err(DidError::KeyRoleViolation)
461        );
462        let mut unsigned_document = alice.document().clone();
463        unsigned_document.as_object_mut().unwrap().remove("proof");
464        let signed_document = alice
465            .sign_document_proof(
466                &unsigned_document,
467                &format!("{}#root", alice.did()),
468                ProofGenerationOptions {
469                    proof_purpose: Some("assertionMethod".to_string()),
470                    proof_type: Some(anp::proof::PROOF_TYPE_DATA_INTEGRITY.to_string()),
471                    cryptosuite: Some(anp::proof::CRYPTOSUITE_EDDSA_JCS_2022.to_string()),
472                    domain: Some("example.com".to_string()),
473                    ..ProofGenerationOptions::default()
474                },
475            )
476            .unwrap();
477        assert!(anp::authentication::validate_did_document_binding(
478            &signed_document,
479            true
480        ));
481        assert_eq!(
482            alice.verify("#request", b"tampered", &signature),
483            Err(DidError::VerificationFailed)
484        );
485        assert_eq!(
486            alice.sign("#root", message),
487            Err(DidError::KeyRoleViolation)
488        );
489        assert_eq!(
490            alice.sign("#agreement", message),
491            Err(DidError::KeyRoleViolation)
492        );
493        assert_eq!(
494            alice.sign(&format!("{}#request", bob.did()), message),
495            Err(DidError::ForeignKid)
496        );
497
498        let alice_agreement = public_x25519(&alice, "#agreement");
499        let bob_agreement = public_x25519(&bob, "#agreement");
500        assert_eq!(
501            alice.public_key_bytes("#agreement").unwrap(),
502            alice_agreement
503        );
504        assert_eq!(
505            alice.public_key_bytes("#missing"),
506            Err(DidError::KeyNotFound)
507        );
508        let alice_shared = alice.ecdh("#agreement", &bob_agreement).unwrap();
509        let bob_shared = bob.ecdh("#agreement", &alice_agreement).unwrap();
510        assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
511        assert_eq!(
512            alice.ecdh("#agreement", &[0_u8; 31]).err(),
513            Some(DidError::InvalidPeerKey)
514        );
515        assert_eq!(
516            alice.ecdh("#agreement", &[0_u8; 32]).err(),
517            Some(DidError::InvalidPeerKey)
518        );
519        let mut low_order = [0_u8; 32];
520        low_order[0] = 1;
521        assert_eq!(
522            alice.ecdh("#agreement", &low_order).err(),
523            Some(DidError::InvalidPeerKey)
524        );
525
526        let legacy = alice
527            .legacy_did_wba_header("#request", "api.example.com", "1.1")
528            .unwrap();
529        verify_auth_header_signature(&legacy, alice.document(), "api.example.com").unwrap();
530        let http = alice
531            .http_signature_headers(
532                "#request",
533                "https://api.example.com/messages",
534                "POST",
535                None,
536                Some(br#"{"message":"hello"}"#),
537            )
538            .unwrap();
539        verify_http_message_signature(
540            alice.document(),
541            "POST",
542            "https://api.example.com/messages",
543            &http,
544            Some(br#"{"message":"hello"}"#),
545        )
546        .unwrap();
547        let challenged = alice
548            .http_signature_headers_with_options(
549                "#device",
550                "https://api.example.com/messages",
551                "POST",
552                None,
553                Some(br#"{"message":"hello"}"#),
554                crate::HttpSignatureOptions {
555                    keyid: Some("did:example:ignored#foreign".to_string()),
556                    nonce: Some("challenge-123".to_string()),
557                    created: Some(1_700_000_000),
558                    expires: Some(1_700_000_300),
559                    covered_components: Some(vec![
560                        "@method".to_string(),
561                        "@target-uri".to_string(),
562                        "content-digest".to_string(),
563                    ]),
564                },
565            )
566            .unwrap();
567        let signature_input = challenged.get("Signature-Input").unwrap();
568        assert!(signature_input.contains("nonce=\"challenge-123\""));
569        assert!(signature_input.contains("created=1700000000"));
570        assert!(signature_input.contains("expires=1700000300"));
571        assert!(signature_input.contains(&format!("keyid=\"{}#device\"", alice.did())));
572        verify_http_message_signature(
573            alice.document(),
574            "POST",
575            "https://api.example.com/messages",
576            &challenged,
577            Some(br#"{"message":"hello"}"#),
578        )
579        .unwrap();
580    }
581
582    fn spec(name: &str) -> DidCreateSpec {
583        DidCreateSpec {
584            profile: DidProfile::E1,
585            domain: "example.com".to_string(),
586            port: None,
587            path_segments: vec!["agents".to_string(), name.to_string()],
588            capabilities: Capabilities { did_wba: true },
589            managed_keys: vec![
590                managed("root", KeyRole::RootControl),
591                managed("device", KeyRole::DeviceSigning),
592                managed("request", KeyRole::RequestSigning),
593                managed("e2ee-signing", KeyRole::E2eeSigning),
594                managed("agreement", KeyRole::E2eeAgreement),
595            ],
596            external_keys: Vec::new(),
597            services: Vec::new(),
598            agent_description_url: None,
599            extensions: Vec::new(),
600        }
601    }
602
603    fn managed(fragment: &str, role: KeyRole) -> ManagedKeySpec {
604        ManagedKeySpec {
605            fragment: fragment.to_string(),
606            role,
607        }
608    }
609
610    fn public_x25519(identity: &DidIdentity, kid: &str) -> [u8; 32] {
611        let kid = identity.key_metadata(kid).unwrap().kid.clone();
612        let method = find_verification_method(identity.document(), &kid).unwrap();
613        match anp::authentication::extract_public_key(&method).unwrap() {
614            anp::PublicKeyMaterial::X25519(bytes) => bytes,
615            _ => panic!("expected X25519 public key"),
616        }
617    }
618}