bitwarden_crypto/keys/
asymmetric_crypto_key.rs

1use std::pin::Pin;
2
3use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey};
4
5use super::key_encryptable::CryptoKey;
6use crate::error::{CryptoError, Result};
7
8/// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to
9/// encrypt [UnsignedSharedKey](crate::UnsignedSharedKey).
10pub trait AsymmetricEncryptable {
11    fn to_public_key(&self) -> &RsaPublicKey;
12}
13
14/// An asymmetric public encryption key. Can only encrypt
15/// [UnsignedSharedKey](crate::UnsignedSharedKey), usually accompanied by a
16/// [AsymmetricCryptoKey]
17pub struct AsymmetricPublicCryptoKey {
18    key: RsaPublicKey,
19}
20
21impl AsymmetricPublicCryptoKey {
22    /// Build a public key from the SubjectPublicKeyInfo DER.
23    pub fn from_der(der: &[u8]) -> Result<Self> {
24        Ok(Self {
25            key: rsa::RsaPublicKey::from_public_key_der(der)
26                .map_err(|_| CryptoError::InvalidKey)?,
27        })
28    }
29}
30
31impl AsymmetricEncryptable for AsymmetricPublicCryptoKey {
32    fn to_public_key(&self) -> &RsaPublicKey {
33        &self.key
34    }
35}
36
37/// An asymmetric encryption key. Contains both the public and private key. Can be used to both
38/// encrypt and decrypt [`UnsignedSharedKey`](crate::UnsignedSharedKey).
39#[derive(Clone)]
40pub struct AsymmetricCryptoKey {
41    // RsaPrivateKey is not a Copy type so this isn't completely necessary, but
42    // to keep the compiler from making stack copies when moving this struct around,
43    // we use a Box to keep the values on the heap. We also pin the box to make sure
44    // that the contents can't be pulled out of the box and moved
45    pub(crate) key: Pin<Box<RsaPrivateKey>>,
46}
47
48// Note that RsaPrivateKey already implements ZeroizeOnDrop, so we don't need to do anything
49// We add this assertion to make sure that this is still true in the future
50const _: () = {
51    fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
52    fn assert_all() {
53        assert_zeroize_on_drop::<RsaPrivateKey>();
54    }
55};
56
57impl zeroize::ZeroizeOnDrop for AsymmetricCryptoKey {}
58
59impl AsymmetricCryptoKey {
60    /// Generate a random AsymmetricCryptoKey (RSA-2048).
61    pub fn generate<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
62        let bits = 2048;
63
64        Self {
65            key: Box::pin(RsaPrivateKey::new(rng, bits).expect("failed to generate a key")),
66        }
67    }
68
69    pub fn from_pem(pem: &str) -> Result<Self> {
70        use rsa::pkcs8::DecodePrivateKey;
71        Ok(Self {
72            key: Box::pin(RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?),
73        })
74    }
75
76    pub fn from_der(der: &[u8]) -> Result<Self> {
77        use rsa::pkcs8::DecodePrivateKey;
78        Ok(Self {
79            key: Box::pin(RsaPrivateKey::from_pkcs8_der(der).map_err(|_| CryptoError::InvalidKey)?),
80        })
81    }
82
83    pub fn to_der(&self) -> Result<Vec<u8>> {
84        use rsa::pkcs8::EncodePrivateKey;
85        Ok(self
86            .key
87            .to_pkcs8_der()
88            .map_err(|_| CryptoError::InvalidKey)?
89            .as_bytes()
90            .to_owned())
91    }
92
93    pub fn to_public_der(&self) -> Result<Vec<u8>> {
94        use rsa::pkcs8::EncodePublicKey;
95        Ok(self
96            .to_public_key()
97            .to_public_key_der()
98            .map_err(|_| CryptoError::InvalidKey)?
99            .as_bytes()
100            .to_owned())
101    }
102}
103
104impl AsymmetricEncryptable for AsymmetricCryptoKey {
105    fn to_public_key(&self) -> &RsaPublicKey {
106        (*self.key).as_ref()
107    }
108}
109
110impl CryptoKey for AsymmetricCryptoKey {}
111
112// We manually implement these to make sure we don't print any sensitive data
113impl std::fmt::Debug for AsymmetricCryptoKey {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_struct("AsymmetricCryptoKey").finish()
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use base64::{engine::general_purpose::STANDARD, Engine};
122
123    use crate::{
124        AsymmetricCryptoKey, AsymmetricPublicCryptoKey, SymmetricCryptoKey, UnsignedSharedKey,
125    };
126
127    #[test]
128    fn test_asymmetric_crypto_key() {
129        let pem_key_str = "-----BEGIN PRIVATE KEY-----
130MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
131qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
132afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
133qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
134b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
135P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
136rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
137szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
1380d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
1398vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
140jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
141vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
1421u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
143J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
144l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
145TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
146ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
147KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
148wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
149UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
150kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
151pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
152Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
153CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
154PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
155DnqOsltgPomWZ7xVfMkm9niL2OA=
156-----END PRIVATE KEY-----";
157
158        let der_key_vec = STANDARD.decode("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=").unwrap();
159
160        // Load the two different formats and check they are the same key
161        let pem_key = AsymmetricCryptoKey::from_pem(pem_key_str).unwrap();
162        let der_key = AsymmetricCryptoKey::from_der(&der_key_vec).unwrap();
163        assert_eq!(pem_key.key, der_key.key);
164
165        // Check that the keys can be converted back to DER
166        assert_eq!(der_key.to_der().unwrap(), der_key_vec);
167        assert_eq!(pem_key.to_der().unwrap(), der_key_vec);
168    }
169
170    #[test]
171    fn test_encrypt_public_decrypt_private() {
172        let private_key = STANDARD
173            .decode(concat!(
174                "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH",
175                "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY",
176                "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL",
177                "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK",
178                "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2",
179                "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO",
180                "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ",
181                "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj",
182                "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B",
183                "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL",
184                "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4",
185                "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk",
186                "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro",
187                "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv",
188                "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH",
189                "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j",
190                "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU",
191                "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM",
192                "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC",
193                "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G",
194                "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/",
195                "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J",
196                "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94",
197                "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs",
198                "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R",
199                "GDgRMUB6cL3IRVzcR0dC6cY=",
200            ))
201            .unwrap();
202
203        let public_key = STANDARD
204            .decode(concat!(
205                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc",
206                "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X",
207                "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd",
208                "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui",
209                "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK",
210                "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS",
211                "XQIDAQAB",
212            ))
213            .unwrap();
214
215        let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap();
216        let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap();
217
218        let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
219        let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap();
220        let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap();
221
222        assert_eq!(raw_key, decrypted);
223    }
224}