pooly 0.2.1

A protobuf to Postgres adapter + connection pooling middleware.
Documentation
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use chacha20poly1305::aead::{Aead, NewAead, Payload};
use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::XChaCha20Poly1305;
use sharks::{Share, Sharks};

use crate::data::files::{FilesService, SimpleFilesService};
use crate::models::errors::SecretsError;
use crate::models::sec::secrets::{EncryptedPayload, EncryptionKey, KEY_LENGTH, MasterKey, MasterKeyShare, NUM_SHARES};
use crate::models::sec::zeroize::ZeroizeWrapper;
use crate::services::secrets::encryption::EncryptionService;
use crate::services::secrets::random::VecGenerator;
use crate::services::secrets::shares::MasterKeySharesService;

const NONCE_SIZE: usize = 24;

pub mod random;
pub mod shares;

mod encryption;

pub type LocalSecretsService = SecretsService<SimpleFilesService>;

pub struct SecretsService<T: FilesService> {

    encryption_service: EncryptionService,
    files_service: T,
    is_sealed: AtomicBool,
    shares_service: Arc<MasterKeySharesService>,
    vec_generator: Arc<VecGenerator>,

}

pub struct SecretServiceFactory;

impl SecretServiceFactory {

    pub fn create(shares_service: Arc<MasterKeySharesService>,
                  vec_generator: Arc<VecGenerator>) -> LocalSecretsService {
        SecretsService::new(SimpleFilesService::new(), shares_service, vec_generator)
    }

}

impl<T: FilesService> SecretsService<T> {

    fn new(files_service: T,
           shares_service: Arc<MasterKeySharesService>,
           vec_generator: Arc<VecGenerator>) -> SecretsService<T> {
        SecretsService {
            encryption_service: EncryptionService::new(vec_generator.clone()),
            files_service,
            is_sealed: AtomicBool::new(true),
            shares_service,
            vec_generator
        }
    }

    pub fn encrypt(&self, target: &Vec<u8>) -> Result<EncryptedPayload, SecretsError> {
        if self.is_sealed() {
            return Err(SecretsError::Sealed);
        }

        self.encryption_service.encrypt(target)
    }

    pub fn decrypt(&self, target: &EncryptedPayload) -> Result<ZeroizeWrapper, SecretsError> {
        if self.is_sealed() {
            return Err(SecretsError::Sealed);
        }

        self.encryption_service.decrypt(target)
    }

    pub fn initialize(&self) -> Result<Vec<MasterKeyShare>, SecretsError> {
        if self.is_initialized()? {
            return Err(SecretsError::AlreadyInitialized);
        }

        let enc_key = EncryptionKey::new(
            self.vec_generator.generate_random(KEY_LENGTH)?
        );

        let master_key = MasterKey::new(
            self.vec_generator.generate_random(KEY_LENGTH)?
        );

        let aad = self.vec_generator.generate_random(KEY_LENGTH)?;

        let nonce = self.vec_generator.generate_random(NONCE_SIZE)?;

        let encrypted_enc_key =
            XChaCha20Poly1305::new(
                GenericArray::from_slice(master_key.get_value()))
                .encrypt(&GenericArray::from_slice(&nonce),
                         Payload {
                             msg: enc_key.get_value(),
                             aad: &aad
                         })?;

        self.files_service.store_key(
            EncryptedPayload::new(nonce, encrypted_enc_key))?;
        self.files_service.store_aad(aad)?;

        let sharks = Sharks(NUM_SHARES);

        Ok(
            sharks
                .dealer(master_key.get_value())
                .take(NUM_SHARES as usize)
                .map(|share|
                    MasterKeyShare::new((&share).into()))
                .collect()
        )
    }

    pub fn unseal(&self) -> Result<(), SecretsError> {
        if !self.is_sealed() {
            return Err(SecretsError::AlreadyUnsealed);
        }

        let master_key_shares = self.shares_service.get();

        let sharks = Sharks(NUM_SHARES);

        let mut shares = vec![];

        for master_key_share in master_key_shares.iter() {
            shares.push(
                Share::try_from(master_key_share.get_value())
                    .map_err(|err| SecretsError::MasterKeyShareError(err.to_string()))?
            );
        }

        let master_key = MasterKey::new(
            sharks
                .recover(&shares)
                .map_err(|err| SecretsError::MasterKeyShareError(err.to_string()))?
        );

        let encrypted_enc_key = self.files_service.read_key()?;
        let aad = self.files_service.read_aad()?;

        let enc_key =
            XChaCha20Poly1305::new(GenericArray::from_slice(master_key.get_value()))
                .decrypt(&GenericArray::from_slice(encrypted_enc_key.get_nonce()),
                         Payload {
                             msg: encrypted_enc_key.get_payload(),
                             aad: &aad
                         })?;

        self.encryption_service.set_key(
            ZeroizeWrapper::new(aad), EncryptionKey::new(enc_key))?;

        self.is_sealed.store(false, Ordering::Relaxed);

        Ok(())
    }

    pub fn is_initialized(&self) -> Result<bool, SecretsError> {
        Ok(
            !self.is_sealed()
                || self.files_service.exists_key()?
                || self.files_service.exists_aad()?
        )
    }

    pub fn is_sealed(&self) -> bool {
        self.is_sealed.load(Ordering::Relaxed)
    }

    pub fn clear(&self) -> Result<(), SecretsError> {
        self.files_service.clear()
    }

}

#[cfg(test)]
mod tests {
    use std::sync::{Arc, RwLock};

    use ring::rand::SystemRandom;

    use crate::data::files::MockFilesService;
    use crate::models::errors::SecretsError;
    use crate::models::sec::secrets::EncryptedPayload;
    use crate::services::secrets::random::VecGenerator;
    use crate::services::secrets::SecretsService;
    use crate::services::secrets::shares::MasterKeySharesService;

    #[test]
    fn test_returns_error_on_encdec_when_sealed() {
        let service = build_default_service(MockFilesService::new());

        assert!(service.encrypt(&vec![1,2,3]).is_err());
        assert!(service.decrypt(
            &EncryptedPayload::new(vec![1,1], vec![3,4])).is_err());
    }

    #[test]
    fn test_initializes_and_useals_correctly() {
        let mut mock = MockFilesService::new();

        mock.expect_exists_key()
            .times(1)
            .returning(|| Ok(false));

        mock.expect_exists_aad()
            .times(1)
            .returning(|| Ok(false));

        let key_spy = Arc::new(
            FileSpy::new(EncryptedPayload::new(vec![], vec![])));
        let ks_ptr_one = key_spy.clone();
        let ks_ptr_two = key_spy.clone();

        mock.expect_store_key()
            .times(1)
            .returning(move |key| ks_ptr_one.consume(key));

        mock.expect_read_key()
            .times(1)
            .returning(move || Ok(ks_ptr_two.get()));

        let aad_spy: Arc<FileSpy<Vec<u8>>> = Arc::new(FileSpy::new(Vec::new()));
        let as_ptr_one = aad_spy.clone();
        let as_ptr_two = aad_spy.clone();

        mock.expect_store_aad()
            .times(1)
            .returning(move |aad| as_ptr_one.consume(aad));

        mock.expect_read_aad()
            .times(1)
            .returning(move || Ok(as_ptr_two.get()));

        let shares_service = Arc::new(MasterKeySharesService::new());

        let service =
            build_service(mock, shares_service.clone());

        let shares = service.initialize().unwrap();

        assert!(!shares.is_empty());

        let saved_key = key_spy.get();
        let saved_aad = aad_spy.get();

        assert!(!saved_key.get_payload().is_empty());
        assert!(!saved_key.get_nonce().is_empty());
        assert!(!saved_aad.is_empty());

        shares
            .into_iter()
            .for_each(|share|
                shares_service.add(share).unwrap());

        assert!(service.unseal().is_ok());

        let payload = vec![1,2,3,4];

        assert_eq!(
            service.decrypt(&service.encrypt(&payload).unwrap()).unwrap().get_value(),
            &payload);

        assert!(service.unseal().is_err());
        assert!(service.initialize().is_err());

        assert_eq!(
            service.decrypt(&service.encrypt(&payload).unwrap()).unwrap().get_value(),
            &payload);
    }

    #[test]
    fn test_will_not_initialize_if_key_data_is_present() {
        let mut mock = MockFilesService::new();

        mock.expect_exists_key()
            .times(1)
            .returning(|| Ok(true));

        let service = build_default_service(mock);

        assert!(service.initialize().is_err());
    }

    fn build_service(mock: MockFilesService,
                     key_shares_service: Arc<MasterKeySharesService>)
                     -> SecretsService<MockFilesService> {
        SecretsService::new(mock,
                            key_shares_service,
                            Arc::new(
                                VecGenerator::new(
                                    Arc::new(SystemRandom::new()))))
    }

    fn build_default_service(mock: MockFilesService) -> SecretsService<MockFilesService> {
        build_service(mock, Arc::new(MasterKeySharesService::new()))
    }

    struct FileSpy<T: Clone> {
        data: RwLock<T>
    }

    impl<T: Clone> FileSpy<T> {

        fn new(initial: T) -> FileSpy<T> {
            FileSpy {
                data: RwLock::new(initial)
            }
        }

        fn consume(&self,
                   data: T) -> Result<(), SecretsError> {
            *self.data.write().unwrap() = data;
            Ok(())
        }

        fn get(&self) -> T {
            self.data.read().unwrap().clone()
        }
    }

}