diff --git a/Cargo.lock b/Cargo.lock index 4b388fe..d6d43fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,6 +383,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core", "serde", "sha2", "subtle", diff --git a/Cargo.toml b/Cargo.toml index 87681de..ea32976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ blake3 = "1.5.1" clap = { version = "4.5.4", features = ["derive"] } csv = "1.3.0" ed25519 = "2.2.3" -ed25519-dalek = "2.1.1" +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } rand = "0.8.5" serde = { version = "1.0.198", features = ["derive"] } serde_json = "1.0.116" diff --git a/fixtures/blake3.key b/fixtures/blake3.key new file mode 100644 index 0000000..a82dde9 --- /dev/null +++ b/fixtures/blake3.key @@ -0,0 +1 @@ +xUSZqzRNnuCE%~FWTh?axY8LuE*Mb4D4 diff --git a/fixtures/ed25519.pk b/fixtures/ed25519.pk new file mode 100644 index 0000000..73ed452 Binary files /dev/null and b/fixtures/ed25519.pk differ diff --git a/fixtures/ed25519.sk b/fixtures/ed25519.sk new file mode 100644 index 0000000..63c2b53 --- /dev/null +++ b/fixtures/ed25519.sk @@ -0,0 +1 @@ +\{+w^Hy$|vqKƯ:G \ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 8e8cdd1..9fb2e7b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,6 +4,7 @@ mod genpass_opts; mod text; use std::fs; +use std::path::{Path, PathBuf}; pub use self::base64_opts::{Base64Format, Base64Opts, Base64SubCommand}; pub use self::csv_opts::{CsvOpts, OutputFormat}; @@ -34,7 +35,7 @@ pub enum SubCommand { } // 模块级别的函数,共享input file的解析逻辑 -fn parse_input_file(path: &str) -> Result { +pub fn parse_input_file(path: &str) -> Result { if path == "-" || fs::metadata(path).is_ok() { Ok(path.to_string()) } else { @@ -42,6 +43,15 @@ fn parse_input_file(path: &str) -> Result { } } +pub fn verify_dir(path: &str) -> Result { + let path = Path::new(path); + if path.exists() && path.is_dir() { + Ok(path.into()) + } else { + Err("directory not found") + } +} + // 单元测试 #[cfg(test)] mod tests { diff --git a/src/cli/text.rs b/src/cli/text.rs index b28e57d..4db0909 100644 --- a/src/cli/text.rs +++ b/src/cli/text.rs @@ -1,7 +1,7 @@ use clap::Parser; -use std::{fmt::Display, str::FromStr}; +use std::{fmt::Display, path::PathBuf, str::FromStr}; -use super::parse_input_file; +use super::{parse_input_file, verify_dir}; #[derive(Debug, Parser)] pub struct TextOpts { #[command(subcommand)] @@ -14,6 +14,16 @@ pub enum TextSubCommand { Sign(TextSignOpts), #[command(name = "verify", about = "Verify text with public/shared key.")] Verify(TextVerifyOpts), + #[command(name = "generate", about = "Generate random key.")] + Generate(TextKeyGenerateOpts), +} + +#[derive(Debug, Parser)] +pub struct TextKeyGenerateOpts { + #[arg(long, value_parser=TextSignFormat::from_str, default_value="blake3", help = "key file path, or '-' for stdin")] + pub format: TextSignFormat, + #[arg(short, long, value_parser=verify_dir)] + pub output: PathBuf, } #[derive(Debug, Parser)] diff --git a/src/lib.rs b/src/lib.rs index 21f72cd..f13c8fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,12 @@ mod cli; mod process; mod utils; -pub use cli::{Base64SubCommand, Opts, SubCommand, TextSignFormat, TextSubCommand}; +pub use cli::{ + parse_input_file, verify_dir, Base64SubCommand, Opts, SubCommand, TextSignFormat, + TextSubCommand, +}; pub use process::{ - process_b64decode, process_b64encode, process_csv, process_genpass, process_sign, - process_verify, + process_b64decode, process_b64encode, process_csv, process_generate, process_genpass, + process_sign, process_verify, }; pub use utils::{get_content, get_reader}; diff --git a/src/main.rs b/src/main.rs index de8d366..7883368 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ +use std::fs; + use anyhow::Result; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use clap::Parser; use zxcvbn::zxcvbn; use rcli::{ - get_content, get_reader, process_b64decode, process_b64encode, process_csv, process_genpass, - process_sign, process_verify, Base64SubCommand, Opts, SubCommand, TextSubCommand, + get_content, get_reader, process_b64decode, process_b64encode, process_csv, process_generate, + process_genpass, process_sign, process_verify, Base64SubCommand, Opts, SubCommand, + TextSignFormat, TextSubCommand, }; // usage: @@ -58,6 +61,7 @@ fn main() -> Result<()> { let mut reader = get_reader(&opts.input)?; let key = get_content(&opts.key)?; let sig = URL_SAFE_NO_PAD.decode(&opts.sig)?; + println!("sig: {:?}", sig.len()); let verified = process_verify(&mut reader, &key, &sig, opts.format)?; if verified { println!("✓ Signature verified"); @@ -65,6 +69,20 @@ fn main() -> Result<()> { println!("⚠ Signature not verified"); } } + TextSubCommand::Generate(opts) => { + let keys = process_generate(opts.format)?; + match opts.format { + TextSignFormat::Blake3 => { + let name = opts.output.join("blake3.key"); + let keys = process_generate(opts.format)?; + fs::write(name, &keys[0])?; + } + TextSignFormat::Ed25519 => { + fs::write(opts.output.join("ed25519.sk"), &keys[0])?; + fs::write(opts.output.join("ed25519.pk"), &keys[1])?; + } + } + } }, } Ok(()) diff --git a/src/process/mod.rs b/src/process/mod.rs index be02e21..a5dbce6 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -7,4 +7,4 @@ pub use base64_processor::process_decode as process_b64decode; pub use base64_processor::process_encode as process_b64encode; pub use csv_processor::process as process_csv; pub use genpass_processor::process as process_genpass; -pub use text::{process_sign, process_verify}; +pub use text::{process_generate, process_sign, process_verify}; diff --git a/src/process/text.rs b/src/process/text.rs index 7d8e1ac..8186b7f 100644 --- a/src/process/text.rs +++ b/src/process/text.rs @@ -1,9 +1,12 @@ +use crate::process_genpass; use crate::TextSignFormat; use anyhow::anyhow; use anyhow::Result; use ed25519::signature::{Signer, Verifier}; use ed25519_dalek::{Signature, SigningKey, VerifyingKey}; +use rand::rngs::OsRng; use std::io::Read; +use std::vec; trait TextSign { fn sign(&self, reader: &mut dyn Read) -> Result>; @@ -13,6 +16,10 @@ trait TextVerify { fn verify(&self, reader: &mut dyn Read, sig: &[u8]) -> Result; } +trait KeyGenerator { + fn generate() -> Result>>; +} + struct Blake3 { key: [u8; 32], } @@ -42,6 +49,14 @@ impl TextSign for Blake3 { } } +impl KeyGenerator for Blake3 { + fn generate() -> Result>> { + let ret = process_genpass(false, false, false, false, 32); + println!("{:?}", ret); + Ok(vec![ret]) + } +} + impl TextVerify for Blake3 { fn verify(&self, reader: &mut dyn Read, sig: &[u8]) -> Result { let mut data = Vec::new(); @@ -76,6 +91,18 @@ impl TextSign for Ed25519Signer { } } +impl KeyGenerator for Ed25519Signer { + fn generate() -> Result>> { + let mut rng = OsRng; + let signing_key = SigningKey::generate(&mut rng); + let verifying_key = VerifyingKey::from(&signing_key); + Ok(vec![ + signing_key.as_bytes().to_vec(), + verifying_key.as_bytes().to_vec(), + ]) + } +} + struct Ed25519Verifier { key: VerifyingKey, } @@ -125,6 +152,13 @@ pub fn process_verify( verifier.verify(reader, sig) } +pub fn process_generate(format: TextSignFormat) -> Result>> { + match format { + TextSignFormat::Blake3 => Blake3::generate(), + TextSignFormat::Ed25519 => Ed25519Signer::generate(), + } +} + // 生成测试用例 #[cfg(test)] mod tests {