use lair_keystore_api::dependencies::*;
use lair_keystore_api::in_proc_keystore::*;
use lair_keystore_api::mem_store::*;
use lair_keystore_api::prelude::*;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let (_keystore1, client1) = new_keystore().await;
println!("# Generating random root seed for client1");
let root_pk = client1
.new_seed("root".into(), None, true)
.await
.unwrap()
.x25519_pub_key;
let to_save = extract_root_mnemonic(&client1, root_pk).await;
println!("# SAVE THIS: {to_save}");
println!("# Deriving keys from client1");
let client1_keys = derive_keys(&client1).await;
println!("# IMPORTING SAVED MNEMONIC into client2");
let (_keystore2, client2) = new_keystore().await;
import_root_mnemonic(&client2, to_save).await;
println!("# Deriving keys from client2");
let client2_keys = derive_keys(&client2).await;
assert_eq!(client1_keys, client2_keys);
println!("# They are equal!");
}
async fn new_keystore() -> (InProcKeystore, LairClient) {
let passphrase = Arc::new(Mutex::new(sodoken::LockedArray::from(
b"passphrase".to_vec(),
)));
let config = hc_seed_bundle::PwHashLimits::Minimum
.with_exec(|| LairServerConfigInner::new("/", passphrase.clone()))
.await
.unwrap();
let keystore = InProcKeystore::new(
Arc::new(config),
create_mem_store_factory(),
passphrase.clone(),
)
.await
.unwrap();
let client = keystore.new_client().await.unwrap();
(keystore, client)
}
async fn extract_root_mnemonic(
client: &LairClient,
root_pk: X25519PubKey,
) -> String {
let mut tmp_pk = [0; sodoken::crypto_box::XSALSA_PUBLICKEYBYTES];
let mut tmp_sk = sodoken::SizedLockedArray::new().unwrap();
sodoken::crypto_box::xsalsa_keypair(&mut tmp_pk, &mut tmp_sk.lock())
.unwrap();
let (nonce, enc) = client
.export_seed_by_tag("root".into(), root_pk.clone(), tmp_pk.into(), None)
.await
.unwrap();
let mut key = [0; 32];
sodoken::crypto_box::xsalsa_open_easy(
&mut key,
&enc,
&nonce,
&root_pk,
&tmp_sk.lock(),
)
.unwrap();
mnemonic::to_string(&key[..])
}
async fn import_root_mnemonic(client: &LairClient, to_save: String) {
let mut key = [0; 32];
mnemonic::decode(to_save, &mut key[..]).unwrap();
let import_pk = client
.new_seed("import".into(), None, true)
.await
.unwrap()
.x25519_pub_key;
let mut tmp_pk = [0; sodoken::crypto_box::XSALSA_PUBLICKEYBYTES];
let mut tmp_sk = sodoken::SizedLockedArray::new().unwrap();
sodoken::crypto_box::xsalsa_keypair(&mut tmp_pk, &mut tmp_sk.lock())
.unwrap();
let mut nonce = [0; sodoken::crypto_box::XSALSA_NONCEBYTES];
sodoken::random::randombytes_buf(&mut nonce).unwrap();
let mut cipher = vec![0; 32 + sodoken::crypto_box::XSALSA_MACBYTES];
sodoken::crypto_box::xsalsa_easy(
&mut cipher,
&key,
&nonce,
&import_pk,
&tmp_sk.lock(),
)
.unwrap();
client
.import_seed(
tmp_pk.into(),
import_pk,
None,
nonce,
cipher.into(),
"root".into(),
true,
)
.await
.unwrap();
}
async fn derive_key(client: &LairClient, path: Box<[u32]>) -> String {
use base64::prelude::*;
BASE64_STANDARD.encode(
&*client
.derive_seed(
"root".into(),
None,
format!("sub-key-{path:?}").into(),
None,
path,
)
.await
.unwrap()
.ed25519_pub_key,
)
}
async fn derive_keys(client: &LairClient) -> Vec<String> {
const REVOKE: u32 = 1;
const DEVICE: u32 = 2;
let revoke = derive_key(client, vec![REVOKE].into()).await;
println!("- revoke: {revoke}");
let device1app1 = derive_key(client, vec![DEVICE, 1, 1].into()).await;
println!("- device1app1: {device1app1}");
let device1app2 = derive_key(client, vec![DEVICE, 1, 2].into()).await;
println!("- device1app2: {device1app2}");
vec![revoke, device1app1, device1app2]
}