secret_service/
session.rs

1// key exchange and crypto for session:
2// 1. Before session negotiation (openSession), set private key and public key using DH method.
3// 2. In session negotiation, send public key.
4// 3. As result of session negotiation, get object path for session, which (I think
5//      it means that it uses the same server public key to create an aes key which is used
6//      to decode the encoded secret using the aes seed that's sent with the secret).
7// 4. As result of session negotition, get server public key.
8// 5. Use server public key, my private key, to set an aes key using HKDF.
9// 6. Format Secret: aes iv is random seed, in secret struct it's the parameter (Array(Byte))
10// 7. Format Secret: encode the secret value for the value field in secret struct.
11//      This encoding uses the aes_key from the associated Session.
12
13use 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
29// for key exchange
30static 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        // Derive the shared secret the server and us.
81        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        // hkdf
88
89        // input keying material
90        let ikm = common_secret_padded;
91        let salt = None;
92
93        // output keying material
94        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
215/// from https://github.com/plietar/librespot/blob/master/core/src/util/mod.rs#L53
216fn 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    // There is no async test because this tests that an encryption session can be made, nothing more.
306
307    #[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}