1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
use apt_swarm::errors::*;
use apt_swarm::signed::Signed;
use chrono::Utc;
use clap::Parser;
use env_logger::Env;
use std::ffi::OsStr;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[derive(Debug, Parser)]
#[command(version)]
pub struct Args {
#[arg(long)]
pub date: Option<String>,
#[arg(long)]
pub commit: Option<String>,
#[arg(long)]
pub secret_key: PathBuf,
/// The binary file to issue as update
pub path: PathBuf,
}
fn get_commit() -> Result<String> {
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.stdout(Stdio::piped())
.spawn()
.context("Failed to run git binary")?
.wait_with_output()
.context("Failed to wait for git child")?;
if !output.status.success() {
bail!("Git did not exit successfully");
}
let mut output =
String::from_utf8(output.stdout).context("Git output contains invalid utf8")?;
output.truncate(output.find('\n').unwrap_or(output.len()));
Ok(output)
}
fn sign(data: &[u8], secret_key: &Path) -> Result<Vec<u8>> {
let mut child = Command::new("sh4d0wup")
.args([
OsStr::new("sign"),
OsStr::new("pgp-detached"),
OsStr::new("--binary"),
OsStr::new("--secret-key"),
secret_key.as_os_str(),
OsStr::new("/dev/stdin"),
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.context("Failed to run sh4d0wup binary")?;
let mut stdin = child.stdin.take().unwrap();
stdin.write_all(data)?;
stdin.flush()?;
drop(stdin);
let output = child
.wait_with_output()
.context("Failed to wait for sh4d0wup child")?;
if !output.status.success() {
bail!("Git did not exit successfully");
}
Ok(output.stdout)
}
fn mkupdate(date: &str, commit: &str, data: &[u8]) -> Result<Vec<u8>> {
let header = format!("Date: {date}\nCommit: {commit}\n\n");
info!("Generated header: {header:?}");
let mut buf = header.into_bytes();
buf.extend(data);
if !buf.ends_with(b"\n") {
buf.push(b'\n');
}
Ok(buf)
}
fn normalize(data: Vec<u8>, signature: Vec<u8>) -> Result<Vec<u8>> {
let signed = Signed {
content: data.into(),
signature,
};
signed.to_clear_signed()
}
fn main() -> Result<()> {
env_logger::init_from_env(Env::default().default_filter_or("info"));
let args = Args::parse();
let date = args
.date
.unwrap_or_else(|| Utc::now().format("%a, %d %b %Y %T %Z").to_string());
let commit = args.commit.map(Ok).unwrap_or_else(get_commit)?;
let content = fs::read(&args.path)
.with_context(|| anyhow!("Failed to read content from file: {:?}", args.path))?;
let buf = mkupdate(&date, &commit, &content)?;
info!("Full update: {} bytes", buf.len());
info!("Generating signature...");
let signature = sign(&buf, &args.secret_key)?;
info!("Signature generated successfully");
let mut stdout = io::stdout();
let buf = normalize(buf, signature)?;
stdout.write_all(&buf)?;
Ok(())
}
|