Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion PLANS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ CLI tool for kylix-pqc post-quantum cryptography library.
| Bench Release Build Fix | HIGH | `--release` missing from external-tool-compare CI job, causing ~100x slowdown vs liboqs |
| [H-2] Shared Secret Output Control | HIGH | `--secret-file` option for encaps/decaps to write shared secret to file instead of console |
| [M-2] Split main.rs into Modules | MEDIUM | Split 1256-line main.rs into cli.rs, io.rs, macros.rs, and commands/ directory |
| [M-3] Input Format Disambiguation | MEDIUM | `--format` now uses `Option<OutputFormat>`: explicit format prevents fallback, None preserves auto-detect |
| [L-6] PEM Label Validation | LOW | PR #45 - Validate BEGIN/END labels match exactly in `decode_pem`, reject empty labels and malformed headers/footers |

---

Expand All @@ -34,9 +36,9 @@ CLI tool for kylix-pqc post-quantum cryptography library.
| OpenSSL Dedup | LOW | Extract common logic from KEM/SIG benchmark functions |
| liboqs Parsing | LOW | Parse column headers instead of hardcoded indices |
| wolfSSL Support | LOW | Add wolfSSL as external benchmark tool |
| [M-3] Input Format Disambiguation | MEDIUM | Improve hex vs base64 auto-detection for edge cases |
| [M-4] Deep Zeroization in encode/decode | MEDIUM | Zeroize intermediate strings in `encode_output`/`decode_input` (PEM wrapping, base64) |
| [L-5] Windows ACL for Secret Keys | LOW | Enforce restrictive ACLs on secret key files on Windows (e.g. `windows-acl` crate) |
| [L-7] Separate Input/Output Format Flags | LOW | Split `--format` into `--in-format` / `--out-format` so users can read PEM keys and output hex (currently a single flag controls both) |

---

Expand Down
33 changes: 17 additions & 16 deletions kylix-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ pub(crate) enum Commands {
#[arg(short, long)]
output: String,

/// Output format
#[arg(short, long, value_enum, default_value = "hex")]
format: OutputFormat,
/// Output encoding format (default: hex)
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
},

/// Encapsulate a shared secret using a public key
Expand All @@ -53,9 +53,9 @@ pub(crate) enum Commands {
#[arg(long = "secret-file")]
secret_file: Option<PathBuf>,

/// Output format
#[arg(short, long, value_enum, default_value = "hex")]
format: OutputFormat,
/// Encoding format (default: hex for output, auto-detect for input)
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
},

/// Decapsulate a shared secret using a secret key
Expand All @@ -72,9 +72,9 @@ pub(crate) enum Commands {
#[arg(long = "secret-file")]
secret_file: Option<PathBuf>,

/// Output format for shared secret
#[arg(short, long, value_enum, default_value = "hex")]
format: OutputFormat,
/// Encoding format (default: hex for output, auto-detect for input)
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
},

/// Sign a file using ML-DSA or SLH-DSA
Expand All @@ -91,9 +91,9 @@ pub(crate) enum Commands {
#[arg(short, long)]
output: PathBuf,

/// Output format
#[arg(short, long, value_enum, default_value = "hex")]
format: OutputFormat,
/// Encoding format (default: hex for output, auto-detect for input)
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,

/// Algorithm (required for SLH-DSA to distinguish -s/-f variants)
#[arg(long, value_enum)]
Expand All @@ -114,9 +114,9 @@ pub(crate) enum Commands {
#[arg(short, long)]
signature: PathBuf,

/// Input format for key and signature files
#[arg(short, long, value_enum, default_value = "hex")]
format: OutputFormat,
/// Input encoding (auto-detect if omitted)
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,

/// Algorithm (required for SLH-DSA to distinguish -s/-f variants)
#[arg(long, value_enum)]
Expand Down Expand Up @@ -445,9 +445,10 @@ impl Algorithm {
}
}

#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
#[derive(Copy, Clone, Default, PartialEq, Eq, ValueEnum)]
pub(crate) enum OutputFormat {
/// Hexadecimal encoding
#[default]
Hex,
/// Base64 encoding
Base64,
Expand Down
5 changes: 3 additions & 2 deletions kylix-cli/src/commands/decaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ pub(crate) fn cmd_decaps(
key: &PathBuf,
input: Option<&PathBuf>,
secret_file: Option<&PathBuf>,
format: OutputFormat,
format: Option<OutputFormat>,
verbose: bool,
) -> Result<()> {
let sk_data =
Zeroizing::new(fs::read_to_string(key).context("Failed to read secret key file")?);
let sk_bytes = Zeroizing::new(decode_input(&sk_data, format)?);
drop(sk_data); // zeroize raw key string immediately after decoding

let out_format = format.unwrap_or_default();
let algo = Algorithm::detect_kem_from_sec_key(sk_bytes.len())?;

if verbose {
Expand Down Expand Up @@ -60,7 +61,7 @@ pub(crate) fn cmd_decaps(
drop(sk_bytes); // zeroize secret key bytes immediately after decapsulation

let ss_len = ss_bytes.len();
let ss_encoded = Zeroizing::new(encode_output(&ss_bytes, format, "SHARED SECRET"));
let ss_encoded = Zeroizing::new(encode_output(&ss_bytes, out_format, "SHARED SECRET"));
drop(ss_bytes); // zeroize shared secret bytes immediately after encoding
if let Some(sf_path) = secret_file {
write_secret_file(sf_path, &ss_encoded)?;
Expand Down
8 changes: 5 additions & 3 deletions kylix-cli/src/commands/encaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ pub(crate) fn cmd_encaps(
pubkey: &PathBuf,
output: Option<&PathBuf>,
secret_file: Option<&PathBuf>,
format: OutputFormat,
format: Option<OutputFormat>,
verbose: bool,
) -> Result<()> {
let pk_data = fs::read_to_string(pubkey).context("Failed to read public key file")?;
let pk_bytes = decode_input(&pk_data, format)?;

let out_format = format.unwrap_or_default();

let algo = Algorithm::detect_kem_from_pub_key(pk_bytes.len())?;

if verbose {
Expand All @@ -34,7 +36,7 @@ pub(crate) fn cmd_encaps(
};
let ss_bytes = Zeroizing::new(ss_bytes_raw);

let ct_encoded = encode_output(&ct_bytes, format, "ML-KEM CIPHERTEXT");
let ct_encoded = encode_output(&ct_bytes, out_format, "ML-KEM CIPHERTEXT");

if let Some(out_path) = output {
fs::write(out_path, &ct_encoded).context("Failed to write ciphertext")?;
Expand All @@ -47,7 +49,7 @@ pub(crate) fn cmd_encaps(
}

let ss_len = ss_bytes.len();
let ss_encoded = Zeroizing::new(encode_output(&ss_bytes, format, "SHARED SECRET"));
let ss_encoded = Zeroizing::new(encode_output(&ss_bytes, out_format, "SHARED SECRET"));
drop(ss_bytes); // zeroize shared secret bytes immediately after encoding
if let Some(sf_path) = secret_file {
write_secret_file(sf_path, &ss_encoded)?;
Expand Down
7 changes: 4 additions & 3 deletions kylix-cli/src/commands/keygen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::io::{encode_output, write_secret_file};
pub(crate) fn cmd_keygen(
algo: Algorithm,
output: &str,
format: OutputFormat,
format: Option<OutputFormat>,
verbose: bool,
) -> Result<()> {
if verbose {
Expand Down Expand Up @@ -41,8 +41,9 @@ pub(crate) fn cmd_keygen(
let pk_size = pk_bytes.len();
let sk_size = sk_bytes.len();

let pk_encoded = encode_output(&pk_bytes, format, pk_label);
let sk_encoded = Zeroizing::new(encode_output(&sk_bytes, format, sk_label));
let out_format = format.unwrap_or_default();
let pk_encoded = encode_output(&pk_bytes, out_format, pk_label);
let sk_encoded = Zeroizing::new(encode_output(&sk_bytes, out_format, sk_label));
// sk_bytes is Zeroizing<Vec<u8>>, automatically zeroized on drop

let pub_path = format!("{}.pub", output);
Expand Down
6 changes: 4 additions & 2 deletions kylix-cli/src/commands/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) fn cmd_sign(
key: &PathBuf,
input: &PathBuf,
output: &PathBuf,
format: OutputFormat,
format: Option<OutputFormat>,
explicit_algo: Option<Algorithm>,
verbose: bool,
) -> Result<()> {
Expand All @@ -22,6 +22,8 @@ pub(crate) fn cmd_sign(
let sk_bytes = Zeroizing::new(decode_input(&sk_data, format)?);
drop(sk_data); // zeroize raw key string immediately after decoding

let out_format = format.unwrap_or_default();

// Use explicit algorithm if provided, otherwise detect from key size
let algo = if let Some(a) = explicit_algo {
// Validate key size matches the explicit algorithm
Expand Down Expand Up @@ -114,7 +116,7 @@ pub(crate) fn cmd_sign(
} else {
"ML-DSA SIGNATURE"
};
let sig_encoded = encode_output(&sig_bytes, format, sig_label);
let sig_encoded = encode_output(&sig_bytes, out_format, sig_label);
fs::write(output, &sig_encoded).context("Failed to write signature")?;

if verbose {
Expand Down
2 changes: 1 addition & 1 deletion kylix-cli/src/commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) fn cmd_verify(
pubkey: &PathBuf,
input: &PathBuf,
signature: &PathBuf,
format: OutputFormat,
format: Option<OutputFormat>,
explicit_algo: Option<Algorithm>,
verbose: bool,
) -> Result<()> {
Expand Down
Loading
Loading