init
This commit is contained in:
commit
41042ecbb0
7 changed files with 1576 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/data
|
||||||
1322
Cargo.lock
generated
Normal file
1322
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
48
flake.lock
generated
Normal 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
29
flake.nix
Normal 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
158
src/main.rs
Normal 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(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue