Skip to content
Snippets Groups Projects
Commit 2ace7998 authored by Éloïs's avatar Éloïs
Browse files

[feat] impl dewif

parent e1a30828
No related branches found
No related tags found
1 merge request!1Resolve "Impl DEWIF format"
Pipeline #8147 passed
......@@ -38,6 +38,12 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "base64"
version = "0.11.0"
......@@ -144,6 +150,7 @@ name = "dup-crypto"
version = "0.8.0"
dependencies = [
"aes",
"arrayvec",
"base64",
"bincode",
"bs58",
......
......@@ -14,6 +14,7 @@ path = "src/lib.rs"
[dependencies]
aes = { version = "0.3.2", optional = true }
arrayvec = { version = "0.5.1", features = ["array-sizes-33-128", "array-sizes-129-255"], optional = true }
base64 = "0.11.0"
bs58 = "0.3.0"
byteorder = "1.3.2"
......@@ -28,6 +29,8 @@ zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
bincode = "1.2.0"
[features]
default = ["ser"]
default = ["dewip", "ser"]
aes256 = ["aes"]
dewip = ["aes256", "arrayvec"]
ser = ["serde"]
// 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/>.
//! Handle [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) format
//!
//! # Write ed25519 key-pair in DEWIF file
//!
//! ```
//! use dup_crypto::dewip::write_dewif_v1_content;
//! use dup_crypto::keys::ed25519::{KeyPairFromSaltedPasswordGenerator, SaltedPassword};
//!
//! // Get user credentials (from cli prompt or gui)
//! let credentials = SaltedPassword::new("user salt".to_owned(), "user password".to_owned());
//!
//! // Generate ed25519 keypair
//! let keypair = KeyPairFromSaltedPasswordGenerator::with_default_parameters().generate(credentials);
//!
//! // Get user passphrase for DEWIF encryption
//! let encryption_passphrase = "toto titi tata";
//!
//! // Serialize keypair in DEWIF format
//! let dewif_content = write_dewif_v1_content(&keypair, encryption_passphrase);
//!
//! assert_eq!(
//! "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=",
//! dewif_content
//! )
//! ```
//!
//! # Read DEWIF file
//!
//! ```
//! use dup_crypto::dewip::read_dewip_file_content;
//! use dup_crypto::keys::{KeyPair, Signator};
//!
//! // Get DEWIP file content (Usually from disk)
//! let dewip_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
//!
//! // Get user passphrase for DEWIF decryption (from cli prompt or gui)
//! let encryption_passphrase = "toto titi tata";
//!
//! // Read DEWIP file content
//! // If the file content is correct, we get a key-pair iterator.
//! let mut key_pair_iter = read_dewip_file_content(dewip_file_content, encryption_passphrase)
//! .expect("invalid DEWIF file.")
//! .into_iter();
//!
//! // 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()
//! )
//! ```
//!
mod read;
mod write;
pub use read::{read_dewip_file_content, DewipReadError};
pub use write::{write_dewif_v1_content, write_dewif_v2_content};
use crate::hashs::Hash;
use crate::seeds::Seed32;
use arrayvec::ArrayVec;
use unwrap::unwrap;
const VERSION_BYTES: usize = 4;
// v1
static VERSION_V1: &[u8] = &[0, 0, 0, 1];
const V1_BYTES_LEN: usize = 68;
const V1_ENCRYPTED_BYTES_LEN: usize = 64;
const V1_AES_BLOCKS_COUNT: usize = 4;
// v2
static VERSION_V2: &[u8] = &[0, 0, 0, 2];
const V2_BYTES_LEN: usize = 132;
const V2_ENCRYPTED_BYTES_LEN: usize = 128;
fn gen_aes_seed(passphrase: &str) -> Seed32 {
let mut salt = ArrayVec::<[u8; 37]>::new();
unwrap!(salt.try_extend_from_slice(b"dewif"));
let hash = Hash::compute(passphrase.as_bytes());
unwrap!(salt.try_extend_from_slice(hash.as_ref()));
let mut aes_seed_bytes = [0u8; 32];
scrypt::scrypt(
passphrase.as_bytes(),
salt.as_ref(),
&scrypt::ScryptParams::new(12, 16, 1).expect("dev error: invalid scrypt params"),
&mut aes_seed_bytes,
)
.expect("dev error: invalid seed len");
Seed32::new(aes_seed_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keys::ed25519::KeyPairFromSeed32Generator;
use crate::keys::KeyPairEnum;
use crate::seeds::Seed32;
#[test]
fn dewip_v1() {
let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let dewif_content = write_dewif_v1_content(&written_keypair, "toto");
let mut keypairs_iter = read_dewip_file_content(&dewif_content, "toto")
.expect("dewip content must be readed successfully")
.into_iter();
let keypair_read = keypairs_iter.next().expect("Must read one keypair");
assert_eq!(KeyPairEnum::Ed25519(written_keypair), keypair_read,)
}
#[test]
fn dewip_v1_corrupted() -> Result<(), ()> {
let written_keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let mut dewif_content = write_dewif_v1_content(&written_keypair, "toto");
// Corrupt one byte in dewif_content
let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
dewif_bytes_mut[13] = 0x52;
if let Err(DewipReadError::CorruptedContent) =
read_dewip_file_content(&dewif_content, "toto")
{
Ok(())
} else {
panic!("dewif content must be corrupted.")
}
}
#[test]
fn dewip_v2() {
let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
let dewif_content = write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
let mut keypairs_iter = read_dewip_file_content(&dewif_content, "toto")
.expect("dewip content must be readed successfully")
.into_iter();
let keypair1_read = keypairs_iter.next().expect("Must read one keypair");
let keypair2_read = keypairs_iter.next().expect("Must read one keypair");
assert_eq!(KeyPairEnum::Ed25519(written_keypair1), keypair1_read,);
assert_eq!(KeyPairEnum::Ed25519(written_keypair2), keypair2_read,);
}
#[test]
fn dewip_v2_corrupted() -> Result<(), ()> {
let written_keypair1 = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let written_keypair2 = KeyPairFromSeed32Generator::generate(Seed32::new([1u8; 32]));
let mut dewif_content =
write_dewif_v2_content(&written_keypair1, &written_keypair2, "toto");
// Corrupt one byte in dewif_content
let dewif_bytes_mut = unsafe { dewif_content.as_bytes_mut() };
dewif_bytes_mut[13] = 0x52;
if let Err(DewipReadError::CorruptedContent) =
read_dewip_file_content(&dewif_content, "toto")
{
Ok(())
} else {
panic!("dewif content must be corrupted.")
}
}
}
// 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 [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
use crate::keys::ed25519::{KeyPairFromSeed32Generator, PublicKey, PUBKEY_SIZE_IN_BYTES};
use crate::keys::KeyPairEnum;
use crate::seeds::Seed32;
use arrayvec::ArrayVec;
use byteorder::ByteOrder;
use std::convert::{TryFrom, TryInto};
use thiserror::Error;
const MAX_KEYPAIRS_COUNT: usize = 2;
/// Error when try to read DEWIP file content
#[derive(Clone, Debug, Error)]
pub enum DewipReadError {
/// DEWIP file content is corrupted
#[error("DEWIP 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,
/// Unsupported version
#[error("Version {actual:?} is not supported. Supported versions: [1, 2].")]
UnsupportedVersion {
/// Actual version
actual: u32,
},
}
/// read dewip file content with user passphrase
pub fn read_dewip_file_content(
file_content: &str,
passphrase: &str,
) -> Result<impl IntoIterator<Item = KeyPairEnum>, DewipReadError> {
let mut bytes = base64::decode(file_content).map_err(DewipReadError::InvalidBase64Str)?;
if bytes.len() < 4 {
return Err(DewipReadError::TooShortContent);
}
let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
match version {
1 => Ok({
let mut array_keypairs = ArrayVec::new();
array_keypairs.push(read_dewip_v1(&mut bytes[4..], passphrase)?);
array_keypairs
}),
2 => read_dewip_v2(&mut bytes[4..], passphrase),
other_version => Err(DewipReadError::UnsupportedVersion {
actual: other_version,
}),
}
}
fn read_dewip_v1(bytes: &mut [u8], passphrase: &str) -> Result<KeyPairEnum, DewipReadError> {
match bytes.len() {
len if len < super::V1_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooShortContent),
len if len > super::V1_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::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_dewip_v2(
bytes: &mut [u8],
passphrase: &str,
) -> Result<ArrayVec<[KeyPairEnum; MAX_KEYPAIRS_COUNT]>, DewipReadError> {
let mut array_keypairs = ArrayVec::new();
match bytes.len() {
len if len < super::V2_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::TooShortContent),
len if len > super::V2_ENCRYPTED_BYTES_LEN => return Err(DewipReadError::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)
}
fn bytes_to_checked_keypair(bytes: &[u8]) -> Result<KeyPairEnum, DewipReadError> {
// Wrap bytes into Seed32 and PublicKey
let seed = Seed32::new(
(&bytes[..PUBKEY_SIZE_IN_BYTES])
.try_into()
.expect("dev error"),
);
let expected_pubkey = PublicKey::try_from(&bytes[PUBKEY_SIZE_IN_BYTES..]).expect("dev error");
// Get keypair
let keypair = KeyPairFromSeed32Generator::generate(seed);
// Check pubkey
if keypair.pubkey() != expected_pubkey {
Err(DewipReadError::CorruptedContent)
} else {
Ok(KeyPairEnum::Ed25519(keypair))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_unsupported_version() -> Result<(), ()> {
if let Err(DewipReadError::UnsupportedVersion { .. }) = read_dewip_file_content(
"ABAAAfKjMzOFfhwgypF3mAx0QDXyozMzhX4cIMqRd5gMdEA1WZwQjCR49iZDK2QhYfdTbPz9AGB01edt4iRSzdTp3c4=",
"toto"
) {
Ok(())
} else {
panic!("Read must be fail with error UnsupportedVersion.")
}
}
#[test]
fn read_too_short_content() -> Result<(), ()> {
if let Err(DewipReadError::TooShortContent) = read_dewip_file_content("AAA", "toto") {
Ok(())
} else {
panic!("Read must be fail with error TooShortContent.")
}
}
#[test]
fn tmp() {
use crate::keys::{KeyPair, Signator};
// Get DEWIP file content (Usually from disk)
let dewip_file_content = "AAAAATHfJ3vTvEPcXm22NwhJtnNdGuSjikpSYIMgX96Z9xVT0y8GoIlBL1HaxaWpu0jVDfuwtCGSP9bu2pj6HGbuYVA=";
// Get user passphrase for DEWIF decryption (from cli prompt or gui)
let encryption_passphrase = "toto titi tata";
// Read DEWIP file content
// If the file content is correct, we get a key-pair iterator.
let mut key_pair_iter = read_dewip_file_content(dewip_file_content, encryption_passphrase)
.expect("invalid DEWIF file.")
.into_iter();
// 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()
)
}
}
// 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/>.
//! Write [DEWIP](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
use crate::keys::ed25519::Ed25519KeyPair;
use arrayvec::ArrayVec;
use unwrap::unwrap;
/// Write dewip v1 file content with user passphrase
pub fn write_dewif_v1_content(keypair: &Ed25519KeyPair, passphrase: &str) -> String {
let mut bytes = ArrayVec::<[u8; super::V1_BYTES_LEN]>::new();
unwrap!(bytes.try_extend_from_slice(super::VERSION_V1));
unwrap!(bytes.try_extend_from_slice(keypair.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair.pubkey().as_ref()));
let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
crate::aes256::encrypt::encrypt_n_blocks(&cipher, &mut bytes[4..], super::V1_AES_BLOCKS_COUNT);
base64::encode(bytes.as_ref())
}
/// Write dewip v2 file content with user passphrase
pub fn write_dewif_v2_content(
keypair1: &Ed25519KeyPair,
keypair2: &Ed25519KeyPair,
passphrase: &str,
) -> String {
let mut bytes = ArrayVec::<[u8; super::V2_BYTES_LEN]>::new();
unwrap!(bytes.try_extend_from_slice(super::VERSION_V2));
unwrap!(bytes.try_extend_from_slice(keypair1.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair1.pubkey().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair2.seed().as_ref()));
unwrap!(bytes.try_extend_from_slice(keypair2.pubkey().as_ref()));
let cipher = crate::aes256::new_cipher(super::gen_aes_seed(passphrase));
crate::aes256::encrypt::encrypt_8_blocks(&cipher, &mut bytes[super::VERSION_BYTES..]);
base64::encode(bytes.as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keys::ed25519::KeyPairFromSeed32Generator;
use crate::seeds::Seed32;
#[test]
fn write_dewif_v1() {
let keypair = KeyPairFromSeed32Generator::generate(Seed32::new([0u8; 32]));
let dewif_content = write_dewif_v1_content(&keypair, "toto");
println!("{}", dewif_content);
assert_eq!(
"AAAAAb30ng3kI9QGMbR7TYCqPhS99J4N5CPUBjG0e02Aqj4UElionaHOt0kv+eaWgGSGkrP1LQfuwivuvg7+9n0gd18=",
dewif_content
)
}
}
......@@ -286,6 +286,9 @@ impl Ed25519KeyPair {
pub fn pubkey(&self) -> PublicKey {
self.pubkey
}
pub(crate) fn seed(&self) -> &Seed32 {
&self.seed
}
}
impl Display for Ed25519KeyPair {
......
......@@ -22,7 +22,6 @@
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
......@@ -32,6 +31,8 @@
#[cfg(feature = "aes256")]
pub mod aes256;
pub mod bases;
#[cfg(feature = "dewip")]
pub mod dewip;
pub mod hashs;
pub mod keys;
pub mod rand;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment