This commit is contained in:
Cilly Leang 2026-06-23 01:35:22 +10:00
commit 41042ecbb0
Signed by: cilly
GPG key ID: 6500251E087653C9
7 changed files with 1576 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/data

1322
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "thistle"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = { version = "1.0.102", features = ["backtrace"] }
base16ct = { version = "1.0.0", features = ["alloc"] }
ctap-hid-fido2 = "3.5.11"
hkdf = "0.13.0"
postcard = { version = "1.1.3", features = ["use-std"] }
rand = "0.10.1"
rpassword = "7.5.4"
serde = { version = "1.0.228", features = ["derive"] }
sha2 = "0.11.0"
sha2-const = "0.1.3"

48
flake.lock generated Normal file
View file

@ -0,0 +1,48 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1779560665,
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1781061510,
"narHash": "sha256-tVuGHgt/TsWu1rUAqEL+eWRIJJHtiPE2+yQ63b+/WTU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d286e9691bb03045febbf8304a658eab1487d1b5",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

29
flake.nix Normal file
View file

@ -0,0 +1,29 @@
{
description = "nix dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/64c08a7ca051951c8eae34e3e3cb1e202fe36786";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, rust-overlay, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
toolchain = pkgs.rust-bin.fromRustupToolchain { channel = "nightly-2026-06-08"; };
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
pkg-config
openssl
toolchain
udev
];
};
};
}

158
src/main.rs Normal file
View file

@ -0,0 +1,158 @@
use std::collections::HashMap;
use std::fs;
use std::io::stdin;
use ctap_hid_fido2::fidokey::{GetAssertionArgsBuilder, MakeCredentialArgsBuilder};
use ctap_hid_fido2::public_key::PublicKey;
use ctap_hid_fido2::public_key_credential_user_entity::PublicKeyCredentialUserEntity;
use ctap_hid_fido2::util::to_hex_str;
use ctap_hid_fido2::{Cfg, FidoKeyHidFactory, verifier};
use serde::{Serialize, Deserialize};
use sha2::Digest;
const RPID: &str = "thistle.lava.moe";
const RPID_HASH: [u8; 32] = sha2_const::Sha256::new()
.update(RPID.as_bytes())
.finalize();
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct Directory {
nonresidents: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum DirectoryFormat {
Version1(Directory)
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct VaultBlock {
salt: [u8; 8],
keys: Vec<[u8; 32]>,
data: Vec<u8>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct Vault {
id: u128,
salt: [u8; 32],
blocks: Vec<VaultBlock>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum VaultFormat {
Version1(Vault)
}
fn prompt(msg: &str) -> anyhow::Result<String> {
print!("{}", msg);
let mut res = String::new();
stdin().read_line(&mut res)?;
Ok(res.trim().to_string())
}
fn cmd_auth() -> anyhow::Result<()> {
// let f = format!("{:x}",
let device = FidoKeyHidFactory::create(&Cfg::init())?;
let challenge = verifier::create_challenge();
let pin = rpassword::prompt_password("Input PIN: ")?;
let get_assertion_args = GetAssertionArgsBuilder::new(RPID, &challenge)
.pin(&pin)
// .without_pin_and_uv()
.without_up()
.build();
let assertions = device.get_assertion_with_args(&get_assertion_args)?;
println!("-- Authenticate Success");
println!("-- Assertion Num = {:?}", assertions.len());
for assertion in &assertions {
println!("- assertion = {}", assertion);
println!("- user = {}", assertion.user);
}
let enu = device.credential_management_enumerate_credentials(Some(&pin), &RPID_HASH)?;
println!("-- Cred Num = {:?}", enu.len());
for enume in &enu {
println!("{}", enume);
}
Ok(())
}
fn cmd_create() -> anyhow::Result<()> {
let device = FidoKeyHidFactory::create(&Cfg::init())?;
let pin = rpassword::prompt_password("Input PIN: ")?;
let file_id: u128 = rand::random();
let ent = PublicKeyCredentialUserEntity::new(
Some(&file_id.to_le_bytes()),
None, None,
);
let challenge = verifier::create_challenge();
let make_credential_args = MakeCredentialArgsBuilder::new(RPID, &challenge)
.user_entity(&ent)
.resident_key()
.pin(&pin)
.build();
let attestation = device.make_credential_with_args(&make_credential_args)?;
println!("{}", attestation);
println!("Registered");
let verify_result = verifier::verify_attestation(RPID, &challenge, &attestation);
if !verify_result.is_success {
println!("! Verification failed, faulty key?");
return Ok(());
}
println!("Verified");
let vault = Vault {
id: file_id,
salt: rand::random(),
blocks: vec![],
};
fs::write(format!("./data/{}.vault", to_hex_str(&file_id.to_le_bytes())), postcard::to_stdvec(&vault)?)?;
Ok(())
}
fn cmd_decrypt(name: &str) -> anyhow::Result<()> {
Ok(())
}
fn cmd_encrypt(name: &str) -> anyhow::Result<()> {
Ok(())
}
fn cmd_info() -> anyhow::Result<()> {
let device = FidoKeyHidFactory::create(&Cfg::init())?;
println!("{}", device.get_info()?);
Ok(())
}
fn main() -> anyhow::Result<()> {
println!("Hello, world!");
let args = std::env::args().collect::<Vec<_>>();
let Some(cmd) = args.get(1) else {
eprintln!("Missing args");
return Ok(())
};
let fname = args.get(2);
match cmd.as_str() {
"auth" => cmd_auth()?,
"create" => cmd_create()?,
"decrypt" => cmd_create()?,
"encrypt" => cmd_create()?,
"info" => cmd_info()?,
_ => {
eprintln!("Unknown command");
}
}
Ok(())
}