Skip to main content

otplus_core/header/
mod.rs

1use serde::{Serialize, Deserialize, Serializer, Deserializer};
2use serde::ser::SerializeStruct;
3pub mod kdf;
4pub use kdf::Kdf;
5pub mod wraps;
6pub use wraps::Wraps;
7pub use wraps::WrapsWithUserPassphrase;
8pub use wraps::WrapsWithOsKeychain;
9pub use crate::cipher::RootKey;
10pub use crate::cipher::RootKeyMetadata;
11pub use crate::cipher::DerivedKey;
12pub use crate::cipher::Cipher;
13use chacha20poly1305::{Key, Nonce};
14use chrono::Utc;
15
16#[derive(Debug)]
17pub struct Header {
18  pub version: String,
19  pub kdf: Kdf,
20  pub wraps: Wraps,
21}
22
23impl Header {
24  pub fn new(version: String, kdf: Kdf, wraps: Wraps) -> Self {
25    Self { version, kdf, wraps }
26  }
27
28  pub fn get_root_key(&self, user_passphrase: String) -> RootKey {
29    let dk = DerivedKey::new(user_passphrase, self.kdf.salt.clone(), self.kdf.key_length);
30    let wrapped_dek = &self.wraps.from_user_passphrase.dek_wrapped;
31    let wrapped_nonce = &self.wraps.from_user_passphrase.nonce;
32    
33    let cipher = Cipher::new(dk.get_value(), Nonce::from_slice(&wrapped_nonce).clone());
34    let decrypted_dek = cipher.decrypt(&wrapped_dek);
35
36    RootKey { value: Key::from_slice(&decrypted_dek).to_owned(), metadata: RootKeyMetadata { created_at: Utc::now().timestamp_millis(), key_length: self.kdf.key_length } }
37  }
38}
39
40impl Serialize for Header {
41  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
42  where
43    S: Serializer,
44  {
45    let mut state = serializer.serialize_struct("Header", 3)?;
46    state.serialize_field("version", &self.version)?;
47    state.serialize_field("kdf", &self.kdf)?;
48    state.serialize_field("wraps", &self.wraps)?;
49    state.end()
50  }
51}
52
53impl<'de> Deserialize<'de> for Header {
54  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55  where
56    D: Deserializer<'de>,
57  {
58    #[derive(Deserialize)]
59    struct HeaderHelper {
60      version: String,
61      kdf: Kdf,
62      wraps: Wraps,
63    }
64
65    let helper = HeaderHelper::deserialize(deserializer)?;
66    Ok(Header::new(helper.version, helper.kdf, helper.wraps))
67  }
68}
69
70
71#[cfg(test)]
72mod header_tests {
73  use super::*;
74  use crate::cipher::{DerivedKey, RootKey};
75  use chacha20poly1305::{aead::{Aead, AeadCore, OsRng}, ChaCha20Poly1305, Key, KeyInit};
76
77  #[test]
78  fn test_header_serialize_deserialize() {
79    let kdf = Kdf::default();
80    let root_key = RootKey::default(); // This is a 32 byte key use for encrypting and decrypting user data
81    let user_passpharase = "Test@123"; // This is the user passphrase used to derive the key (to generate the key to decrypt root_key)
82    let os_keychain_raw_key = ChaCha20Poly1305::generate_key(&mut OsRng::default()); // This is a 32 byte key store in os keychain
83    let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng::default()); // Use the same nonce for both encryption and decryption
84
85    println!("Root key: {:?}", root_key);
86    // Use user passphrase -> encryption key -> encrypt root_key -> encode encrypted root_key -> wrapped dek
87    let dk: DerivedKey = DerivedKey::new(user_passpharase.to_string(), kdf.salt.clone(), 32);
88    
89    println!("Derived key: {:?}", dk);
90    let encrypted_root_key_from_user_passphrase = ChaCha20Poly1305::new(&Key::from_slice(&dk.get_value().as_slice())).encrypt(&nonce, root_key.value.as_slice()).unwrap();
91    println!("Encrypted root key from user passphrase: {:?}", encrypted_root_key_from_user_passphrase);
92    // Use os keychain -> encryption key -> encrypt root_key -> encode encrypted root_key -> wrapped dek
93    let encrypted_root_key_from_os_keychain = ChaCha20Poly1305::new(&Key::from_slice(&os_keychain_raw_key)).encrypt(&nonce, root_key.value.as_slice()).unwrap();
94    println!("Encrypted root key from os keychain: {:?}", encrypted_root_key_from_os_keychain);
95    let wraps = Wraps::new(
96      WrapsWithUserPassphrase::new(encrypted_root_key_from_user_passphrase, nonce.as_slice().to_vec()), 
97      WrapsWithOsKeychain::new(encrypted_root_key_from_os_keychain, nonce.as_slice().to_vec())
98    );
99    println!("Wraps: {:?}", wraps);
100    let header = Header::new("1.0.0".to_string(), kdf, wraps);
101    let serialized = serde_json::to_string(&header).unwrap();
102    println!("Serialized: {}", serialized);
103
104    let deserialized: Header = serde_json::from_str(&serialized).unwrap();
105    println!("Deserialized: {:?}", deserialized);
106    
107    assert_eq!(header.version, deserialized.version);
108    assert_eq!(header.kdf.algorithm, deserialized.kdf.algorithm);
109    assert_eq!(header.kdf.salt, deserialized.kdf.salt);
110    assert_eq!(header.kdf.key_length, deserialized.kdf.key_length);
111    assert_eq!(header.wraps.from_user_passphrase.dek_wrapped, deserialized.wraps.from_user_passphrase.dek_wrapped);
112    assert_eq!(header.wraps.from_user_passphrase.nonce, deserialized.wraps.from_user_passphrase.nonce);
113    assert_eq!(header.wraps.from_os_keychain.dek_wrapped, deserialized.wraps.from_os_keychain.dek_wrapped);
114    assert_eq!(header.wraps.from_os_keychain.nonce, deserialized.wraps.from_os_keychain.nonce);
115  }
116
117
118  #[test]
119  fn test_get_root_key_from_header() {
120    let json_string = r#"{"version":"1.0.0","kdf":{"algorithm":"argon2","salt":"NyBCbIDTytY/ax0D","key_length":32},"wraps":{"from_user_passphrase":{"dek_wrapped":"B7FWTyLOPzNP9FmkiN9Z9BO/jQ8qfbGIBrP9Ofa9ENaoubkj0/QynKRI9qUZIt3c","nonce":"GWvPXvL7Kt5H7VT4"},"from_os_keychain":{"dek_wrapped":"ED+cxDyI8+e/7DVYP+HmKhlPv+MmFk8o2Ajgn16u1u6Sd36eaR9sSFeWWm43MHGo","nonce":"GWvPXvL7Kt5H7VT4"}}}"#;
121    // Root key should be: Root Key { value: [66, 36, 250, 215, 250, 180, 233, 47, 193, 55, 184, 164, 196, 136, 144, 51, 67, 151, 5, 179, 65, 70, 236, 179, 142, 141, 10, 148, 119, 143, 41, 160], metadata: RootKeyMetadata { created_at: 1757053727401, key_length: 32 } }
122    let header: Header = serde_json::from_str(json_string).unwrap();
123
124    println!("Header: {:?}", header);
125
126    let root_key = header.get_root_key("Test@123".to_string());
127    println!("Root key: {:?}", root_key);
128
129    assert_eq!(root_key.value.as_slice(), [66, 36, 250, 215, 250, 180, 233, 47, 193, 55, 184, 164, 196, 136, 144, 51, 67, 151, 5, 179, 65, 70, 236, 179, 142, 141, 10, 148, 119, 143, 41, 160]);
130    assert_eq!(root_key.metadata.key_length, 32);
131  }
132}