secret_service/
session.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
//Copyright 2022 secret-service-rs Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

// key exchange and crypto for session:
// 1. Before session negotiation (openSession), set private key and public key using DH method.
// 2. In session negotiation, send public key.
// 3. As result of session negotiation, get object path for session, which (I think
//      it means that it uses the same server public key to create an aes key which is used
//      to decode the encoded secret using the aes seed that's sent with the secret).
// 4. As result of session negotition, get server public key.
// 5. Use server public key, my private key, to set an aes key using HKDF.
// 6. Format Secret: aes iv is random seed, in secret struct it's the parameter (Array(Byte))
// 7. Format Secret: encode the secret value for the value field in secret struct.
//      This encoding uses the aes_key from the associated Session.

use crate::proxy::service::{OpenSessionResult, ServiceProxy, ServiceProxyBlocking};
use crate::ss::{ALGORITHM_DH, ALGORITHM_PLAIN};
use crate::Error;

use generic_array::{typenum::U16, GenericArray};
use num::{
    bigint::BigUint,
    integer::Integer,
    traits::{One, Zero},
    FromPrimitive,
};
use once_cell::sync::Lazy;
use rand::{rngs::OsRng, Rng};
use zbus::zvariant::OwnedObjectPath;

use std::ops::{Mul, Rem, Shr};

// for key exchange
static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_u64(0x2).unwrap());
static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
    BigUint::from_bytes_be(&[
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2,
        0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67,
        0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E,
        0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
        0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5,
        0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF,
        0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE,
        0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    ])
});

#[allow(unused_macros)]
macro_rules! feature_needed {
    () => {
        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")
    }
}

type AesKey = GenericArray<u8, U16>;

#[derive(Debug, Eq, PartialEq)]
pub enum EncryptionType {
    Plain,
    Dh,
}

struct Keypair {
    private: BigUint,
    public: BigUint,
}

impl Keypair {
    fn generate() -> Self {
        let mut rng = OsRng {};
        let mut private_key_bytes = [0; 128];
        rng.fill(&mut private_key_bytes);

        let private_key = BigUint::from_bytes_be(&private_key_bytes);
        let public_key = powm(&DH_GENERATOR, &private_key, &DH_PRIME);

        Self {
            private: private_key,
            public: public_key,
        }
    }

    fn derive_shared(&self, server_public_key: &BigUint) -> AesKey {
        // Derive the shared secret the server and us.
        let common_secret = powm(server_public_key, &self.private, &DH_PRIME);

        let mut common_secret_bytes = common_secret.to_bytes_be();
        let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()];
        common_secret_padded.append(&mut common_secret_bytes);

        // hkdf

        // input keying material
        let ikm = common_secret_padded;
        let salt = None;

        // output keying material
        let mut okm = [0; 16];
        hkdf(ikm, salt, &mut okm);

        GenericArray::clone_from_slice(&okm)
    }
}

#[cfg(feature = "crypto-openssl")]
fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
    let mut ctx = openssl::pkey_ctx::PkeyCtx::new_id(openssl::pkey::Id::HKDF)
        .expect("hkdf context should not fail");
    ctx.derive_init().expect("hkdf derive init should not fail");
    ctx.set_hkdf_md(openssl::md::Md::sha256())
        .expect("hkdf set md should not fail");

    ctx.set_hkdf_key(&ikm)
        .expect("hkdf set key should not fail");
    if let Some(salt) = salt {
        ctx.set_hkdf_salt(salt)
            .expect("hkdf set salt should not fail");
    }

    ctx.add_hkdf_info(&[]).unwrap();
    ctx.derive(Some(okm))
        .expect("hkdf expand should never fail");
}

#[cfg(feature = "crypto-rust")]
fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
    use hkdf::Hkdf;
    use sha2::Sha256;

    let info = [];
    let (_, hk) = Hkdf::<Sha256>::extract(salt, &ikm);
    hk.expand(&info, okm)
        .expect("hkdf expand should never fail");
}

#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
fn hkdf(ikm: Vec<u8>, salt: Option<&[u8]>, okm: &mut [u8]) {
    feature_needed!()
}

pub struct Session {
    pub object_path: OwnedObjectPath,
    aes_key: Option<AesKey>,
}

impl Session {
    fn encrypted_session(keypair: &Keypair, session: OpenSessionResult) -> Result<Self, Error> {
        let server_public_key = session
            .output
            .try_into()
            .map(|key: Vec<u8>| BigUint::from_bytes_be(&key))?;

        let aes_key = keypair.derive_shared(&server_public_key);

        Ok(Session {
            object_path: session.result,
            aes_key: Some(aes_key),
        })
    }

    pub fn new_blocking(
        service_proxy: &ServiceProxyBlocking,
        encryption: EncryptionType,
    ) -> Result<Self, Error> {
        match encryption {
            EncryptionType::Plain => {
                let session = service_proxy.open_session(ALGORITHM_PLAIN, "".into())?;
                let session_path = session.result;

                Ok(Session {
                    object_path: session_path,
                    aes_key: None,
                })
            }
            EncryptionType::Dh => {
                let keypair = Keypair::generate();

                let session = service_proxy
                    .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into())?;

                Self::encrypted_session(&keypair, session)
            }
        }
    }

    pub async fn new(
        service_proxy: &ServiceProxy<'_>,
        encryption: EncryptionType,
    ) -> Result<Self, Error> {
        match encryption {
            EncryptionType::Plain => {
                let session = service_proxy
                    .open_session(ALGORITHM_PLAIN, "".into())
                    .await?;
                let session_path = session.result;

                Ok(Session {
                    object_path: session_path,
                    aes_key: None,
                })
            }
            EncryptionType::Dh => {
                let keypair = Keypair::generate();

                let session = service_proxy
                    .open_session(ALGORITHM_DH, keypair.public.to_bytes_be().into())
                    .await?;

                Self::encrypted_session(&keypair, session)
            }
        }
    }

    pub fn get_aes_key(&self) -> Option<&AesKey> {
        self.aes_key.as_ref()
    }
}

/// from https://github.com/plietar/librespot/blob/master/core/src/util/mod.rs#L53
fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
    let mut base = base.clone();
    let mut exp = exp.clone();
    let mut result: BigUint = One::one();

    while !exp.is_zero() {
        if exp.is_odd() {
            result = result.mul(&base).rem(modulus);
        }
        exp = exp.shr(1);
        base = (&base).mul(&base).rem(modulus);
    }

    result
}

#[cfg(feature = "crypto-rust")]
pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
    use aes::cipher::block_padding::Pkcs7;
    use aes::cipher::{BlockEncryptMut, KeyIvInit};

    type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;

    let iv = GenericArray::from_slice(iv);

    Aes128CbcEnc::new(key, iv).encrypt_padded_vec_mut::<Pkcs7>(data)
}

#[cfg(feature = "crypto-rust")]
pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
    use aes::cipher::block_padding::Pkcs7;
    use aes::cipher::{BlockDecryptMut, KeyIvInit};

    type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;

    let iv = GenericArray::from_slice(iv);
    Aes128CbcDec::new(key, iv)
        .decrypt_padded_vec_mut::<Pkcs7>(encrypted_data)
        .map_err(|_| Error::Crypto("message decryption failed"))
}

#[cfg(feature = "crypto-openssl")]
pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
    use openssl::cipher::Cipher;
    use openssl::cipher_ctx::CipherCtx;

    let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
    ctx.encrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv))
        .expect("cipher init should not fail");

    let mut output = vec![];
    ctx.cipher_update_vec(data, &mut output)
        .expect("cipher update should not fail");
    ctx.cipher_final_vec(&mut output)
        .expect("cipher final should not fail");
    output
}

#[cfg(feature = "crypto-openssl")]
pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
    use openssl::cipher::Cipher;
    use openssl::cipher_ctx::CipherCtx;

    let mut ctx = CipherCtx::new().expect("cipher creation should not fail");
    ctx.decrypt_init(Some(Cipher::aes_128_cbc()), Some(key), Some(iv))
        .expect("cipher init should not fail");

    let mut output = vec![];
    ctx.cipher_update_vec(encrypted_data, &mut output)
        .map_err(|_| Error::Crypto("message decryption failed"))?;
    ctx.cipher_final_vec(&mut output)
        .map_err(|_| Error::Crypto("message decryption failed"))?;
    Ok(output)
}

#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
pub fn encrypt(data: &[u8], key: &AesKey, iv: &[u8]) -> Vec<u8> {
    feature_needed!()
}

#[cfg(all(not(feature = "crypto-rust"), not(feature = "crypto-openssl")))]
pub fn decrypt(encrypted_data: &[u8], key: &AesKey, iv: &[u8]) -> Result<Vec<u8>, Error> {
    feature_needed!()
}

#[cfg(test)]
mod test {
    use super::*;

    // There is no async test because this tests that an encryption session can be made, nothing more.

    #[test]
    fn should_create_plain_session() {
        let conn = zbus::blocking::Connection::session().unwrap();
        let service_proxy = ServiceProxyBlocking::new(&conn).unwrap();
        let session = Session::new_blocking(&service_proxy, EncryptionType::Plain).unwrap();
        assert!(session.get_aes_key().is_none());
    }

    #[test]
    fn should_create_encrypted_session() {
        let conn = zbus::blocking::Connection::session().unwrap();
        let service_proxy = ServiceProxyBlocking::new(&conn).unwrap();
        let session = Session::new_blocking(&service_proxy, EncryptionType::Dh).unwrap();
        assert!(session.get_aes_key().is_some());
    }
}