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