#![allow(dead_code)]
mod check;
mod crypto;
mod generate;
mod generate_error;
mod generate_types;
#[cfg(feature = "sign")]
mod sign;
use check::utils::{MessageType, ReporterCallback};
use check::ProofCheck;
use check::{check_cids, check_files, check_urls};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::process;
use std::sync::Arc;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Check {
#[arg(short, long)]
file: Option<Vec<String>>,
#[arg(short, long)]
dir: Option<Vec<String>>,
#[arg(short, long)]
url: Option<Vec<String>>,
#[arg(short, long)]
cid: Option<Vec<String>>,
#[arg(short, long)]
output_file: Option<String>,
},
Generate {
#[arg(short, long)]
file: Option<Vec<String>>,
#[arg(short, long)]
dir: Option<Vec<String>>,
#[arg(short, long, help = "Storage directory for proof data")]
storage: Option<String>,
#[arg(short, long, help = "Email for PGP key generation")]
email: Option<String>,
#[arg(short, long, help = "Passphrase for PGP key")]
passphrase: Option<String>,
},
#[cfg(feature = "sign")]
Sign {
#[command(subcommand)]
action: SignAction,
},
}
#[cfg(feature = "sign")]
#[derive(Subcommand)]
enum SignAction {
GenerateKey {
#[arg(short, long, help = "Output directory for key files")]
output: Option<String>,
},
Data {
#[arg(short, long, help = "Path to the private key PEM file")]
key: String,
#[arg(short, long, help = "File to sign")]
file: String,
#[arg(short, long, help = "Platform (ios or android)", default_value = "ios")]
platform: String,
},
Verify {
#[arg(
short,
long,
help = "Path to the public key file (SEC1 uncompressed bytes, hex-encoded)"
)]
key: String,
#[arg(short, long, help = "File whose signature to verify")]
file: String,
#[arg(short, long, help = "Signature file (raw bytes)")]
signature: String,
#[arg(short, long, help = "Platform (ios or android)", default_value = "ios")]
platform: String,
},
GenerateCsr {
#[arg(short, long, help = "Subject Alternative Name(s)")]
san: Vec<String>,
#[arg(short, long, help = "Output path for the CSR PEM")]
output: Option<String>,
},
SignCsr {
#[arg(short, long, help = "Path to CSR PEM file")]
csr: String,
#[arg(short, long, help = "Domain for the CA certificate")]
domain: String,
#[arg(short, long, help = "Output path for the signed certificate")]
output: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
match &cli.command {
Some(Commands::Check {
file,
dir,
url,
cid,
output_file,
}) => {
let callback: ReporterCallback =
Arc::new(|message_type: MessageType, message: String| {
println!("{:?}: {}", message_type, message);
});
let mut result: Option<ProofCheck> = None;
if let Some(url) = url {
match check_urls(url, callback.clone()) {
Ok(check_result) => result = Some(check_result),
Err(e) => {
eprintln!("Error checking URLs: {}", e);
std::process::exit(1);
}
}
}
if let Some(cid) = cid {
match check_cids(cid, callback.clone()) {
Ok(check_result) => result = Some(check_result),
Err(e) => {
eprintln!("Error checking CIDs: {}", e);
std::process::exit(1);
}
}
}
if let Some(files) = file {
match check_files(files, callback.clone()) {
Ok(check_result) => result = Some(check_result),
Err(e) => {
eprintln!("Error checking files: {}", e);
std::process::exit(1);
}
}
}
if let Some(directories) = dir {
let mut files = Vec::new();
for directory in directories {
match std::fs::read_dir(directory) {
Ok(paths) => {
for path in paths {
match path {
Ok(entry) => {
let path = entry.path();
if let Some(path_str) = path.to_str() {
files.push(path_str.to_string());
} else {
eprintln!(
"Warning: Skipping file with non-UTF8 path: {:?}",
path
);
}
}
Err(e) => {
eprintln!("Error reading directory entry: {}", e);
}
}
}
}
Err(e) => {
eprintln!("Error reading directory '{}': {}", directory, e);
std::process::exit(1);
}
}
}
match check_files(&files, callback.clone()) {
Ok(check_result) => result = Some(check_result),
Err(e) => {
eprintln!("Error checking files: {}", e);
std::process::exit(1);
}
}
}
if let Some(result) = result {
if let Some(output_file) = output_file {
let json_output = match serde_json::to_string_pretty(&result) {
Ok(json) => json,
Err(e) => {
eprintln!("Error serializing output to JSON: {}", e);
std::process::exit(1);
}
};
match std::fs::write(output_file, json_output) {
Ok(_) => println!("Output written to {}", output_file),
Err(e) => eprintln!("Failed to write output to {}: {}", output_file, e),
}
}
}
}
Some(Commands::Generate {
file,
dir,
storage,
email,
passphrase,
}) => {
let storage_path = storage.as_deref().unwrap_or("./proofmode");
let email = email.as_deref().unwrap_or("user@example.com");
let passphrase = passphrase.as_deref().unwrap_or("default_passphrase");
let storage_path = PathBuf::from(storage_path);
let mut has_error = false;
if let Some(files) = file {
for file_path in files {
match generate::generate_proof_from_file(
&PathBuf::from(&file_path),
&storage_path,
email,
passphrase,
None,
) {
Ok(hash) => println!("Generated proof for {}: {}", file_path, hash),
Err(e) => {
eprintln!("Failed to generate proof for {}: {}", file_path, e);
has_error = true;
}
}
}
}
if let Some(directories) = dir {
for directory in directories {
match std::fs::read_dir(directory) {
Ok(paths) => {
for path in paths {
match path {
Ok(entry) => {
let path = entry.path();
if path.is_file() {
match generate::generate_proof_from_file(
&path,
&storage_path,
email,
passphrase,
None,
) {
Ok(hash) => println!(
"Generated proof for {:?}: {}",
path, hash
),
Err(e) => {
eprintln!(
"Failed to generate proof for {:?}: {}",
path, e
);
has_error = true;
}
}
}
}
Err(e) => {
eprintln!(
"Error reading directory entry in '{}': {}",
directory, e
);
has_error = true;
}
}
}
}
Err(e) => {
eprintln!("Error reading directory '{}': {}", directory, e);
has_error = true;
}
}
}
}
if has_error {
process::exit(1);
}
}
#[cfg(feature = "sign")]
Some(Commands::Sign { action }) => {
handle_sign_command(action);
}
None => {}
}
}
#[cfg(feature = "sign")]
fn handle_sign_command(action: &SignAction) {
use sign::signer::Signer as _;
match action {
SignAction::GenerateKey { output } => {
let output_dir = output.as_deref().unwrap_or("./keys");
std::fs::create_dir_all(output_dir).unwrap_or_else(|e| {
eprintln!("Failed to create output directory: {}", e);
process::exit(1);
});
let signer = sign::LocalES256Signer::random();
let ios_pub = signer
.public_key(&sign::Platform::Ios)
.expect("Failed to get iOS public key");
let android_pub = signer
.public_key(&sign::Platform::Android)
.expect("Failed to get Android public key");
let ios_path = PathBuf::from(output_dir).join("ios_public.hex");
let android_path = PathBuf::from(output_dir).join("android_public.hex");
std::fs::write(&ios_path, hex::encode(&ios_pub)).unwrap_or_else(|e| {
eprintln!("Failed to write iOS public key: {}", e);
process::exit(1);
});
std::fs::write(&android_path, hex::encode(&android_pub)).unwrap_or_else(|e| {
eprintln!("Failed to write Android public key: {}", e);
process::exit(1);
});
println!("Generated ES256 key pair:");
println!(" iOS public key: {}", ios_path.display());
println!(" Android public key: {}", android_path.display());
println!(
"Note: For production use, load keys from PEM files using --key-path options."
);
}
SignAction::Data {
key,
file,
platform,
} => {
let platform = platform.parse::<sign::Platform>().unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);
});
let key_pem = std::fs::read_to_string(key).unwrap_or_else(|e| {
eprintln!("Failed to read key file: {}", e);
process::exit(1);
});
let signer = sign::LocalES256Signer::from_pem(&key_pem, &key_pem).unwrap_or_else(|e| {
eprintln!("Failed to create signer: {}", e);
process::exit(1);
});
let data = std::fs::read(file).unwrap_or_else(|e| {
eprintln!("Failed to read file: {}", e);
process::exit(1);
});
let signature = signer.sign(&platform, &data).unwrap_or_else(|e| {
eprintln!("Signing failed: {}", e);
process::exit(1);
});
let sig_path = format!("{}.sig", file);
std::fs::write(&sig_path, &signature).unwrap_or_else(|e| {
eprintln!("Failed to write signature: {}", e);
process::exit(1);
});
println!("Signature written to {}", sig_path);
}
SignAction::Verify {
key,
file,
signature,
platform,
} => {
let platform = platform.parse::<sign::Platform>().unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);
});
let pub_key_hex = std::fs::read_to_string(key).unwrap_or_else(|e| {
eprintln!("Failed to read key file: {}", e);
process::exit(1);
});
let pub_key_bytes = hex::decode(pub_key_hex.trim()).unwrap_or_else(|e| {
eprintln!("Failed to decode public key hex: {}", e);
process::exit(1);
});
let data = std::fs::read(file).unwrap_or_else(|e| {
eprintln!("Failed to read file: {}", e);
process::exit(1);
});
let sig_bytes = std::fs::read(signature).unwrap_or_else(|e| {
eprintln!("Failed to read signature file: {}", e);
process::exit(1);
});
use p256::ecdsa::{signature::Verifier as _, Signature, VerifyingKey};
let verifying_key = VerifyingKey::from_sec1_bytes(&pub_key_bytes).unwrap_or_else(|e| {
eprintln!("Invalid public key: {}", e);
process::exit(1);
});
let sig = if sig_bytes.len() == 64 {
Signature::from_slice(&sig_bytes)
} else {
Signature::from_der(&sig_bytes)
}
.unwrap_or_else(|e| {
eprintln!("Invalid signature: {}", e);
process::exit(1);
});
match verifying_key.verify(&data, &sig) {
Ok(()) => {
println!("Signature is valid for platform: {}", platform);
}
Err(e) => {
eprintln!("Signature verification failed: {}", e);
process::exit(1);
}
}
}
SignAction::GenerateCsr { san, output } => {
let sans: Vec<String> = if san.is_empty() {
vec!["localhost".to_string()]
} else {
san.clone()
};
let (csr_pem, key_pem) = sign::cert::generate_csr(&sans).unwrap_or_else(|e| {
eprintln!("Failed to generate CSR: {}", e);
process::exit(1);
});
if let Some(output_path) = output {
std::fs::write(output_path, &csr_pem).unwrap_or_else(|e| {
eprintln!("Failed to write CSR: {}", e);
process::exit(1);
});
let key_path = format!("{}.key", output_path);
std::fs::write(&key_path, &key_pem).unwrap_or_else(|e| {
eprintln!("Failed to write private key: {}", e);
process::exit(1);
});
println!("CSR written to {}", output_path);
println!("Private key written to {}", key_path);
} else {
println!("{}", csr_pem);
}
}
SignAction::SignCsr {
csr,
domain,
output,
} => {
let csr_pem = std::fs::read_to_string(csr).unwrap_or_else(|e| {
eprintln!("Failed to read CSR file: {}", e);
process::exit(1);
});
let signed_cert = sign::cert::sign_csr(&csr_pem, domain).unwrap_or_else(|e| {
eprintln!("Failed to sign CSR: {}", e);
process::exit(1);
});
if let Some(output_path) = output {
std::fs::write(output_path, &signed_cert).unwrap_or_else(|e| {
eprintln!("Failed to write certificate: {}", e);
process::exit(1);
});
println!("Signed certificate written to {}", output_path);
} else {
println!("{}", signed_cert);
}
}
}
}