1use crate::proxy::service::{OpenSessionResult, ServiceProxy, ServiceProxyBlocking};
14use crate::ss::{ALGORITHM_DH, ALGORITHM_PLAIN};
15use crate::Error;
16
17use generic_array::{typenum::U16, GenericArray};
18use num::{
19 bigint::BigUint,
20 integer::Integer,
21 traits::{One, Zero},
22 FromPrimitive,
23};
24use once_cell::sync::Lazy;
25use zbus::zvariant::OwnedObjectPath;
26
27use std::ops::{Mul, Rem, Shr};
28
29static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_u64(0x2).unwrap());
31static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
32 BigUint::from_bytes_be(&[
33 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2,
34 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67,
35 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E,
36 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
37 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5,
38 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF,
39 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE,
40 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
41 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
42 ])
43});
44
45#[allow(unused_macros)]
46macro_rules! feature_needed {
47 () => {
48 compile_error!("Please enable a feature to pick a runtime (such as rt-async-io-crypto-rust or rt-tokio-crypto-rust) for the secret-service crate")
49 }
50}
51
52type AesKey = GenericArray<u8, U16>;
53
54#[derive(Debug, Eq, PartialEq)]
55pub enum EncryptionType {
56 Plain,
57 Dh,
58}
59
60struct Keypair {
61 private: BigUint,
62 public: BigUint,
63}
64
65impl Keypair {
66 fn generate() -> Self {
67 let mut private_key_bytes = [0; 128];
68 getrandom::getrandom(&mut private_key_bytes).expect("platform RNG failed");
69
70 let private_key = BigUint::from_bytes_be(&private_key_bytes);
71 let public_key = powm(&DH_GENERATOR, &private_key, &DH_PRIME);
72
73 Self {
74 private: private_key,
75 public: public_key,
76 }
77 }
78
79 fn derive_shared(&self, server_public_key: &BigUint) -> AesKey {
80 let common_secret = powm(server_public_key, &self.private, &DH_PRIME);
82
83 let mut common_secret_bytes = common_secret.to_bytes_be();
84 let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()];
85 common_secret_padded.append(&mut common_secret_bytes);
86
87 let ikm = common_secret_padded;
91 let salt = None;
92
93 let mut okm = [0; 16];
95 hkdf(ikm, salt, &mut okm);
96
97 GenericArray::clone_from_slice(&okm)
98 }
99}
100
101#[cfg(feature = "crypto-openssl")]
102fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
103 let mut ctx = openssl::pkey_ctx::PkeyCtx::new_id(openssl::pkey::Id::HKDF)
104 .expect("hkdf context should not fail");
105 ctx.derive_init().expect("hkdf derive init should not fail");
106 ctx.set_hkdf_md(openssl::md::Md::sha256())
107 .expect("hkdf set md should not fail");
108
109 ctx.set_hkdf_key(&ikm)
110 .expect("hkdf set key should not fail");
111 if let Some(salt) = salt {
112 ctx.set_hkdf_salt(salt)
113 .expect("hkdf set salt should not fail");
114 }
115
116 ctx.add_hkdf_info(&[]).unwrap();
117 ctx.derive(Some(okm))
118 .expect("hkdf expand should never fail");
119}
120
121#[cfg(feature = "crypto-rust")]
122fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
123 use hkdf::Hkdf;
124 use sha2::Sha256;
125
126 let info = [];
127 let (_, hk) = Hkdf::<Sha256>::extract(salt, &ikm);
128 hk.expand(&info, okm)
129 .expect("hkdf expand should never fail");
130}
131
132#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
133fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
134 feature_needed!()
135}
136
137pub struct Session {
138 pub object_path: OwnedObjectPath,
139 aes_key: Option<AesKey>,
140}
141
142impl Session {
143 fn encrypted_session(keypair: &Keypair, session: OpenSessionResult) -> Result<Self, Error> {
144 let server_public_key = session
145 .output
146 .try_into()
147 .map(|key: Vec<u8>| BigUint::from_bytes_be(&key))?;
148
149 let aes_key = keypair.derive_shared(&server_public_key);
150
151 Ok(Session {
152 object_path: session.result,
153 aes_key: Some(aes_key),
154 })
155 }
156
157 pub fn new_blocking(
158 service_proxy: &ServiceProxyBlocking,
159 encryption: EncryptionType,
160 ) -> Result<Self, Error> {
161 match encryption {
162 EncryptionType::Plain => {
163 let session = service_proxy.open_session(ALGORITHM_PLAIN, "".into())?;
164 let session_path = session.result;
165
166 Ok(Session {
167 object_path: session_path,
168 aes_key: None,
169 })
170 }
171 EncryptionType::Dh => {
172 let keypair = Keypair::generate();
173
174 let session = service_proxy
175 .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into())?;
176
177 Self::encrypted_session(&keypair, session)
178 }
179 }
180 }
181
182 pub async fn new(
183 service_proxy: &ServiceProxy<'_>,
184 encryption: EncryptionType,
185 ) -> Result<Self, Error> {
186 match encryption {
187 EncryptionType::Plain => {
188 let session = service_proxy
189 .open_session(ALGORITHM_PLAIN, "".into())
190 .await?;
191 let session_path = session.result;
192
193 Ok(Session {
194 object_path: session_path,
195 aes_key: None,
196 })
197 }
198 EncryptionType::Dh => {
199 let keypair = Keypair::generate();
200
201 let session = service_proxy
202 .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into())
203 .await?;
204
205 Self::encrypted_session(&keypair, session)
206 }
207 }
208 }
209
210 pub fn get_aes_key(&self) -> Option<&AesKey> {
211 self.aes_key.as_ref()
212 }
213}
214
215fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
217 let mut base = base.clone();
218 let mut exp = exp.clone();
219 let mut result: BigUint = One::one();
220
221 while !exp.is_zero() {
222 if exp.is_odd() {
223 result = result.mul(&base).rem(modulus);
224 }
225 exp = exp.shr(1);
226 base = (&base).mul(&base).rem(modulus);
227 }
228
229 result
230}
231
232#[cfg(feature = "crypto-rust")]
233pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
234 use aes::cipher::block_padding::Pkcs7;
235 use aes::cipher::{BlockEncryptMut, KeyIvInit};
236
237 type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
238
239 let iv = GenericArray::from_slice(iv);
240
241 Aes128CbcEnc::new(key, iv).encrypt_padded_vec_mut::<Pkcs7>(data)
242}
243
244#[cfg(feature = "crypto-rust")]
245pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
246 use aes::cipher::block_padding::Pkcs7;
247 use aes::cipher::{BlockDecryptMut, KeyIvInit};
248
249 type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
250
251 let iv = GenericArray::from_slice(iv);
252 Aes128CbcDec::new(key, iv)
253 .decrypt_padded_vec_mut::<Pkcs7>(encrypted_data)
254 .map_err(|_| Error::Crypto("message decryption failed"))
255}
256
257#[cfg(feature = "crypto-openssl")]
258pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
259 use openssl::cipher::Cipher;
260 use openssl::cipher_ctx::CipherCtx;
261
262 let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
263 ctx.encrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv))
264 .expect("cipher init should not fail");
265
266 let mut output = vec![];
267 ctx.cipher_update_vec(data, &mut output)
268 .expect("cipher update should not fail");
269 ctx.cipher_final_vec(&mut output)
270 .expect("cipher final should not fail");
271 output
272}
273
274#[cfg(feature = "crypto-openssl")]
275pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
276 use openssl::cipher::Cipher;
277 use openssl::cipher_ctx::CipherCtx;
278
279 let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
280 ctx.decrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv))
281 .expect("cipher init should not fail");
282
283 let mut output = vec![];
284 ctx.cipher_update_vec(encrypted_data, &mut output)
285 .map_err(|_| Error::Crypto("message decryption failed"))?;
286 ctx.cipher_final_vec(&mut output)
287 .map_err(|_| Error::Crypto("message decryption failed"))?;
288 Ok(output)
289}
290
291#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
292pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
293 feature_needed!()
294}
295
296#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
297pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
298 feature_needed!()
299}
300
301#[cfg(test)]
302mod test {
303 use super::*;
304
305 #[test]
308 fn should_create_plain_session() {
309 let conn = zbus::blocking::Connection::session().unwrap();
310 let service_proxy = ServiceProxyBlocking::new(&conn).unwrap();
311 let session = Session::new_blocking(&service_proxy, EncryptionType::Plain).unwrap();
312 assert!(session.get_aes_key().is_none());
313 }
314
315 #[test]
316 fn should_create_encrypted_session() {
317 let conn = zbus::blocking::Connection::session().unwrap();
318 let service_proxy = ServiceProxyBlocking::new(&conn).unwrap();
319 let session = Session::new_blocking(&service_proxy, EncryptionType::Dh).unwrap();
320 assert!(session.get_aes_key().is_some());
321 }
322}