add encrypted import of aegis files

This commit is contained in:
mutluler 2022-01-28 16:07:53 +01:00
parent 5b658dd295
commit 0cf7ad7654
3 changed files with 419 additions and 77 deletions

203
Cargo.lock generated
View File

@ -23,6 +23,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.6.0"
@ -31,7 +40,19 @@ checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft",
"aesni",
"cipher",
"cipher 0.2.5",
]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if 1.0.0",
"cipher 0.3.0",
"cpufeatures",
"opaque-debug",
]
[[package]]
@ -40,11 +61,25 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"aead 0.3.2",
"aes 0.6.0",
"cipher 0.2.5",
"ctr 0.6.0",
"ghash 0.3.1",
"subtle",
]
[[package]]
name = "aes-gcm"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead 0.4.3",
"aes 0.7.5",
"cipher 0.3.0",
"ctr 0.8.0",
"ghash 0.4.4",
"subtle",
]
@ -54,7 +89,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"cipher 0.2.5",
"opaque-debug",
]
@ -64,7 +99,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"cipher 0.2.5",
"opaque-debug",
]
@ -297,9 +332,11 @@ dependencies = [
name = "authenticator"
version = "0.1.0"
dependencies = [
"aes-gcm 0.9.4",
"anyhow",
"ashpd",
"async-std",
"base64",
"binascii",
"diesel",
"diesel_migrations",
@ -321,6 +358,7 @@ dependencies = [
"rand 0.8.4",
"ring",
"rust-argon2",
"scrypt",
"search-provider",
"secret-service",
"serde",
@ -423,6 +461,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95"
dependencies = [
"generic-array",
]
[[package]]
name = "block-modes"
version = "0.7.0"
@ -430,7 +477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
dependencies = [
"block-padding",
"cipher",
"cipher 0.2.5",
]
[[package]]
@ -585,6 +632,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]]
name = "clang-sys"
version = "1.3.0"
@ -644,13 +700,13 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
dependencies = [
"aes-gcm",
"aes-gcm 0.8.0",
"base64",
"hkdf",
"hmac",
"hmac 0.10.1",
"percent-encoding",
"rand 0.8.4",
"sha2",
"sha2 0.9.9",
"time",
"version_check",
]
@ -729,6 +785,15 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crypto-common"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
dependencies = [
"generic-array",
]
[[package]]
name = "crypto-mac"
version = "0.10.1"
@ -755,7 +820,16 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
dependencies = [
"cipher",
"cipher 0.2.5",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher 0.3.0",
]
[[package]]
@ -862,6 +936,18 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
dependencies = [
"block-buffer 0.10.0",
"crypto-common",
"generic-array",
"subtle",
]
[[package]]
name = "discard"
version = "1.0.4"
@ -1332,7 +1418,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
dependencies = [
"opaque-debug",
"polyval",
"polyval 0.4.5",
]
[[package]]
name = "ghash"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug",
"polyval 0.5.3",
]
[[package]]
@ -1716,6 +1812,9 @@ name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
dependencies = [
"serde",
]
[[package]]
name = "hkdf"
@ -1723,8 +1822,8 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
dependencies = [
"digest",
"hmac",
"digest 0.9.0",
"hmac 0.10.1",
]
[[package]]
@ -1734,7 +1833,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac",
"digest",
"digest 0.9.0",
]
[[package]]
name = "hmac"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2"
dependencies = [
"digest 0.10.1",
]
[[package]]
@ -2493,6 +2601,15 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "pbkdf2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4628cc3cf953b82edcd3c1388c5715401420ce5524fedbab426bd5aba017434"
dependencies = [
"digest 0.10.1",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@ -2617,6 +2734,18 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
@ -2946,6 +3075,15 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "salsa20"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0fbb5f676da676c260ba276a8f43a8dc67cf02d1438423aeb1c677a7212686"
dependencies = [
"cipher 0.3.0",
]
[[package]]
name = "schannel"
version = "0.1.19"
@ -2983,6 +3121,18 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scrypt"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e73d6d7c6311ebdbd9184ad6c4447b2f36337e327bda107d3ba9e3c374f9d325"
dependencies = [
"hmac 0.12.0",
"pbkdf2",
"salsa20",
"sha2 0.10.1",
]
[[package]]
name = "search-provider"
version = "0.3.0"
@ -3002,14 +3152,14 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2400fb1bf2a87b303ada204946294f932ade4929477e9e2bf66d7b49a66656ec"
dependencies = [
"aes",
"aes 0.6.0",
"block-modes",
"hkdf",
"lazy_static",
"num",
"rand 0.8.4",
"serde",
"sha2",
"sha2 0.9.9",
"zbus 1.9.1",
"zbus_macros 1.9.1",
"zvariant 2.10.0",
@ -3135,13 +3285,24 @@ version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.10.1",
]
[[package]]
name = "shlex"
version = "1.1.0"

View File

@ -18,7 +18,7 @@ gst4gtk = { package = "gst-plugin-gtk4", version = "0.1"}
gtk = {package = "gtk4", version = "0.4"}
gtk-macros = "0.3"
search-provider = "0.3"
hex = "0.4"
hex = { version = "0.4.3", features = [ "serde" ] }
image = "0.23"
log = "0.4"
once_cell = "1.9"
@ -37,3 +37,6 @@ unicase = "2.6"
url = "2.2"
zbar-rust = "0.0"
svg_metadata = "0.4"
scrypt = { version = "0.8.1", default-features = false }
aes-gcm = "0.9.4"
base64 = "0.13.0"

View File

@ -5,46 +5,88 @@
use super::{Backupable, Restorable, RestorableItem};
use crate::models::{Account, Algorithm, OTPMethod, Provider, ProvidersModel};
use aes_gcm::aead::Aead;
use aes_gcm::NewAead;
use anyhow::Result;
use gettextrs::gettext;
use gtk::{glib::Cast, prelude::*};
use serde::{Deserialize, Serialize};
use hex;
use log;
use serde::{Deserialize, Serialize};
/// Root of the Aegis JSON Backup Format
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Aegis {
pub version: u32,
pub header: std::collections::HashMap<String, serde_json::Value>,
pub db: AegisDatabase,
#[serde(untagged)]
pub enum Aegis {
/// Plaintext version of the JSON format.
Plaintext {
version: u32,
header: std::collections::HashMap<String, serde_json::Value>,
db: Database,
},
/// Encrypted version of the JSON format. `db` is simply a base64 encoded string with encrypted AegisDatabase.
Encrypted {
version: u32,
header: HeaderEncrypted,
db: String,
},
}
/// Header of the Aegis JSON File
/// Header of the Encrypted Aegis JSON File
///
/// Contains all necessary information for encrypting / decrypting the vault (db field).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AegisHeader {
pub slots: Vec<std::collections::HashMap<String, serde_json::Value>>,
pub params: std::collections::HashMap<String, serde_json::Value>
pub struct HeaderEncrypted {
pub slots: Vec<HeaderSlot>,
pub params: HeaderParam,
}
/// Header Slots
///
/// Containts information to decrypt the master key.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum HeaderSlot {
// We are not interested in this field. Thus, we omit the other information.
#[serde(rename = "2")]
Biometric,
#[serde(rename = "1")]
Password(HeaderSlotPassword),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HeaderSlotPassword {
pub uuid: String,
#[serde(with = "hex::serde")]
pub key: [u8; 32],
// First tuple entry is the nonce, the second is the tag.
pub key_params: HeaderParam,
pub n: u32,
pub r: u32,
pub p: u32,
#[serde(with = "hex::serde")]
pub salt: [u8; 32],
}
/// Parameters to Database Encryption
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HeaderParam {
#[serde(with = "hex::serde")]
pub nonce: [u8; 12],
#[serde(with = "hex::serde")]
pub tag: [u8; 16],
}
/// Contains All OTP Entries
///
/// This database can be either encrypted or in plaintext format.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AegisDatabase {
Encrypted(String),
Plaintext {
version: u32,
entries: Vec<AegisItem>,
},
pub struct Database {
pub version: u32,
pub entries: Vec<Item>,
}
/// An OTP Entry
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AegisItem {
pub struct Item {
#[serde(rename = "type")]
pub method: OTPMethod,
// UUID is omitted
@ -58,15 +100,15 @@ pub struct AegisItem {
// Icon:
// TODO: Aegis encodes icons as JPEG's encoded in Base64 with padding. Does authenticator support
// this?
// TODO tags are not importet/exported right now.
// TODO tags are not imported/exported right now.
#[serde(rename = "icon")]
pub thumbnail: Option<String>,
pub info: AegisDetail,
pub info: Detail,
}
/// OTP Entry Details
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AegisDetail {
pub struct Detail {
pub secret: String,
#[serde(rename = "algo")]
pub algorithm: Algorithm,
@ -76,35 +118,139 @@ pub struct AegisDetail {
}
impl Aegis {
fn restore_from_slice(data: &[u8]) -> Result<Vec<AegisItem>> {
fn restore_from_slice(data: &[u8], password: Option<&str>) -> Result<Vec<Item>> {
// TODO check whether file / database is encrypted by aegis
let aegis_root: Aegis = serde_json::de::from_slice(data)?;
// Check for correct aegis file version and correct database version.
// Additionally, we check whether the file is encrypted. We can't open / decrypt them yet,
// because there is no UI possibility to enter the password.
log::info!("Found aegis database version {}", aegis_root.version);
if aegis_root.version != 1 {
anyhow::bail!(
"Aegis file version expected to be 1. Found {} instead.",
aegis_root.version
);
}
// Check whether file is encrypted or in plaintext
match aegis_root {
Aegis::Plaintext { version, db, .. } => {
log::info!(
"Found unencrypted aegis vault with version {} and database version {}.",
version,
db.version
);
match aegis_root.db {
AegisDatabase::Encrypted(_) => anyhow::bail!(
"Aegis file is encrypted. Authenticator supports only plaintext files."
),
AegisDatabase::Plaintext { version, entries } if version == 2 => Ok(entries),
AegisDatabase::Plaintext { version, .. } => anyhow::bail!(
"Aegis file version expected to be 2. Found {} instead.",
version
),
// Check for correct aegis vault version and correct database version.
if version != 1 {
anyhow::bail!(
"Aegis vault version expected to be 1. Found {} instead.",
version
);
// There is no version 0. So this should be okay ...
} else if db.version > 2 {
anyhow::bail!(
"Aegis database version expected to be 1 or 2. Found {} instead.",
db.version
);
} else {
Ok(db.entries)
}
}
Aegis::Encrypted {
version,
header,
db,
} => {
log::info!("Found encrypted aegis vault with version {}.", version);
// Check for correct aegis vault version and whether a password was supplied.
if version != 1 {
anyhow::bail!(
"Aegis vault version expected to be 1. Found {} instead.",
version
);
} else if password.is_none() {
anyhow::bail!("Found encrypted aegis database but no password given.");
}
// Ciphertext is stored in base64, we have to decode it.
let mut ciphertext =
base64::decode(db).expect("Cannot decode (base64) encrypted database");
// Add the encryption tag
ciphertext.append(&mut header.params.tag.into());
// Find slots with type password and derive the corresponding key. This key is used
// to decrypt the master key which in turn can be used to decrypt the database.
let master_keys: Vec<Vec<u8>> = header
.slots
.iter()
.map(|slot| match slot {
HeaderSlot::Password(slot) => slot,
_ => unreachable!("All biometric slots are filtered by serde."),
})
.map(|slot| {
log::info!("Found possible master key with UUID {}.", slot.uuid);
// Create parameters for scrypt function and derive decryption key for master key
let params = scrypt::Params::new(
// TODO log2 for u64 is not stable yet. Change this in the future.
(slot.n as f64).log2() as u8, // Defaults to 15 by aegis
slot.r, // Defaults to 8 by aegis
slot.p, // Defaults to 1 by aegis
)
.expect("Invalid scrypt params in header");
let mut temp_key: [u8; 32] = [0u8; 32];
scrypt::scrypt(
password.unwrap().as_bytes(),
&slot.salt,
&params,
&mut temp_key,
)
.expect("Scrypt key derivation failed");
// Now, try to decrypt the master key.
let cipher = aes_gcm::Aes256Gcm::new(aes_gcm::Key::from_slice(&temp_key));
let mut ciphertext: Vec<u8> = slot.key.to_vec();
ciphertext.append(&mut slot.key_params.tag.to_vec());
let master_key = cipher
.decrypt(
aes_gcm::Nonce::from_slice(&slot.key_params.nonce),
ciphertext.as_ref(),
)
.expect("failed decrypting master key");
master_key
})
.collect();
// Choose the first valid master key. I don't think there are aegis installations with two valid password slots.
log::info!(
"Found {} valid password slots / master keys. Using only the first one.",
master_keys.len()
);
let master_key = &master_keys[0];
// Try to decrypt the database with this master key
let cipher = aes_gcm::Aes256Gcm::new(aes_gcm::Key::from_slice(master_key));
let plaintext = cipher
.decrypt(
aes_gcm::Nonce::from_slice(&header.params.nonce),
ciphertext.as_ref(),
)
.expect("failed decrypting database");
// Now, we have the decrypted string. Trying to load it with JSON.
let db: Database = serde_json::de::from_slice(&plaintext).unwrap();
// Check version of the database
log::info!("Found aegis database with version {}.", db.version);
if version > 2 {
anyhow::bail!(
"Aegis database version expected to be 1 or 2. Found {} instead.",
db.version
);
}
// Return items
Ok(db.entries)
}
}
}
}
impl RestorableItem for AegisItem {
impl RestorableItem for Item {
fn account(&self) -> String {
self.label.clone()
}
@ -164,15 +310,17 @@ impl Backupable for Aegis {
for j in 0..accounts.n_items() {
let account = accounts.item(j).unwrap().downcast::<Account>().unwrap();
let aegis_detail = AegisDetail {
let aegis_detail = Detail {
secret: account.token(),
algorithm: provider.algorithm(),
digits: provider.digits(),
// TODO should be none for hotp
period: Some(provider.period()),
// TODO should be none for totp
counter: Some(account.counter()),
};
let aegis_item = AegisItem {
let aegis_item = Item {
method: provider.method(),
label: account.name(),
issuer: provider.name(),
@ -186,11 +334,15 @@ impl Backupable for Aegis {
}
// Create structure around items
let aegis_db = AegisDatabase::Plaintext {
let aegis_db = Database {
version: 2,
entries: items,
};
let aegis_root = Aegis {
// let aegis_header = AegisHeader {
// slots: vec![],
// params: std::collections::HashMap::new(),
// };
let aegis_root = Aegis::Plaintext {
version: 1,
header: std::collections::HashMap::from([
(String::from("slots"), serde_json::Value::Null),
@ -215,7 +367,7 @@ impl Backupable for Aegis {
impl Restorable for Aegis {
const ENCRYPTABLE: bool = true;
type Item = AegisItem;
type Item = Item;
fn identifier() -> String {
"Aegis".to_string()
@ -230,9 +382,9 @@ impl Restorable for Aegis {
gettext("From a JSON file containing plain-text or encrypted fields.")
}
fn restore(from: &gtk::gio::File, _key: Option<&str>) -> Result<Vec<Self::Item>> {
fn restore(from: &gtk::gio::File, key: Option<&str>) -> Result<Vec<Self::Item>> {
let (data, _) = from.load_contents(gtk::gio::Cancellable::NONE)?;
Aegis::restore_from_slice(&data)
Aegis::restore_from_slice(&data, key)
}
}
@ -294,7 +446,7 @@ mod tests {
}
}"#;
let aegis_items = Aegis::restore_from_slice(&aegis_data.as_bytes())
let aegis_items = Aegis::restore_from_slice(&aegis_data.as_bytes(), None)
.expect("Restoring from json should work");
assert_eq!(aegis_items[0].account(), "Bob");
@ -326,8 +478,7 @@ mod tests {
}
#[test]
#[should_panic]
fn detect_encrypted_file() {
fn restore_encrypted_file() {
// See https://github.com/beemdevelopment/Aegis/blob/master/app/src/test/resources/com/beemdevelopment/aegis/importers/aegis_encrypted.json
// for this example file.
let aegis_data = r#"{
@ -357,8 +508,35 @@ mod tests {
"db": "RtGfUrZ01nzRnvHjPJGyWjfa6shQ7NYwa491CgAWNBM8OeGZVIHhnDAVlVWNlSoq2V097p5Yq5m+SFl5g9nBBBQBNePQnj6CCvu1NfNtoA6R3hyp77gd+e+O2MRnOGH1Z1laV2Tl6p3q8IUHWgAJ36LbUxiCXmfh7bWm198uA4bgLwrEmo04MrqeYXggLuXrJrp6dUJQFD72dgoPbHijlSycY5GLel3ZbAXRsUHszd+xdywpj7\/TYa4OYFel0M0QcCpsKA1LRQz365X9OXPJdTsmVyR4dJ6x5RIVeh39lAYKUf7T4w7BLC8taST5m4J\/VXDueKbvg8R13bNWF0aRHUgeuI9BNzMZINJlzKFKNRknTaJ\/1kEUU0sLkgcaVkX\/DVTGG+pWi5MHijicrK0i4LHN3CUwV2\/\/ZNJCGXM5ErsKMOnJfma52gMdifPiXU317Klvc5oOZFYGnhbhJ2WtPIuqjdvnfuLat2JxA7Xx3LqquRWGL2113yjzVzGBDCVY6iIdedBEgH8CGD826\/3R3m6dR5sfSggQ2SbtQA\/DZNhLSNSU+bfNScVQvUWfR2Lf7Q\/4FR\/xATAQJ9IIBeL+w2ErLUPjURocFXup5YOBHxFdDjZ2FqhbAq4h3Zn\/BJ57xUcYEA+YtP5uOP2lQwUh\/0vFWizDVotzraO8tZiBZBsODyb69eJrXNwFbIjeUczY6wrJs1+676IilbCsmtoYvWEpUZF4hIi7TYAD+nyXX\/olrkog9omWZk8R7hJ9KRDfckXEc\/XSzWhk3Kmfa7pRNh9wYZsaR7VPZGZebQMuUKfRRci2qMsZOJvQsDBJvVze0xW9SqiySDgGyRX\/DwzuaZEGZZriaLf6ox7LwY2Qi6QpYOYbAaEaXAesCR1DPxFfGKsUHVjF8hKA6ZBXDXdqM3Y+14naIOH9S7UzYn32botoVLOykSjnW6z6M0ZPkz3dwowMJiVQcyD7p+9p4J6f1S81pFS7DP+jF+PTyC3c3q\/dwFhNdoG6iV9eQEAxjUi6MpzvFRsk9RsLcQqYgzJGmRjYeXlKH8k8tTu1A4puo6w3Daz8hZz9NafMgMsuqY0oKVLgdNqFz8yVMsxYfBW\/oW56SuQyyVWyxXjXmbk1vpYCTL5kXvIZWoTmBRRDb0ay5S\/dlD6z\/WR45\/C4AwcCE9m4Yf3zisRNa7AqWLVgkmJxFdfJxjiuPtUIK79s+lIJkyRENEqkvm809qIxDhkQzY8zcCt4oXCEbJUfSG4awBs1VvilJIwe6qi0bNtqXtAb5TctgxTh29A9oGlsRG4o8sHqA1mtjp5QiLWp5Hh6rOH95W6+fnBiOW+Iw0evBTduroWvx37HBTktJz79zGe0l3c0Y6VmiFvB7knmT2CrgP7woRkxGbXxdE9zMPQJM9ursD538MVDdD\/0tdkxHxilt47f1DPo2CKUWU8Q1KMm1zLXfVO8BbGUWIv4YeDKHfMUL\/HcStv5VJY+LbnOEjzGT4e1\/avSQmqBL4G9XNkYmyMhC8tlLQcmMMH4bNfPOO3vi5Pb5E7XveSgxlOHs4F0+nqxnFOAu8494MEtx6u5+B7d8LI\/DhEO5zTDwE+THiKej6vCsFxTZ519rm67HycOwRR4LKrwfDeUEK3X1PzryOD5zcv3PMcSBgZ8EWvTfZ9ygKP8BmRQRpydTbSt8Hj5fTUuajADCP0Ggw+6G7n+5FhExJNd+o9D8d4KgLPOe08M8InW7pLB389TWtSo4v3VNjcmmJNQ26wlPkhO\/xBU1URFR0fXU3eCO+w++IMt\/fOSqSpNF9bWElfWHIQ23ntxVke\/hR9j\/GG3tHGxYS5pL42sJF\/Re\/UlUJTGSQP6up2xVYs6gncQ0zACDOPjLQmQzYhz\/hr8S6EjYfK++yLZmRTjEI7xT9u\/B5YLyOQCYVTaF\/pDEegjsehXM3qJBfsA+XY7F9TRsmM\/MSVaPDkdIJ7zvL9xtaF6bXdZoZ6po3ml8uu41pSkNmMKgyEy5E0UQUTWMPLC8drUoQ\/KWQnVIN6HUXGBjYy6aax\/LYZaBcbZi97FHK0h+wsx3WN\/uQozNkQjwGYE8fwYxRYh1RaFi5PkiCM505ib7e82Yuts0l+cBb6nG1IruDplg9BD\/G9w4vVDePEikhcPyY\/p7AZ4i7u\/bL2YKlbE3HyJa+7dkbWJgGidtRZgu+Fdl2T\/rrRJ4+lVaKPVKGKT7ItZdIeitIYUdRxCzrOf1ItZCC8BWa4PElDAjj2yDNmMYRpXJBe3gQHWs\/H5SZgFuwsfCu23uzNRQYib8SuwIJQDvPiXo7m4oIySO8VyvemcExlbXSlbZbvwVxYavTVfcUpAXI6qlsg2jjk+JZahfKrWNC5COZPdVjdAXCoiKU+HBPmEFCwQv\/7zlSBEiI2piyqd+MPwnP63RdGO+oXYid6hn4Nm8kcOhtRyvYm95p66jzGlEugsfxJCED7MTh3XShqa2tt4lFG25icllzTvIJboRkz5oIB4dZVS9+q2TgGUoX7UCpobD8WkHo\/y0cpTuZr8vzXqx2fObxzPNoVgxJmp9E06G2bhMVHPpT17xbfq\/KhJJn7k1S0sfXPG+SmYlX4U7zNSe1M7JXtLf3uVOLz7Ccjp3yvcdq8nRmVym3Zwsz+vv57FA2A0dy3Db97ypJa9HGaxnnYIZHHzep0gJCeeIKE9L32zGCoUg+cPu9B2lPEgIr64iGiuvKSRwNQpOBktM6qqjQntE0Me6mh426irFQ\/3tcfH9a4lZEwwuU1X+lUBUWQp3n5Ej4BSJEs8E6H0EjBvyk69q3qjy5yi7ROVRis6y6S1v4er77RHQUf3phK5354VJHrp9pR926t5qngH5RVF4eljwtXDs3MkejADJ6stBHa\/w7FcbUClO8U+S4Bidxb3mZCiZkUVTpbzvBfYAiQvAfdkMa49o3a5DXKsbXyUPrmr6fWRfM1fS0Ehp0lUv6BDj0yR13CLMpKDU4GfDrl8UEvwh7gwtBRkuaBFzyMtd3NeE7kIGf9vFs6MEl2dmMDFSDid7MdVSDVTlhaAtp+zsRejKW3OQr5n051FzkUsIFGty9AWOkwjZCbstHYCOtyJnsnXP1i9lRDFBgPpFgmDD+bzzg0g9AOAxzqTiLF7bb1jejfe5qVr5V9+7zLpwRLiYaLkNOmpsqvNMuYVwdqTp6nyoougdgBlvve3EG0k09sFKi2Ep9lq+QkS7zGre2jJDrqgdC08+V4PXHYkP3V3Zjgn1x6RfQ2PE+2zvk1GGEgzcNww3byoYw0Ra5qS5yftMy\/2WahbA8fjUYvtmksFH8VjN3yasZt3sdQLWtv8qXxZscy+pCyjTdyxW+ddFnrWuqMIV3jbGMvngq6dL\/n5+DumjbA1gmBJVOpmyEsc1iwHDS36cNnyi1htGFO\/6\/Va4YPYK7dG6LY387UoBUU9Q9ijrBrSGpzPWYmXBLZ8e1MMPfHIN1WsaTgYO9leg3MAJTjQFTFrQ5dguYpWhlm2sWJT45jrda4uWqduB+aQLzYRWhEDBFzPV3ZgIe0SB+7h04Vm0Pu\/LDRvqaolpZ86CEm+zgjBOKeEGFwzTXxH\/5pBoca1bZ6wvsbVZxJNBeH8\/w=="
}"#;
Aegis::restore_from_slice(&aegis_data.as_bytes())
.expect("Reading encrypted file should fail.");
let aegis_items = Aegis::restore_from_slice(&aegis_data.as_bytes(), Some("test"))
.expect("Restoring from encrypted json should work");
assert_eq!(aegis_items[0].account(), "Mason");
assert_eq!(aegis_items[0].issuer(), "Deno");
assert_eq!(aegis_items[0].secret(), "4SJHB4GSD43FZBAI7C2HLRJGPQ");
assert_eq!(aegis_items[0].period(), Some(30));
assert_eq!(aegis_items[0].algorithm(), Algorithm::SHA1);
assert_eq!(aegis_items[0].digits(), Some(6));
assert_eq!(aegis_items[0].counter(), None);
assert_eq!(aegis_items[0].method(), OTPMethod::TOTP);
assert_eq!(aegis_items[3].account(), "James");
assert_eq!(aegis_items[3].issuer(), "Issuu");
assert_eq!(aegis_items[3].secret(), "YOOMIXWS5GN6RTBPUFFWKTW5M4");
assert_eq!(aegis_items[3].period(), None);
assert_eq!(aegis_items[3].algorithm(), Algorithm::SHA1);
assert_eq!(aegis_items[3].digits(), Some(6));
assert_eq!(aegis_items[3].counter(), Some(1));
assert_eq!(aegis_items[3].method(), OTPMethod::HOTP);
assert_eq!(aegis_items[6].account(), "Sophia");
assert_eq!(aegis_items[6].issuer(), "Boeing");
assert_eq!(aegis_items[6].secret(), "JRZCL47CMXVOQMNPZR2F7J4RGI");
assert_eq!(aegis_items[6].period(), Some(30));
assert_eq!(aegis_items[6].algorithm(), Algorithm::SHA1);
assert_eq!(aegis_items[6].digits(), Some(5));
assert_eq!(aegis_items[6].counter(), None);
assert_eq!(aegis_items[6].method(), OTPMethod::Steam);
}
// TODO: add tests for importing