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};
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 {
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);
let ikm = common_secret_padded;
let salt = None;
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()
}
}
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::*;
#[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());
}
}