Initial commit
This commit is contained in:
commit
b0b6fdf309
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1498
Cargo.lock
generated
Normal file
1498
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "fido_ssh_maker"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.72"
|
||||||
|
clap = { version = "4.3.21", features = ["derive"] }
|
||||||
|
ctap-hid-fido2 = "3.5.0"
|
||||||
|
ssh-encoding = { version = "0.1.0" }
|
||||||
|
ssh-key = { version = "0.5.0", features = ["ed25519"] }
|
131
src/main.rs
Normal file
131
src/main.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use std::{
|
||||||
|
fs::Permissions,
|
||||||
|
os::unix::prelude::PermissionsExt,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use clap::{Parser, ValueEnum};
|
||||||
|
use ctap_hid_fido2::{
|
||||||
|
fidokey::{CredentialSupportedKeyType, MakeCredentialArgsBuilder},
|
||||||
|
verifier, FidoKeyHidFactory, LibCfg,
|
||||||
|
};
|
||||||
|
use ssh_encoding::{Decode, Encode, LineEnding};
|
||||||
|
use ssh_key::{private, PrivateKey};
|
||||||
|
|
||||||
|
/// Generate FIDO-backed SSH keys
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None, arg_required_else_help = true)]
|
||||||
|
struct Args {
|
||||||
|
/// SSH key comment
|
||||||
|
#[arg(short = 'c', long, default_value = "")]
|
||||||
|
comment: String,
|
||||||
|
/// Overwrite existing key files
|
||||||
|
#[arg(short = 'f', long)]
|
||||||
|
force: bool,
|
||||||
|
#[arg(short = 't', long = "type")]
|
||||||
|
key_type: KeyTypeArg,
|
||||||
|
#[arg(long = "write-attestation")]
|
||||||
|
write_attestation: bool,
|
||||||
|
/// Name of key to generate
|
||||||
|
key_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Copy, Debug)]
|
||||||
|
enum KeyTypeArg {
|
||||||
|
Ed25519,
|
||||||
|
Ecdsa,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyTypeArg> for CredentialSupportedKeyType {
|
||||||
|
fn from(value: KeyTypeArg) -> Self {
|
||||||
|
match value {
|
||||||
|
KeyTypeArg::Ed25519 => Self::Ed25519,
|
||||||
|
KeyTypeArg::Ecdsa => Self::Ecdsa256,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let privkey_path = Path::new(&args.key_name);
|
||||||
|
let pubkey_path = PathBuf::from(format!("{}.pub", &args.key_name));
|
||||||
|
let attest_info_path = PathBuf::from(format!("{}_attest.bin", &args.key_name));
|
||||||
|
let attest_challenge_path = PathBuf::from(format!("{}_attest_chall.bin", &args.key_name));
|
||||||
|
|
||||||
|
if !args.force && (privkey_path.exists() || pubkey_path.exists() || args.write_attestation && (attest_info_path.exists() || attest_challenge_path.exists())) {
|
||||||
|
return Err(anyhow!("Key files exist, use -f to overwrite."));
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_type = args.key_type.into();
|
||||||
|
|
||||||
|
match key_type {
|
||||||
|
CredentialSupportedKeyType::Ecdsa256 => {
|
||||||
|
println!("Generating public/private ecdsa-sk key pair.")
|
||||||
|
}
|
||||||
|
CredentialSupportedKeyType::Ed25519 => {
|
||||||
|
println!("Generating public/private ed25519-sk key pair.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let challenge = verifier::create_challenge();
|
||||||
|
let make_credential_args = MakeCredentialArgsBuilder::new("ssh:", &challenge)
|
||||||
|
.without_pin_and_uv()
|
||||||
|
.key_type(key_type)
|
||||||
|
.build();
|
||||||
|
let mut libcfg = LibCfg::init();
|
||||||
|
libcfg.keep_alive_msg = "Touch the authenticator now.".into();
|
||||||
|
let device = FidoKeyHidFactory::create(&libcfg)?;
|
||||||
|
let attestation = device.make_credential_with_args(&make_credential_args)?;
|
||||||
|
dbg!(&attestation.attstmt_alg);
|
||||||
|
let verify_result = verifier::verify_attestation("ssh:", &challenge, &attestation);
|
||||||
|
if !verify_result.is_success {
|
||||||
|
return Err(anyhow!("Failed to verify attestation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut privkey_bytes = Vec::new();
|
||||||
|
if matches!(key_type, CredentialSupportedKeyType::Ecdsa256) {
|
||||||
|
"nistp256".encode(&mut privkey_bytes)?;
|
||||||
|
}
|
||||||
|
verify_result
|
||||||
|
.credential_public_key
|
||||||
|
.der
|
||||||
|
.encode(&mut privkey_bytes)?;
|
||||||
|
"ssh:".encode(&mut privkey_bytes)?;
|
||||||
|
let flags = (attestation.flags_user_present_result as u8)
|
||||||
|
| (attestation.flags_user_verified_result as u8) << 2
|
||||||
|
| (attestation.flags_attested_credential_data_included as u8) << 6
|
||||||
|
| (attestation.flags_extension_data_included as u8) << 7;
|
||||||
|
privkey_bytes.push(flags);
|
||||||
|
verify_result.credential_id.encode(&mut privkey_bytes)?;
|
||||||
|
"".encode(&mut privkey_bytes)?;
|
||||||
|
let privkey = match key_type {
|
||||||
|
CredentialSupportedKeyType::Ecdsa256 => PrivateKey::new(
|
||||||
|
private::SkEcdsaSha2NistP256::decode(&mut privkey_bytes.as_slice())?.into(),
|
||||||
|
args.comment,
|
||||||
|
)?,
|
||||||
|
CredentialSupportedKeyType::Ed25519 => PrivateKey::new(
|
||||||
|
private::SkEd25519::decode(&mut privkey_bytes.as_slice())?.into(),
|
||||||
|
args.comment,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ssh_attest = Vec::new();
|
||||||
|
"ssh-sk-attest-v01".encode(&mut ssh_attest)?;
|
||||||
|
attestation.attstmt_x5c[0].encode(&mut ssh_attest)?;
|
||||||
|
attestation.attstmt_sig.encode(&mut ssh_attest)?;
|
||||||
|
attestation.auth_data.encode(&mut ssh_attest)?;
|
||||||
|
0u32.encode(&mut ssh_attest)?;
|
||||||
|
"".encode(&mut ssh_attest)?;
|
||||||
|
|
||||||
|
std::fs::write(&privkey_path, &*privkey.to_openssh(LineEnding::default())?)?;
|
||||||
|
std::fs::set_permissions(&privkey_path, Permissions::from_mode(0o600))?;
|
||||||
|
std::fs::write(&pubkey_path, privkey.public_key().to_openssh()?)?;
|
||||||
|
if args.write_attestation {
|
||||||
|
std::fs::write(&attest_info_path, &ssh_attest)?;
|
||||||
|
std::fs::write(&attest_challenge_path, &challenge)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user