Skip to main content

tap_agent/
local_agent_key.rs

1//! Local Agent Key implementation for the TAP Agent
2//!
3//! This module provides a concrete implementation of the AgentKey trait
4//! for keys that are stored locally, either in memory or on disk.
5
6use crate::agent_key::{
7    AgentKey, DecryptionKey, EncryptionKey, JweAlgorithm, JweEncryption, JwsAlgorithm, SigningKey,
8    VerificationKey,
9};
10use crate::did::{KeyType, VerificationMaterial};
11use crate::error::{Error, Result};
12use crate::key_manager::{Secret, SecretMaterial};
13use crate::message::{EphemeralPublicKey, Jwe, JweProtected, Jws, JwsProtected, JwsSignature};
14#[cfg(feature = "crypto-p256")]
15use crate::message::{JweHeader, JweRecipient};
16use aes_gcm::{AeadInPlace, Aes256Gcm, KeyInit, Nonce};
17use async_trait::async_trait;
18use base64::Engine;
19#[cfg(feature = "crypto-ed25519")]
20use ed25519_dalek::{Signer as Ed25519Signer, Verifier, VerifyingKey};
21#[cfg(feature = "crypto-secp256k1")]
22use k256::{ecdsa::Signature as Secp256k1Signature, ecdsa::SigningKey as Secp256k1SigningKey};
23#[cfg(feature = "crypto-p256")]
24use p256::ecdh::EphemeralSecret as P256EphemeralSecret;
25#[cfg(feature = "crypto-p256")]
26use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
27#[cfg(feature = "crypto-p256")]
28use p256::EncodedPoint as P256EncodedPoint;
29#[cfg(feature = "crypto-p256")]
30use p256::PublicKey as P256PublicKey;
31#[cfg(feature = "crypto-p256")]
32use p256::{ecdsa::Signature as P256Signature, ecdsa::SigningKey as P256SigningKey};
33#[cfg(any(
34    feature = "crypto-ed25519",
35    feature = "crypto-p256",
36    feature = "crypto-secp256k1"
37))]
38use rand::{rngs::OsRng, RngCore};
39use serde_json::Value;
40use std::convert::TryFrom;
41use std::sync::Arc;
42use uuid::Uuid;
43
44/// A local implementation of the AgentKey that stores the key material directly
45#[derive(Debug, Clone)]
46pub struct LocalAgentKey {
47    /// The key's ID
48    kid: String,
49    /// The associated DID
50    did: String,
51    /// The secret containing the key material
52    pub secret: Secret,
53    /// The key type
54    key_type: KeyType,
55}
56
57impl LocalAgentKey {
58    /// Verify a JWS against this key
59    pub async fn verify_jws(&self, jws: &crate::message::Jws) -> Result<Vec<u8>> {
60        // Find the signature that matches our key ID
61        let our_kid = crate::agent_key::AgentKey::key_id(self).to_string();
62        let signature = jws
63            .signatures
64            .iter()
65            .find(|s| s.get_kid() == Some(our_kid.clone()))
66            .ok_or_else(|| {
67                Error::Cryptography(format!("No signature found with kid: {}", our_kid))
68            })?;
69
70        // Parse the protected header to get the algorithm
71        let protected = signature.get_protected_header().map_err(|e| {
72            Error::Cryptography(format!("Failed to decode protected header: {}", e))
73        })?;
74
75        // Decode the signature (accept both base64 and base64url)
76        let signature_bytes = crate::message::base64_decode_flexible(&signature.signature)
77            .map_err(|e| Error::Cryptography(format!("Failed to decode signature: {}", e)))?;
78
79        // Construct the signing input: {protected}.{payload}
80        let signing_input = format!("{}.{}", signature.protected, jws.payload);
81
82        // Verify the signature using the actual cryptographic verification
83        let verified = self
84            .verify_signature(signing_input.as_bytes(), &signature_bytes, &protected)
85            .await?;
86
87        if !verified {
88            return Err(Error::Cryptography(
89                "Signature verification failed".to_string(),
90            ));
91        }
92
93        // Decode and return the payload (accept both base64 and base64url)
94        let payload_bytes = crate::message::base64_decode_flexible(&jws.payload)
95            .map_err(|e| Error::Cryptography(format!("Failed to decode payload: {}", e)))?;
96
97        Ok(payload_bytes)
98    }
99
100    /// Unwrap a JWE to retrieve the plaintext using proper ECDH-ES+A256KW decryption
101    pub async fn decrypt_jwe(&self, jwe: &crate::message::Jwe) -> Result<Vec<u8>> {
102        // Use the proper JWE unwrapping implementation from DecryptionKey trait
103        DecryptionKey::unwrap_jwe(self, jwe).await
104    }
105
106    /// Verify a signature against this key
107    pub async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> {
108        // Create a protected header with the appropriate algorithm
109        let protected = JwsProtected {
110            typ: "JWT".to_string(),
111            alg: self.recommended_jws_alg().as_str().to_string(),
112            kid: crate::agent_key::AgentKey::key_id(self).to_string(),
113        };
114
115        // Verify the signature
116        let result = self
117            .verify_signature(payload, signature, &protected)
118            .await?;
119        if result {
120            Ok(())
121        } else {
122            Err(Error::Cryptography(
123                "Signature verification failed".to_string(),
124            ))
125        }
126    }
127
128    /// Encrypt data to a JWK recipient using proper ECDH-ES+A256KW
129    pub async fn encrypt_to_jwk(
130        &self,
131        plaintext: &[u8],
132        recipient_jwk: &Value,
133        protected_header: Option<JweProtected>,
134    ) -> Result<Jwe> {
135        #[cfg(feature = "crypto-p256")]
136        {
137            // 1. Generate random CEK (Content Encryption Key)
138            let mut cek = [0u8; 32];
139            OsRng.fill_bytes(&mut cek);
140
141            // 2. Generate random IV for AES-GCM
142            let mut iv_bytes = [0u8; 12];
143            OsRng.fill_bytes(&mut iv_bytes);
144
145            // 3. Generate ephemeral key pair for ECDH
146            let ephemeral_secret = P256EphemeralSecret::random(&mut OsRng);
147            let ephemeral_public_key = ephemeral_secret.public_key();
148
149            // Convert ephemeral public key to JWK coordinates
150            let point = ephemeral_public_key.to_encoded_point(false);
151            let x_bytes = point.x().unwrap().to_vec();
152            let y_bytes = point.y().unwrap().to_vec();
153            let x_b64 = base64::engine::general_purpose::STANDARD.encode(&x_bytes);
154            let y_b64 = base64::engine::general_purpose::STANDARD.encode(&y_bytes);
155
156            let ephemeral_key = crate::message::EphemeralPublicKey::Ec {
157                crv: "P-256".to_string(),
158                x: x_b64,
159                y: y_b64,
160            };
161
162            // 4. Extract recipient public key from JWK
163            let recipient_x_b64 =
164                recipient_jwk
165                    .get("x")
166                    .and_then(|v| v.as_str())
167                    .ok_or_else(|| {
168                        Error::Cryptography("Missing x coordinate in recipient JWK".to_string())
169                    })?;
170            let recipient_y_b64 =
171                recipient_jwk
172                    .get("y")
173                    .and_then(|v| v.as_str())
174                    .ok_or_else(|| {
175                        Error::Cryptography("Missing y coordinate in recipient JWK".to_string())
176                    })?;
177
178            let recipient_x = base64::engine::general_purpose::STANDARD
179                .decode(recipient_x_b64)
180                .map_err(|e| Error::Cryptography(format!("Failed to decode x: {}", e)))?;
181            let recipient_y = base64::engine::general_purpose::STANDARD
182                .decode(recipient_y_b64)
183                .map_err(|e| Error::Cryptography(format!("Failed to decode y: {}", e)))?;
184
185            // Reconstruct recipient public key
186            let mut recipient_point_bytes = vec![0x04]; // Uncompressed
187            recipient_point_bytes.extend_from_slice(&recipient_x);
188            recipient_point_bytes.extend_from_slice(&recipient_y);
189
190            let recipient_encoded_point = P256EncodedPoint::from_bytes(&recipient_point_bytes)
191                .map_err(|e| Error::Cryptography(format!("Invalid recipient point: {}", e)))?;
192
193            let recipient_pk = P256PublicKey::from_encoded_point(&recipient_encoded_point);
194            if recipient_pk.is_none().into() {
195                return Err(Error::Cryptography(
196                    "Invalid recipient public key".to_string(),
197                ));
198            }
199            let recipient_pk = recipient_pk.unwrap();
200
201            // 5. Perform ECDH
202            let shared_secret = ephemeral_secret.diffie_hellman(&recipient_pk);
203            let shared_bytes = shared_secret.raw_secret_bytes();
204
205            // 6. Create protected header
206            let apv_raw = Uuid::new_v4();
207            let apv_b64 = base64::engine::general_purpose::STANDARD.encode(apv_raw.as_bytes());
208
209            let protected = protected_header.unwrap_or_else(|| crate::message::JweProtected {
210                epk: ephemeral_key,
211                apv: apv_b64.clone(),
212                apu: String::new(),
213                typ: crate::message::DIDCOMM_ENCRYPTED.to_string(),
214                enc: "A256GCM".to_string(),
215                alg: "ECDH-ES+A256KW".to_string(),
216            });
217
218            // 7. Derive KEK using Concat KDF
219            let apv_bytes = base64::engine::general_purpose::STANDARD
220                .decode(&protected.apv)
221                .unwrap_or_default();
222            let kek =
223                crate::crypto::derive_key_ecdh_es(shared_bytes.as_slice(), b"", &apv_bytes, 256)?;
224
225            // 8. Wrap CEK with AES-KW
226            let mut kek_array = [0u8; 32];
227            kek_array.copy_from_slice(&kek);
228            let wrapped_cek = crate::crypto::wrap_key_aes_kw(&kek_array, &cek)?;
229
230            // 9. Encrypt plaintext with AES-GCM
231            let cipher = Aes256Gcm::new_from_slice(&cek)
232                .map_err(|e| Error::Cryptography(format!("Failed to create cipher: {}", e)))?;
233
234            let nonce = Nonce::from_slice(&iv_bytes);
235            let mut buffer = plaintext.to_vec();
236            let tag = cipher
237                .encrypt_in_place_detached(nonce, b"", &mut buffer)
238                .map_err(|e| Error::Cryptography(format!("Encryption failed: {:?}", e)))?;
239
240            // 10. Serialize protected header
241            let protected_json = serde_json::to_string(&protected).map_err(|e| {
242                Error::Serialization(format!("Failed to serialize protected header: {}", e))
243            })?;
244            let protected_b64 = base64::engine::general_purpose::STANDARD.encode(protected_json);
245
246            // Get recipient kid from JWK if available
247            let recipient_kid = recipient_jwk
248                .get("kid")
249                .and_then(|v| v.as_str())
250                .unwrap_or("recipient-key")
251                .to_string();
252
253            // 11. Build JWE
254            let jwe = crate::message::Jwe {
255                ciphertext: base64::engine::general_purpose::STANDARD.encode(&buffer),
256                protected: protected_b64,
257                recipients: vec![crate::message::JweRecipient {
258                    encrypted_key: base64::engine::general_purpose::STANDARD.encode(&wrapped_cek),
259                    header: crate::message::JweHeader {
260                        kid: recipient_kid,
261                        sender_kid: Some(AgentKey::key_id(self).to_string()),
262                    },
263                }],
264                tag: base64::engine::general_purpose::STANDARD.encode(tag),
265                iv: base64::engine::general_purpose::STANDARD.encode(iv_bytes),
266            };
267
268            Ok(jwe)
269        }
270
271        #[cfg(not(feature = "crypto-p256"))]
272        {
273            let _ = (plaintext, recipient_jwk, protected_header);
274            Err(Error::Cryptography(
275                "P-256 encryption not available - enable crypto-p256 feature".to_string(),
276            ))
277        }
278    }
279
280    /// Create a new LocalAgentKey from a Secret and key type
281    pub fn new(secret: Secret, key_type: KeyType) -> Self {
282        let did = secret.id.clone();
283        let kid = match &secret.secret_material {
284            SecretMaterial::JWK { private_key_jwk } => {
285                if let Some(kid) = private_key_jwk.get("kid").and_then(|k| k.as_str()) {
286                    kid.to_string()
287                } else {
288                    // Generate default key ID based on DID method
289                    if let Some(key_part) = did.strip_prefix("did:key:") {
290                        // For did:key, extract the multibase key from the DID and use it as fragment
291                        // did:key:z6Mk... -> did:key:z6Mk...#z6Mk...
292                        format!("{}#{}", did, key_part)
293                    } else if did.starts_with("did:web:") {
294                        format!("{}#keys-1", did)
295                    } else {
296                        format!("{}#key-1", did)
297                    }
298                }
299            }
300        };
301
302        Self {
303            kid,
304            did,
305            secret,
306            key_type,
307        }
308    }
309
310    /// Generate a new Ed25519 key with the given key ID
311    #[cfg(feature = "crypto-ed25519")]
312    pub fn generate_ed25519(_kid: &str) -> Result<Self> {
313        // Generate a new Ed25519 keypair
314        let mut csprng = OsRng;
315        let signing_key = ed25519_dalek::SigningKey::generate(&mut csprng);
316        let verifying_key = VerifyingKey::from(&signing_key);
317
318        // Extract public and private keys
319        let public_key = verifying_key.to_bytes();
320        let private_key = signing_key.to_bytes();
321
322        // Create did:key identifier
323        // Multicodec prefix for Ed25519: 0xed01
324        let mut prefixed_key = vec![0xed, 0x01];
325        prefixed_key.extend_from_slice(&public_key);
326
327        // Encode the key with multibase (base58btc with 'z' prefix)
328        let multibase_encoded = multibase::encode(multibase::Base::Base58Btc, &prefixed_key);
329        let did = format!("did:key:{}", multibase_encoded);
330
331        // Generate the proper verification method ID (did:key:z...#z...)
332        let kid = format!("{}#{}", did, multibase_encoded);
333
334        // Create the secret
335        let secret = Secret {
336            id: did.clone(),
337            type_: crate::key_manager::SecretType::JsonWebKey2020,
338            secret_material: SecretMaterial::JWK {
339                private_key_jwk: serde_json::json!({
340                    "kty": "OKP",
341                    "kid": kid.clone(),
342                    "crv": "Ed25519",
343                    "x": base64::engine::general_purpose::STANDARD.encode(public_key),
344                    "d": base64::engine::general_purpose::STANDARD.encode(private_key)
345                }),
346            },
347        };
348
349        Ok(Self {
350            kid,
351            did,
352            secret,
353            key_type: KeyType::Ed25519,
354        })
355    }
356
357    /// Generate a new P-256 key with the given key ID
358    #[cfg(feature = "crypto-p256")]
359    pub fn generate_p256(_kid: &str) -> Result<Self> {
360        // Generate a new P-256 keypair
361        let mut rng = OsRng;
362        let signing_key = p256::ecdsa::SigningKey::random(&mut rng);
363
364        // Extract public and private keys
365        let private_key = signing_key.to_bytes().to_vec();
366        let public_key = signing_key
367            .verifying_key()
368            .to_encoded_point(false)
369            .to_bytes();
370
371        // Create did:key identifier
372        // Multicodec prefix for P-256: 0x1200
373        let mut prefixed_key = vec![0x12, 0x00];
374        prefixed_key.extend_from_slice(&public_key);
375
376        // Encode the key with multibase (base58btc with 'z' prefix)
377        let multibase_encoded = multibase::encode(multibase::Base::Base58Btc, &prefixed_key);
378        let did = format!("did:key:{}", multibase_encoded);
379
380        // Generate the proper verification method ID (did:key:z...#z...)
381        let kid = format!("{}#{}", did, multibase_encoded);
382
383        // Extract x and y coordinates from public key
384        let x = &public_key[1..33]; // Skip the first byte (0x04 for uncompressed)
385        let y = &public_key[33..65];
386
387        // Create the secret
388        let secret = Secret {
389            id: did.clone(),
390            type_: crate::key_manager::SecretType::JsonWebKey2020,
391            secret_material: SecretMaterial::JWK {
392                private_key_jwk: serde_json::json!({
393                    "kty": "EC",
394                    "kid": kid.clone(),
395                    "crv": "P-256",
396                    "x": base64::engine::general_purpose::STANDARD.encode(x),
397                    "y": base64::engine::general_purpose::STANDARD.encode(y),
398                    "d": base64::engine::general_purpose::STANDARD.encode(&private_key)
399                }),
400            },
401        };
402
403        Ok(Self {
404            kid,
405            did,
406            secret,
407            key_type: KeyType::P256,
408        })
409    }
410
411    /// Generate a new secp256k1 key with the given key ID
412    #[cfg(feature = "crypto-secp256k1")]
413    pub fn generate_secp256k1(_kid: &str) -> Result<Self> {
414        // Generate a new secp256k1 keypair
415        let mut rng = OsRng;
416        let signing_key = k256::ecdsa::SigningKey::random(&mut rng);
417
418        // Extract public and private keys
419        let private_key = signing_key.to_bytes().to_vec();
420        let public_key = signing_key
421            .verifying_key()
422            .to_encoded_point(false)
423            .to_bytes();
424
425        // Create did:key identifier
426        // Multicodec prefix for secp256k1: 0xe701
427        let mut prefixed_key = vec![0xe7, 0x01];
428        prefixed_key.extend_from_slice(&public_key);
429
430        // Encode the key with multibase (base58btc with 'z' prefix)
431        let multibase_encoded = multibase::encode(multibase::Base::Base58Btc, &prefixed_key);
432        let did = format!("did:key:{}", multibase_encoded);
433
434        // Generate the proper verification method ID (did:key:z...#z...)
435        let kid = format!("{}#{}", did, multibase_encoded);
436
437        // Extract x and y coordinates from public key
438        let x = &public_key[1..33]; // Skip the first byte (0x04 for uncompressed)
439        let y = &public_key[33..65];
440
441        // Create the secret
442        let secret = Secret {
443            id: did.clone(),
444            type_: crate::key_manager::SecretType::JsonWebKey2020,
445            secret_material: SecretMaterial::JWK {
446                private_key_jwk: serde_json::json!({
447                    "kty": "EC",
448                    "kid": kid.clone(),
449                    "crv": "secp256k1",
450                    "x": base64::engine::general_purpose::STANDARD.encode(x),
451                    "y": base64::engine::general_purpose::STANDARD.encode(y),
452                    "d": base64::engine::general_purpose::STANDARD.encode(&private_key)
453                }),
454            },
455        };
456
457        Ok(Self {
458            kid,
459            did,
460            secret,
461            key_type: KeyType::Secp256k1,
462        })
463    }
464
465    /// Extract the private key JWK from the secret
466    fn private_key_jwk(&self) -> Result<&Value> {
467        match &self.secret.secret_material {
468            SecretMaterial::JWK { private_key_jwk } => Ok(private_key_jwk),
469        }
470    }
471
472    /// Get the key type and curve from the private key JWK
473    fn key_type_and_curve(&self) -> Result<(Option<&str>, Option<&str>)> {
474        let jwk = self.private_key_jwk()?;
475        let kty = jwk.get("kty").and_then(|v| v.as_str());
476        let crv = jwk.get("crv").and_then(|v| v.as_str());
477        Ok((kty, crv))
478    }
479
480    /// Convert the key to a complete JWK (including private key)
481    pub fn to_jwk(&self) -> Result<Value> {
482        Ok(self.private_key_jwk()?.clone())
483    }
484}
485
486#[async_trait]
487impl AgentKey for LocalAgentKey {
488    fn key_id(&self) -> &str {
489        &self.kid
490    }
491
492    fn public_key_jwk(&self) -> Result<Value> {
493        let jwk = self.private_key_jwk()?;
494
495        // Create a copy without the private key parts
496        let mut public_jwk = serde_json::Map::new();
497
498        // Copy all fields except 'd' (private key)
499        for (key, value) in jwk
500            .as_object()
501            .ok_or_else(|| Error::Cryptography("Invalid JWK format: not an object".to_string()))?
502        {
503            if key != "d" {
504                public_jwk.insert(key.clone(), value.clone());
505            }
506        }
507
508        Ok(Value::Object(public_jwk))
509    }
510
511    fn did(&self) -> &str {
512        &self.did
513    }
514
515    fn key_type(&self) -> &str {
516        match self.key_type {
517            #[cfg(feature = "crypto-ed25519")]
518            KeyType::Ed25519 => "Ed25519",
519            #[cfg(feature = "crypto-p256")]
520            KeyType::P256 => "P-256",
521            #[cfg(feature = "crypto-secp256k1")]
522            KeyType::Secp256k1 => "secp256k1",
523        }
524    }
525}
526
527#[async_trait]
528impl SigningKey for LocalAgentKey {
529    async fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
530        let (kty, crv) = self.key_type_and_curve()?;
531        let jwk = self.private_key_jwk()?;
532
533        match (kty, crv) {
534            #[cfg(feature = "crypto-ed25519")]
535            (Some("OKP"), Some("Ed25519")) => {
536                // Extract the private key
537                let private_key_base64 = jwk
538                    .get("d")
539                    .and_then(|v| v.as_str())
540                    .ok_or_else(|| Error::Cryptography("Missing private key in JWK".to_string()))?;
541
542                // Decode the private key from base64
543                let private_key_bytes = base64::engine::general_purpose::STANDARD
544                    .decode(private_key_base64)
545                    .map_err(|e| {
546                        Error::Cryptography(format!("Failed to decode private key: {}", e))
547                    })?;
548
549                // Ed25519 keys must be exactly 32 bytes
550                if private_key_bytes.len() != 32 {
551                    return Err(Error::Cryptography(format!(
552                        "Invalid Ed25519 private key length: {}, expected 32 bytes",
553                        private_key_bytes.len()
554                    )));
555                }
556
557                // Create an Ed25519 signing key
558                let signing_key =
559                    match ed25519_dalek::SigningKey::try_from(private_key_bytes.as_slice()) {
560                        Ok(key) => key,
561                        Err(e) => {
562                            return Err(Error::Cryptography(format!(
563                                "Failed to create Ed25519 signing key: {:?}",
564                                e
565                            )))
566                        }
567                    };
568
569                // Sign the message
570                let signature = signing_key.sign(data);
571
572                // Return the signature bytes
573                Ok(signature.to_vec())
574            }
575            #[cfg(feature = "crypto-p256")]
576            (Some("EC"), Some("P-256")) => {
577                // Extract the private key (d parameter in JWK)
578                let private_key_base64 =
579                    jwk.get("d").and_then(|v| v.as_str()).ok_or_else(|| {
580                        Error::Cryptography("Missing private key (d) in JWK".to_string())
581                    })?;
582
583                // Decode the private key from base64
584                let private_key_bytes = base64::engine::general_purpose::STANDARD
585                    .decode(private_key_base64)
586                    .map_err(|e| {
587                        Error::Cryptography(format!("Failed to decode P-256 private key: {}", e))
588                    })?;
589
590                // Create a P-256 signing key
591                let signing_key = P256SigningKey::from_slice(&private_key_bytes).map_err(|e| {
592                    Error::Cryptography(format!("Failed to create P-256 signing key: {:?}", e))
593                })?;
594
595                // Sign the message using ECDSA
596                let signature: P256Signature = signing_key.sign(data);
597
598                // Convert to raw bytes for JWS (R||S format, 64 bytes total)
599                let signature_bytes = signature.to_bytes();
600                Ok(signature_bytes.to_vec())
601            }
602            #[cfg(feature = "crypto-secp256k1")]
603            (Some("EC"), Some("secp256k1")) => {
604                // Extract the private key (d parameter in JWK)
605                let private_key_base64 =
606                    jwk.get("d").and_then(|v| v.as_str()).ok_or_else(|| {
607                        Error::Cryptography("Missing private key (d) in JWK".to_string())
608                    })?;
609
610                // Decode the private key from base64
611                let private_key_bytes = base64::engine::general_purpose::STANDARD
612                    .decode(private_key_base64)
613                    .map_err(|e| {
614                        Error::Cryptography(format!(
615                            "Failed to decode secp256k1 private key: {}",
616                            e
617                        ))
618                    })?;
619
620                // Create a secp256k1 signing key
621                let signing_key =
622                    Secp256k1SigningKey::from_slice(&private_key_bytes).map_err(|e| {
623                        Error::Cryptography(format!(
624                            "Failed to create secp256k1 signing key: {:?}",
625                            e
626                        ))
627                    })?;
628
629                // Sign the message using ECDSA
630                let signature: Secp256k1Signature = signing_key.sign(data);
631
632                // Convert to raw bytes for JWS (R||S format)
633                let signature_bytes = signature.to_bytes();
634                Ok(signature_bytes.to_vec())
635            }
636            // Handle unsupported key types
637            _ => Err(Error::Cryptography(format!(
638                "Unsupported key type for signing: kty={:?}, crv={:?}",
639                kty, crv
640            ))),
641        }
642    }
643
644    fn recommended_jws_alg(&self) -> JwsAlgorithm {
645        match self.key_type {
646            #[cfg(feature = "crypto-ed25519")]
647            KeyType::Ed25519 => JwsAlgorithm::EdDSA,
648            #[cfg(feature = "crypto-p256")]
649            KeyType::P256 => JwsAlgorithm::ES256,
650            #[cfg(feature = "crypto-secp256k1")]
651            KeyType::Secp256k1 => JwsAlgorithm::ES256K,
652        }
653    }
654
655    async fn create_jws(
656        &self,
657        payload: &[u8],
658        protected_header: Option<JwsProtected>,
659    ) -> Result<Jws> {
660        // Base64URL encode the payload (no padding) per RFC 7515
661        let payload_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload);
662
663        // Create the protected header if not provided, respecting the key type
664        let protected = if let Some(mut header) = protected_header {
665            // Override the algorithm to match the key type
666            header.alg = self.recommended_jws_alg().as_str().to_string();
667            // Only set kid if not already provided in the header
668            if header.kid.is_empty() {
669                header.kid = crate::agent_key::AgentKey::key_id(self).to_string();
670            }
671            header
672        } else {
673            JwsProtected {
674                typ: crate::message::DIDCOMM_SIGNED.to_string(),
675                alg: self.recommended_jws_alg().as_str().to_string(),
676                kid: crate::agent_key::AgentKey::key_id(self).to_string(),
677            }
678        };
679
680        // Serialize and encode the protected header
681        let protected_json = serde_json::to_string(&protected).map_err(|e| {
682            Error::Serialization(format!("Failed to serialize protected header: {}", e))
683        })?;
684
685        let protected_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(protected_json);
686
687        // Create the signing input (protected.payload)
688        let signing_input = format!("{}.{}", protected_b64, payload_b64);
689
690        // Sign the input
691        let signature = self.sign(signing_input.as_bytes()).await?;
692
693        // Base64URL encode the signature (no padding) per RFC 7515
694        let signature_value = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&signature);
695
696        // Create the JWS with signature
697        let jws = Jws {
698            payload: payload_b64,
699            signatures: vec![JwsSignature {
700                protected: protected_b64,
701                signature: signature_value,
702            }],
703        };
704
705        Ok(jws)
706    }
707}
708
709#[async_trait]
710impl VerificationKey for LocalAgentKey {
711    fn key_id(&self) -> &str {
712        &self.kid
713    }
714
715    fn public_key_jwk(&self) -> Result<Value> {
716        AgentKey::public_key_jwk(self)
717    }
718
719    async fn verify_signature(
720        &self,
721        payload: &[u8],
722        signature: &[u8],
723        protected_header: &JwsProtected,
724    ) -> Result<bool> {
725        let (kty, crv) = self.key_type_and_curve()?;
726        let jwk = self.private_key_jwk()?;
727
728        match (kty, crv, protected_header.alg.as_str()) {
729            #[cfg(feature = "crypto-ed25519")]
730            (Some("OKP"), Some("Ed25519"), "EdDSA") => {
731                // Extract the public key
732                let public_key_base64 = jwk.get("x").and_then(|v| v.as_str()).ok_or_else(|| {
733                    Error::Cryptography("Missing public key (x) in JWK".to_string())
734                })?;
735
736                // Decode the public key from base64
737                let public_key_bytes = base64::engine::general_purpose::STANDARD
738                    .decode(public_key_base64)
739                    .map_err(|e| {
740                        Error::Cryptography(format!("Failed to decode public key: {}", e))
741                    })?;
742
743                // Ed25519 public keys must be exactly 32 bytes
744                if public_key_bytes.len() != 32 {
745                    return Err(Error::Cryptography(format!(
746                        "Invalid Ed25519 public key length: {}, expected 32 bytes",
747                        public_key_bytes.len()
748                    )));
749                }
750
751                // Create an Ed25519 verifying key
752                let verifying_key = match VerifyingKey::try_from(public_key_bytes.as_slice()) {
753                    Ok(key) => key,
754                    Err(e) => {
755                        return Err(Error::Cryptography(format!(
756                            "Failed to create Ed25519 verifying key: {:?}",
757                            e
758                        )))
759                    }
760                };
761
762                // Verify the signature
763                if signature.len() != 64 {
764                    return Err(Error::Cryptography(format!(
765                        "Invalid Ed25519 signature length: {}, expected 64 bytes",
766                        signature.len()
767                    )));
768                }
769
770                let mut sig_bytes = [0u8; 64];
771                sig_bytes.copy_from_slice(signature);
772                let ed_signature = ed25519_dalek::Signature::from_bytes(&sig_bytes);
773
774                match verifying_key.verify(payload, &ed_signature) {
775                    Ok(()) => Ok(true),
776                    Err(_) => Ok(false),
777                }
778            }
779            #[cfg(feature = "crypto-p256")]
780            (Some("EC"), Some("P-256"), "ES256") => {
781                // Extract the public key coordinates
782                let x_b64 = jwk.get("x").and_then(|v| v.as_str()).ok_or_else(|| {
783                    Error::Cryptography("Missing x coordinate in JWK".to_string())
784                })?;
785                let y_b64 = jwk.get("y").and_then(|v| v.as_str()).ok_or_else(|| {
786                    Error::Cryptography("Missing y coordinate in JWK".to_string())
787                })?;
788
789                // Decode the coordinates
790                let x_bytes = base64::engine::general_purpose::STANDARD
791                    .decode(x_b64)
792                    .map_err(|e| {
793                        Error::Cryptography(format!("Failed to decode x coordinate: {}", e))
794                    })?;
795                let y_bytes = base64::engine::general_purpose::STANDARD
796                    .decode(y_b64)
797                    .map_err(|e| {
798                        Error::Cryptography(format!("Failed to decode y coordinate: {}", e))
799                    })?;
800
801                // Create a P-256 encoded point from the coordinates
802                let mut point_bytes = vec![0x04]; // Uncompressed point format
803                point_bytes.extend_from_slice(&x_bytes);
804                point_bytes.extend_from_slice(&y_bytes);
805
806                let encoded_point = P256EncodedPoint::from_bytes(&point_bytes).map_err(|e| {
807                    Error::Cryptography(format!("Failed to create P-256 encoded point: {}", e))
808                })?;
809
810                // This checks if the point is on the curve and returns the public key
811                let public_key_opt = P256PublicKey::from_encoded_point(&encoded_point);
812                if public_key_opt.is_none().into() {
813                    return Err(Error::Cryptography("Invalid P-256 public key".to_string()));
814                }
815                let public_key = public_key_opt.unwrap();
816
817                // Parse the signature from raw bytes (R||S format)
818                let p256_signature = P256Signature::from_slice(signature).map_err(|e| {
819                    Error::Cryptography(format!("Failed to parse P-256 signature: {:?}", e))
820                })?;
821
822                // Verify the signature using P-256 ECDSA
823                let verifier = p256::ecdsa::VerifyingKey::from(public_key);
824                match verifier.verify(payload, &p256_signature) {
825                    Ok(()) => Ok(true),
826                    Err(_) => Ok(false),
827                }
828            }
829            #[cfg(feature = "crypto-secp256k1")]
830            (Some("EC"), Some("secp256k1"), "ES256K") => {
831                // Extract the public key coordinates
832                let x_b64 = jwk.get("x").and_then(|v| v.as_str()).ok_or_else(|| {
833                    Error::Cryptography("Missing x coordinate in JWK".to_string())
834                })?;
835                let y_b64 = jwk.get("y").and_then(|v| v.as_str()).ok_or_else(|| {
836                    Error::Cryptography("Missing y coordinate in JWK".to_string())
837                })?;
838
839                // Decode the coordinates
840                let x_bytes = base64::engine::general_purpose::STANDARD
841                    .decode(x_b64)
842                    .map_err(|e| {
843                        Error::Cryptography(format!("Failed to decode x coordinate: {}", e))
844                    })?;
845                let y_bytes = base64::engine::general_purpose::STANDARD
846                    .decode(y_b64)
847                    .map_err(|e| {
848                        Error::Cryptography(format!("Failed to decode y coordinate: {}", e))
849                    })?;
850
851                // Create a secp256k1 public key from the coordinates
852                let mut point_bytes = vec![0x04]; // Uncompressed point format
853                point_bytes.extend_from_slice(&x_bytes);
854                point_bytes.extend_from_slice(&y_bytes);
855
856                // Parse the verifying key from the SEC1 encoded point
857                let verifier =
858                    k256::ecdsa::VerifyingKey::from_sec1_bytes(&point_bytes).map_err(|e| {
859                        Error::Cryptography(format!(
860                            "Failed to create secp256k1 verifying key: {:?}",
861                            e
862                        ))
863                    })?;
864
865                // Parse the signature from raw bytes (R||S format)
866                let k256_signature = Secp256k1Signature::from_slice(signature).map_err(|e| {
867                    Error::Cryptography(format!("Failed to parse secp256k1 signature: {:?}", e))
868                })?;
869
870                // Verify the signature
871                match verifier.verify(payload, &k256_signature) {
872                    Ok(()) => Ok(true),
873                    Err(_) => Ok(false),
874                }
875            }
876            // Unsupported algorithm or key type combination
877            _ => Err(Error::Cryptography(format!(
878                "Unsupported key type/algorithm combination for verification: kty={:?}, crv={:?}, alg={}",
879                kty, crv, protected_header.alg
880            ))),
881        }
882    }
883}
884
885#[async_trait]
886impl EncryptionKey for LocalAgentKey {
887    async fn encrypt(
888        &self,
889        plaintext: &[u8],
890        aad: Option<&[u8]>,
891        _recipient_public_key: &dyn VerificationKey,
892    ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
893        // We'll implement AES-GCM encryption with key derived from ECDH
894
895        // 1. Generate a random content encryption key (CEK)
896        let mut cek = [0u8; 32]; // 256-bit key for AES-GCM
897        OsRng.fill_bytes(&mut cek);
898
899        // 2. Generate IV for AES-GCM
900        let mut iv_bytes = [0u8; 12]; // 96-bit IV for AES-GCM
901        OsRng.fill_bytes(&mut iv_bytes);
902
903        // 3. Encrypt the plaintext with AES-GCM
904        let cipher = Aes256Gcm::new_from_slice(&cek)
905            .map_err(|e| Error::Cryptography(format!("Failed to create AES-GCM cipher: {}", e)))?;
906
907        // Create a nonce from the IV
908        let nonce = Nonce::from_slice(&iv_bytes);
909
910        // Encrypt the plaintext
911        let mut buffer = plaintext.to_vec();
912        let aad_bytes = aad.unwrap_or(b"");
913        let tag = cipher
914            .encrypt_in_place_detached(nonce, aad_bytes, &mut buffer)
915            .map_err(|e| Error::Cryptography(format!("AES-GCM encryption failed: {}", e)))?;
916
917        // Return ciphertext, IV, and tag
918        Ok((buffer, iv_bytes.to_vec(), tag.to_vec()))
919    }
920
921    fn recommended_jwe_alg_enc(&self) -> (JweAlgorithm, JweEncryption) {
922        // If we need special handling for P-256, we could implement it here
923        // but for now keep it simple
924        (JweAlgorithm::EcdhEsA256kw, JweEncryption::A256GCM)
925    }
926
927    async fn create_jwe(
928        &self,
929        plaintext: &[u8],
930        recipients: &[Arc<dyn VerificationKey>],
931        protected_header: Option<JweProtected>,
932    ) -> Result<Jwe> {
933        if recipients.is_empty() {
934            return Err(Error::Validation(
935                "No recipients specified for JWE".to_string(),
936            ));
937        }
938
939        // 1. Generate a random content encryption key (CEK)
940        let mut cek = [0u8; 32]; // 256-bit key for AES-GCM
941        OsRng.fill_bytes(&mut cek);
942
943        // 2. Generate IV for AES-GCM
944        let mut iv_bytes = [0u8; 12]; // 96-bit IV for AES-GCM
945        OsRng.fill_bytes(&mut iv_bytes);
946
947        // 3. Generate an ephemeral key pair for ECDH
948        #[cfg(feature = "crypto-p256")]
949        let ephemeral_secret = P256EphemeralSecret::random(&mut OsRng);
950        #[cfg(feature = "crypto-p256")]
951        let ephemeral_public_key = ephemeral_secret.public_key();
952
953        // 4. Convert the public key to coordinates for the header
954        #[cfg(feature = "crypto-p256")]
955        let point = ephemeral_public_key.to_encoded_point(false); // Uncompressed format
956        #[cfg(feature = "crypto-p256")]
957        let x_bytes = point.x().unwrap().to_vec();
958        #[cfg(feature = "crypto-p256")]
959        let y_bytes = point.y().unwrap().to_vec();
960
961        // Base64 encode the coordinates for the ephemeral public key
962        #[cfg(feature = "crypto-p256")]
963        let x_b64 = base64::engine::general_purpose::STANDARD.encode(&x_bytes);
964        #[cfg(feature = "crypto-p256")]
965        let y_b64 = base64::engine::general_purpose::STANDARD.encode(&y_bytes);
966
967        // Create the ephemeral public key structure
968        #[cfg(feature = "crypto-p256")]
969        let ephemeral_key = EphemeralPublicKey::Ec {
970            crv: "P-256".to_string(),
971            x: x_b64,
972            y: y_b64,
973        };
974        #[cfg(not(feature = "crypto-p256"))]
975        let ephemeral_key = EphemeralPublicKey::Ec {
976            crv: "P-256".to_string(),
977            x: "".to_string(),
978            y: "".to_string(),
979        };
980
981        // 5. Create protected header
982        let protected = protected_header.unwrap_or_else(|| {
983            let (alg, enc) = self.recommended_jwe_alg_enc();
984            JweProtected {
985                epk: ephemeral_key,
986                apv: base64::engine::general_purpose::STANDARD.encode(Uuid::new_v4().as_bytes()),
987                apu: String::new(),
988                typ: crate::message::DIDCOMM_ENCRYPTED.to_string(),
989                enc: enc.as_str().to_string(),
990                alg: alg.as_str().to_string(),
991            }
992        });
993
994        // 6. Encrypt the plaintext with AES-GCM
995        let cipher = Aes256Gcm::new_from_slice(&cek)
996            .map_err(|e| Error::Cryptography(format!("Failed to create AES-GCM cipher: {}", e)))?;
997
998        // Create a nonce from the IV
999        let nonce = Nonce::from_slice(&iv_bytes);
1000
1001        // Encrypt the plaintext
1002        let mut buffer = plaintext.to_vec();
1003        let tag = cipher
1004            .encrypt_in_place_detached(nonce, b"", &mut buffer)
1005            .map_err(|e| Error::Cryptography(format!("AES-GCM encryption failed: {}", e)))?;
1006
1007        // 7. Process each recipient and create JWE
1008        #[cfg(not(feature = "crypto-p256"))]
1009        {
1010            let _ = (recipients, protected, buffer, tag, iv_bytes); // Suppress unused variable warnings
1011            return Err(Error::Cryptography(
1012                "P-256 encryption not available - enable crypto-p256 feature".to_string(),
1013            ));
1014        }
1015
1016        #[cfg(feature = "crypto-p256")]
1017        {
1018            let mut jwe_recipients = Vec::with_capacity(recipients.len());
1019
1020            for recipient in recipients {
1021                // Extract recipient's public key as JWK
1022                let recipient_jwk = recipient.public_key_jwk()?;
1023
1024                // Extract key type and curve
1025                let kty = recipient_jwk.get("kty").and_then(|v| v.as_str());
1026                let crv = recipient_jwk.get("crv").and_then(|v| v.as_str());
1027
1028                let encrypted_key = match (kty, crv) {
1029                    (Some("EC"), Some("P-256")) => {
1030                        // Extract the public key coordinates
1031                        let x_b64 =
1032                            recipient_jwk
1033                                .get("x")
1034                                .and_then(|v| v.as_str())
1035                                .ok_or_else(|| {
1036                                    Error::Cryptography(
1037                                        "Missing x coordinate in recipient JWK".to_string(),
1038                                    )
1039                                })?;
1040                        let y_b64 =
1041                            recipient_jwk
1042                                .get("y")
1043                                .and_then(|v| v.as_str())
1044                                .ok_or_else(|| {
1045                                    Error::Cryptography(
1046                                        "Missing y coordinate in recipient JWK".to_string(),
1047                                    )
1048                                })?;
1049
1050                        let x_bytes = base64::engine::general_purpose::STANDARD
1051                            .decode(x_b64)
1052                            .map_err(|e| {
1053                                Error::Cryptography(format!("Failed to decode x coordinate: {}", e))
1054                            })?;
1055                        let y_bytes = base64::engine::general_purpose::STANDARD
1056                            .decode(y_b64)
1057                            .map_err(|e| {
1058                                Error::Cryptography(format!("Failed to decode y coordinate: {}", e))
1059                            })?;
1060
1061                        // Create a P-256 encoded point from the coordinates
1062                        let mut point_bytes = vec![0x04]; // Uncompressed point format
1063                        point_bytes.extend_from_slice(&x_bytes);
1064                        point_bytes.extend_from_slice(&y_bytes);
1065
1066                        let encoded_point =
1067                            P256EncodedPoint::from_bytes(&point_bytes).map_err(|e| {
1068                                Error::Cryptography(format!(
1069                                    "Failed to create P-256 encoded point: {}",
1070                                    e
1071                                ))
1072                            })?;
1073
1074                        // This checks if the point is on the curve and returns the public key
1075                        let recipient_pk_opt = P256PublicKey::from_encoded_point(&encoded_point);
1076                        if recipient_pk_opt.is_none().into() {
1077                            return Err(Error::Cryptography(
1078                                "Invalid P-256 public key".to_string(),
1079                            ));
1080                        }
1081                        let recipient_pk = recipient_pk_opt.unwrap();
1082
1083                        // Perform ECDH to derive a shared secret
1084                        let shared_secret = ephemeral_secret.diffie_hellman(&recipient_pk);
1085                        let shared_bytes = shared_secret.raw_secret_bytes();
1086
1087                        // Derive KEK using Concat KDF per RFC 7518
1088                        // Use APV from protected header for consistency with decryption
1089                        let apv_bytes = base64::engine::general_purpose::STANDARD
1090                            .decode(&protected.apv)
1091                            .unwrap_or_default();
1092                        let kek = crate::crypto::derive_key_ecdh_es(
1093                            shared_bytes.as_slice(),
1094                            b"", // apu - empty for anonymous sender
1095                            &apv_bytes,
1096                            256, // 256 bits for AES-256-KW
1097                        )?;
1098
1099                        // Wrap CEK with AES-KW per RFC 3394
1100                        let mut kek_array = [0u8; 32];
1101                        kek_array.copy_from_slice(&kek);
1102
1103                        crate::crypto::wrap_key_aes_kw(&kek_array, &cek)?
1104                    }
1105                    // Handle other key types
1106                    _ => {
1107                        return Err(Error::Cryptography(format!(
1108                            "Unsupported recipient key type for encryption: kty={:?}, crv={:?}",
1109                            kty, crv
1110                        )));
1111                    }
1112                };
1113
1114                // Add this recipient to the JWE
1115                jwe_recipients.push(JweRecipient {
1116                    encrypted_key: base64::engine::general_purpose::STANDARD.encode(encrypted_key),
1117                    header: JweHeader {
1118                        kid: (**recipient).key_id().to_string(),
1119                        sender_kid: Some(crate::agent_key::AgentKey::key_id(self).to_string()),
1120                    },
1121                });
1122            }
1123
1124            // 8. Serialize and encode the protected header
1125            let protected_json = serde_json::to_string(&protected).map_err(|e| {
1126                Error::Serialization(format!("Failed to serialize protected header: {}", e))
1127            })?;
1128
1129            let protected_b64 = base64::engine::general_purpose::STANDARD.encode(protected_json);
1130
1131            // 9. Create the JWE
1132            let jwe = Jwe {
1133                ciphertext: base64::engine::general_purpose::STANDARD.encode(buffer),
1134                protected: protected_b64,
1135                recipients: jwe_recipients,
1136                tag: base64::engine::general_purpose::STANDARD.encode(tag),
1137                iv: base64::engine::general_purpose::STANDARD.encode(iv_bytes),
1138            };
1139
1140            Ok(jwe)
1141        }
1142    }
1143}
1144
1145#[async_trait]
1146impl DecryptionKey for LocalAgentKey {
1147    async fn decrypt(
1148        &self,
1149        ciphertext: &[u8],
1150        encrypted_key: &[u8],
1151        iv: &[u8],
1152        tag: &[u8],
1153        aad: Option<&[u8]>,
1154        _sender_key: Option<&dyn VerificationKey>,
1155    ) -> Result<Vec<u8>> {
1156        // The decrypt() trait method does not receive the ephemeral public key (EPK)
1157        // required for ECDH-ES+A256KW key agreement. Use unwrap_jwe() instead, which
1158        // parses the full JWE structure including the EPK from the protected header.
1159        let _ = (ciphertext, encrypted_key, iv, tag, aad);
1160        Err(Error::Cryptography(
1161            "decrypt() is not supported for ECDH-ES+A256KW keys; use unwrap_jwe() instead"
1162                .to_string(),
1163        ))
1164    }
1165
1166    async fn unwrap_jwe(&self, jwe: &Jwe) -> Result<Vec<u8>> {
1167        let our_kid = crate::agent_key::AgentKey::key_id(self);
1168
1169        // Find recipient matching our key ID or any X25519 key agreement key derived from our DID
1170        let our_did = our_kid.split('#').next().unwrap_or(our_kid);
1171        let recipient = jwe
1172            .recipients
1173            .iter()
1174            .find(|r| r.header.kid == our_kid || r.header.kid.starts_with(&format!("{}#", our_did)))
1175            .ok_or_else(|| {
1176                Error::Cryptography(format!(
1177                    "No matching recipient found for key ID: {}",
1178                    our_kid
1179                ))
1180            })?;
1181
1182        // Decode and parse the protected header to get the EPK
1183        let protected_bytes =
1184            crate::message::base64_decode_flexible(&jwe.protected).map_err(|e| {
1185                Error::Cryptography(format!("Failed to decode protected header: {}", e))
1186            })?;
1187
1188        let protected: JweProtected = serde_json::from_slice(&protected_bytes)
1189            .map_err(|e| Error::Cryptography(format!("Failed to parse protected header: {}", e)))?;
1190
1191        // Decode the JWE elements (accept both base64 and base64url)
1192        let ciphertext = crate::message::base64_decode_flexible(&jwe.ciphertext)
1193            .map_err(|e| Error::Cryptography(format!("Failed to decode ciphertext: {}", e)))?;
1194
1195        let wrapped_cek = crate::message::base64_decode_flexible(&recipient.encrypted_key)
1196            .map_err(|e| Error::Cryptography(format!("Failed to decode encrypted key: {}", e)))?;
1197
1198        let iv = crate::message::base64_decode_flexible(&jwe.iv)
1199            .map_err(|e| Error::Cryptography(format!("Failed to decode IV: {}", e)))?;
1200
1201        let tag = crate::message::base64_decode_flexible(&jwe.tag)
1202            .map_err(|e| Error::Cryptography(format!("Failed to decode tag: {}", e)))?;
1203
1204        // Perform ECDH based on EPK type
1205        let shared_bytes: Vec<u8> = match &protected.epk {
1206            #[cfg(feature = "crypto-ed25519")]
1207            EphemeralPublicKey::Okp { crv, x } if crv == "X25519" => {
1208                // X25519 ECDH: convert our Ed25519 private key to X25519 and perform DH
1209                let epk_bytes = crate::message::base64_decode_flexible(x).map_err(|e| {
1210                    Error::Cryptography(format!("Failed to decode X25519 EPK: {}", e))
1211                })?;
1212                if epk_bytes.len() != 32 {
1213                    return Err(Error::Cryptography("Invalid X25519 EPK length".to_string()));
1214                }
1215
1216                // Get our Ed25519 private key and convert to X25519
1217                let jwk = self.private_key_jwk()?;
1218                let d_b64 = jwk.get("d").and_then(|v| v.as_str()).ok_or_else(|| {
1219                    Error::Cryptography("Missing private key (d) in JWK".to_string())
1220                })?;
1221                let d_bytes = crate::message::base64_decode_flexible(d_b64).map_err(|e| {
1222                    Error::Cryptography(format!("Failed to decode private key: {}", e))
1223                })?;
1224
1225                // Ed25519 seed → X25519 secret: SHA-512 hash of seed, first 32 bytes
1226                // x25519-dalek handles clamping internally
1227                use sha2::Digest;
1228                let hash = sha2::Sha512::digest(&d_bytes);
1229                let mut x25519_key_bytes = [0u8; 32];
1230                x25519_key_bytes.copy_from_slice(&hash[..32]);
1231
1232                let secret = x25519_dalek::StaticSecret::from(x25519_key_bytes);
1233                let epk_array: [u8; 32] = epk_bytes[..32].try_into().unwrap();
1234                let public = x25519_dalek::PublicKey::from(epk_array);
1235                let shared = secret.diffie_hellman(&public);
1236                shared.as_bytes().to_vec()
1237            }
1238            #[cfg(feature = "crypto-p256")]
1239            EphemeralPublicKey::Ec { x, y, .. } => {
1240                // P-256 ECDH
1241                let epk_x = crate::message::base64_decode_flexible(x)
1242                    .map_err(|e| Error::Cryptography(format!("Failed to decode EPK x: {}", e)))?;
1243                let epk_y = crate::message::base64_decode_flexible(y)
1244                    .map_err(|e| Error::Cryptography(format!("Failed to decode EPK y: {}", e)))?;
1245
1246                let mut epk_point_bytes = vec![0x04];
1247                epk_point_bytes.extend_from_slice(&epk_x);
1248                epk_point_bytes.extend_from_slice(&epk_y);
1249
1250                let epk_encoded_point = P256EncodedPoint::from_bytes(&epk_point_bytes)
1251                    .map_err(|e| Error::Cryptography(format!("Invalid EPK point: {}", e)))?;
1252
1253                let epk_public_key = P256PublicKey::from_encoded_point(&epk_encoded_point);
1254                if epk_public_key.is_none().into() {
1255                    return Err(Error::Cryptography("Invalid EPK public key".to_string()));
1256                }
1257                let epk_public_key = epk_public_key.unwrap();
1258
1259                let jwk = self.private_key_jwk()?;
1260                let d_b64 = jwk.get("d").and_then(|v| v.as_str()).ok_or_else(|| {
1261                    Error::Cryptography("Missing private key (d) in JWK".to_string())
1262                })?;
1263                let d_bytes = base64::engine::general_purpose::STANDARD
1264                    .decode(d_b64)
1265                    .map_err(|e| {
1266                        Error::Cryptography(format!("Failed to decode private key: {}", e))
1267                    })?;
1268
1269                let secret_key = p256::SecretKey::from_bytes((&d_bytes[..]).into())
1270                    .map_err(|e| Error::Cryptography(format!("Invalid private key: {}", e)))?;
1271
1272                let shared_secret = p256::ecdh::diffie_hellman(
1273                    secret_key.to_nonzero_scalar(),
1274                    epk_public_key.as_affine(),
1275                );
1276                shared_secret.raw_secret_bytes().to_vec()
1277            }
1278            _ => {
1279                return Err(Error::Cryptography(
1280                    "Unsupported EPK type for JWE decryption".to_string(),
1281                ))
1282            }
1283        };
1284
1285        // Derive KEK using Concat KDF
1286        // Per RFC 7518 Section 4.6.2, apu and apv are base64url-decoded before use
1287        let apu = if protected.apu.is_empty() {
1288            Vec::new()
1289        } else {
1290            crate::message::base64_decode_flexible(&protected.apu).unwrap_or_default()
1291        };
1292        let apv = if protected.apv.is_empty() {
1293            Vec::new()
1294        } else {
1295            crate::message::base64_decode_flexible(&protected.apv).unwrap_or_default()
1296        };
1297
1298        let kek = crate::crypto::derive_key_ecdh_es(&shared_bytes, &apu, &apv, 256)?;
1299
1300        // Unwrap CEK using AES-KW
1301        let mut kek_array = [0u8; 32];
1302        kek_array.copy_from_slice(&kek);
1303        let cek = crate::crypto::unwrap_key_aes_kw(&kek_array, &wrapped_cek)?;
1304
1305        // Decrypt ciphertext with AES-GCM using the CEK
1306        let cipher = Aes256Gcm::new_from_slice(&cek)
1307            .map_err(|e| Error::Cryptography(format!("Failed to create AES-GCM cipher: {}", e)))?;
1308
1309        let nonce = Nonce::from_slice(&iv);
1310
1311        let mut padded_tag = [0u8; 16];
1312        let copy_len = std::cmp::min(tag.len(), 16);
1313        padded_tag[..copy_len].copy_from_slice(&tag[..copy_len]);
1314        let tag_array = aes_gcm::Tag::from_slice(&padded_tag);
1315
1316        let mut buffer = ciphertext.to_vec();
1317        cipher
1318            .decrypt_in_place_detached(nonce, jwe.protected.as_bytes(), &mut buffer, tag_array)
1319            .or_else(|_| {
1320                // Retry with empty AAD for backwards compatibility
1321                buffer = ciphertext.to_vec();
1322                cipher.decrypt_in_place_detached(nonce, b"", &mut buffer, tag_array)
1323            })
1324            .map_err(|e| Error::Cryptography(format!("AES-GCM decryption failed: {:?}", e)))?;
1325
1326        Ok(buffer)
1327    }
1328}
1329
1330/// A standalone verification key that can be used to verify signatures
1331#[derive(Debug, Clone)]
1332pub struct PublicVerificationKey {
1333    /// The key's ID
1334    kid: String,
1335    /// The public key material as a JWK
1336    public_jwk: Value,
1337}
1338
1339impl PublicVerificationKey {
1340    /// Verify a JWS
1341    pub async fn verify_jws(&self, jws: &crate::message::Jws) -> Result<Vec<u8>> {
1342        // Find the signature that matches our key ID
1343        let signature = jws
1344            .signatures
1345            .iter()
1346            .find(|s| s.get_kid().as_ref() == Some(&self.kid))
1347            .ok_or_else(|| {
1348                Error::Cryptography(format!("No signature found with kid: {}", self.kid))
1349            })?;
1350
1351        // Get the protected header
1352        let protected = signature.get_protected_header().map_err(|e| {
1353            Error::Cryptography(format!("Failed to decode protected header: {}", e))
1354        })?;
1355
1356        // Decode the signature (accept both base64 and base64url)
1357        let signature_bytes = crate::message::base64_decode_flexible(&signature.signature)
1358            .map_err(|e| Error::Cryptography(format!("Failed to decode signature: {}", e)))?;
1359
1360        // Create the signing input (protected.payload)
1361        let signing_input = format!("{}.{}", signature.protected, jws.payload);
1362
1363        // Verify the signature
1364        let verified = self
1365            .verify_signature(signing_input.as_bytes(), &signature_bytes, &protected)
1366            .await
1367            .map_err(|e| Error::Cryptography(e.to_string()))?;
1368
1369        if !verified {
1370            return Err(Error::Cryptography(
1371                "Signature verification failed".to_string(),
1372            ));
1373        }
1374
1375        // Decode the payload (accept both base64 and base64url)
1376        let payload_bytes = crate::message::base64_decode_flexible(&jws.payload)
1377            .map_err(|e| Error::Cryptography(format!("Failed to decode payload: {}", e)))?;
1378
1379        Ok(payload_bytes)
1380    }
1381
1382    /// Verify a signature against this key
1383    pub async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> {
1384        // Get the key type and curve
1385        let kty = self.public_jwk.get("kty").and_then(|v| v.as_str());
1386        let crv = self.public_jwk.get("crv").and_then(|v| v.as_str());
1387
1388        // Create a protected header with the appropriate algorithm
1389        let alg = match (kty, crv) {
1390            (Some("OKP"), Some("Ed25519")) => "EdDSA",
1391            (Some("EC"), Some("P-256")) => "ES256",
1392            (Some("EC"), Some("secp256k1")) => "ES256K",
1393            _ => return Err(Error::Cryptography("Unsupported key type".to_string())),
1394        };
1395
1396        let protected = JwsProtected {
1397            typ: "JWT".to_string(),
1398            alg: alg.to_string(),
1399            kid: self.kid.clone(),
1400        };
1401
1402        // Verify the signature
1403        let result = self
1404            .verify_signature(payload, signature, &protected)
1405            .await?;
1406        if result {
1407            Ok(())
1408        } else {
1409            Err(Error::Cryptography(
1410                "Signature verification failed".to_string(),
1411            ))
1412        }
1413    }
1414
1415    /// Create a new PublicVerificationKey from a JWK
1416    pub fn new(kid: String, public_jwk: Value) -> Self {
1417        Self { kid, public_jwk }
1418    }
1419
1420    /// Create a PublicVerificationKey from a JWK
1421    pub fn from_jwk(jwk: &Value, kid: &str, _did: &str) -> Result<Self> {
1422        // Create a copy without the private key parts
1423        let mut public_jwk = serde_json::Map::new();
1424
1425        if let Some(obj) = jwk.as_object() {
1426            // Copy all fields except 'd' (private key)
1427            for (key, value) in obj {
1428                if key != "d" {
1429                    public_jwk.insert(key.clone(), value.clone());
1430                }
1431            }
1432        } else {
1433            return Err(Error::Cryptography(
1434                "Invalid JWK format: not an object".to_string(),
1435            ));
1436        }
1437
1438        Ok(Self {
1439            kid: kid.to_string(),
1440            public_jwk: Value::Object(public_jwk),
1441        })
1442    }
1443
1444    /// Create a PublicVerificationKey from a VerificationMaterial
1445    pub fn from_verification_material(
1446        kid: String,
1447        material: &VerificationMaterial,
1448    ) -> Result<Self> {
1449        match material {
1450            VerificationMaterial::JWK { public_key_jwk } => {
1451                Ok(Self::new(kid, public_key_jwk.clone()))
1452            }
1453            VerificationMaterial::Base58 { public_key_base58 } => {
1454                // Convert Base58 to JWK
1455                let public_key_bytes = bs58::decode(public_key_base58).into_vec().map_err(|e| {
1456                    Error::Cryptography(format!("Failed to decode Base58 key: {}", e))
1457                })?;
1458
1459                // Assume Ed25519 for Base58 keys
1460                Ok(Self::new(
1461                    kid,
1462                    serde_json::json!({
1463                        "kty": "OKP",
1464                        "crv": "Ed25519",
1465                        "x": base64::engine::general_purpose::STANDARD.encode(public_key_bytes),
1466                    }),
1467                ))
1468            }
1469            VerificationMaterial::Multibase {
1470                public_key_multibase,
1471            } => {
1472                // Convert Multibase to JWK
1473                let (_, bytes) = multibase::decode(public_key_multibase).map_err(|e| {
1474                    Error::Cryptography(format!("Failed to decode Multibase key: {}", e))
1475                })?;
1476
1477                // Check if this is an Ed25519 key with multicodec prefix
1478                if bytes.len() >= 2 && bytes[0] == 0xed && bytes[1] == 0x01 {
1479                    // Strip multicodec prefix for Ed25519
1480                    let key_bytes = &bytes[2..];
1481                    Ok(Self::new(
1482                        kid,
1483                        serde_json::json!({
1484                            "kty": "OKP",
1485                            "crv": "Ed25519",
1486                            "x": base64::engine::general_purpose::STANDARD.encode(key_bytes),
1487                        }),
1488                    ))
1489                } else {
1490                    // Just use the bytes as is
1491                    Ok(Self::new(
1492                        kid,
1493                        serde_json::json!({
1494                            "kty": "OKP",
1495                            "crv": "Ed25519",
1496                            "x": base64::engine::general_purpose::STANDARD.encode(bytes),
1497                        }),
1498                    ))
1499                }
1500            }
1501        }
1502    }
1503}
1504
1505#[async_trait]
1506impl VerificationKey for PublicVerificationKey {
1507    fn key_id(&self) -> &str {
1508        &self.kid
1509    }
1510
1511    fn public_key_jwk(&self) -> Result<Value> {
1512        Ok(self.public_jwk.clone())
1513    }
1514
1515    async fn verify_signature(
1516        &self,
1517        payload: &[u8],
1518        signature: &[u8],
1519        protected_header: &JwsProtected,
1520    ) -> Result<bool> {
1521        let kty = self.public_jwk.get("kty").and_then(|v| v.as_str());
1522        let crv = self.public_jwk.get("crv").and_then(|v| v.as_str());
1523
1524        match (kty, crv, protected_header.alg.as_str()) {
1525            #[cfg(feature = "crypto-ed25519")]
1526            (Some("OKP"), Some("Ed25519"), "EdDSA") => {
1527                // Extract the public key
1528                let public_key_base64 = self
1529                    .public_jwk
1530                    .get("x")
1531                    .and_then(|v| v.as_str())
1532                    .ok_or_else(|| {
1533                        Error::Cryptography("Missing public key (x) in JWK".to_string())
1534                    })?;
1535
1536                // Decode the public key from base64
1537                let public_key_bytes = base64::engine::general_purpose::STANDARD
1538                    .decode(public_key_base64)
1539                    .map_err(|e| {
1540                        Error::Cryptography(format!("Failed to decode public key: {}", e))
1541                    })?;
1542
1543                // Ed25519 public keys must be exactly 32 bytes
1544                if public_key_bytes.len() != 32 {
1545                    return Err(Error::Cryptography(format!(
1546                        "Invalid Ed25519 public key length: {}, expected 32 bytes",
1547                        public_key_bytes.len()
1548                    )));
1549                }
1550
1551                // Create an Ed25519 verifying key
1552                let verifying_key = match VerifyingKey::try_from(public_key_bytes.as_slice()) {
1553                    Ok(key) => key,
1554                    Err(e) => {
1555                        return Err(Error::Cryptography(format!(
1556                            "Failed to create Ed25519 verifying key: {:?}",
1557                            e
1558                        )))
1559                    }
1560                };
1561
1562                // Verify the signature
1563                if signature.len() != 64 {
1564                    return Err(Error::Cryptography(format!(
1565                        "Invalid Ed25519 signature length: {}, expected 64 bytes",
1566                        signature.len()
1567                    )));
1568                }
1569
1570                let mut sig_bytes = [0u8; 64];
1571                sig_bytes.copy_from_slice(signature);
1572                let ed_signature = ed25519_dalek::Signature::from_bytes(&sig_bytes);
1573
1574                match verifying_key.verify(payload, &ed_signature) {
1575                    Ok(()) => Ok(true),
1576                    Err(_) => Ok(false),
1577                }
1578            }
1579            #[cfg(feature = "crypto-p256")]
1580            (Some("EC"), Some("P-256"), "ES256") => {
1581                // Extract the public key coordinates
1582                let x_b64 = self
1583                    .public_jwk
1584                    .get("x")
1585                    .and_then(|v| v.as_str())
1586                    .ok_or_else(|| {
1587                        Error::Cryptography("Missing x coordinate in JWK".to_string())
1588                    })?;
1589                let y_b64 = self
1590                    .public_jwk
1591                    .get("y")
1592                    .and_then(|v| v.as_str())
1593                    .ok_or_else(|| {
1594                        Error::Cryptography("Missing y coordinate in JWK".to_string())
1595                    })?;
1596
1597                // Decode the coordinates
1598                let x_bytes = base64::engine::general_purpose::STANDARD
1599                    .decode(x_b64)
1600                    .map_err(|e| {
1601                        Error::Cryptography(format!("Failed to decode x coordinate: {}", e))
1602                    })?;
1603                let y_bytes = base64::engine::general_purpose::STANDARD
1604                    .decode(y_b64)
1605                    .map_err(|e| {
1606                        Error::Cryptography(format!("Failed to decode y coordinate: {}", e))
1607                    })?;
1608
1609                // Create a P-256 encoded point from the coordinates
1610                let mut point_bytes = vec![0x04]; // Uncompressed point format
1611                point_bytes.extend_from_slice(&x_bytes);
1612                point_bytes.extend_from_slice(&y_bytes);
1613
1614                let encoded_point = P256EncodedPoint::from_bytes(&point_bytes).map_err(|e| {
1615                    Error::Cryptography(format!("Failed to create P-256 encoded point: {}", e))
1616                })?;
1617
1618                // This checks if the point is on the curve and returns the public key
1619                let public_key_opt = P256PublicKey::from_encoded_point(&encoded_point);
1620                if public_key_opt.is_none().into() {
1621                    return Err(Error::Cryptography("Invalid P-256 public key".to_string()));
1622                }
1623                let public_key = public_key_opt.unwrap();
1624
1625                // Parse the signature from DER format
1626                let p256_signature = P256Signature::from_der(signature).map_err(|e| {
1627                    Error::Cryptography(format!("Failed to parse P-256 signature: {:?}", e))
1628                })?;
1629
1630                // Verify the signature using P-256 ECDSA
1631                let verifier = p256::ecdsa::VerifyingKey::from(public_key);
1632                match verifier.verify(payload, &p256_signature) {
1633                    Ok(()) => Ok(true),
1634                    Err(_) => Ok(false),
1635                }
1636            }
1637            #[cfg(feature = "crypto-secp256k1")]
1638            (Some("EC"), Some("secp256k1"), "ES256K") => {
1639                // Extract the public key coordinates
1640                let x_b64 = self
1641                    .public_jwk
1642                    .get("x")
1643                    .and_then(|v| v.as_str())
1644                    .ok_or_else(|| {
1645                        Error::Cryptography("Missing x coordinate in JWK".to_string())
1646                    })?;
1647                let y_b64 = self
1648                    .public_jwk
1649                    .get("y")
1650                    .and_then(|v| v.as_str())
1651                    .ok_or_else(|| {
1652                        Error::Cryptography("Missing y coordinate in JWK".to_string())
1653                    })?;
1654
1655                // Decode the coordinates
1656                let x_bytes = base64::engine::general_purpose::STANDARD
1657                    .decode(x_b64)
1658                    .map_err(|e| {
1659                        Error::Cryptography(format!("Failed to decode x coordinate: {}", e))
1660                    })?;
1661                let y_bytes = base64::engine::general_purpose::STANDARD
1662                    .decode(y_b64)
1663                    .map_err(|e| {
1664                        Error::Cryptography(format!("Failed to decode y coordinate: {}", e))
1665                    })?;
1666
1667                // Create a secp256k1 public key from the coordinates
1668                let mut point_bytes = vec![0x04]; // Uncompressed point format
1669                point_bytes.extend_from_slice(&x_bytes);
1670                point_bytes.extend_from_slice(&y_bytes);
1671
1672                // Parse the verifying key from the SEC1 encoded point
1673                let verifier =
1674                    k256::ecdsa::VerifyingKey::from_sec1_bytes(&point_bytes).map_err(|e| {
1675                        Error::Cryptography(format!(
1676                            "Failed to create secp256k1 verifying key: {:?}",
1677                            e
1678                        ))
1679                    })?;
1680
1681                // Parse the signature from DER format
1682                let k256_signature = Secp256k1Signature::from_der(signature).map_err(|e| {
1683                    Error::Cryptography(format!("Failed to parse secp256k1 signature: {:?}", e))
1684                })?;
1685
1686                // Verify the signature
1687                match verifier.verify(payload, &k256_signature) {
1688                    Ok(()) => Ok(true),
1689                    Err(_) => Ok(false),
1690                }
1691            }
1692            // Unsupported algorithm or key type combination
1693            _ => Err(Error::Cryptography(format!(
1694                "Unsupported key type/algorithm combination for verification: kty={:?}, crv={:?}, alg={}",
1695                kty, crv, protected_header.alg
1696            ))),
1697        }
1698    }
1699}