bitwarden_importers/importers/onepassword/access/
rsa.rs1use ::rsa::{BoxedUint, Oaep, RsaPrivateKey};
7use sha1::Sha1;
8use sha2::Sha256;
9
10use super::{
11 error::OnePasswordError,
12 opdata::{Encrypted, decode64_loose},
13 wire::RsaKeyJwk,
14};
15
16const OAEP_SHA1: &str = "RSA-OAEP";
17const OAEP_SHA256: &str = "RSA-OAEP-256";
18
19pub(super) struct RsaKey {
21 pub id: String,
22 key: RsaPrivateKey,
23}
24
25impl RsaKey {
26 pub(super) fn parse(jwk: &RsaKeyJwk) -> Result<RsaKey, OnePasswordError> {
28 let uint = |s: &str| -> Result<BoxedUint, OnePasswordError> {
29 Ok(BoxedUint::from_be_slice_vartime(&decode64_loose(s)?))
30 };
31
32 let key = RsaPrivateKey::from_components(
33 uint(&jwk.n)?,
34 uint(&jwk.e)?,
35 uint(&jwk.d)?,
36 vec![uint(&jwk.p)?, uint(&jwk.q)?],
37 )
38 .map_err(|_| OnePasswordError::Internal("invalid RSA key".into()))?;
39
40 Ok(RsaKey {
41 id: jwk.kid.clone(),
42 key,
43 })
44 }
45
46 pub(super) fn decrypt(&self, encrypted: &Encrypted) -> Result<Vec<u8>, OnePasswordError> {
48 if encrypted.key_id != self.id {
49 return Err(OnePasswordError::Internal("mismatching key id".into()));
50 }
51
52 let result = match encrypted.scheme.as_str() {
53 OAEP_SHA1 => self.key.decrypt(Oaep::<Sha1>::new(), &encrypted.ciphertext),
54 OAEP_SHA256 => self
55 .key
56 .decrypt(Oaep::<Sha256>::new(), &encrypted.ciphertext),
57 other => {
58 return Err(OnePasswordError::Internal(format!(
59 "invalid encryption scheme '{other}'"
60 )));
61 }
62 };
63
64 result.map_err(|_| OnePasswordError::Decryption)
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use data_encoding::BASE64;
71
72 use super::*;
73
74 const SHA1_CIPHERTEXT: &str = "plF49e+3R0IpxBqWinosrPxWS8GdzKULvo4myIS1Gam5LCl1TmvvtntAiwOaL+/x8Ie7JApxksrpzrg9UAIaJeOJcoSzPA/hT4nn2jnglWLt+Dwz6RiEyQXhHqnyEOZ56RhNrVR8qKrnApUX2J/FWmrSYXQduIM2xbbx1LQwCGJJxCHp/pFf3Eb0fwtaw2AB5QEF5uTXOnOY+NYaPUJLKTX63uas+uPGUtdJP66WT15zHEK/WRx4ekafJvIjueSTaiceq+IVXc5niMzTMYvRb5rIEiNm3WSX7EteqaU9T46ytm9748ILQNeuGSjzIqhO4H7mO47/e8wdEh3WZk8Alg==";
75 const SHA256_CIPHERTEXT: &str = "R2wRx7neV9M/hMyWhr6heE43Q48xL+6lZuy9k03+G0FVPmXsVPRK4q7nWq6UDVwcj42nxMychMKfurCuecLEd+h5zum9Py9y6r702GnymQAl0ReM6NyjxW2m1YOp6zFVlqa69Tptn+ewOD1Fqr14yJTgVtcSJCKjQxI0ALrFst/tMvOjMFFtYPCsQ3oC0ka7kDnjbikOD0AL7Q6/19Nilr3C/TjQdNRC1Y3c5sKtyDZj++OkwgB2nac1V9IfLbpum5nqQim4UBOwE8f1axTDSYtKLJ31rr+z5bHxraUMzz96BnOmIzsZ2jj0fHrZBsBUs1L5Bg5XmGwHTz01z4HQ9A==";
76
77 fn key(fixture: &str) -> RsaKey {
78 let jwk: RsaKeyJwk = serde_json::from_str(fixture).expect("valid jwk");
79 RsaKey::parse(&jwk).expect("valid key")
80 }
81
82 fn envelope(key_id: &str, scheme: &str, ciphertext: Vec<u8>) -> Encrypted {
83 Encrypted {
84 key_id: key_id.into(),
85 scheme: scheme.into(),
86 iv: Vec::new(),
87 ciphertext,
88 }
89 }
90
91 #[test]
92 fn parses_sha1_key() {
93 assert_eq!(
94 key(include_str!("fixtures/rsa-key.json")).id,
95 "szerdhg2ww2ahjo4ilz57x7cce"
96 );
97 }
98
99 #[test]
100 fn parses_sha256_key() {
101 assert_eq!(
102 key(include_str!("fixtures/rsa-key-oaep-256.json")).id,
103 "sfaijsnbchbtznlar7mx6yrhae"
104 );
105 }
106
107 #[test]
108 fn decrypts_oaep_sha1() {
109 let key = key(include_str!("fixtures/rsa-key.json"));
110 let ciphertext = BASE64.decode(SHA1_CIPHERTEXT.as_bytes()).expect("base64");
111 let encrypted = envelope("szerdhg2ww2ahjo4ilz57x7cce", "RSA-OAEP", ciphertext);
112 let plain = key.decrypt(&encrypted).expect("decrypts");
113 assert_eq!(plain, b"All your base are belong to us");
114 }
115
116 #[test]
117 fn decrypts_oaep_sha256() {
118 let key = key(include_str!("fixtures/rsa-key-oaep-256.json"));
119 let ciphertext = BASE64.decode(SHA256_CIPHERTEXT.as_bytes()).expect("base64");
120 let encrypted = envelope("sfaijsnbchbtznlar7mx6yrhae", "RSA-OAEP-256", ciphertext);
121 let plain = key.decrypt(&encrypted).expect("decrypts");
122 assert_eq!(plain, b"All your base are belong to us");
123 }
124
125 #[test]
126 fn rejects_mismatching_key_id() {
127 let key = key(include_str!("fixtures/rsa-key.json"));
128 let encrypted = envelope("invalid-id", "RSA-OAEP", b"ciphertext".to_vec());
129 let err = key.decrypt(&encrypted).expect_err("mismatch");
130 assert!(err.to_string().contains("mismatching key id"));
131 }
132}