ctoolbox/storage/user/
auth.rs

1use crate::utilities::password::Password;
2use anyhow::Result;
3use argon2::Argon2;
4use argon2::password_hash::rand_core::OsRng;
5use argon2::password_hash::{PasswordHash, SaltString};
6use serde::{Deserialize, Serialize};
7use zeroize::ZeroizeOnDrop;
8
9/// Simple secret holder that zeroes memory on drop.
10#[derive(Debug, Default, ZeroizeOnDrop)]
11pub struct Secret {
12    bytes: Vec<u8>,
13}
14impl Secret {
15    pub fn new(bytes: Vec<u8>) -> Self {
16        Secret { bytes }
17    }
18    fn as_slice(&self) -> &[u8] {
19        &self.bytes
20    }
21}
22
23/// KEK derivation parameters to be stored in `users/key_encryption_key_params.redb`
24/// For now this is a stub structure that you can later wire to Argon2id properly.
25#[derive(Debug, Clone, Serialize, Deserialize, ZeroizeOnDrop, PartialEq)]
26pub struct KekParams {
27    pub salt: Vec<u8>,
28    // Argon2id cost parameters
29    pub m_cost: u32,
30    pub t_cost: u32,
31    pub p_cost: u32,
32}
33
34/// Derive a Key Encryption Key (KEK) from a password using Argon2id.
35/// Returns the derived KEK and the salt used in the derivation.
36/// The KEK is suitable for AES-256 encryption.
37/// It's not clear if this is all correct. See:
38/// <https://docs.rs/argon2/latest/argon2/#key-derivation>
39/// <https://rustcrypto.org/key-derivation/index.html>
40/// <https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html#key-encryption-keys>
41/// (last two are basically blank.)
42pub fn derive_kek(password: &Password) -> (Vec<u8>, KekParams) {
43    // If in debug or test and password is TEST_USER_PASS, use hardcoded PHC
44    #[cfg(any(debug_assertions, test))]
45    {
46        if password.password == crate::storage::user::TEST_USER_PASS.as_bytes()
47        {
48            // Hardcoded PHC string
49
50            use crate::storage::user::TEST_USER_PHC;
51            let parsed =
52                PasswordHash::new(TEST_USER_PHC).expect("Failed to parse PHC");
53            let salt_bytes = &mut [0u8; 16];
54            parsed
55                .salt
56                .expect("Could not decode test password salt")
57                .decode_b64(salt_bytes)
58                .expect("Failed to decode salt");
59            let output_key_material = parsed.hash.unwrap().as_bytes().to_vec();
60            return (
61                output_key_material,
62                KekParams {
63                    salt: salt_bytes.to_vec(),
64                    m_cost: parsed.params.get("m").map_or(19456, |v| {
65                        u32::try_from(v.decimal().unwrap_or(19456))
66                            .unwrap_or(19456)
67                    }),
68                    t_cost: parsed.params.get("t").map_or(2, |v| {
69                        u32::try_from(v.decimal().unwrap_or(2)).unwrap_or(2)
70                    }),
71                    p_cost: parsed.params.get("p").map_or(1, |v| {
72                        u32::try_from(v.decimal().unwrap_or(1)).unwrap_or(1)
73                    }),
74                },
75            );
76        }
77    }
78    // 32 * 8 = 256 bits which is suitable for AES-256
79    let mut output_key_material = vec![0u8; 32];
80    let salt = SaltString::generate(&mut OsRng);
81    // Recommended length from password-hash 0.5.0 salt.rs is 16 bytes
82    let mut salt_bytes = [0u8; 16];
83    salt.decode_b64(&mut salt_bytes)
84        .expect("Failed to decode salt");
85    let hasher = Argon2::default();
86    let params = hasher.params();
87    hasher
88        .hash_password_into(
89            &password.password,
90            &salt_bytes,
91            &mut output_key_material,
92        )
93        .expect("Failed to derive KEK");
94    (
95        output_key_material,
96        KekParams {
97            salt: salt_bytes.to_vec(),
98            m_cost: params.m_cost(),
99            t_cost: params.t_cost(),
100            p_cost: params.p_cost(),
101        },
102    )
103}
104
105/// TODO Stub: Generate DEK
106/// <https://developers.google.com/tink/client-side-encryption>
107pub fn generate_dek() -> Vec<u8> {
108    let mut out = Vec::with_capacity(32);
109    out.extend_from_slice(uuid::Uuid::new_v4().as_bytes());
110    out.extend_from_slice(uuid::Uuid::new_v4().as_bytes());
111    out
112}
113
114/// TODO Stub AEAD wrappers: no-op "encryption"/"wrapping".
115pub fn wrap_key(_kek: &[u8], dek: &[u8], _aad: &[u8]) -> Vec<u8> {
116    dek.to_vec()
117}
118pub fn unwrap_key(_kek: &[u8], wrapped: &[u8], _aad: &[u8]) -> Result<Vec<u8>> {
119    Ok(wrapped.to_vec())
120}
121
122pub fn seal_bytes(_key: &[u8], _aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
123    plaintext.to_vec()
124}
125pub fn open_bytes(
126    _key: &[u8],
127    _aad: &[u8],
128    ciphertext: &[u8],
129) -> Result<Vec<u8>> {
130    Ok(ciphertext.to_vec())
131}