1use dbus::{
18 arg::{RefArg, Variant},
19 blocking::{Connection, Proxy},
20 Path,
21};
22use zeroize::ZeroizeOnDrop;
23
24use crate::Error;
25
26#[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))]
27compile_error!("You cannot specify both feature \"crypto-rust\" and feature \"crypto-openssl\"");
28
29#[derive(Debug, Eq, PartialEq)]
38pub enum EncryptionType {
39 Plain,
41 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
42 Dh,
44}
45
46#[derive(ZeroizeOnDrop)]
47pub(crate) struct EncryptedSecret {
48 #[zeroize(skip)]
49 path: Path<'static>, salt: Vec<u8>, data: Vec<u8>, pub(crate) mime: String, }
54
55impl EncryptedSecret {
56 pub(crate) fn from_dbus(value: (Path<'static>, Vec<u8>, Vec<u8>, String)) -> Self {
57 Self {
58 path: value.0,
59 salt: value.1,
60 data: value.2,
61 mime: value.3.to_string(),
62 }
63 }
64
65 pub(crate) fn to_dbus(&self) -> (Path<'static>, Vec<u8>, Vec<u8>, &str) {
66 (
67 self.path.clone(),
68 self.salt.clone(),
69 self.data.clone(),
70 &self.mime,
71 )
72 }
73}
74
75#[derive(ZeroizeOnDrop)]
76pub struct Session {
77 #[zeroize(skip)]
78 pub(crate) path: Path<'static>,
79 #[zeroize(skip)]
80 encryption: EncryptionType,
81 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
82 shared_key: Option<crypto::AesKey>,
83}
84
85impl std::fmt::Debug for Session {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("Session")
88 .field("path", &self.path)
89 .field(
90 "secrets",
91 if self.is_encrypted() {
92 &"(Hidden)"
93 } else {
94 &"None"
95 },
96 )
97 .finish()
98 }
99}
100
101impl Session {
102 pub fn new(p: Proxy<'_, &'_ Connection>, encryption: EncryptionType) -> Result<Session, Error> {
103 use crate::proxy::service::Service;
104 match encryption {
105 EncryptionType::Plain => {
106 use crate::ss::ALGORITHM_PLAIN;
107 #[allow(clippy::box_default)]
110 let bytes_arg = Box::new(String::new()) as Box<dyn RefArg>;
111 let (_, path) = p.open_session(ALGORITHM_PLAIN, Variant(bytes_arg))?;
112 Ok(Session {
113 path,
114 encryption,
115 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
116 shared_key: None,
117 })
118 }
119 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
120 EncryptionType::Dh => {
121 use crate::ss::ALGORITHM_DH;
122 use dbus::arg::cast;
123
124 let keypair = crypto::Keypair::generate();
126
127 let public_bytes = keypair.public.to_bytes_be();
129 let bytes_arg = Variant(Box::new(public_bytes) as Box<dyn RefArg>);
130 let (out, path) = p.open_session(ALGORITHM_DH, bytes_arg)?;
131
132 if let Some(server_public_key_bytes) = cast::<Vec<u8>>(&out.0) {
134 let shared_key = keypair.derive_shared(server_public_key_bytes);
135 Ok(Session {
136 path,
137 encryption,
138 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
139 shared_key: Some(shared_key),
140 })
141 } else {
142 Err(Error::Parse)
143 }
144 }
145 }
146 }
147
148 pub fn is_encrypted(&self) -> bool {
149 match self.encryption {
150 EncryptionType::Plain => false,
151 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
152 EncryptionType::Dh => true,
153 }
154 }
155
156 pub(crate) fn encrypt_secret(&self, data: &[u8], mime: &str) -> EncryptedSecret {
157 match self.encryption {
158 EncryptionType::Plain => EncryptedSecret {
159 path: self.path.clone(),
160 salt: vec![],
161 data: data.to_vec(),
162 mime: mime.to_string(),
163 },
164 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
165 EncryptionType::Dh => {
166 let (encrypted, salt) = crypto::encrypt(data, &self.shared_key.unwrap());
168 EncryptedSecret {
169 path: self.path.clone(),
170 salt,
171 data: encrypted,
172 mime: mime.to_string(),
173 }
174 }
175 }
176 }
177
178 pub(crate) fn decrypt_secret(&self, secret: EncryptedSecret) -> Result<Vec<u8>, Error> {
179 match self.encryption {
180 EncryptionType::Plain => Ok(secret.data.clone()),
181 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
182 EncryptionType::Dh => {
183 let clear = crypto::decrypt(&secret.data, &self.shared_key.unwrap(), &secret.salt)?;
184 Ok(clear)
185 }
186 }
187 }
188}
189
190#[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
191mod crypto {
192 use std::ops::{Mul, Rem, Shr};
193
194 use fastrand::Rng;
195 use num::{
196 bigint::BigUint,
197 integer::Integer,
198 traits::{One, Zero},
199 FromPrimitive,
200 };
201 use once_cell::sync::Lazy;
202
203 #[cfg(feature = "crypto-rust")]
204 pub(super) fn encrypt(data: &[u8], key: &AesKey) -> (Vec<u8>, Vec<u8>) {
205 use aes::cipher::block_padding::Pkcs7;
206 use aes::cipher::generic_array::GenericArray;
207 use aes::cipher::{BlockEncryptMut, KeyIvInit};
208
209 type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
210
211 let aes_iv = salt();
213
214 let key = GenericArray::from_slice(key);
216 let iv = GenericArray::from_slice(&aes_iv);
217
218 (
220 Aes128CbcEnc::new(key, iv).encrypt_padded_vec_mut::<Pkcs7>(data),
221 aes_iv.to_vec(),
222 )
223 }
224
225 #[cfg(feature = "crypto-rust")]
226 pub(super) fn decrypt(
227 encrypted_data: &[u8],
228 key: &AesKey,
229 iv: &[u8],
230 ) -> Result<Vec<u8>, crate::Error> {
231 use aes::cipher::block_padding::Pkcs7;
232 use aes::cipher::generic_array::GenericArray;
233 use aes::cipher::{BlockDecryptMut, KeyIvInit};
234
235 type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
236
237 let key = GenericArray::from_slice(key);
238 let iv = GenericArray::from_slice(iv);
239
240 let output = Aes128CbcDec::new(key, iv).decrypt_padded_vec_mut::<Pkcs7>(encrypted_data)?;
241 Ok(output)
242 }
243
244 #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))]
245 pub(super) fn encrypt(data: &[u8], key: &AesKey) -> (Vec<u8>, Vec<u8>) {
246 use openssl::cipher::Cipher;
247 use openssl::cipher_ctx::CipherCtx;
248
249 let aes_iv = salt();
251
252 let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
253 ctx.encrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(&aes_iv))
254 .expect("cipher init should not fail");
255
256 let mut output = vec![];
257 ctx.cipher_update_vec(data, &mut output)
258 .expect("cipher update should not fail");
259 ctx.cipher_final_vec(&mut output)
260 .expect("cipher final should not fail");
261 (output, aes_iv.to_vec())
262 }
263
264 #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))]
265 pub(super) fn decrypt(
266 encrypted_data: &[u8],
267 key: &AesKey,
268 iv: &[u8],
269 ) -> Result<Vec<u8>, crate::Error> {
270 use openssl::cipher::Cipher;
271 use openssl::cipher_ctx::CipherCtx;
272
273 let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
274 ctx.decrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv))
275 .expect("cipher init should not fail");
276
277 let mut output = vec![];
278 ctx.cipher_update_vec(encrypted_data, &mut output)?;
279 ctx.cipher_final_vec(&mut output)?;
280 Ok(output)
281 }
282
283 fn salt() -> [u8; 16] {
284 let mut rng = Rng::new();
285 let mut salt = [0; 16];
286 rng.fill(&mut salt);
287 salt
288 }
289
290 static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_u64(0x2).unwrap());
292 static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
293 BigUint::from_bytes_be(&[
294 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68,
295 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08,
296 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A,
297 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
298 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51,
299 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
300 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38,
301 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
302 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
303 0xFF, 0xFF,
304 ])
305 });
306
307 pub(super) type AesKey = [u8; 16];
308
309 #[derive(Clone)]
310 pub(super) struct Keypair {
311 pub(super) private: BigUint,
312 pub(super) public: BigUint,
313 }
314
315 impl Keypair {
316 pub(super) fn generate() -> Self {
317 let mut rng = Rng::new();
318 let mut private_key_bytes = [0; 128];
319 rng.fill(&mut private_key_bytes);
320
321 let private_key = BigUint::from_bytes_be(&private_key_bytes);
322 let public_key = pow_base_exp_mod(&DH_GENERATOR, &private_key, &DH_PRIME);
323
324 Self {
325 private: private_key,
326 public: public_key,
327 }
328 }
329
330 pub(super) fn derive_shared(&self, server_public_key_bytes: &[u8]) -> AesKey {
331 let server_public_key = BigUint::from_bytes_be(server_public_key_bytes);
333 let common_secret = pow_base_exp_mod(&server_public_key, &self.private, &DH_PRIME);
334
335 let common_secret_bytes = common_secret.to_bytes_be();
336 let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()];
337 common_secret_padded.extend(common_secret_bytes);
338
339 let ikm = common_secret_padded;
343 let salt = None;
344
345 let mut okm = [0; 16];
347 hkdf(ikm, salt, &mut okm);
348
349 okm
350 }
351 }
352
353 #[cfg(all(feature = "crypto-openssl", not(feature = "crypto-rust")))]
354 pub(super) fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
355 let mut ctx = openssl::pkey_ctx::PkeyCtx::new_id(openssl::pkey::Id::HKDF)
356 .expect("hkdf context should not fail");
357 ctx.derive_init().expect("hkdf derive init should not fail");
358 ctx.set_hkdf_md(openssl::md::Md::sha256())
359 .expect("hkdf set md should not fail");
360
361 ctx.set_hkdf_key(&ikm)
362 .expect("hkdf set key should not fail");
363 if let Some(salt) = salt {
364 ctx.set_hkdf_salt(salt)
365 .expect("hkdf set salt should not fail");
366 }
367
368 ctx.add_hkdf_info(&[]).unwrap();
369 ctx.derive(Some(okm))
370 .expect("hkdf expand should never fail");
371 }
372
373 #[cfg(feature = "crypto-rust")]
374 pub(super) fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
375 use sha2::Sha256;
376
377 let info = [];
378 let (_, hk) = hkdf::Hkdf::<Sha256>::extract(salt, &ikm);
379 hk.expand(&info, okm)
380 .expect("hkdf expand should never fail");
381 }
382
383 pub(super) fn pow_base_exp_mod(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
385 let mut base = base.clone();
386 let mut exp = exp.clone();
387 let mut result: BigUint = One::one();
388
389 while !exp.is_zero() {
390 if exp.is_odd() {
391 result = result.mul(&base).rem(modulus);
392 }
393 exp = exp.shr(1);
394 base = (&base).mul(&base).rem(modulus);
395 }
396
397 result
398 }
399}
400
401#[cfg(test)]
402mod test {
403 use dbus::blocking::Connection;
404
405 use crate::proxy::new_proxy;
406 use crate::ss::SS_DBUS_PATH;
407
408 use super::*;
409
410 #[test]
411 fn should_create_plain_session() {
412 let connection = Connection::new_session().unwrap();
413 let proxy = new_proxy(&connection, SS_DBUS_PATH);
414 let session = Session::new(proxy, EncryptionType::Plain).unwrap();
415 assert!(!session.is_encrypted());
416 }
417
418 #[cfg(any(feature = "crypto-rust", feature = "crypto-openssl"))]
419 #[test]
420 fn should_create_encrypted_session() {
421 let connection = Connection::new_session().unwrap();
422 let proxy = new_proxy(&connection, SS_DBUS_PATH);
423 let session = Session::new(proxy, EncryptionType::Dh).unwrap();
424 assert!(session.is_encrypted());
425 }
426}