chore: uuhhhhhh crypto lol (#805)

* chore: uuhhhhhh crypto lol

* remove dead code

* fix key decoding

* use inplace encryption
This commit is contained in:
Conrad Ludgate 2023-04-17 21:12:02 +01:00 committed by GitHub
parent 678323b543
commit c7d89c1703
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 196 additions and 74 deletions

125
Cargo.lock generated
View file

@ -2,6 +2,16 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@ -37,6 +47,17 @@ version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2",
"password-hash",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.58" version = "0.1.58"
@ -121,6 +142,7 @@ dependencies = [
"directories", "directories",
"eyre", "eyre",
"fs-err", "fs-err",
"generic-array",
"hex", "hex",
"interim", "interim",
"itertools", "itertools",
@ -145,6 +167,7 @@ dependencies = [
"urlencoding", "urlencoding",
"uuid", "uuid",
"whoami", "whoami",
"xsalsa20poly1305",
] ]
[[package]] [[package]]
@ -161,6 +184,7 @@ dependencies = [
name = "atuin-server" name = "atuin-server"
version = "14.0.1" version = "14.0.1"
dependencies = [ dependencies = [
"argon2",
"async-trait", "async-trait",
"atuin-common", "atuin-common",
"axum", "axum",
@ -253,6 +277,12 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "beef" name = "beef"
version = "0.5.2" version = "0.5.2"
@ -265,6 +295,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.3" version = "0.10.3"
@ -335,6 +374,17 @@ dependencies = [
"chrono", "chrono",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.1.14" version = "4.1.14"
@ -527,6 +577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core",
"typenum", "typenum",
] ]
@ -794,6 +845,7 @@ version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [ dependencies = [
"serde",
"typenum", "typenum",
"version_check", "version_check",
] ]
@ -1029,6 +1081,15 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -1363,6 +1424,12 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.5" version = "0.1.5"
@ -1434,6 +1501,17 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.9" version = "1.0.9"
@ -1499,6 +1577,17 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "0.3.19" version = "0.3.19"
@ -1552,9 +1641,9 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom",
] ]
@ -1776,6 +1865,15 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@ -2506,6 +2604,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"
@ -2895,6 +3003,19 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "xsalsa20poly1305"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "472c385ee974833d7e59979eeb74175d56774be3768b5bcc581337e21396bda3"
dependencies = [
"aead",
"poly1305",
"salsa20",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.6.0" version = "1.6.0"

View file

@ -15,12 +15,13 @@ repository = { workspace = true }
default = ["sync"] default = ["sync"]
sync = [ sync = [
"urlencoding", "urlencoding",
"sodiumoxide",
"reqwest", "reqwest",
"sha2", "sha2",
"hex", "hex",
"rmp-serde", "rmp-serde",
"base64", "base64",
"generic-array",
"xsalsa20poly1305",
] ]
[dependencies] [dependencies]
@ -60,6 +61,8 @@ rmp-serde = { version = "1.1.1", optional = true }
base64 = { workspace = true, optional = true } base64 = { workspace = true, optional = true }
tokio = { workspace = true } tokio = { workspace = true }
semver = { workspace = true } semver = { workspace = true }
xsalsa20poly1305 = { version = "0.9.0", optional = true }
generic-array = { version = "0.14", optional = true, features = ["serde"] }
[dev-dependencies] [dev-dependencies]
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }

View file

@ -7,13 +7,13 @@ use reqwest::{
header::{HeaderMap, AUTHORIZATION, USER_AGENT}, header::{HeaderMap, AUTHORIZATION, USER_AGENT},
StatusCode, Url, StatusCode, Url,
}; };
use sodiumoxide::crypto::secretbox;
use atuin_common::api::{ use atuin_common::api::{
AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse, AddHistoryRequest, CountResponse, DeleteHistoryRequest, ErrorResponse, IndexResponse,
LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse, LoginRequest, LoginResponse, RegisterResponse, StatusResponse, SyncHistoryResponse,
}; };
use semver::Version; use semver::Version;
use xsalsa20poly1305::Key;
use crate::{ use crate::{
encryption::{decode_key, decrypt}, encryption::{decode_key, decrypt},
@ -28,7 +28,7 @@ static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),);
pub struct Client<'a> { pub struct Client<'a> {
sync_addr: &'a str, sync_addr: &'a str,
key: secretbox::Key, key: Key,
client: reqwest::Client, client: reqwest::Client,
} }
@ -182,7 +182,7 @@ impl<'a> Client<'a> {
.iter() .iter()
// TODO: handle deletion earlier in this chain // TODO: handle deletion earlier in this chain
.map(|h| serde_json::from_str(h).expect("invalid base64")) .map(|h| serde_json::from_str(h).expect("invalid base64"))
.map(|h| decrypt(&h, &self.key).expect("failed to decrypt history! check your key")) .map(|h| decrypt(h, &self.key).expect("failed to decrypt history! check your key"))
.map(|mut h| { .map(|mut h| {
if deleted.contains(&h.id) { if deleted.contains(&h.id) {
h.deleted_at = Some(chrono::Utc::now()); h.deleted_at = Some(chrono::Utc::now());

View file

@ -14,7 +14,11 @@ use base64::prelude::{Engine, BASE64_STANDARD};
use eyre::{eyre, Context, Result}; use eyre::{eyre, Context, Result};
use fs_err as fs; use fs_err as fs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sodiumoxide::crypto::secretbox; pub use xsalsa20poly1305::Key;
use xsalsa20poly1305::{
aead::{Nonce, OsRng},
AeadInPlace, KeyInit, XSalsa20Poly1305,
};
use crate::{ use crate::{
history::{History, HistoryWithoutDelete}, history::{History, HistoryWithoutDelete},
@ -24,14 +28,14 @@ use crate::{
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct EncryptedHistory { pub struct EncryptedHistory {
pub ciphertext: Vec<u8>, pub ciphertext: Vec<u8>,
pub nonce: secretbox::Nonce, pub nonce: Nonce<XSalsa20Poly1305>,
} }
pub fn new_key(settings: &Settings) -> Result<secretbox::Key> { pub fn new_key(settings: &Settings) -> Result<Key> {
let path = settings.key_path.as_str(); let path = settings.key_path.as_str();
let key = secretbox::gen_key(); let key = XSalsa20Poly1305::generate_key(&mut OsRng);
let encoded = encode_key(key.clone())?; let encoded = encode_key(&key)?;
let mut file = fs::File::create(path)?; let mut file = fs::File::create(path)?;
file.write_all(encoded.as_bytes())?; file.write_all(encoded.as_bytes())?;
@ -40,7 +44,7 @@ pub fn new_key(settings: &Settings) -> Result<secretbox::Key> {
} }
// Loads the secret key, will create + save if it doesn't exist // Loads the secret key, will create + save if it doesn't exist
pub fn load_key(settings: &Settings) -> Result<secretbox::Key> { pub fn load_key(settings: &Settings) -> Result<Key> {
let path = settings.key_path.as_str(); let path = settings.key_path.as_str();
let key = if PathBuf::from(path).exists() { let key = if PathBuf::from(path).exists() {
@ -60,8 +64,8 @@ pub fn load_encoded_key(settings: &Settings) -> Result<String> {
let key = fs::read_to_string(path)?; let key = fs::read_to_string(path)?;
Ok(key) Ok(key)
} else { } else {
let key = secretbox::gen_key(); let key = XSalsa20Poly1305::generate_key(&mut OsRng);
let encoded = encode_key(key)?; let encoded = encode_key(&key)?;
let mut file = fs::File::create(path)?; let mut file = fs::File::create(path)?;
file.write_all(encoded.as_bytes())?; file.write_all(encoded.as_bytes())?;
@ -70,38 +74,47 @@ pub fn load_encoded_key(settings: &Settings) -> Result<String> {
} }
} }
pub type Key = secretbox::Key; pub fn encode_key(key: &Key) -> Result<String> {
pub fn encode_key(key: secretbox::Key) -> Result<String> { let buf = rmp_serde::to_vec(key.as_slice()).wrap_err("could not encode key to message pack")?;
let buf = rmp_serde::to_vec(&key).wrap_err("could not encode key to message pack")?;
let buf = BASE64_STANDARD.encode(buf); let buf = BASE64_STANDARD.encode(buf);
Ok(buf) Ok(buf)
} }
pub fn decode_key(key: String) -> Result<secretbox::Key> { pub fn decode_key(key: String) -> Result<Key> {
let buf = BASE64_STANDARD let buf = BASE64_STANDARD
.decode(key.trim_end()) .decode(key.trim_end())
.wrap_err("encryption key is not a valid base64 encoding")?; .wrap_err("encryption key is not a valid base64 encoding")?;
let buf: secretbox::Key = rmp_serde::from_slice(&buf) let buf: &[u8] = rmp_serde::from_slice(&buf)
.wrap_err("encryption key is not a valid message pack encoding")?; .wrap_err("encryption key is not a valid message pack encoding")?;
Ok(buf) Ok(*Key::from_slice(buf))
} }
pub fn encrypt(history: &History, key: &secretbox::Key) -> Result<EncryptedHistory> { pub fn encrypt(history: &History, key: &Key) -> Result<EncryptedHistory> {
// serialize with msgpack // serialize with msgpack
let buf = rmp_serde::to_vec(history)?; let mut buf = rmp_serde::to_vec(history)?;
let nonce = secretbox::gen_nonce(); let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng);
XSalsa20Poly1305::new(key)
.encrypt_in_place(&nonce, &[], &mut buf)
.map_err(|_| eyre!("could not encrypt"))?;
let ciphertext = secretbox::seal(&buf, &nonce, key); Ok(EncryptedHistory {
ciphertext: buf,
Ok(EncryptedHistory { ciphertext, nonce }) nonce,
})
} }
pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Result<History> { pub fn decrypt(mut encrypted_history: EncryptedHistory, key: &Key) -> Result<History> {
let plaintext = secretbox::open(&encrypted_history.ciphertext, &encrypted_history.nonce, key) XSalsa20Poly1305::new(key)
.map_err(|_| eyre!("failed to open secretbox - invalid key?"))?; .decrypt_in_place(
&encrypted_history.nonce,
&[],
&mut encrypted_history.ciphertext,
)
.map_err(|_| eyre!("could not encrypt"))?;
let plaintext = encrypted_history.ciphertext;
let history = rmp_serde::from_slice(&plaintext); let history = rmp_serde::from_slice(&plaintext);
@ -126,7 +139,7 @@ pub fn decrypt(encrypted_history: &EncryptedHistory, key: &secretbox::Key) -> Re
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use sodiumoxide::crypto::secretbox; use xsalsa20poly1305::{aead::OsRng, KeyInit, XSalsa20Poly1305};
use crate::history::History; use crate::history::History;
@ -134,8 +147,8 @@ mod test {
#[test] #[test]
fn test_encrypt_decrypt() { fn test_encrypt_decrypt() {
let key1 = secretbox::gen_key(); let key1 = XSalsa20Poly1305::generate_key(&mut OsRng);
let key2 = secretbox::gen_key(); let key2 = XSalsa20Poly1305::generate_key(&mut OsRng);
let history = History::new( let history = History::new(
chrono::Utc::now(), chrono::Utc::now(),
@ -156,12 +169,12 @@ mod test {
// test decryption works // test decryption works
// this should pass // this should pass
match decrypt(&e1, &key1) { match decrypt(e1, &key1) {
Err(e) => panic!("failed to decrypt, got {}", e), Err(e) => panic!("failed to decrypt, got {}", e),
Ok(h) => assert_eq!(h, history), Ok(h) => assert_eq!(h, history),
}; };
// this should err // this should err
let _ = decrypt(&e2, &key1).expect_err("expected an error decrypting with invalid key"); let _ = decrypt(e2, &key1).expect_err("expected an error decrypting with invalid key");
} }
} }

View file

@ -33,3 +33,4 @@ chronoutil = "0.2.3"
tower = "0.4" tower = "0.4"
tower-http = { version = "0.3", features = ["trace"] } tower-http = { version = "0.3", features = ["trace"] }
reqwest = { workspace = true } reqwest = { workspace = true }
argon2 = "0.5.0"

View file

@ -2,12 +2,16 @@ use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use argon2::{
password_hash::SaltString, Algorithm, Argon2, Params, PasswordHash, PasswordHasher,
PasswordVerifier, Version,
};
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
Json, Json,
}; };
use http::StatusCode; use http::StatusCode;
use sodiumoxide::crypto::pwhash::argon2id13; use rand::rngs::OsRng;
use tracing::{debug, error, info, instrument}; use tracing::{debug, error, info, instrument};
use uuid::Uuid; use uuid::Uuid;
@ -22,18 +26,10 @@ use reqwest::header::CONTENT_TYPE;
use atuin_common::api::*; use atuin_common::api::*;
pub fn verify_str(secret: &str, verify: &str) -> bool { pub fn verify_str(hash: &str, password: &str) -> bool {
sodiumoxide::init().unwrap(); let arg2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default());
let Ok(hash) = PasswordHash::new(hash) else { return false };
let mut padded = [0_u8; 128]; arg2.verify_password(password.as_bytes(), &hash).is_ok()
secret.as_bytes().iter().enumerate().for_each(|(i, val)| {
padded[i] = *val;
});
match argon2id13::HashedPassword::from_slice(&padded) {
Some(hp) => argon2id13::pwhash_verify(&hp, verify.as_bytes()),
None => false,
}
} }
// Try to send a Discord webhook once - if it fails, we don't retry. "At most once", and best effort. // Try to send a Discord webhook once - if it fails, we don't retry. "At most once", and best effort.
@ -185,16 +181,9 @@ pub async fn login<DB: Database>(
})) }))
} }
fn hash_secret(secret: &str) -> String { fn hash_secret(password: &str) -> String {
sodiumoxide::init().unwrap(); let arg2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default());
let hash = argon2id13::pwhash( let salt = SaltString::generate(&mut OsRng);
secret.as_bytes(), let hash = arg2.hash_password(password.as_bytes(), &salt).unwrap();
argon2id13::OPSLIMIT_INTERACTIVE, hash.to_string()
argon2id13::MEMLIMIT_INTERACTIVE,
)
.unwrap();
let texthash = std::str::from_utf8(&hash.0).unwrap().to_string();
// postgres hates null chars. don't do that to postgres
texthash.trim_end_matches('\u{0}').to_string()
} }

View file

@ -50,10 +50,10 @@ impl Cmd {
let key = load_key(&settings).wrap_err("could not load encryption key")?; let key = load_key(&settings).wrap_err("could not load encryption key")?;
if base64 { if base64 {
let encode = encode_key(key).wrap_err("could not encode encryption key")?; let encode = encode_key(&key).wrap_err("could not encode encryption key")?;
println!("{encode}"); println!("{encode}");
} else { } else {
let mnemonic = bip39::Mnemonic::from_entropy(&key.0, bip39::Language::English) let mnemonic = bip39::Mnemonic::from_entropy(&key, bip39::Language::English)
.map_err(|_| eyre::eyre!("invalid key"))?; .map_err(|_| eyre::eyre!("invalid key"))?;
println!("{mnemonic}"); println!("{mnemonic}");
} }

View file

@ -1,7 +1,7 @@
use std::{io, path::PathBuf}; use std::{io, path::PathBuf};
use clap::Parser; use clap::Parser;
use eyre::{bail, Context, ContextCompat, Result}; use eyre::{bail, Context, Result};
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
use atuin_client::{ use atuin_client::{
@ -62,10 +62,7 @@ impl Cmd {
} else { } else {
// try parse the key as a mnemonic... // try parse the key as a mnemonic...
let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) { let key = match bip39::Mnemonic::from_phrase(&key, bip39::Language::English) {
Ok(mnemonic) => encode_key( Ok(mnemonic) => encode_key(Key::from_slice(mnemonic.entropy()))?,
Key::from_slice(mnemonic.entropy())
.context("key was not the correct length")?,
)?,
Err(err) => { Err(err) => {
if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() { if let Some(err) = err.downcast_ref::<bip39::ErrorKind>() {
match err { match err {
@ -131,17 +128,15 @@ mod tests {
#[test] #[test]
fn mnemonic_round_trip() { fn mnemonic_round_trip() {
let key = Key { let key = Key::from([
0: [ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2,
3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 7, 9, 5,
2, 7, 9, 5, ]);
], let phrase = bip39::Mnemonic::from_entropy(&key, bip39::Language::English)
};
let phrase = bip39::Mnemonic::from_entropy(&key.0, bip39::Language::English)
.unwrap() .unwrap()
.into_phrase(); .into_phrase();
let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap(); let mnemonic = bip39::Mnemonic::from_phrase(&phrase, bip39::Language::English).unwrap();
assert_eq!(mnemonic.entropy(), &key.0); assert_eq!(mnemonic.entropy(), key.as_slice());
assert_eq!(phrase, "adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink"); assert_eq!(phrase, "adapt amused able anxiety mother adapt beef gaze amount else seat alcohol cage lottery avoid scare alcohol cactus school avoid coral adjust catch pink");
} }
} }