dup_crypto/dewif/
read.rs

1//  Copyright (C) 2020 Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Read [DEWIF](https://git.duniter.org/nodes/common/doc/blob/dewif/rfc/0013_Duniter_Encrypted_Wallet_Import_Format.md) file content
17
18use super::{Currency, DewifContent, DewifPayload, ExpectedCurrency};
19use crate::keys::{KeyPair, Signator};
20use crate::seeds::{Seed32, SEED_32_SIZE_IN_BYTES};
21use crate::{
22    keys::{
23        ed25519::{PublicKey, PUBKEY_DATAS_SIZE_IN_BYTES},
24        KeysAlgo,
25    },
26    mnemonic::{Language, Mnemonic, MnemonicError},
27};
28use byteorder::ByteOrder;
29use std::{
30    convert::{TryFrom, TryInto},
31    hint::unreachable_unchecked,
32};
33use thiserror::Error;
34
35/// Error when try to read DEWIF file content
36#[derive(Clone, Debug, Error)]
37pub enum DewifReadError {
38    /// DEWIF file content is corrupted
39    #[error("DEWIF file content is corrupted.")]
40    CorruptedContent,
41    /// Invalid base 64 string
42    #[error("Invalid base 64 string: {0}.")]
43    InvalidBase64Str(base64::DecodeError),
44    /// Invalid format
45    #[error("Invalid format.")]
46    InvalidFormat,
47    /// Too short content
48    #[error("Too short content.")]
49    TooShortContent,
50    /// Too long content
51    #[error("Too long content.")]
52    TooLongContent,
53    /// Unexpected currency
54    #[error("Unexpected currency '{actual}', expected: '{expected}'.")]
55    UnexpectedCurrency {
56        /// Expected currency
57        expected: ExpectedCurrency,
58        /// Actual currency
59        actual: Currency,
60    },
61    /// Unknown algorithm
62    #[error("unknown algorithm")]
63    UnknownAlgo,
64    /// Unspecified rand error
65    #[error("Unspecified rand error")]
66    UnspecifiedRandError,
67    /// Unsupported version
68    #[error("Version {actual} is not supported. Supported versions: [1, 2].")]
69    UnsupportedVersion {
70        /// Actual version
71        actual: u32,
72    },
73}
74
75/// read dewif log N
76pub fn read_dewif_log_n(
77    expected_currency: ExpectedCurrency,
78    file_content: &str,
79) -> Result<u8, DewifReadError> {
80    let bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
81
82    if bytes.len() < 9 {
83        return Err(DewifReadError::TooShortContent);
84    }
85
86    let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
87    let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
88
89    if !expected_currency.is_valid(currency) {
90        return Err(DewifReadError::UnexpectedCurrency {
91            expected: expected_currency,
92            actual: currency,
93        });
94    }
95
96    match version {
97        1 => Ok(bytes[8]),
98        other_version => Err(DewifReadError::UnsupportedVersion {
99            actual: other_version,
100        }),
101    }
102}
103
104/// read dewif meta data
105pub fn read_dewif_meta(file_content: &str) -> Result<super::DewifMeta, DewifReadError> {
106    let bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
107
108    if bytes.len() < 10 {
109        return Err(DewifReadError::TooShortContent);
110    }
111
112    let version = byteorder::BigEndian::read_u32(&bytes[0..4]);
113    let currency = Currency::from(byteorder::BigEndian::read_u32(&bytes[4..8]));
114
115    let log_n = bytes[8];
116    let algo = KeysAlgo::from_u8(bytes[9]).map_err(|_| DewifReadError::UnknownAlgo)?;
117
118    Ok(super::DewifMeta {
119        algo,
120        currency,
121        log_n,
122        version,
123    })
124}
125
126/// read dewif content with user passphrase
127pub fn read_dewif_content(
128    expected_currency: ExpectedCurrency,
129    file_content: &str,
130    passphrase: &str,
131) -> Result<DewifContent, DewifReadError> {
132    let meta = read_dewif_meta(file_content)?;
133
134    let mut bytes = base64::decode(file_content).map_err(DewifReadError::InvalidBase64Str)?;
135
136    let version = meta.version;
137    let currency = meta.currency;
138
139    if !expected_currency.is_valid(currency) {
140        return Err(DewifReadError::UnexpectedCurrency {
141            expected: expected_currency,
142            actual: currency,
143        });
144    }
145
146    let payload = match version {
147        1 => read_dewif_v1(&mut bytes[8..], passphrase)?,
148        other_version => {
149            return Err(DewifReadError::UnsupportedVersion {
150                actual: other_version,
151            });
152        }
153    };
154    Ok(DewifContent { meta, payload })
155}
156
157fn read_dewif_v1(bytes: &mut [u8], passphrase: &str) -> Result<DewifPayload, DewifReadError> {
158    match bytes.len() {
159        len if len < super::V1_BIP32_ED25519_DATA_LEN => {
160            return Err(DewifReadError::TooShortContent)
161        }
162        len if len > super::V1_ED25519_DATA_LEN => return Err(DewifReadError::TooLongContent),
163        _ => (),
164    }
165
166    // Read log_n
167    let log_n = bytes[0];
168    // Read algo
169    let algo = KeysAlgo::from_u8(bytes[1]).map_err(|_| DewifReadError::UnknownAlgo)?;
170    // Read nonce
171    let nonce = super::read_nonce(&bytes[2..14]);
172
173    match algo {
174        KeysAlgo::Ed25519 => {
175            // Decrypt bytes
176            let mut decrypted_bytes = [0u8; 64];
177            crate::xor_cipher::xor_cipher(
178                &bytes[super::V1_CLEAR_HEADERS_LEN..],
179                super::gen_xor_seed64(log_n, nonce, passphrase).as_ref(),
180                &mut decrypted_bytes,
181            );
182
183            // Get checked keypair
184            Ok(DewifPayload::Ed25519(bytes_to_checked_keypair(
185                &decrypted_bytes,
186            )?))
187        }
188        KeysAlgo::Bip32Ed25519 => {
189            // Decrypt bytes
190            let mut decrypted_bytes = [0u8; 42];
191            crate::xor_cipher::xor_cipher(
192                &bytes[super::V1_CLEAR_HEADERS_LEN..(super::V1_CLEAR_HEADERS_LEN + 42)],
193                super::gen_xor_seed42(log_n, nonce, passphrase).as_ref(),
194                &mut decrypted_bytes,
195            );
196
197            // Get checked keypair
198            let mnemonic = get_dewif_mnemonic(&decrypted_bytes)
199                .map_err(|_| DewifReadError::CorruptedContent)?;
200            let checksum = super::compute_checksum(&nonce, decrypted_bytes[0], mnemonic.entropy());
201            let expected_checksum = &decrypted_bytes[34..];
202            if &checksum[..] != expected_checksum {
203                Err(DewifReadError::CorruptedContent)
204            } else {
205                Ok(DewifPayload::Bip32Ed25519(mnemonic))
206            }
207        }
208    }
209}
210
211// Internal insecure function, should not expose on public API
212pub(super) fn get_dewif_seed_unchecked(bytes: &mut [u8], passphrase: &str) -> Seed32 {
213    // Read log_n
214    let log_n = bytes[0];
215    // Read nonce
216    let nonce = super::read_nonce(&bytes[2..14]);
217
218    // Decrypt bytes
219    let mut decrypted_bytes = [0u8; 64];
220    crate::xor_cipher::xor_cipher(
221        &bytes[super::V1_CLEAR_HEADERS_LEN..],
222        super::gen_xor_seed64(log_n, nonce, passphrase).as_ref(),
223        &mut decrypted_bytes,
224    );
225
226    // Wrap bytes into Seed32
227    Seed32::new(
228        (&decrypted_bytes[..SEED_32_SIZE_IN_BYTES])
229            .try_into()
230            .unwrap_or_else(|_| unsafe { unreachable_unchecked() }),
231    )
232}
233
234fn bytes_to_checked_keypair<
235    S: Signator<PublicKey = PublicKey>,
236    KP: KeyPair<Seed = Seed32, Signator = S>,
237>(
238    bytes: &[u8],
239) -> Result<KP, DewifReadError> {
240    // Wrap bytes into Seed32 and PublicKey
241    let seed = Seed32::new(
242        (&bytes[..SEED_32_SIZE_IN_BYTES])
243            .try_into()
244            .unwrap_or_else(|_| unsafe { unreachable_unchecked() }),
245    );
246    let expected_pubkey = PublicKey::try_from(&bytes[PUBKEY_DATAS_SIZE_IN_BYTES..])
247        .map_err(|_| DewifReadError::InvalidFormat)?;
248
249    // Get keypair
250    let keypair = KP::from_seed(seed);
251
252    // Check pubkey
253    if keypair.public_key() != expected_pubkey {
254        Err(DewifReadError::CorruptedContent)
255    } else {
256        Ok(keypair)
257    }
258}
259
260#[cfg(feature = "bip32-ed25519")]
261fn get_dewif_mnemonic(decrypted_bytes: &[u8]) -> Result<Mnemonic, MnemonicError> {
262    // Read mnemonic language
263    let lang = Language::from_u8(decrypted_bytes[0])?;
264
265    // Read mnemonic entropy length
266    let mnemonic_entropy_len = decrypted_bytes[1];
267
268    Mnemonic::from_entropy(
269        &decrypted_bytes[2..(2 + mnemonic_entropy_len as usize)],
270        lang,
271    )
272}
273
274#[cfg(test)]
275mod tests {
276    use crate::dewif::DewifMeta;
277
278    use super::*;
279    use unwrap::unwrap;
280
281    #[test]
282    fn read_unsupported_version() -> Result<(), ()> {
283        if let Err(DewifReadError::UnsupportedVersion { .. }) = read_dewif_content(
284            ExpectedCurrency::Any,
285            "ABAAARAAAAEMAAqqbWsirdvN0W7IkpmKdG/Zbt4ZszPx9VcWUu0o4cdxIZ4HHUybCVbyVmQL9Wid8KE6FCWeMRtr5OKAUKYwsNI=",
286            "toto"
287        ) {
288            Ok(())
289        } else {
290            panic!("Read must be fail with error UnsupportedVersion.")
291        }
292    }
293
294    #[test]
295    fn read_too_short_content() -> Result<(), ()> {
296        if let Err(DewifReadError::TooShortContent) =
297            read_dewif_content(ExpectedCurrency::Any, "AAA", "toto")
298        {
299            Ok(())
300        } else {
301            panic!("Read must be fail with error TooShortContent.")
302        }
303    }
304
305    #[test]
306    fn read_unexpected_currency() -> Result<(), ()> {
307        if let Err(DewifReadError::UnexpectedCurrency { .. }) = read_dewif_content(
308            ExpectedCurrency::Specific(Currency::from(42)),
309            "AAAAARAAAAEMAAqqbWsirdvN0W7IkpmKdG/Zbt4ZszPx9VcWUu0o4cdxIZ4HHUybCVbyVmQL9Wid8KE6FCWeMRtr5OKAUKYwsNI=",
310            "toto titi tata"
311        ) {
312            Ok(())
313        } else {
314            panic!("Read must be fail with error UnexpectedCurrency.")
315        }
316    }
317
318    #[test]
319    fn read_v1_ok() {
320        use crate::dewif::Currency;
321        use std::str::FromStr;
322
323        // Get DEWIF file content (Usually from disk)
324        let dewif_file_content = "AAAAARAAAAEMACoqKioqKioqKioqKufSmkhlv1gbkEomswG4hQ2uMIVh+YOEym0ZRyNRwX226lsjB2UT2cnWLR11Wf3xm8Dm2lLB4IAfxd+iFiza7h4=";
325
326        // Get user passphrase for DEWIF decryption (from cli prompt or gui)
327        let encryption_passphrase = "toto titi tata";
328
329        // Expected currency
330        let expected_currency = ExpectedCurrency::Specific(unwrap!(Currency::from_str("g1-test")));
331
332        // Read DEWIF file content
333        // If the file content is correct, we get a key-pair iterator.
334        assert_eq!(
335            unwrap!(read_dewif_log_n(expected_currency, dewif_file_content)),
336            12u8
337        );
338        assert_eq!(
339            unwrap!(read_dewif_meta(dewif_file_content)),
340            DewifMeta {
341                algo: KeysAlgo::Ed25519,
342                currency: unwrap!(Currency::from_str("g1-test")),
343                log_n: 12u8,
344                version: 1
345            }
346        );
347        if let DewifContent {
348            payload: DewifPayload::Ed25519(key_pair),
349            ..
350        } = unwrap!(read_dewif_content(
351            expected_currency,
352            dewif_file_content,
353            encryption_passphrase
354        )) {
355            assert_eq!(
356                "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS",
357                &key_pair.public_key().to_string()
358            );
359
360            // Generate signator
361            // `Signator` is a non-copiable and non-clonable type,
362            // so only generate it when you are in the scope where you effectively sign.
363            let signator = key_pair.generate_signator();
364
365            // Sign a message with keypair
366            let sig = signator.sign(b"message");
367
368            assert_eq!(
369                "JPurBgnHExHND1woow9nB7xVQjKkdHGs1znQbgv0ttZwOz16OlOCDDfvXfKE8e0xUfs2u7winav8IDwo7d1EBQ==",
370                &sig.to_string()
371            )
372        } else {
373            panic!("corrupted dewif");
374        }
375    }
376
377    #[test]
378    fn read_v1_bip32_ok() {
379        use crate::dewif::Currency;
380        use crate::keys::{KeyPair, PublicKey, Signator};
381        use std::str::FromStr;
382
383        // Get DEWIF file content (Usually from disk)
384        let dewif_file_content =
385            "AAAAARAAAAEOASoqKioqKioqKioqKkIx/qkP1PWhtDNca4MdsvxPWAtvCd7nYriwMOHKxIFO8GJy9ElNngbSVQ==";
386
387        // Get user passphrase for DEWIF decryption (from cli prompt or gui)
388        let encryption_passphrase = "toto titi tata";
389
390        // Expected currency
391        let expected_currency = ExpectedCurrency::Specific(unwrap!(Currency::from_str("g1-test")));
392
393        // Read DEWIF file content
394        // If the file content is correct, we get a key-pair iterator.
395        assert_eq!(
396            unwrap!(read_dewif_log_n(expected_currency, dewif_file_content)),
397            14u8
398        );
399        assert_eq!(
400            unwrap!(read_dewif_meta(dewif_file_content)),
401            DewifMeta {
402                algo: KeysAlgo::Bip32Ed25519,
403                currency: unwrap!(Currency::from_str("g1-test")),
404                log_n: 14u8,
405                version: 1
406            }
407        );
408        if let DewifContent {
409            payload: DewifPayload::Bip32Ed25519(mnemonic),
410            ..
411        } = unwrap!(read_dewif_content(
412            expected_currency,
413            dewif_file_content,
414            encryption_passphrase
415        )) {
416            let key_pair = crate::keys::ed25519::bip32::KeyPair::from_mnemonic(&mnemonic);
417
418            assert_eq!(
419                "9TgSNiJPFtQV89Wt2GPnoozpSWTJzAxERpmTQr5Lhv7G",
420                &key_pair.public_key().to_string()
421            );
422
423            // Generate signator
424            // `Signator` is a non-copiable and non-clonable type,
425            // so only generate it when you are in the scope where you effectively sign.
426            let signator = key_pair.generate_signator();
427
428            // Sign a message with keypair
429            let sig = signator.sign(b"message");
430
431            assert_eq!(
432                "N1+7Dzjde71hBCkoqSWRc3Ywn4+z7FChKjCgG8OtIlki4BH9w6QLXQ8Pkb7uyoCa9N9VuUgtJDgYSn09ll6yCg==",
433                &sig.to_string()
434            );
435            assert!(key_pair.public_key().verify(b"message", &sig).is_ok());
436        } else {
437            panic!("corrupted dewif");
438        }
439    }
440}