bitwarden_crypto/keys/
asymmetric_crypto_key.rs

1use std::pin::Pin;
2
3use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::DecodePublicKey};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5
6use super::key_encryptable::CryptoKey;
7use crate::{
8    Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes,
9    error::{CryptoError, Result},
10};
11
12/// Algorithm / public key encryption scheme used for encryption/decryption.
13#[derive(Serialize_repr, Deserialize_repr)]
14#[repr(u8)]
15pub enum PublicKeyEncryptionAlgorithm {
16    /// RSA with OAEP padding and SHA-1 hashing.
17    RsaOaepSha1 = 0,
18}
19
20#[derive(Clone, PartialEq)]
21pub(crate) enum RawPublicKey {
22    RsaOaepSha1(RsaPublicKey),
23}
24
25/// Public key of a key pair used in a public key encryption scheme. It is used for
26/// encrypting data.
27#[derive(Clone, PartialEq)]
28pub struct AsymmetricPublicCryptoKey {
29    inner: RawPublicKey,
30}
31
32impl AsymmetricPublicCryptoKey {
33    pub(crate) fn inner(&self) -> &RawPublicKey {
34        &self.inner
35    }
36
37    /// Build a public key from the SubjectPublicKeyInfo DER.
38    pub fn from_der(der: &SpkiPublicKeyBytes) -> Result<Self> {
39        Ok(AsymmetricPublicCryptoKey {
40            inner: RawPublicKey::RsaOaepSha1(
41                RsaPublicKey::from_public_key_der(der.as_ref())
42                    .map_err(|_| CryptoError::InvalidKey)?,
43            ),
44        })
45    }
46
47    /// Makes a SubjectPublicKeyInfo DER serialized version of the public key.
48    pub fn to_der(&self) -> Result<SpkiPublicKeyBytes> {
49        use rsa::pkcs8::EncodePublicKey;
50        match &self.inner {
51            RawPublicKey::RsaOaepSha1(public_key) => Ok(public_key
52                .to_public_key_der()
53                .map_err(|_| CryptoError::InvalidKey)?
54                .as_bytes()
55                .to_owned()
56                .into()),
57        }
58    }
59}
60
61#[derive(Clone)]
62pub(crate) enum RawPrivateKey {
63    // RsaPrivateKey is not a Copy type so this isn't completely necessary, but
64    // to keep the compiler from making stack copies when moving this struct around,
65    // we use a Box to keep the values on the heap. We also pin the box to make sure
66    // that the contents can't be pulled out of the box and moved
67    RsaOaepSha1(Pin<Box<RsaPrivateKey>>),
68}
69
70/// Private key of a key pair used in a public key encryption scheme. It is used for
71/// decrypting data that was encrypted with the corresponding public key.
72#[derive(Clone)]
73pub struct AsymmetricCryptoKey {
74    inner: RawPrivateKey,
75}
76
77// Note that RsaPrivateKey already implements ZeroizeOnDrop, so we don't need to do anything
78// We add this assertion to make sure that this is still true in the future
79const _: fn() = || {
80    fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
81    assert_zeroize_on_drop::<RsaPrivateKey>();
82};
83impl zeroize::ZeroizeOnDrop for AsymmetricCryptoKey {}
84impl CryptoKey for AsymmetricCryptoKey {}
85
86impl AsymmetricCryptoKey {
87    /// Generate a random AsymmetricCryptoKey (RSA-2048).
88    pub fn make(algorithm: PublicKeyEncryptionAlgorithm) -> Self {
89        Self::make_internal(algorithm, &mut rand::thread_rng())
90    }
91
92    fn make_internal<R: rand::CryptoRng + rand::RngCore>(
93        algorithm: PublicKeyEncryptionAlgorithm,
94        rng: &mut R,
95    ) -> Self {
96        match algorithm {
97            PublicKeyEncryptionAlgorithm::RsaOaepSha1 => Self {
98                inner: RawPrivateKey::RsaOaepSha1(Box::pin(
99                    RsaPrivateKey::new(rng, 2048).expect("failed to generate a key"),
100                )),
101            },
102        }
103    }
104
105    #[allow(missing_docs)]
106    pub fn from_pem(pem: &str) -> Result<Self> {
107        use rsa::pkcs8::DecodePrivateKey;
108        Ok(Self {
109            inner: RawPrivateKey::RsaOaepSha1(Box::pin(
110                RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?,
111            )),
112        })
113    }
114
115    #[allow(missing_docs)]
116    pub fn from_der(der: &Pkcs8PrivateKeyBytes) -> Result<Self> {
117        use rsa::pkcs8::DecodePrivateKey;
118        Ok(Self {
119            inner: RawPrivateKey::RsaOaepSha1(Box::pin(
120                RsaPrivateKey::from_pkcs8_der(der.as_ref()).map_err(|_| CryptoError::InvalidKey)?,
121            )),
122        })
123    }
124
125    #[allow(missing_docs)]
126    pub fn to_der(&self) -> Result<Pkcs8PrivateKeyBytes> {
127        match &self.inner {
128            RawPrivateKey::RsaOaepSha1(private_key) => {
129                use rsa::pkcs8::EncodePrivateKey;
130                Ok(private_key
131                    .to_pkcs8_der()
132                    .map_err(|_| CryptoError::InvalidKey)?
133                    .as_bytes()
134                    .to_owned()
135                    .into())
136            }
137        }
138    }
139
140    /// Derives the public key corresponding to this private key. This is deterministic
141    /// and always derives the same public key.
142    pub fn to_public_key(&self) -> AsymmetricPublicCryptoKey {
143        match &self.inner {
144            RawPrivateKey::RsaOaepSha1(private_key) => AsymmetricPublicCryptoKey {
145                inner: RawPublicKey::RsaOaepSha1(private_key.to_public_key()),
146            },
147        }
148    }
149
150    pub(crate) fn inner(&self) -> &RawPrivateKey {
151        &self.inner
152    }
153}
154
155// We manually implement these to make sure we don't print any sensitive data
156impl std::fmt::Debug for AsymmetricCryptoKey {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_struct("AsymmetricCryptoKey").finish()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164
165    use bitwarden_encoding::B64;
166
167    use crate::{
168        AsymmetricCryptoKey, AsymmetricPublicCryptoKey, Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes,
169        SymmetricCryptoKey, UnsignedSharedKey,
170        content_format::{Bytes, Pkcs8PrivateKeyDerContentFormat},
171    };
172
173    #[test]
174    fn test_asymmetric_crypto_key() {
175        let pem_key_str = "-----BEGIN PRIVATE KEY-----
176MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
177qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
178afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
179qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
180b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
181P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
182rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
183szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
1840d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
1858vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
186jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
187vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
1881u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
189J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
190l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
191TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
192ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
193KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
194wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
195UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
196kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
197pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
198Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
199CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
200PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
201DnqOsltgPomWZ7xVfMkm9niL2OA=
202-----END PRIVATE KEY-----";
203
204        let der_key: B64 = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=".parse().unwrap();
205        let der_key_vec: Vec<u8> = der_key.into();
206
207        // Load the two different formats and check they are the same key
208        let pem_key = AsymmetricCryptoKey::from_pem(pem_key_str).unwrap();
209        let der_key = AsymmetricCryptoKey::from_der(
210            &Bytes::<Pkcs8PrivateKeyDerContentFormat>::from(der_key_vec.clone()),
211        )
212        .unwrap();
213        assert_eq!(pem_key.to_der().unwrap(), der_key.to_der().unwrap());
214
215        // Check that the keys can be converted back to DER
216
217        assert_eq!(der_key.to_der().unwrap().to_vec(), der_key_vec.clone());
218        assert_eq!(pem_key.to_der().unwrap().to_vec(), der_key_vec);
219    }
220
221    #[test]
222    fn test_encrypt_public_decrypt_private() {
223        let private_key: B64 = concat!(
224            "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH",
225            "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY",
226            "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL",
227            "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK",
228            "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2",
229            "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO",
230            "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ",
231            "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj",
232            "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B",
233            "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL",
234            "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4",
235            "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk",
236            "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro",
237            "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv",
238            "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH",
239            "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j",
240            "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU",
241            "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM",
242            "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC",
243            "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G",
244            "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/",
245            "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J",
246            "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94",
247            "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs",
248            "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R",
249            "GDgRMUB6cL3IRVzcR0dC6cY=",
250        )
251        .parse()
252        .unwrap();
253
254        let public_key: B64 = concat!(
255            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
256            "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
257            "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
258            "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
259            "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
260            "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
261            "XQIDAQAB",
262        )
263        .parse()
264        .unwrap();
265
266        let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
267        let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap();
268        let public_key =
269            AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&public_key)).unwrap();
270
271        let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
272        let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap();
273        let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap();
274
275        assert_eq!(raw_key, decrypted);
276    }
277}