bitwarden_crypto/keys/
shareable_key.rs

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
use std::pin::Pin;

use aes::cipher::typenum::U64;
use generic_array::GenericArray;
use hmac::Mac;
use zeroize::Zeroizing;

use crate::{
    keys::SymmetricCryptoKey,
    util::{hkdf_expand, PbkdfSha256Hmac},
};

/// Derive a shareable key using hkdf from secret and name.
///
/// A specialized variant of this function was called `CryptoService.makeSendKey` in the Bitwarden
/// `clients` repository.
pub fn derive_shareable_key(
    secret: Zeroizing<[u8; 16]>,
    name: &str,
    info: Option<&str>,
) -> SymmetricCryptoKey {
    // Because all inputs are fixed size, we can unwrap all errors here without issue
    let res = Zeroizing::new(
        PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes())
            .expect("hmac new_from_slice should not fail")
            .chain_update(secret)
            .finalize()
            .into_bytes(),
    );

    let mut key: Pin<Box<GenericArray<u8, U64>>> =
        hkdf_expand(&res, info).expect("Input is a valid size");

    SymmetricCryptoKey::try_from(key.as_mut_slice()).expect("Key is a valid size")
}

#[cfg(test)]
mod tests {
    use zeroize::Zeroizing;

    use super::derive_shareable_key;

    #[test]
    fn test_derive_shareable_key() {
        let key = derive_shareable_key(Zeroizing::new(*b"&/$%F1a895g67HlX"), "test_key", None);
        assert_eq!(key.to_base64(), "4PV6+PcmF2w7YHRatvyMcVQtI7zvCyssv/wFWmzjiH6Iv9altjmDkuBD1aagLVaLezbthbSe+ktR+U6qswxNnQ==");

        let key = derive_shareable_key(
            Zeroizing::new(*b"67t9b5g67$%Dh89n"),
            "test_key",
            Some("test"),
        );
        assert_eq!(key.to_base64(), "F9jVQmrACGx9VUPjuzfMYDjr726JtL300Y3Yg+VYUnVQtQ1s8oImJ5xtp1KALC9h2nav04++1LDW4iFD+infng==");
    }
}