diff --git a/.idea/e3pf.iml b/.idea/e3pf.iml index 2e9f75f..2387604 100644 --- a/.idea/e3pf.iml +++ b/.idea/e3pf.iml @@ -2,6 +2,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index 92c17b2..b8ea49b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,6 +149,16 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "certtoolpf" +version = "0.1.0" +dependencies = [ + "clap", + "inquire", + "libepf", + "rand", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -296,6 +306,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -399,6 +434,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "ed25519" version = "2.2.1" @@ -641,6 +682,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inquire" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bf420bd01f298a3ed8f57af9babedb296b4edfc1dbd8b159cee883aa90edaa" +dependencies = [ + "bitflags", + "crossterm", + "dyn-clone", + "lazy_static", + "newline-converter", + "thiserror", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -792,6 +849,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "newline-converter" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1106,6 +1172,36 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.1.0" @@ -1212,6 +1308,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "time" version = "0.1.45" @@ -1292,6 +1408,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" diff --git a/Cargo.toml b/Cargo.toml index 40d12ec..4107e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "libepf", "ncpf", - "netcatpf" + "netcatpf", + "certtoolpf" ] \ No newline at end of file diff --git a/certtoolpf/Cargo.toml b/certtoolpf/Cargo.toml new file mode 100644 index 0000000..2d94bd0 --- /dev/null +++ b/certtoolpf/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "certtoolpf" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +inquire = "0.6.1" +libepf = { version = "0.1.0", path = "../libepf" } +clap = { version = "4", features = ["derive", "cargo"] } +rand = "0.8.5" \ No newline at end of file diff --git a/certtoolpf/src/main.rs b/certtoolpf/src/main.rs new file mode 100644 index 0000000..bae55b3 --- /dev/null +++ b/certtoolpf/src/main.rs @@ -0,0 +1,328 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; +use clap::{Parser, Subcommand}; +use inquire::{Confirm, Select, Text}; +use rand::rngs::OsRng; +use libepf::ca_pool::load_ca_pool; +use libepf::pki::{EPFCertificate, EPFCertificateDetails, EpfPkiCertificateOps, EpfPkiSerializable, EpfPrivateKey, EpfPublicKey}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands { + /// Generate a public and private keypair + GenerateKeypair { + #[arg(short = 'p', long)] + out_public_key: PathBuf, + #[arg(short = 'k', long)] + out_private_key: PathBuf + }, + /// Create a new **unsigned** certificate + CreateCertificate { + #[arg(short, long)] + output: PathBuf, + #[arg(short, long)] + public_key: PathBuf + }, + /// Dump information about the certificate + DumpCertificate { + cert: PathBuf + }, + /// Verify the certificate + VerifyCertificate { + cert: PathBuf + }, + /// Sign the certificate using the given private key + SignCertificate { + cert: PathBuf, + #[arg(short, long)] + key: PathBuf, + #[arg(short, long)] + output: PathBuf + } +} + +fn main() { + let args = Cli::parse(); + + if let Some(subcommand) = args.command { + match subcommand { + Commands::GenerateKeypair { out_private_key, out_public_key } => { + let private_key = EpfPrivateKey::generate(&mut OsRng); + let public_key = private_key.verifying_key(); + + let private_key_pem = match private_key.as_pem() { + Ok(pem) => pem, + Err(e) => { + println!("Error while serializing private key: {}", e); + std::process::exit(1); + } + }; + let public_key_pem = match public_key.as_pem() { + Ok(pem) => pem, + Err(e) => { + println!("Error while serializing public key: {}", e); + std::process::exit(1); + } + }; + + match std::fs::write(out_private_key, private_key_pem) { + Ok(_) => (), + Err(e) => { + println!("Error saving private key: {}", e); + std::process::exit(1); + } + } + + match std::fs::write(out_public_key, public_key_pem) { + Ok(_) => (), + Err(e) => { + println!("Error saving public key: {}", e); + std::process::exit(1); + } + } + }, + Commands::CreateCertificate { output, public_key } => { + // load public key + let public_key_pem = match fs::read(public_key) { + Ok(pem) => pem, + Err(e) => { + println!("Unable to load public key: {}", e); + std::process::exit(1); + } + }; + let public_key = match EpfPublicKey::from_pem(&public_key_pem) { + Ok(k) => k, + Err(e) => { + println!("Error parsing public key: {}", e); + std::process::exit(1); + } + }; + + let name = match Text::new("Certificate name?").prompt() { + Ok(n) => n, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + + let options = vec!["1 day", "1 week", "1 month", "6 months", "1 year", "2 years", "5 years", "10 years", "Forever"]; + let expires_in = match Select::new("How long should the certificate be valid for?", options).prompt() { + Ok(expires_in) => expires_in, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + let expires_in = match expires_in { + "1 day" => 60 * 60 * 24, + "1 week" => 60 * 60 * 24 * 7, + "1 month" => 60 * 60 * 24 * 7 * 4, + "6 months" => 60 * 60 * 24 * 7 * 4 * 6, + "1 year" => 60 * 60 * 24 * 7 * 4 * 6 * 2, + "2 years" => 60 * 60 * 24 * 7 * 4 * 6 * 2 * 2, + "5 years" => 60 * 60 * 24 * 7 * 4 * 6 * 2 * 5, + "10 years" => 60 * 60 * 24 * 7 * 4 * 6 * 2 * 10, + "Forever" => 60 * 60 * 24 * 7 * 4 * 6 * 2 * 100000, // 100,000 years + _ => unreachable!() + }; + + let add_claims = match Confirm::new("Would you like to add additional claims to the certificate?").with_default(false).prompt() { + Ok(ac) => ac, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + + let mut claims = HashMap::new(); + + if add_claims { + loop { + let name = match Text::new("Claim name:").prompt() { + Ok(n) => n, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + let value = match Text::new("Claim value:").prompt() { + Ok(n) => n, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + claims.insert(name, value); + let add_another = match Confirm::new("Would you like to add additional claims to the certificate?").with_default(true).prompt() { + Ok(ac) => ac, + Err(e) => { + println!("Error with prompt: {}", e); + std::process::exit(1); + } + }; + if !add_another { + break; + } + } + } + + let mut cert = EPFCertificate { + details: EPFCertificateDetails { + name, + not_before: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(), + not_after: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + expires_in, + public_key: public_key.to_bytes(), + issuer_public_key: [0u8; 32], + claims, + }, + fingerprint: "".to_string(), + signature: [0u8; 64], + }; + match cert.recalculate_fingerprint() { + Ok(_) => (), + Err(e) => { + println!("Error calculating certificate fingerprint: {}", e); + std::process::exit(1); + } + } + + let cert_pem = match cert.as_pem() { + Ok(pem) => pem, + Err(e) => { + println!("Error serializing certificate: {}", e); + std::process::exit(1); + } + }; + + match fs::write(output, cert_pem) { + Ok(_) => (), + Err(e) => { + println!("Error saving certificate: {}", e); + } + } + }, + Commands::DumpCertificate { cert } => { + // load cert + let cert_pem = match fs::read(cert) { + Ok(pem) => pem, + Err(e) => { + println!("Unable to load certificate: {}", e); + std::process::exit(1); + } + }; + let cert = match EPFCertificate::from_pem(&cert_pem) { + Ok(k) => k, + Err(e) => { + println!("Error parsing certificate: {}", e); + std::process::exit(1); + } + }; + println!("{}", cert); + }, + Commands::SignCertificate { cert, key, output }=> { + // load cert + let cert_pem = match fs::read(cert) { + Ok(pem) => pem, + Err(e) => { + println!("Unable to load certificate: {}", e); + std::process::exit(1); + } + }; + let mut cert = match EPFCertificate::from_pem(&cert_pem) { + Ok(k) => k, + Err(e) => { + println!("Error parsing certificate: {}", e); + std::process::exit(1); + } + }; + + // load key + let key_pem = match fs::read(key) { + Ok(pem) => pem, + Err(e) => { + println!("Unable to load private key: {}", e); + std::process::exit(1); + } + }; + let key = match EpfPrivateKey::from_pem(&key_pem) { + Ok(k) => k, + Err(e) => { + println!("Error parsing private key: {}", e); + std::process::exit(1); + } + }; + + match cert.sign(&key) { + Ok(_) => (), + Err(e) => { + println!("Error signing certificate: {}", e); + std::process::exit(1); + } + } + + let cert_pem = match cert.as_pem() { + Ok(pem) => pem, + Err(e) => { + println!("Error serializing certificate: {}", e); + std::process::exit(1); + } + }; + + match fs::write(output, cert_pem) { + Ok(_) => (), + Err(e) => { + println!("Error saving certificate: {}", e); + } + } + }, + Commands::VerifyCertificate { cert } => { + // load cert + let cert_pem = match fs::read(cert) { + Ok(pem) => pem, + Err(e) => { + println!("Unable to load certificate: {}", e); + std::process::exit(1); + } + }; + let cert = match EPFCertificate::from_pem(&cert_pem) { + Ok(k) => k, + Err(e) => { + println!("Error parsing certificate: {}", e); + std::process::exit(1); + } + }; + + let ca_pool = match load_ca_pool() { + Ok(pool) => pool, + Err(e) => { + println!("Unable to load trusted certs pool: {}", e); + std::process::exit(1); + } + }; + match cert.verify(&ca_pool) { + Ok(trusted) => { + if !trusted { + println!("Certificate valid but not trusted"); + std::process::exit(3); + } + }, + Err(e) => { + println!("Certificate invalid: {}", e); + std::process::exit(2); + } + } + }, + } + } else { + println!("No subcommand specified. Run with -h/--help for help."); + } +} diff --git a/libepf/src/ca_pool.rs b/libepf/src/ca_pool.rs index e927224..567625d 100644 --- a/libepf/src/ca_pool.rs +++ b/libepf/src/ca_pool.rs @@ -56,7 +56,7 @@ pub fn load_ca_pool() -> Result> { for entry in fs::read_dir("/etc/e3pf/certs")? { let entry = entry?; - if entry.path().extension() == Some(OsStr::new(".pem")) { + if entry.path().extension() == Some(OsStr::new("pem")) { cert_strings.push(fs::read_to_string(entry.path())?); } } diff --git a/libepf/src/pki.rs b/libepf/src/pki.rs index c9131a8..44f02da 100644 --- a/libepf/src/pki.rs +++ b/libepf/src/pki.rs @@ -250,7 +250,7 @@ impl Display for EPFCertificate { )?; writeln!( f, - "\t\tIssuer Fingerprint: {}", + "\t\tIssuer Public Key: {}", hex::encode(self.details.issuer_public_key) )?; writeln!(f, "\t}}")?; diff --git a/ncpf/src/main.rs b/ncpf/src/main.rs index 3b386b8..25f6d2c 100644 --- a/ncpf/src/main.rs +++ b/ncpf/src/main.rs @@ -7,19 +7,20 @@ use tokio::net::{TcpSocket}; use tokio::select; use libepf::ca_pool::load_ca_pool; use libepf::handshake_stream::{ClientAuthentication, EpfClientHandshaker, EpfClientUpgradable, EpfStreamOps}; +use std::io; #[derive(Parser)] #[command(author, version, about, long_about = None)] pub struct Cli { - bind_ip: IpAddr, - bind_port: u16 + connect_ip: IpAddr, + connect_port: u16 } #[tokio::main] async fn main() -> Result<(), Box> { let cli = Cli::parse(); - let bind_addr = SocketAddr::new(cli.bind_ip, cli.bind_port); + let bind_addr = SocketAddr::new(cli.connect_ip, cli.connect_port); let tcp_stream = TcpSocket::new_v4()?.connect(bind_addr).await?; @@ -30,7 +31,7 @@ async fn main() -> Result<(), Box> { let mut client = handshake_client.upgrade().await; let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()); - let mut input = String::new(); + let mut input = [0u8; 32767]; loop { select! { @@ -38,21 +39,35 @@ async fn main() -> Result<(), Box> { match packet_data { Ok(d) => std::io::stdout().write_all(&d).unwrap(), Err(e) => { - println!("{}", e); - std::process::exit(1); + match e.downcast_ref::() { + Some(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + std::process::exit(0); + }, + Some(_) | None => { + println!("{}", e); + std::process::exit(1); + } + } } } }, - stdin_data = stdin.read_to_string(&mut input) => { + stdin_data = stdin.read(&mut input) => { match stdin_data { Ok(amt) => { - match client.write(input.as_bytes()).await { + match client.write(&input[..amt]).await { Ok(_) => { - input = String::new(); + input = [0u8; 32767]; }, Err(e) => { - println!("{}", e); - std::process::exit(1); + match e.downcast_ref::() { + Some(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + std::process::exit(0); + }, + Some(_) | None => { + println!("{}", e); + std::process::exit(1); + } + } } } if amt == 0 { diff --git a/netcatpf/src/main.rs b/netcatpf/src/main.rs index a739998..8341f5c 100644 --- a/netcatpf/src/main.rs +++ b/netcatpf/src/main.rs @@ -1,5 +1,5 @@ use std::error::Error; -use std::fs; +use std::{fs, io}; use std::io::{Write}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; @@ -17,7 +17,7 @@ pub struct Cli { bind_ip: IpAddr, bind_port: u16, certificate: PathBuf, - key: PathBuf + key: PathBuf, } #[tokio::main] @@ -45,7 +45,7 @@ async fn main() -> Result<(), Box> { let mut client = handshake_server.upgrade().await; let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()); - let mut input = String::new(); + let mut input = [0u8; 32767]; loop { select! { @@ -53,21 +53,35 @@ async fn main() -> Result<(), Box> { match packet_data { Ok(d) => std::io::stdout().write_all(&d).unwrap(), Err(e) => { - println!("{}", e); - std::process::exit(1); + match e.downcast_ref::() { + Some(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + std::process::exit(0); + }, + Some(_) | None => { + println!("{}", e); + std::process::exit(1); + } + } } } }, - stdin_data = stdin.read_to_string(&mut input) => { + stdin_data = stdin.read(&mut input) => { match stdin_data { Ok(amt) => { - match client.write(input.as_bytes()).await { + match client.write(&input[..amt]).await { Ok(_) => { - input = String::new(); + input = [0u8; 32767]; }, Err(e) => { - println!("{}", e); - std::process::exit(1); + match e.downcast_ref::() { + Some(e) if e.kind() == io::ErrorKind::UnexpectedEof => { + std::process::exit(0); + }, + Some(_) | None => { + println!("{}", e); + std::process::exit(1); + } + } } } if amt == 0 {