Skip to content
Snippets Groups Projects
Select Git revision
  • feature/build-apple-silicon
  • dev default protected
  • elois/legacy-crypto-box
  • hotfix/0.8
  • elois/fix-empty-pubkey
  • v0.8.4
  • v0.13.0
  • v0.12.1
  • v0.8.3
  • v0.8.2
  • v0.12.0
  • v0.11.1
  • v0.8.1
  • v0.11.0
  • v0.10.0
  • v0.9.1
  • v0.9.0
  • v0.8.0
18 results

read.rs

Blame
  • Forked from libs / dup-crypto-rs
    Up to date with the upstream repository.
    read.rs 8.30 KiB
    //  Copyright (C) 2020 Éloïs SANCHEZ.
    //
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Affero General Public License as
    // published by the Free Software Foundation, either version 3 of the
    // License, or (at your option) any later version.
    //
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Affero General Public License for more details.
    //
    // You should have received a copy of the GNU Affero General Public License
    // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    //! Read [DEWIF](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
    
    use super::{Currency, ExpectedCurrency};
    use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_DATAS_SIZE_IN_BYTES};
    use crate::keys::{KeyPair, KeyPairEnum};
    use crate::seeds::{Seed32, SEED_32_SIZE_IN_BYTES};
    use arrayvec::ArrayVec;
    use byteorder::ByteOrder;
    use std::convert::{TryFrom, TryInto};
    use thiserror::Error;
    
    const MAX_KEYPAIRS_COUNT: usize = 2;
    
    type KeyPairsArray = ArrayVec<[KeyPairEnum; MAX_KEYPAIRS_COUNT]>;
    type KeyPairsIter = arrayvec::IntoIter<[KeyPairEnum; MAX_KEYPAIRS_COUNT]>;
    
    /// Error when try to read DEWIF file content
    #[derive(Clone, Debug, Error)]
    pub enum DewifReadError {
        /// DEWIF file content is corrupted
        #[error("DEWIF file content is corrupted.")]
        CorruptedContent,
        /// Invalid base 64 string
        #[error("Invalid base 64 string: {0}.")]
        InvalidBase64Str(base64::DecodeError),
        /// Invalid format
        #[error("Invalid format.")]
        InvalidFormat,
        /// Too short content
        #[error("Too short content.")]
        TooShortContent,
        /// Too long content
        #[error("Too long content.")]
        TooLongContent,
        /// Unexpected currency
        #[error("Unexpected currency '{actual}', expected: '{expected}'.")]
        UnexpectedCurrency {
            /// Expected currency
            expected: ExpectedCurrency,
            /// Actual currency
            actual: Currency,
        },
        /// Unsupported version
        #[error("Version {actual} is not supported. Supported versions: [1, 2].")]
        UnsupportedVersion {
            /// Actual version
            actual: u32,
        },
    }
    
    /// read dewif file content with user passphrase
    pub fn read_dewif_file_content(
        expected_currency: ExpectedCurrency,
        file_content: &str,
        passphrase: &str,
    ) -> Result<impl Iterator<Item = KeyPairEnum>, DewifReadError> {
        let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
    
        if bytes.len() < 8 {
            return Err(DewifReadError::TooShortContent);
        }
    
        let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
        let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
    
        if !expected_currency.is_valid(currency) {
            return Err(DewifReadError::UnexpectedCurrency {
                expected: expected_currency,
                actual: currency,
            });
        }
    
        match version {
            1 => Ok({
                let mut array_keypairs = KeyPairsArray::new();
                array_keypairs.push(read_dewif_v1(&mut bytes[8..], passphrase)?);
                array_keypairs.into_iter()
            }),
            2 => read_dewif_v2(&mut bytes[8..], passphrase),
            other_version => Err(DewifReadError::UnsupportedVersion {
                actual: other_version,
            }),
        }
    }
    
    fn read_dewif_v1(bytes: &mut [u8], passphrase: &str) -> Result<KeyPairEnum, DewifReadError> {
        match bytes.len() {
            len if len < super::V1_ENCRYPTED_BYTES_LEN => return Err(DewifReadError::TooShortContent),
            len if len > super::V1_ENCRYPTED_BYTES_LEN => return Err(DewifReadError::TooLongContent),
            _ => (),
        }
    
        // Decrypt bytes
        let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
        crate::aes256::decrypt::decrypt_n_blocks(&cipher, bytes, super::V1_AES_BLOCKS_COUNT);
    
        // Get checked keypair
        bytes_to_checked_keypair(bytes)
    }
    
    fn read_dewif_v2(bytes: &mut [u8], passphrase: &str) -> Result<KeyPairsIter, DewifReadError> {
        let mut array_keypairs = KeyPairsArray::new();
    
        match bytes.len() {
            len if len < super::V2_ENCRYPTED_BYTES_LEN => return Err(DewifReadError::TooShortContent),
            len if len > super::V2_ENCRYPTED_BYTES_LEN => return Err(DewifReadError::TooLongContent),
            _ => (),
        }
    
        // Decrypt bytes
        let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
        crate::aes256::decrypt::decrypt_8_blocks(&cipher, bytes);
    
        array_keypairs.push(bytes_to_checked_keypair(&bytes[..64])?);
        array_keypairs.push(bytes_to_checked_keypair(&bytes[64..])?);
    
        Ok(array_keypairs.into_iter())
    }
    
    fn bytes_to_checked_keypair(bytes: &[u8]) -> Result<KeyPairEnum, DewifReadError> {
        // Wrap bytes into Seed32 and PublicKey
        let seed = Seed32::new(
            (&bytes[..SEED_32_SIZE_IN_BYTES])
                .try_into()
                .expect("dev error"),
        );
        let expected_pubkey =
            PublicKey::try_from(&bytes[PUBKEY_DATAS_SIZE_IN_BYTES..]).expect("dev error");
    
        // Get keypair
        let keypair = KeyPairFromSeed32Generator::generate(seed);
    
        // Check pubkey
        if keypair.public_key() != expected_pubkey {
            Err(DewifReadError::CorruptedContent)
        } else {
            Ok(KeyPairEnum::Ed25519(keypair))
        }
    }
    
    #[cfg(test)]
    mod tests {
    
        use super::*;
    
        #[test]
        fn read_unsupported_version() -> Result<(), ()> {
            if let Err(DewifReadError::UnsupportedVersion { .. }) = read_dewif_file_content(
                ExpectedCurrency::Any,
                "ABAAAb30ng3kI9QGMbR7TYCqPhS99J4N5CPUBjG0e02Aqj4U1UmOemI6pweNm1Ab1AR4V6ZWFnwkkp9QPxppVeMv7aaLWdop",
                "toto"
            ) {
                Ok(())
            } else {
                panic!("Read must be fail with error UnsupportedVersion.")
            }
        }
    
        #[test]
        fn read_too_short_content() -> Result<(), ()> {
            if let Err(DewifReadError::TooShortContent) =
                read_dewif_file_content(ExpectedCurrency::Any, "AAA", "toto")
            {
                Ok(())
            } else {
                panic!("Read must be fail with error TooShortContent.")
            }
        }
    
        #[test]
        fn read_unexpected_currency() -> Result<(), ()> {
            if let Err(DewifReadError::UnexpectedCurrency { .. }) = read_dewif_file_content(
                ExpectedCurrency::Specific(Currency::from(42)),
                "AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ",
                "toto titi tata"
            ) {
                Ok(())
            } else {
                panic!("Read must be fail with error UnexpectedCurrency.")
            }
        }
    
        #[test]
        fn read_ok() {
            use crate::dewif::Currency;
            use crate::keys::{KeyPair, Signator};
            use std::str::FromStr;
    
            // Get DEWIF file content (Usually from disk)
            let dewif_file_content = "AAAAARAAAAEx3yd707xD3F5ttjcISbZzXRrko4pKUmCDIF/emfcVU9MvBqCJQS9R2sWlqbtI1Q37sLQhkj/W7tqY+hxm7mFQ";
    
            // Get user passphrase for DEWIF decryption (from cli prompt or gui)
            let encryption_passphrase = "toto titi tata";
    
            // Expected currency
            let expected_currency =
                ExpectedCurrency::Specific(Currency::from_str("g1-test").expect("unknown currency"));
    
            // Read DEWIF file content
            // If the file content is correct, we get a key-pair iterator.
            let mut key_pair_iter =
                read_dewif_file_content(expected_currency, dewif_file_content, encryption_passphrase)
                    .expect("invalid DEWIF file");
    
            // Get first key-pair
            let key_pair = key_pair_iter
                .next()
                .expect("DEWIF file must contain at least one keypair");
    
            assert_eq!(
                "2cC9FrvRiN3uHHcd8S7wuureDS8CAmD5y4afEgSCLHtU",
                &key_pair.public_key().to_string()
            );
    
            // Generate signator
            // `Signator` is a non-copiable and non-clonable type,
            // so only generate it when you are in the scope where you effectively sign.
            let signator = key_pair.generate_signator();
    
            // Sign a message with keypair
            let sig = signator.sign(b"message");
    
            assert_eq!(
                "nCWl7jtCa/nCMKKnk2NJN7daVxd/ER+e1wsFbofdh/pUvDuHxFaa7S5eUMGiqPTJ4uJQOvrmF/BOfOsYIoI2Bg==",
                &sig.to_string()
            )
        }
    }