1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Api functions related to the argon2id password hashing algorithm.
//!
//! Argon2 is a password hashing scheme designed to be both cpu and
//! memory hard. You can configure this hardness with the `ops_limit`
//! for how difficult it is cpu-wise, and `mem_limit` for how much
//! memory it will use during the operation. For tests, please use
//! `MIN`, otherwise your tests will take a long time.
//!
//! #### Example
//!
//! ```
//! # #[tokio::main]
//! # async fn main() {
//! // generate salt
//! let salt = sodoken::BufWriteSized::new_no_lock();
//! sodoken::random::bytes_buf(salt.clone()).await.unwrap();
//! let salt = salt.to_read_sized();
//!
//! // generate the pw hash
//! let hash = <sodoken::BufWriteSized<32>>::new_no_lock();
//! sodoken::hash::argon2id::hash(
//!     hash.clone(),
//!     b"my-passphrase".to_vec(),
//!     salt.clone(),
//!     sodoken::hash::argon2id::OPSLIMIT_MIN,
//!     sodoken::hash::argon2id::MEMLIMIT_MIN,
//! ).await.unwrap();
//! let hash = hash.to_read_sized();
//! # }
//! ```

use crate::*;

/// minimum hash length for argon2id pwhash
pub const BYTES_MIN: usize =
    libsodium_sys::crypto_pwhash_argon2id_BYTES_MIN as usize;

/// salt buffer length for argon2id pwhash
pub const SALTBYTES: usize =
    libsodium_sys::crypto_pwhash_argon2id_SALTBYTES as usize;

/// minimum password length for argon2id pwhash
pub const PASSWD_MIN: usize =
    libsodium_sys::crypto_pwhash_argon2id_PASSWD_MIN as usize;

/// maximum password length for argon2id pwhash
pub const PASSWD_MAX: usize =
    libsodium_sys::crypto_pwhash_argon2id_PASSWD_MAX as usize;

/// argon2id pwhash min ops limit
pub const OPSLIMIT_MIN: u32 =
    libsodium_sys::crypto_pwhash_argon2id_OPSLIMIT_MIN as u32;

/// argon2id pwhash interactive ops limit
pub const OPSLIMIT_INTERACTIVE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE as u32;

/// argon2id pwhash moderate ops limit
pub const OPSLIMIT_MODERATE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_OPSLIMIT_MODERATE as u32;

/// argon2id pwhash sensitive ops limit
pub const OPSLIMIT_SENSITIVE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE as u32;

/// argon2id pwhash max ops limit
pub const OPSLIMIT_MAX: u32 =
    libsodium_sys::crypto_pwhash_argon2id_OPSLIMIT_MAX as u32;

/// argon2id pwhash min mem limit
pub const MEMLIMIT_MIN: u32 =
    libsodium_sys::crypto_pwhash_argon2id_MEMLIMIT_MIN as u32;

/// argon2id pwhash interactive mem limit
pub const MEMLIMIT_INTERACTIVE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE as u32;

/// argon2id pwhash moderate mem limit
pub const MEMLIMIT_MODERATE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_MEMLIMIT_MODERATE as u32;

/// argon2id pwhash sensitive mem limit
pub const MEMLIMIT_SENSITIVE: u32 =
    libsodium_sys::crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE as u32;

/// argon2id13 password hashing scheme
pub async fn hash<H, P, S>(
    hash: H,
    passphrase: P,
    salt: S,
    ops_limit: u32,
    mem_limit: u32,
) -> SodokenResult<()>
where
    H: Into<BufWrite> + 'static + Send,
    P: Into<BufRead> + 'static + Send,
    S: Into<BufReadSized<SALTBYTES>> + 'static + Send,
{
    let hash = hash.into();
    let passphrase = passphrase.into();
    let salt = salt.into();
    tokio_exec_blocking(move || {
        // argon is designed to take a long time...
        // we don't want to hold these locks open for a long time,
        // so let's do some careful shuffling to only hold locks
        // on local single buf instances.

        let tmp_hash = BufWrite::new_mem_locked(hash.len())?;
        let tmp_passphrase = BufWrite::deep_clone_mem_locked(passphrase)?;
        let tmp_salt = BufWriteSized::deep_clone_no_lock(salt);

        let mut tmp_hash_lock = tmp_hash.write_lock();
        let tmp_passphrase_lock = tmp_passphrase.read_lock();
        let tmp_salt_lock = tmp_salt.read_lock_sized();

        safe::sodium::crypto_pwhash_argon2id(
            &mut tmp_hash_lock,
            &tmp_passphrase_lock,
            &tmp_salt_lock,
            ops_limit,
            mem_limit,
        )?;

        hash.write_lock().copy_from_slice(&tmp_hash_lock);

        Ok(())
    })
    .await?
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[tokio::test(flavor = "multi_thread")]
    async fn argon2id() -> SodokenResult<()> {
        let pass = BufRead::new_no_lock(b"my passphrase");
        let salt: BufReadSized<{ hash::argon2id::SALTBYTES }> =
            BufReadSized::new_no_lock([0xdb; hash::argon2id::SALTBYTES]);
        let hash = BufWrite::new_no_lock(hash::argon2id::BYTES_MIN);
        hash::argon2id::hash(
            hash.clone(),
            pass,
            salt,
            hash::argon2id::OPSLIMIT_INTERACTIVE,
            hash::argon2id::MEMLIMIT_INTERACTIVE,
        )
        .await?;
        assert_eq!(
            "[236, 88, 226, 164, 99, 188, 201, 149, 83, 218, 26, 28, 193, 215, 226, 72]",
            format!("{:?}", &*hash.read_lock()),
        );
        Ok(())
    }
}