start client public auth

This commit is contained in:
nora 2024-08-23 15:02:29 +02:00
parent 157d6081b8
commit 85f1def4b5
9 changed files with 132 additions and 16 deletions

View file

@ -1,4 +1,5 @@
use std::{
fmt::Display,
io::Write,
path::{Path, PathBuf},
};
@ -28,9 +29,9 @@ enum Subcommand {
},
/// Generate a new SSH key
Generate {
#[arg(short, long = "type")]
#[arg(short, long = "type", default_value_t = KeyType::Ed25519)]
type_: KeyType,
#[arg(short, long)]
#[arg(short, long, default_value_t = String::default())]
comment: String,
#[arg(short, long)]
path: PathBuf,
@ -55,6 +56,14 @@ enum KeyType {
Ed25519,
}
impl Display for KeyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ed25519 => f.write_str("ed25519"),
}
}
}
fn main() -> eyre::Result<()> {
let args = Args::parse();

View file

@ -16,3 +16,4 @@ tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
tracing.workspace = true
rpassword = "7.3.1"
users = "0.11.0"

View file

@ -1,9 +1,10 @@
use std::io::Write;
use std::{collections::HashSet, io::Write};
use clap::Parser;
use eyre::Context;
use eyre::{bail, Context, ContextCompat, OptionExt};
use rand::RngCore;
use ssh_transport::{key::PublicKey, parse::Writer};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
@ -27,12 +28,19 @@ impl ssh_protocol::transport::SshRng for ThreadRngRand {
struct Args {
#[arg(short = 'p', long, default_value_t = 22)]
port: u16,
#[arg(short, long)]
user: Option<String>,
destination: String,
command: Vec<String>,
}
enum Operation {
PasswordEntered(std::io::Result<String>),
Signature {
key_alg_name: &'static str,
public_key: Vec<u8>,
signature: Vec<u8>,
},
}
#[tokio::main]
@ -42,12 +50,29 @@ async fn main() -> eyre::Result<()> {
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let username = match args.user {
None => {
tokio::task::spawn_blocking(|| {
users::get_current_username()
.wrap_err("getting username")
.and_then(|username| {
username
.to_str()
.ok_or_eyre("your username is invalid UTF-8???")
.map(ToOwned::to_owned)
})
})
.await??
}
Some(user) => user,
};
let mut attempted_public_keys = HashSet::new();
let mut conn = TcpStream::connect(&format!("{}:{}", args.destination, args.port))
.await
.wrap_err("connecting")?;
let username = "hans-peter";
let mut state = ssh_protocol::ClientConnection::new(
transport::client::ClientConnection::new(ThreadRngRand),
ssh_protocol::auth::ClientAuth::new(username.as_bytes().to_vec()),
@ -68,7 +93,7 @@ async fn main() -> eyre::Result<()> {
for req in auth.user_requests() {
match req {
ssh_protocol::auth::ClientUserRequest::Password => {
let username = username.to_owned();
let username = username.clone();
let destination = args.destination.clone();
let send_op = send_op.clone();
std::thread::spawn(move || {
@ -80,16 +105,50 @@ async fn main() -> eyre::Result<()> {
});
}
ssh_protocol::auth::ClientUserRequest::PrivateKeySign {
session_identifier: _,
session_identifier,
} => {
// TODO: support agentless manual key opening
// TODO: move
let mut agent = ssh_agent_client::SocketAgentConnection::from_env()
.await
.wrap_err("failed to connect to SSH agent")?;
let identities = agent.list_identities().await?;
for identity in identities {
debug!(comment = ?identity.comment, "Found identity");
for identity in &identities {
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)
.wrap_err("received invalid public key from SSH agent")?;
debug!(comment = ?identity.comment, %pubkey, "Found identity");
}
if identities.len() > 1 {
todo!("try identities");
}
let identity = &identities[0];
if attempted_public_keys.insert(identity.key_blob.clone()) {
bail!("authentication denied (publickey)");
}
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?;
let mut sig = Writer::new();
sig.string(session_identifier);
sig.string(&username);
sig.string("ssh-connection");
sig.string("publickey");
sig.bool(true);
sig.string(pubkey.algorithm_name());
sig.string(&identity.key_blob);
let data = sig.finish();
let signature = agent
.sign(&identity.key_blob, &data, 0)
.await
.wrap_err("signing for authentication")?;
send_op
.send(Operation::Signature {
key_alg_name: pubkey.algorithm_name(),
public_key: identity.key_blob.clone(),
signature,
})
.await?;
}
ssh_protocol::auth::ClientUserRequest::Banner(banner) => {
let banner = String::from_utf8_lossy(&banner);
@ -128,6 +187,15 @@ async fn main() -> eyre::Result<()> {
debug!("Ignoring entered password as the state has moved on");
}
}
Some(Operation::Signature{
key_alg_name, public_key, signature,
}) => {
if let Some(auth) = state.auth() {
auth.send_signature(key_alg_name, &public_key, &signature);
} else {
debug!("Ignoring signature as the state has moved on");
}
}
None => {}
}
state.progress();