Skip to main content

bwx/
identity.rs

1use crate::prelude::*;
2
3use hmac::Mac as _;
4use sha1::Digest as _;
5
6fn hkdf_expand_sha256(prk: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
7    const HASH_LEN: usize = 32;
8    if out.len() > 255 * HASH_LEN {
9        return Err(Error::HkdfExpand);
10    }
11    let mut prev: [u8; HASH_LEN] = [0; HASH_LEN];
12    let mut prev_len = 0;
13    let mut counter: u8 = 1;
14    let mut offset = 0;
15    while offset < out.len() {
16        let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(prk)
17            .map_err(|_| Error::HkdfExpand)?;
18        mac.update(&prev[..prev_len]);
19        mac.update(info);
20        mac.update(&[counter]);
21        let block = mac.finalize().into_bytes();
22        let take = (out.len() - offset).min(HASH_LEN);
23        out[offset..offset + take].copy_from_slice(&block[..take]);
24        prev.copy_from_slice(&block);
25        prev_len = HASH_LEN;
26        offset += take;
27        counter = counter.wrapping_add(1);
28    }
29    Ok(())
30}
31
32pub struct Identity {
33    pub email: String,
34    pub keys: crate::locked::Keys,
35    pub master_password_hash: crate::locked::PasswordHash,
36}
37
38impl Identity {
39    pub fn new(
40        email: &str,
41        password: &crate::locked::Password,
42        kdf: crate::api::KdfType,
43        iterations: u32,
44        memory: Option<u32>,
45        parallelism: Option<u32>,
46    ) -> Result<Self> {
47        let email = email.trim().to_lowercase();
48
49        let iterations = std::num::NonZeroU32::new(iterations)
50            .ok_or(Error::Pbkdf2ZeroIterations)?;
51
52        let mut keys = crate::locked::Vec::new();
53        keys.extend(std::iter::repeat_n(0, 64));
54
55        let enc_key = &mut keys.data_mut()[0..32];
56
57        match kdf {
58            crate::api::KdfType::Pbkdf2 => {
59                pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
60                    password.password(),
61                    email.as_bytes(),
62                    iterations.get(),
63                    enc_key,
64                )
65                .map_err(|_| Error::Pbkdf2)?;
66            }
67
68            crate::api::KdfType::Argon2id => {
69                let mut hasher = sha2::Sha256::new();
70                hasher.update(email.as_bytes());
71                let salt = hasher.finalize();
72
73                let argon2_config = argon2::Argon2::new(
74                    argon2::Algorithm::Argon2id,
75                    argon2::Version::V0x13,
76                    argon2::Params::new(
77                        memory.unwrap() * 1024,
78                        iterations.get(),
79                        parallelism.unwrap(),
80                        Some(32),
81                    )
82                    .unwrap(),
83                );
84                argon2::Argon2::hash_password_into(
85                    &argon2_config,
86                    password.password(),
87                    &salt,
88                    enc_key,
89                )
90                .map_err(|_| Error::Argon2)?;
91            }
92        }
93
94        let mut hash = crate::locked::Vec::new();
95        hash.extend(std::iter::repeat_n(0, 32));
96        pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
97            enc_key,
98            password.password(),
99            1,
100            hash.data_mut(),
101        )
102        .map_err(|_| Error::Pbkdf2)?;
103
104        let mut prk: [u8; 32] = [0; 32];
105        prk.copy_from_slice(enc_key);
106        hkdf_expand_sha256(&prk, b"enc", enc_key)?;
107        let mac_key = &mut keys.data_mut()[32..64];
108        hkdf_expand_sha256(&prk, b"mac", mac_key)?;
109
110        let keys = crate::locked::Keys::new(keys);
111        let master_password_hash = crate::locked::PasswordHash::new(hash);
112
113        Ok(Self {
114            email: email.clone(),
115            keys,
116            master_password_hash,
117        })
118    }
119}
120
121#[test]
122fn test_hkdf_expand_sha256_rfc5869_a1() {
123    // RFC 5869 Appendix A.1
124    let prk: [u8; 32] = [
125        0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f,
126        0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f,
127        0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
128    ];
129    let info: [u8; 10] =
130        [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
131    let expected: [u8; 42] = [
132        0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f,
133        0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a,
134        0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34,
135        0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
136    ];
137    let mut out = [0_u8; 42];
138    hkdf_expand_sha256(&prk, &info, &mut out).unwrap();
139    assert_eq!(out, expected);
140}
141
142#[test]
143fn test_hkdf_expand_sha256_rfc5869_a2() {
144    // RFC 5869 Appendix A.2 — longer inputs, 82-byte output to exercise
145    // the multi-block counter path (T(1)..T(6)).
146    let prk: [u8; 32] = [
147        0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, 0x06, 0x10, 0x4c,
148        0x9c, 0xeb, 0x35, 0xb4, 0x5c, 0xef, 0x76, 0x00, 0x14, 0x90, 0x46,
149        0x71, 0x01, 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44,
150    ];
151    let info: [u8; 80] = [
152        0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
153        0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
154        0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0,
155        0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
156        0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
157        0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1,
158        0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
159        0xfd, 0xfe, 0xff,
160    ];
161    let expected: [u8; 82] = [
162        0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7,
163        0x8c, 0x59, 0x6a, 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e,
164        0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 0x59,
165        0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6,
166        0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09,
167        0xb8, 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30,
168        0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1,
169        0xf3, 0x43, 0x4f, 0x1d, 0x87,
170    ];
171    let mut out = [0_u8; 82];
172    hkdf_expand_sha256(&prk, &info, &mut out).unwrap();
173    assert_eq!(out, expected);
174}
175
176#[test]
177fn test_hkdf_expand_sha256_rfc5869_a3() {
178    // RFC 5869 Appendix A.3 — empty info (salt was empty upstream too;
179    // only the Expand step is exercised here).
180    let prk: [u8; 32] = [
181        0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9,
182        0x1d, 0x6f, 0x64, 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb,
183        0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
184    ];
185    let expected: [u8; 42] = [
186        0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80,
187        0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1,
188        0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d,
189        0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8,
190    ];
191    let mut out = [0_u8; 42];
192    hkdf_expand_sha256(&prk, &[], &mut out).unwrap();
193    assert_eq!(out, expected);
194}
195
196#[test]
197fn test_hkdf_expand_sha256_rejects_oversize_output() {
198    // HKDF caps L at 255*HashLen = 8160 bytes for SHA-256.
199    let prk = [0u8; 32];
200    let mut out = vec![0_u8; 8161];
201    assert!(hkdf_expand_sha256(&prk, &[], &mut out).is_err());
202}