This commit is contained in:
nora 2024-08-23 14:14:45 +02:00
parent 53bd98f2b3
commit 157d6081b8
3 changed files with 39 additions and 8 deletions

View file

@ -2,8 +2,9 @@ use std::{io::Write, path::PathBuf};
use clap::Parser; use clap::Parser;
use eyre::{bail, Context}; use eyre::{bail, Context};
use sha2::Digest;
use ssh_agent_client::{IdentityAnswer, SocketAgentConnection}; use ssh_agent_client::{IdentityAnswer, SocketAgentConnection};
use ssh_transport::key::PublicKey; use ssh_transport::{key::PublicKey, parse::Writer};
#[derive(clap::Parser, Debug)] #[derive(clap::Parser, Debug)]
struct Args { struct Args {
@ -28,8 +29,11 @@ enum Subcommand {
/// Sign a blob, SSH_AGENTC_SIGN_REQUEST /// Sign a blob, SSH_AGENTC_SIGN_REQUEST
Sign { Sign {
/// The key-id of the key, obtained with list-identities --key-id /// The key-id of the key, obtained with list-identities --key-id
#[arg(short, long = "key")] #[arg(short, long)]
key: Option<String>, key: Option<String>,
/// The domain of the signature, for example 'file'
#[arg(short, long)]
namespace: String,
file: PathBuf, file: PathBuf,
}, },
/// Temporarily lock the agent with a passphrase, SSH_AGENTC_LOCK /// Temporarily lock the agent with a passphrase, SSH_AGENTC_LOCK
@ -64,7 +68,15 @@ async fn main() -> eyre::Result<()> {
Subcommand::ListIdentities { key_id } => { Subcommand::ListIdentities { key_id } => {
list_ids(&mut agent, key_id).await?; list_ids(&mut agent, key_id).await?;
} }
Subcommand::Sign { file, key } => { Subcommand::Sign {
file,
key,
namespace,
} => {
if namespace.is_empty() {
bail!("namespace must not be empty");
}
let file = std::fs::read(&file) let file = std::fs::read(&file)
.wrap_err_with(|| format!("reading file {}", file.display()))?; .wrap_err_with(|| format!("reading file {}", file.display()))?;
@ -107,10 +119,27 @@ async fn main() -> eyre::Result<()> {
} }
}; };
let signature = agent.sign(&key.key_blob, &file, 0).await?; // <https://github.com/openssh/openssh-portable/blob/a76a6b85108e3032c8175611ecc5746e7131f876/PROTOCOL.sshsig>
// TODO: https://github.com/openssh/openssh-portable/blob/a76a6b85108e3032c8175611ecc5746e7131f876/PROTOCOL.sshsig let mut sign_data = Writer::new();
let signature = pem::encode(&pem::Pem::new("SSH SIGNATURE", signature)); sign_data.raw(b"SSHSIG");
sign_data.string(&namespace);
sign_data.string([]);
sign_data.string(b"sha512");
sign_data.string(sha2::Sha512::digest(file));
let signature = agent.sign(&key.key_blob, &sign_data.finish(), 0).await?;
let mut sig = Writer::new();
sig.raw(b"SSHSIG");
sig.u32(1); // version
sig.string(&key.key_blob); // publickey
sig.string(namespace.as_bytes());
sig.string(b""); // reserved
sig.string(b"sha512"); // hash algorithm
sig.string(&signature);
let signature = pem::encode(&pem::Pem::new("SSH SIGNATURE", sig.finish()));
std::io::stdout().write_all(signature.as_bytes())?; std::io::stdout().write_all(signature.as_bytes())?;
} }
Subcommand::Lock => { Subcommand::Lock => {

View file

@ -113,6 +113,7 @@ pub enum ExtensionResponse {
/// A single identity in SSH_AGENT_IDENTITIES_ANSWER. /// A single identity in SSH_AGENT_IDENTITIES_ANSWER.
#[derive(Debug)] #[derive(Debug)]
pub struct IdentityAnswer { pub struct IdentityAnswer {
/// The public key in the SSH wire encoding.
pub key_blob: Vec<u8>, pub key_blob: Vec<u8>,
pub comment: String, pub comment: String,
} }

View file

@ -107,7 +107,7 @@ impl<'a> Parser<'a> {
} }
} }
/// A simplified `byteorder` clone that emits client errors when the data is too short. /// A writer for the SSH wire format.
pub struct Writer(Vec<u8>); pub struct Writer(Vec<u8>);
impl Writer { impl Writer {
@ -149,7 +149,8 @@ impl Writer {
self.raw(bytes); self.raw(bytes);
} }
pub fn string(&mut self, data: &[u8]) { pub fn string(&mut self, data: impl AsRef<[u8]>) {
let data = data.as_ref();
self.u32(data.len() as u32); self.u32(data.len() as u32);
self.raw(data); self.raw(data);
} }