1use hmac::{Hmac, Mac};
10use sha2::Sha256;
11
12use crate::contract::SymmetricCipher;
13use crate::encryptor::{Aes256CbcEncryptor, Aes256GcmEncryptor, SodiumXChaCha20Encryptor};
14use crate::error::{Error, Result};
15use crate::guomi::{Sm4CbcEncryptor, Zuc128Encryptor};
16use crate::manager::EncryptionManager;
17use crate::registry::Registry;
18
19const LABEL_AES_GCM: &[u8] = b"dgn:derive:aes-gcm";
21const LABEL_AES_CBC: &[u8] = b"dgn:derive:aes-cbc";
22const LABEL_SM4: &[u8] = b"dgn:derive:sm4";
23const LABEL_ZUC: &[u8] = b"dgn:derive:zuc";
24const LABEL_SODIUM: &[u8] = b"dgn:derive:sodium";
25
26pub struct EncryptionManagerFactory;
31
32impl EncryptionManagerFactory {
33 pub fn from_master_key(
34 master_key: &[u8],
35 default_identifier: &str,
36 ) -> Result<EncryptionManager> {
37 let master_key: [u8; 32] = master_key.try_into().map_err(|_| Error::InvalidMasterKey {
38 got: master_key.len(),
39 })?;
40
41 let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
42 registry.register(Box::new(Aes256GcmEncryptor::new(derive_subkey(
43 &master_key,
44 LABEL_AES_GCM,
45 ))));
46 registry.register(Box::new(Aes256CbcEncryptor::new(derive_subkey(
47 &master_key,
48 LABEL_AES_CBC,
49 ))));
50 registry.register(Box::new(SodiumXChaCha20Encryptor::new(derive_subkey(
51 &master_key,
52 LABEL_SODIUM,
53 ))));
54 registry.register(Box::new(Sm4CbcEncryptor::new(derive_subkey_16(
55 &master_key,
56 LABEL_SM4,
57 ))));
58 registry.register(Box::new(Zuc128Encryptor::new(derive_subkey_16(
59 &master_key,
60 LABEL_ZUC,
61 ))));
62
63 EncryptionManager::new(registry, default_identifier)
64 }
65}
66
67fn derive_subkey(master_key: &[u8; 32], label: &[u8]) -> [u8; 32] {
69 let mut mac = Hmac::<Sha256>::new_from_slice(master_key).expect("HMAC accepts any key size");
70 mac.update(label);
71 mac.finalize().into_bytes().into()
72}
73
74fn derive_subkey_16(master_key: &[u8; 32], label: &[u8]) -> [u8; 16] {
76 let full = derive_subkey(master_key, label);
77 full[..16].try_into().expect("slice is exactly 16 bytes")
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn factory_validates_master_key_and_default() {
86 assert_eq!(
87 EncryptionManagerFactory::from_master_key(&[0u8; 31], "aes-256-gcm").unwrap_err(),
88 Error::InvalidMasterKey { got: 31 }
89 );
90 assert_eq!(
91 EncryptionManagerFactory::from_master_key(&[0u8; 32], "aes-512").unwrap_err(),
92 Error::UnknownIdentifier {
93 kind: "symmetric cipher",
94 identifier: "aes-512".to_string()
95 }
96 );
97 }
98
99 #[test]
100 fn factory_registers_all_five_and_they_roundtrip() {
101 let manager =
102 EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
103 let expected = [
104 "aes-256-cbc-hmac",
105 "aes-256-gcm",
106 "sm4-cbc",
107 "sodium-xchacha20",
108 "zuc-128",
109 ];
110 assert_eq!(manager.registry().identifiers(), expected);
111
112 for identifier in expected {
113 let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
114 assert_eq!(
115 manager.decrypt_with(identifier, &blob).unwrap(),
116 b"cross-check"
117 );
118 }
119 }
120
121 #[test]
122 fn subkeys_are_independent_per_algorithm() {
123 let manager =
124 EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
125 let blob = manager.encrypt_with("aes-256-gcm", b"secret").unwrap();
126 assert!(manager.decrypt_with("aes-256-cbc-hmac", &blob).is_err());
128 assert!(manager.decrypt_with("sm4-cbc", &blob).is_err());
129 }
130}