Skip to main content

oxirs_did/kms/
mod.rs

1//! Cloud KMS Integration for DID key management
2//!
3//! Defines the [`KmsBackend`] abstraction and the [`KmsDidSigner`] high-level
4//! helper for signing DIDs / Verifiable Credentials through a KMS backend.
5//!
6//! # Security notice
7//!
8//! This crate does **not** ship a real cloud KMS integration (no AWS/GCP/Azure
9//! SDK backend exists). The only bundled backends —
10//! [`InsecureMockAwsKms`], [`InsecureMockGcpKms`], [`InsecureMockAzureKms`] —
11//! are **INSECURE test doubles**: they derive the "private" key
12//! deterministically from the (public, routinely logged) `key_id`, so anyone
13//! who knows the `key_id` can recompute the signing key. They provide **no key
14//! custody whatsoever** and MUST NOT be used in production.
15//!
16//! To keep them out of production builds they are compiled only when the
17//! non-default `insecure-mock-kms` Cargo feature is enabled, and their type
18//! names carry the `Insecure` prefix. Real deployments must supply their own
19//! [`KmsBackend`] implementation wired to an actual HSM / cloud KMS SDK.
20
21// v0.3.0: Simulated PKCS#11 HSM signer
22pub mod audit;
23pub mod pkcs11;
24
25use crate::{DidDocument, DidError, DidResult, VerificationMethod};
26#[cfg(feature = "insecure-mock-kms")]
27use hmac::{Hmac, Mac};
28#[cfg(feature = "insecure-mock-kms")]
29use sha2::Sha256;
30#[cfg(feature = "insecure-mock-kms")]
31use std::collections::HashMap;
32#[cfg(feature = "insecure-mock-kms")]
33use std::sync::RwLock;
34
35#[cfg(feature = "insecure-mock-kms")]
36type HmacSha256 = Hmac<Sha256>;
37
38// ─────────────────────────────────────────────────────────────────────────────
39// Algorithm / metadata types
40// ─────────────────────────────────────────────────────────────────────────────
41
42/// Cryptographic algorithm supported by the KMS
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum KmsAlgorithm {
45    Ed25519,
46    EcP256,
47    EcP384,
48    Rsa2048,
49    Rsa4096,
50}
51
52impl KmsAlgorithm {
53    /// Human-readable name
54    pub fn as_str(&self) -> &'static str {
55        match self {
56            Self::Ed25519 => "Ed25519",
57            Self::EcP256 => "EC_P256",
58            Self::EcP384 => "EC_P384",
59            Self::Rsa2048 => "RSA_2048",
60            Self::Rsa4096 => "RSA_4096",
61        }
62    }
63
64    /// Nominal key size in bytes
65    pub fn key_size_bytes(&self) -> usize {
66        match self {
67            Self::Ed25519 => 32,
68            Self::EcP256 => 32,
69            Self::EcP384 => 48,
70            Self::Rsa2048 => 256,
71            Self::Rsa4096 => 512,
72        }
73    }
74}
75
76/// Intended usage of a KMS key
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum KeyUsage {
79    SignVerify,
80    EncryptDecrypt,
81}
82
83/// Metadata about a managed key
84#[derive(Debug, Clone)]
85pub struct KmsKeyMetadata {
86    pub key_id: String,
87    pub algorithm: KmsAlgorithm,
88    /// Unix epoch seconds at creation
89    pub created_at: i64,
90    pub enabled: bool,
91    pub key_usage: KeyUsage,
92}
93
94// ─────────────────────────────────────────────────────────────────────────────
95// Internal key entry (stored per backend)
96// ─────────────────────────────────────────────────────────────────────────────
97
98#[cfg(feature = "insecure-mock-kms")]
99struct KmsKeyEntry {
100    metadata: KmsKeyMetadata,
101    /// Deterministically derived pseudo-private-key bytes (INSECURE mock only)
102    private_key_bytes: Vec<u8>,
103}
104
105// ─────────────────────────────────────────────────────────────────────────────
106// Backend trait
107// ─────────────────────────────────────────────────────────────────────────────
108
109/// Abstraction over a Cloud KMS backend
110pub trait KmsBackend: Send + Sync {
111    fn create_key(&self, key_id: &str, algorithm: KmsAlgorithm) -> DidResult<KmsKeyMetadata>;
112
113    fn sign(&self, key_id: &str, data: &[u8]) -> DidResult<Vec<u8>>;
114
115    fn verify(&self, key_id: &str, data: &[u8], signature: &[u8]) -> DidResult<bool>;
116
117    fn get_public_key(&self, key_id: &str) -> DidResult<Vec<u8>>;
118
119    fn delete_key(&self, key_id: &str) -> DidResult<()>;
120
121    fn list_keys(&self) -> DidResult<Vec<KmsKeyMetadata>>;
122}
123
124// ─────────────────────────────────────────────────────────────────────────────
125// Shared helper — HMAC-SHA256 signing (INSECURE mock backends only)
126// ─────────────────────────────────────────────────────────────────────────────
127
128/// Sign `data` with HMAC-SHA256 keyed by `private_key_bytes`, prepending `key_id`
129/// to the message for domain separation.
130#[cfg(feature = "insecure-mock-kms")]
131fn hmac_sign(private_key_bytes: &[u8], key_id: &str, data: &[u8]) -> Vec<u8> {
132    let mut mac =
133        HmacSha256::new_from_slice(private_key_bytes).expect("HMAC accepts any key length");
134    mac.update(key_id.as_bytes());
135    mac.update(data);
136    mac.finalize().into_bytes().to_vec()
137}
138
139/// Derive deterministic pseudo-private-key bytes from key_id and algorithm.
140///
141/// INSECURE: the result is fully determined by public inputs (`key_id` and the
142/// algorithm name), so it provides no key custody. Mock backends only.
143#[cfg(feature = "insecure-mock-kms")]
144fn derive_key_bytes(key_id: &str, algorithm: &KmsAlgorithm) -> Vec<u8> {
145    use sha2::Digest;
146    let mut hasher = sha2::Sha256::new();
147    hasher.update(key_id.as_bytes());
148    hasher.update(algorithm.as_str().as_bytes());
149    let base = hasher.finalize().to_vec();
150    // Extend to cover largest key size (512 bytes for RSA-4096)
151    let mut key = Vec::with_capacity(algorithm.key_size_bytes());
152    let mut counter: u8 = 0;
153    while key.len() < algorithm.key_size_bytes() {
154        let mut h2 = sha2::Sha256::new();
155        h2.update(&base);
156        h2.update([counter]);
157        key.extend_from_slice(&h2.finalize());
158        counter = counter.wrapping_add(1);
159    }
160    key.truncate(algorithm.key_size_bytes());
161    key
162}
163
164/// Derive a mock public key from private key bytes (first half, reversed)
165#[cfg(feature = "insecure-mock-kms")]
166fn derive_public_key(private_key_bytes: &[u8]) -> Vec<u8> {
167    let half = private_key_bytes.len() / 2;
168    let mut pub_key = private_key_bytes[..half.max(1)].to_vec();
169    pub_key.reverse();
170    pub_key
171}
172
173#[cfg(feature = "insecure-mock-kms")]
174fn now_unix() -> i64 {
175    std::time::SystemTime::now()
176        .duration_since(std::time::UNIX_EPOCH)
177        .map(|d| d.as_secs() as i64)
178        .unwrap_or(0)
179}
180
181// ─────────────────────────────────────────────────────────────────────────────
182// INSECURE mock backends (feature `insecure-mock-kms`, non-default)
183// ─────────────────────────────────────────────────────────────────────────────
184
185/// Explicit acknowledgement token required to construct an insecure mock KMS.
186///
187/// Obtaining one forces the caller to spell out, at the call site, that they
188/// understand the backend derives its signing key from the public `key_id` and
189/// provides no real key custody.
190#[cfg(feature = "insecure-mock-kms")]
191#[derive(Debug, Clone, Copy)]
192pub struct InsecureAck(());
193
194#[cfg(feature = "insecure-mock-kms")]
195impl InsecureAck {
196    /// Certify that you understand the mock KMS backends are NOT secure and are
197    /// for testing/development only.
198    pub fn acknowledge_insecure() -> Self {
199        InsecureAck(())
200    }
201}
202
203#[cfg(feature = "insecure-mock-kms")]
204macro_rules! impl_mock_kms {
205    ($name:ident, $display:literal) => {
206        #[doc = concat!("INSECURE in-memory mock ", $display, " KMS backend (TEST ONLY).")]
207        ///
208        /// Derives the signing key deterministically from the public `key_id`;
209        /// provides no key custody. Never use in production.
210        pub struct $name {
211            keys: RwLock<HashMap<String, KmsKeyEntry>>,
212        }
213
214        impl $name {
215            /// Construct an insecure mock backend. Requires an explicit
216            /// [`InsecureAck`] to force acknowledgement that this is not secure.
217            pub fn new(_ack: InsecureAck) -> Self {
218                Self {
219                    keys: RwLock::new(HashMap::new()),
220                }
221            }
222        }
223
224        impl KmsBackend for $name {
225            fn create_key(
226                &self,
227                key_id: &str,
228                algorithm: KmsAlgorithm,
229            ) -> DidResult<KmsKeyMetadata> {
230                let mut store = self
231                    .keys
232                    .write()
233                    .map_err(|e| DidError::InternalError(format!("KMS lock poisoned: {}", e)))?;
234
235                if store.contains_key(key_id) {
236                    return Err(DidError::InvalidKey(format!(
237                        "Key '{}' already exists in {} KMS",
238                        key_id, $display
239                    )));
240                }
241
242                let private_key_bytes = derive_key_bytes(key_id, &algorithm);
243                let metadata = KmsKeyMetadata {
244                    key_id: key_id.to_string(),
245                    algorithm,
246                    created_at: now_unix(),
247                    enabled: true,
248                    key_usage: KeyUsage::SignVerify,
249                };
250
251                let entry = KmsKeyEntry {
252                    metadata: metadata.clone(),
253                    private_key_bytes,
254                };
255                store.insert(key_id.to_string(), entry);
256                Ok(metadata)
257            }
258
259            fn sign(&self, key_id: &str, data: &[u8]) -> DidResult<Vec<u8>> {
260                let store = self
261                    .keys
262                    .read()
263                    .map_err(|e| DidError::InternalError(format!("KMS lock poisoned: {}", e)))?;
264                let entry = store.get(key_id).ok_or_else(|| {
265                    DidError::KeyNotFound(format!("Key '{}' not found in {} KMS", key_id, $display))
266                })?;
267                if !entry.metadata.enabled {
268                    return Err(DidError::SigningFailed(format!(
269                        "Key '{}' is disabled",
270                        key_id
271                    )));
272                }
273                Ok(hmac_sign(&entry.private_key_bytes, key_id, data))
274            }
275
276            fn verify(&self, key_id: &str, data: &[u8], signature: &[u8]) -> DidResult<bool> {
277                let expected = self.sign(key_id, data)?;
278                Ok(expected == signature)
279            }
280
281            fn get_public_key(&self, key_id: &str) -> DidResult<Vec<u8>> {
282                let store = self
283                    .keys
284                    .read()
285                    .map_err(|e| DidError::InternalError(format!("KMS lock poisoned: {}", e)))?;
286                let entry = store.get(key_id).ok_or_else(|| {
287                    DidError::KeyNotFound(format!("Key '{}' not found in {} KMS", key_id, $display))
288                })?;
289                Ok(derive_public_key(&entry.private_key_bytes))
290            }
291
292            fn delete_key(&self, key_id: &str) -> DidResult<()> {
293                let mut store = self
294                    .keys
295                    .write()
296                    .map_err(|e| DidError::InternalError(format!("KMS lock poisoned: {}", e)))?;
297                store
298                    .remove(key_id)
299                    .ok_or_else(|| {
300                        DidError::KeyNotFound(format!(
301                            "Key '{}' not found in {} KMS",
302                            key_id, $display
303                        ))
304                    })
305                    .map(|_| ())
306            }
307
308            fn list_keys(&self) -> DidResult<Vec<KmsKeyMetadata>> {
309                let store = self
310                    .keys
311                    .read()
312                    .map_err(|e| DidError::InternalError(format!("KMS lock poisoned: {}", e)))?;
313                let mut list: Vec<KmsKeyMetadata> =
314                    store.values().map(|e| e.metadata.clone()).collect();
315                // Sort by key_id for deterministic output
316                list.sort_by(|a, b| a.key_id.cmp(&b.key_id));
317                Ok(list)
318            }
319        }
320    };
321}
322
323#[cfg(feature = "insecure-mock-kms")]
324impl_mock_kms!(InsecureMockAwsKms, "AWS");
325#[cfg(feature = "insecure-mock-kms")]
326impl_mock_kms!(InsecureMockGcpKms, "GCP");
327#[cfg(feature = "insecure-mock-kms")]
328impl_mock_kms!(InsecureMockAzureKms, "Azure");
329
330// ─────────────────────────────────────────────────────────────────────────────
331// Provider enum + factory (INSECURE mocks only)
332// ─────────────────────────────────────────────────────────────────────────────
333
334/// Select which insecure cloud-provider mock to instantiate.
335#[cfg(feature = "insecure-mock-kms")]
336pub enum KmsProvider {
337    InsecureMockAws,
338    InsecureMockGcp,
339    InsecureMockAzure,
340}
341
342/// Create an INSECURE mock KMS backend for the given provider.
343///
344/// Requires an explicit [`InsecureAck`]; these backends are for testing only
345/// (see the module-level security notice).
346#[cfg(feature = "insecure-mock-kms")]
347pub fn create_insecure_mock_kms(provider: KmsProvider, ack: InsecureAck) -> Box<dyn KmsBackend> {
348    match provider {
349        KmsProvider::InsecureMockAws => Box::new(InsecureMockAwsKms::new(ack)),
350        KmsProvider::InsecureMockGcp => Box::new(InsecureMockGcpKms::new(ack)),
351        KmsProvider::InsecureMockAzure => Box::new(InsecureMockAzureKms::new(ack)),
352    }
353}
354
355// ─────────────────────────────────────────────────────────────────────────────
356// KmsDidSigner — high-level DID operations backed by a KMS
357// ─────────────────────────────────────────────────────────────────────────────
358
359/// Uses a KMS backend to sign DIDs and Verifiable Credentials
360pub struct KmsDidSigner {
361    backend: Box<dyn KmsBackend>,
362    key_id: String,
363}
364
365impl KmsDidSigner {
366    pub fn new(backend: Box<dyn KmsBackend>, key_id: &str) -> Self {
367        Self {
368            backend,
369            key_id: key_id.to_string(),
370        }
371    }
372
373    /// Build a minimal DID Document with a verification method whose public key
374    /// is derived from the KMS-managed key.
375    pub fn create_did_document(&self, did: &str) -> DidResult<DidDocument> {
376        let public_key = self.backend.get_public_key(&self.key_id)?;
377        let key_fragment = format!("{}#kms-key-0", did);
378
379        let vm = VerificationMethod::ed25519(&key_fragment, did, &public_key);
380
381        use crate::did::document::{DidDocument as DocType, VerificationRelationship};
382        use crate::Did;
383
384        let did_obj = Did::new(did)?;
385        let mut doc = DocType::new(did_obj);
386        doc.verification_method.push(vm);
387        doc.authentication
388            .push(VerificationRelationship::Reference(key_fragment.clone()));
389        doc.assertion_method
390            .push(VerificationRelationship::Reference(key_fragment));
391
392        Ok(doc)
393    }
394
395    /// Sign a JSON credential by appending a `proof` object with the HMAC
396    /// signature (base64url encoded) and the KMS key reference.
397    pub fn sign_credential(&self, credential: &serde_json::Value) -> DidResult<serde_json::Value> {
398        let serialized = serde_json::to_vec(credential)
399            .map_err(|e| DidError::SerializationError(e.to_string()))?;
400
401        let sig = self.backend.sign(&self.key_id, &serialized)?;
402        use base64::engine::general_purpose::URL_SAFE_NO_PAD;
403        use base64::Engine;
404        let sig_b64 = URL_SAFE_NO_PAD.encode(&sig);
405
406        let mut signed = credential.clone();
407        if let Some(obj) = signed.as_object_mut() {
408            obj.insert(
409                "proof".to_string(),
410                serde_json::json!({
411                    "type": "KmsHmacSignature2024",
412                    "verificationMethod": self.key_id,
413                    "signatureValue": sig_b64
414                }),
415            );
416        }
417        Ok(signed)
418    }
419
420    /// Verify a credential that was signed by `sign_credential`.
421    pub fn verify_credential(&self, signed_credential: &serde_json::Value) -> DidResult<bool> {
422        use base64::engine::general_purpose::URL_SAFE_NO_PAD;
423        use base64::Engine;
424
425        // Extract and remove the proof to reconstruct original payload
426        let proof = signed_credential
427            .get("proof")
428            .ok_or_else(|| DidError::InvalidProof("Missing proof field".to_string()))?;
429
430        let sig_b64 = proof
431            .get("signatureValue")
432            .and_then(|v| v.as_str())
433            .ok_or_else(|| DidError::InvalidProof("Missing signatureValue".to_string()))?;
434
435        let signature = URL_SAFE_NO_PAD
436            .decode(sig_b64)
437            .map_err(|e| DidError::InvalidProof(format!("Invalid base64: {}", e)))?;
438
439        // Reconstruct credential without proof
440        let mut without_proof = signed_credential.clone();
441        if let Some(obj) = without_proof.as_object_mut() {
442            obj.remove("proof");
443        }
444        let serialized = serde_json::to_vec(&without_proof)
445            .map_err(|e| DidError::SerializationError(e.to_string()))?;
446
447        self.backend.verify(&self.key_id, &serialized, &signature)
448    }
449}
450
451// ─────────────────────────────────────────────────────────────────────────────
452// Tests
453// ─────────────────────────────────────────────────────────────────────────────
454
455#[cfg(all(test, feature = "insecure-mock-kms"))]
456mod tests {
457    use super::*;
458
459    fn ack() -> InsecureAck {
460        InsecureAck::acknowledge_insecure()
461    }
462
463    // ── AWS KMS ──────────────────────────────────────────────────────────────
464
465    #[test]
466    fn test_aws_create_ed25519_key() {
467        let kms = InsecureMockAwsKms::new(ack());
468        let meta = kms.create_key("my-key", KmsAlgorithm::Ed25519).unwrap();
469        assert_eq!(meta.key_id, "my-key");
470        assert_eq!(meta.algorithm.as_str(), "Ed25519");
471        assert!(meta.enabled);
472    }
473
474    #[test]
475    fn test_aws_create_duplicate_key_fails() {
476        let kms = InsecureMockAwsKms::new(ack());
477        kms.create_key("dup", KmsAlgorithm::Ed25519).unwrap();
478        assert!(kms.create_key("dup", KmsAlgorithm::Ed25519).is_err());
479    }
480
481    #[test]
482    fn test_aws_sign_and_verify() {
483        let kms = InsecureMockAwsKms::new(ack());
484        kms.create_key("signing-key", KmsAlgorithm::EcP256).unwrap();
485
486        let data = b"hello world";
487        let sig = kms.sign("signing-key", data).unwrap();
488        assert!(!sig.is_empty());
489
490        let valid = kms.verify("signing-key", data, &sig).unwrap();
491        assert!(valid);
492    }
493
494    #[test]
495    fn test_aws_verify_wrong_data_fails() {
496        let kms = InsecureMockAwsKms::new(ack());
497        kms.create_key("k1", KmsAlgorithm::Ed25519).unwrap();
498
499        let sig = kms.sign("k1", b"original").unwrap();
500        let valid = kms.verify("k1", b"tampered", &sig).unwrap();
501        assert!(!valid);
502    }
503
504    #[test]
505    fn test_aws_sign_missing_key_error() {
506        let kms = InsecureMockAwsKms::new(ack());
507        assert!(kms.sign("nonexistent", b"data").is_err());
508    }
509
510    #[test]
511    fn test_aws_get_public_key() {
512        let kms = InsecureMockAwsKms::new(ack());
513        kms.create_key("pk-key", KmsAlgorithm::Ed25519).unwrap();
514        let pub_key = kms.get_public_key("pk-key").unwrap();
515        assert!(!pub_key.is_empty());
516    }
517
518    #[test]
519    fn test_aws_delete_key() {
520        let kms = InsecureMockAwsKms::new(ack());
521        kms.create_key("del-key", KmsAlgorithm::Ed25519).unwrap();
522        kms.delete_key("del-key").unwrap();
523        assert!(kms.sign("del-key", b"data").is_err());
524    }
525
526    #[test]
527    fn test_aws_delete_missing_key_error() {
528        let kms = InsecureMockAwsKms::new(ack());
529        assert!(kms.delete_key("ghost").is_err());
530    }
531
532    #[test]
533    fn test_aws_list_keys() {
534        let kms = InsecureMockAwsKms::new(ack());
535        kms.create_key("a", KmsAlgorithm::Ed25519).unwrap();
536        kms.create_key("b", KmsAlgorithm::EcP256).unwrap();
537
538        let keys = kms.list_keys().unwrap();
539        assert_eq!(keys.len(), 2);
540        // Sorted by key_id
541        assert_eq!(keys[0].key_id, "a");
542        assert_eq!(keys[1].key_id, "b");
543    }
544
545    #[test]
546    fn test_aws_all_algorithms_create() {
547        let kms = InsecureMockAwsKms::new(ack());
548        kms.create_key("ed", KmsAlgorithm::Ed25519).unwrap();
549        kms.create_key("p256", KmsAlgorithm::EcP256).unwrap();
550        kms.create_key("p384", KmsAlgorithm::EcP384).unwrap();
551        kms.create_key("rsa2048", KmsAlgorithm::Rsa2048).unwrap();
552        kms.create_key("rsa4096", KmsAlgorithm::Rsa4096).unwrap();
553
554        let keys = kms.list_keys().unwrap();
555        assert_eq!(keys.len(), 5);
556    }
557
558    // ── GCP KMS ──────────────────────────────────────────────────────────────
559
560    #[test]
561    fn test_gcp_create_and_sign() {
562        let kms = InsecureMockGcpKms::new(ack());
563        kms.create_key("gcp-key", KmsAlgorithm::EcP256).unwrap();
564
565        let sig = kms.sign("gcp-key", b"gcp-data").unwrap();
566        let valid = kms.verify("gcp-key", b"gcp-data", &sig).unwrap();
567        assert!(valid);
568    }
569
570    #[test]
571    fn test_gcp_list_empty() {
572        let kms = InsecureMockGcpKms::new(ack());
573        let keys = kms.list_keys().unwrap();
574        assert!(keys.is_empty());
575    }
576
577    #[test]
578    fn test_gcp_public_key_differs_from_private() {
579        let kms = InsecureMockGcpKms::new(ack());
580        kms.create_key("gcp-pk", KmsAlgorithm::Ed25519).unwrap();
581
582        let pub_key = kms.get_public_key("gcp-pk").unwrap();
583        // Public key is derived from private key — should not be empty
584        assert!(!pub_key.is_empty());
585    }
586
587    #[test]
588    fn test_gcp_delete_and_recreate() {
589        let kms = InsecureMockGcpKms::new(ack());
590        kms.create_key("reuse", KmsAlgorithm::Ed25519).unwrap();
591        kms.delete_key("reuse").unwrap();
592        // Should succeed after deletion
593        kms.create_key("reuse", KmsAlgorithm::Ed25519).unwrap();
594    }
595
596    // ── Azure KMS ────────────────────────────────────────────────────────────
597
598    #[test]
599    fn test_azure_create_and_sign() {
600        let kms = InsecureMockAzureKms::new(ack());
601        kms.create_key("az-key", KmsAlgorithm::Rsa2048).unwrap();
602
603        let sig = kms.sign("az-key", b"azure-data").unwrap();
604        let valid = kms.verify("az-key", b"azure-data", &sig).unwrap();
605        assert!(valid);
606    }
607
608    #[test]
609    fn test_azure_wrong_signature() {
610        let kms = InsecureMockAzureKms::new(ack());
611        kms.create_key("az2", KmsAlgorithm::EcP256).unwrap();
612
613        let bad_sig = vec![0u8; 32];
614        let valid = kms.verify("az2", b"some-data", &bad_sig).unwrap();
615        assert!(!valid);
616    }
617
618    #[test]
619    fn test_azure_list_after_delete() {
620        let kms = InsecureMockAzureKms::new(ack());
621        kms.create_key("x", KmsAlgorithm::Ed25519).unwrap();
622        kms.create_key("y", KmsAlgorithm::Ed25519).unwrap();
623        kms.delete_key("x").unwrap();
624
625        let keys = kms.list_keys().unwrap();
626        assert_eq!(keys.len(), 1);
627        assert_eq!(keys[0].key_id, "y");
628    }
629
630    // ── Factory ──────────────────────────────────────────────────────────────
631
632    #[test]
633    fn test_create_mock_kms_aws() {
634        let kms = create_insecure_mock_kms(KmsProvider::InsecureMockAws, ack());
635        kms.create_key("factory-aws", KmsAlgorithm::Ed25519)
636            .unwrap();
637        let keys = kms.list_keys().unwrap();
638        assert_eq!(keys.len(), 1);
639    }
640
641    #[test]
642    fn test_create_mock_kms_gcp() {
643        let kms = create_insecure_mock_kms(KmsProvider::InsecureMockGcp, ack());
644        kms.create_key("factory-gcp", KmsAlgorithm::EcP256).unwrap();
645        let keys = kms.list_keys().unwrap();
646        assert_eq!(keys.len(), 1);
647    }
648
649    #[test]
650    fn test_create_mock_kms_azure() {
651        let kms = create_insecure_mock_kms(KmsProvider::InsecureMockAzure, ack());
652        kms.create_key("factory-azure", KmsAlgorithm::Rsa2048)
653            .unwrap();
654        let keys = kms.list_keys().unwrap();
655        assert_eq!(keys.len(), 1);
656    }
657
658    // ── KmsDidSigner ─────────────────────────────────────────────────────────
659
660    #[test]
661    fn test_kms_did_signer_create_document() {
662        let backend = create_insecure_mock_kms(KmsProvider::InsecureMockAws, ack());
663        backend
664            .create_key("did-signer-key", KmsAlgorithm::Ed25519)
665            .unwrap();
666
667        let signer = KmsDidSigner::new(backend, "did-signer-key");
668        let did_str = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
669        let doc = signer.create_did_document(did_str).unwrap();
670
671        assert_eq!(doc.id.as_str(), did_str);
672        assert_eq!(doc.verification_method.len(), 1);
673        assert!(!doc.authentication.is_empty());
674    }
675
676    #[test]
677    fn test_kms_did_signer_sign_credential() {
678        let backend = create_insecure_mock_kms(KmsProvider::InsecureMockGcp, ack());
679        backend.create_key("vc-key", KmsAlgorithm::EcP256).unwrap();
680
681        let signer = KmsDidSigner::new(backend, "vc-key");
682        let credential = serde_json::json!({
683            "@context": ["https://www.w3.org/2018/credentials/v1"],
684            "type": ["VerifiableCredential"],
685            "issuer": "did:example:issuer",
686            "credentialSubject": { "id": "did:example:subject", "name": "Alice" }
687        });
688
689        let signed = signer.sign_credential(&credential).unwrap();
690        assert!(signed.get("proof").is_some());
691        let proof = signed.get("proof").unwrap();
692        assert_eq!(proof["type"].as_str().unwrap(), "KmsHmacSignature2024");
693        assert!(proof.get("signatureValue").is_some());
694    }
695
696    #[test]
697    fn test_kms_did_signer_verify_credential() {
698        let backend = create_insecure_mock_kms(KmsProvider::InsecureMockAzure, ack());
699        backend
700            .create_key("verify-key", KmsAlgorithm::Ed25519)
701            .unwrap();
702
703        let signer = KmsDidSigner::new(backend, "verify-key");
704        let credential = serde_json::json!({
705            "id": "http://example.com/vc/1",
706            "type": ["VerifiableCredential"],
707            "issuer": "did:example:issuer"
708        });
709
710        let signed = signer.sign_credential(&credential).unwrap();
711        let valid = signer.verify_credential(&signed).unwrap();
712        assert!(valid);
713    }
714
715    #[test]
716    fn test_kms_did_signer_tampered_credential_fails() {
717        let backend = create_insecure_mock_kms(KmsProvider::InsecureMockAws, ack());
718        backend
719            .create_key("tamper-key", KmsAlgorithm::EcP256)
720            .unwrap();
721
722        let signer = KmsDidSigner::new(backend, "tamper-key");
723        let credential = serde_json::json!({
724            "type": ["VerifiableCredential"],
725            "issuer": "did:example:issuer"
726        });
727
728        let mut signed = signer.sign_credential(&credential).unwrap();
729        // Tamper with the credential payload
730        if let Some(obj) = signed.as_object_mut() {
731            obj.insert("issuer".to_string(), serde_json::json!("did:evil:attacker"));
732        }
733
734        let valid = signer.verify_credential(&signed).unwrap();
735        assert!(!valid);
736    }
737
738    #[test]
739    fn test_kms_did_signer_missing_proof_error() {
740        let backend = create_insecure_mock_kms(KmsProvider::InsecureMockAws, ack());
741        backend
742            .create_key("no-proof-key", KmsAlgorithm::Ed25519)
743            .unwrap();
744
745        let signer = KmsDidSigner::new(backend, "no-proof-key");
746        let credential = serde_json::json!({ "type": "VerifiableCredential" });
747        // Not signed — no proof field
748        assert!(signer.verify_credential(&credential).is_err());
749    }
750
751    #[test]
752    fn test_key_metadata_fields() {
753        let kms = InsecureMockAwsKms::new(ack());
754        let meta = kms.create_key("meta-test", KmsAlgorithm::EcP384).unwrap();
755
756        assert_eq!(meta.key_id, "meta-test");
757        assert_eq!(meta.algorithm.as_str(), "EC_P384");
758        assert!(matches!(meta.key_usage, KeyUsage::SignVerify));
759        assert!(meta.created_at > 0);
760        assert!(meta.enabled);
761    }
762
763    #[test]
764    fn test_algorithm_key_sizes() {
765        assert_eq!(KmsAlgorithm::Ed25519.key_size_bytes(), 32);
766        assert_eq!(KmsAlgorithm::EcP256.key_size_bytes(), 32);
767        assert_eq!(KmsAlgorithm::EcP384.key_size_bytes(), 48);
768        assert_eq!(KmsAlgorithm::Rsa2048.key_size_bytes(), 256);
769        assert_eq!(KmsAlgorithm::Rsa4096.key_size_bytes(), 512);
770    }
771
772    #[test]
773    fn test_signatures_are_deterministic() {
774        let kms = InsecureMockAwsKms::new(ack());
775        kms.create_key("det-key", KmsAlgorithm::Ed25519).unwrap();
776
777        let sig1 = kms.sign("det-key", b"same data").unwrap();
778        let sig2 = kms.sign("det-key", b"same data").unwrap();
779        assert_eq!(sig1, sig2);
780    }
781
782    #[test]
783    fn test_different_keys_produce_different_signatures() {
784        let kms = InsecureMockAwsKms::new(ack());
785        kms.create_key("key-a", KmsAlgorithm::Ed25519).unwrap();
786        kms.create_key("key-b", KmsAlgorithm::Ed25519).unwrap();
787
788        let sig_a = kms.sign("key-a", b"data").unwrap();
789        let sig_b = kms.sign("key-b", b"data").unwrap();
790        assert_ne!(sig_a, sig_b);
791    }
792}