bitwarden_crypto/keys/
symmetric_crypto_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
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
156
157
158
159
160
161
162
163
use std::pin::Pin;

use aes::cipher::typenum::U32;
use base64::{engine::general_purpose::STANDARD, Engine};
use generic_array::GenericArray;
use rand::Rng;
use zeroize::Zeroize;

use super::key_encryptable::CryptoKey;
use crate::CryptoError;

/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString)
#[derive(Clone)]
pub struct SymmetricCryptoKey {
    // GenericArray is equivalent to [u8; N], which is a Copy type placed on the stack.
    // To keep the compiler from making stack copies when moving this struct around,
    // we use a Box to keep the values on the heap. We also pin the box to make sure
    // that the contents can't be pulled out of the box and moved
    pub(crate) key: Pin<Box<GenericArray<u8, U32>>>,
    pub(crate) mac_key: Option<Pin<Box<GenericArray<u8, U32>>>>,
}

impl Drop for SymmetricCryptoKey {
    fn drop(&mut self) {
        self.key.zeroize();
        if let Some(mac_key) = &mut self.mac_key {
            mac_key.zeroize();
        }
    }
}

impl zeroize::ZeroizeOnDrop for SymmetricCryptoKey {}

impl SymmetricCryptoKey {
    const KEY_LEN: usize = 32;
    const MAC_LEN: usize = 32;

    /// Generate a new random [SymmetricCryptoKey]
    pub fn generate(mut rng: impl rand::RngCore) -> Self {
        let mut key = Box::pin(GenericArray::<u8, U32>::default());
        let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());

        rng.fill(key.as_mut_slice());
        rng.fill(mac_key.as_mut_slice());

        SymmetricCryptoKey {
            key,
            mac_key: Some(mac_key),
        }
    }

    pub(crate) fn new(
        key: Pin<Box<GenericArray<u8, U32>>>,
        mac_key: Option<Pin<Box<GenericArray<u8, U32>>>>,
    ) -> Self {
        Self { key, mac_key }
    }

    fn total_len(&self) -> usize {
        self.key.len() + self.mac_key.as_ref().map_or(0, |mac| mac.len())
    }

    pub fn to_base64(&self) -> String {
        STANDARD.encode(self.to_vec())
    }

    pub fn to_vec(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(self.total_len());

        buf.extend_from_slice(&self.key);
        if let Some(mac) = &self.mac_key {
            buf.extend_from_slice(mac);
        }
        buf
    }
}

impl TryFrom<String> for SymmetricCryptoKey {
    type Error = CryptoError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        let b = STANDARD
            .decode(value)
            .map_err(|_| CryptoError::InvalidKey)?;
        SymmetricCryptoKey::try_from(b)
    }
}

impl TryFrom<Vec<u8>> for SymmetricCryptoKey {
    type Error = CryptoError;

    fn try_from(mut value: Vec<u8>) -> Result<Self, Self::Error> {
        SymmetricCryptoKey::try_from(value.as_mut_slice())
    }
}

impl TryFrom<&mut [u8]> for SymmetricCryptoKey {
    type Error = CryptoError;

    /// Note: This function takes the byte slice by mutable reference and will zero out all
    /// the data in it. This is to prevent the key from being left in memory.
    fn try_from(value: &mut [u8]) -> Result<Self, Self::Error> {
        let result = if value.len() == Self::KEY_LEN + Self::MAC_LEN {
            let mut key = Box::pin(GenericArray::<u8, U32>::default());
            let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());

            key.copy_from_slice(&value[..Self::KEY_LEN]);
            mac_key.copy_from_slice(&value[Self::KEY_LEN..]);

            Ok(SymmetricCryptoKey {
                key,
                mac_key: Some(mac_key),
            })
        } else if value.len() == Self::KEY_LEN {
            let mut key = Box::pin(GenericArray::<u8, U32>::default());

            key.copy_from_slice(&value[..Self::KEY_LEN]);

            Ok(SymmetricCryptoKey { key, mac_key: None })
        } else {
            Err(CryptoError::InvalidKeyLen)
        };

        value.zeroize();
        result
    }
}

impl CryptoKey for SymmetricCryptoKey {}

// We manually implement these to make sure we don't print any sensitive data
impl std::fmt::Debug for SymmetricCryptoKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SymmetricCryptoKey").finish()
    }
}

#[cfg(test)]
pub fn derive_symmetric_key(name: &str) -> SymmetricCryptoKey {
    use zeroize::Zeroizing;

    use crate::{derive_shareable_key, generate_random_bytes};

    let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
    derive_shareable_key(secret, name, None)
}

#[cfg(test)]
mod tests {
    use super::{derive_symmetric_key, SymmetricCryptoKey};

    #[test]
    fn test_symmetric_crypto_key() {
        let key = derive_symmetric_key("test");
        let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
        assert_eq!(key.key, key2.key);
        assert_eq!(key.mac_key, key2.mac_key);

        let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
        let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
        assert_eq!(key, key2.to_base64());
    }
}