// 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() ) } }