diff --git a/Cargo.toml b/Cargo.toml index 21b901e..73af883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.13", features = ["derive"] } log = "0.4.22" pretty_env_logger = "0.5.0" rustls = "0.23.12" diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..5b2024c --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,268 @@ +extern crate pretty_env_logger; +#[macro_use] +extern crate log; +use clap::{Parser, ValueEnum}; +use rustls::version::TLS13; +use rustls::RootCertStore; +use std::error::Error as StdError; +use std::fs::File; +use std::io::{BufReader, Read, Write}; +use std::sync::Arc; +use x509_parser::prelude::*; + +#[derive(Parser)] +#[command(version = "1.0")] +struct Cli { + #[arg(short, long, value_name = "Certificate")] + /// CA Certificate. format PEM + cert: Option, + /// set log level + #[arg(short, long, value_name = "Log Level", value_enum)] + level: Option, + /// No Verify Server Certificate + #[arg(short = 'n', long, default_value_t = false)] + no_verify: bool, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum Level { + /// Log Level + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} + +impl Level { + fn to_level_filter(&self) -> log::LevelFilter { + match self { + Level::Off => log::LevelFilter::Off, + Level::Error => log::LevelFilter::Error, + Level::Warn => log::LevelFilter::Warn, + Level::Info => log::LevelFilter::Info, + Level::Debug => log::LevelFilter::Debug, + Level::Trace => log::LevelFilter::Trace, + } + } +} + +// 모든 인증서를 신뢰하는 인증서 검증기 (모든 인증서 PASS) +#[derive(Debug)] +struct NoCertificateVerification; + +impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { + fn verify_tls12_signature( + &self, + _: &[u8], + _: &rustls::pki_types::CertificateDer<'_>, + _: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _: &[u8], + _: &rustls::pki_types::CertificateDer<'_>, + _: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_server_cert( + &self, + end_entity: &rustls::pki_types::CertificateDer<'_>, + intermediates: &[rustls::pki_types::CertificateDer<'_>], + server_name: &rustls::pki_types::ServerName<'_>, + _: &[u8], + _: rustls::pki_types::UnixTime, + ) -> Result { + let ret_deserial = X509Certificate::from_der(&end_entity.iter().as_slice()); + let x509 = match ret_deserial { + Ok((_, x509)) => x509, + _ => panic!("wtf"), + }; + let cn = x509 + .subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap(); + info!( + "Server Cert: CN: {}, CA: {}, serverName : {:?}", + cn, + x509.is_ca(), + server_name + ); + + // end_entity + for (idx, ica) in intermediates.iter().enumerate() { + let ret_deserial = X509Certificate::from_der(&ica.iter().as_slice()); + let x509 = match ret_deserial { + Ok((_, x509)) => x509, + _ => continue, + }; + let cn = x509 + .subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()); + let cn = match cn { + Some(name) => name, + _ => "", + }; + + info!("[{idx}] CN: {}, CA: {}", cn, x509.is_ca()); + } + info!("verify cert done"); + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + let mut ss = Vec::::new(); + ss.push(rustls::SignatureScheme::RSA_PKCS1_SHA1); + ss.push(rustls::SignatureScheme::ECDSA_SHA1_Legacy); + ss.push(rustls::SignatureScheme::RSA_PKCS1_SHA256); + ss.push(rustls::SignatureScheme::ECDSA_NISTP256_SHA256); + ss.push(rustls::SignatureScheme::RSA_PKCS1_SHA384); + ss.push(rustls::SignatureScheme::ECDSA_NISTP384_SHA384); + ss.push(rustls::SignatureScheme::RSA_PKCS1_SHA512); + ss.push(rustls::SignatureScheme::ECDSA_NISTP521_SHA512); + ss.push(rustls::SignatureScheme::RSA_PSS_SHA256); + ss.push(rustls::SignatureScheme::RSA_PSS_SHA384); + ss.push(rustls::SignatureScheme::RSA_PSS_SHA512); + ss.push(rustls::SignatureScheme::ED25519); + ss.push(rustls::SignatureScheme::ED448); + + ss + } +} + +fn initialize_log(options: &Cli) { + let level = match options.level { + Some(level) => level.to_level_filter(), + None => log::LevelFilter::Debug, + }; + + pretty_env_logger::formatted_timed_builder() + .format(|buf, record| { + // We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your + // preferred styling crate. + let mut level_style = buf.default_level_style(record.level()); + let arg_style = level_style.clone(); + let timestamp = buf.timestamp_micros(); + + writeln!( + buf, + "[{timestamp} {:6}] {:>30}:{:<5} - {}", + level_style.set_bold(true).value(record.level()), + record.file_static().unwrap(), + record.line().unwrap(), + arg_style.value(record.args()) + ) + }) + .format_timestamp_micros() + .filter_level(level) + .init(); + + info!("Hi -"); +} + +fn load_use_certificate(crt_name: &str, store: &mut RootCertStore) { + let file = File::open(crt_name); + if let Ok(mut file) = file { + if let Ok(certs) = + rustls_pemfile::certs(&mut BufReader::new(&mut file)).collect::, _>>() + { + for (i, cert) in certs.iter().enumerate() { + let deserialized_cert = X509Certificate::from_der(cert.iter().as_slice()); + match deserialized_cert { + Ok((_, x509)) => { + let cn = x509 + .subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()); + match cn { + Some(name) => { + info!("[{}] CA's CN: {}", i, name); + } + None => (), + } + store.add(cert.clone()).unwrap(); + } + _ => panic!("x509 parsing failed: {:?}", deserialized_cert), + } + } + } + } +} + +fn main() -> Result<(), Box> { + let cli = Cli::parse(); + + initialize_log(&cli); + + let mut store = RootCertStore::empty(); + + if let Some(crt_name) = cli.cert { + load_use_certificate(&crt_name, &mut store); + } + + for cert in rustls_native_certs::load_native_certs()? { + if let Err(e) = store.add(cert) { + error!("push cert error: {}", e.to_string()); + } + } + + let mut config = rustls::ClientConfig::builder_with_protocol_versions(&[&TLS13]) + .with_root_certificates(store) + .with_no_client_auth(); + + if cli.no_verify { + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification)); + } + config.enable_early_data = false; + + let server_name = "scope.co.kr".try_into()?; + let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name)?; + let mut sock = std::net::TcpStream::connect("localhost:10080")?; + let mut tls_conn = rustls::Stream::new(&mut conn, &mut sock); + let mut rbuf: [u8; 1024] = [0; 1024]; + + tls_conn. + + let wstring = "Hello Rust!"; + let wsize = match tls_conn.write(wstring.as_bytes()) { + Ok(size) => size, + Err(ec) => { + error!("Write Error: {}", ec.kind().to_string()); + usize::MAX + } + }; + info!("Send Data ({wsize}): {wstring}"); + + let rsize = match tls_conn.read(&mut rbuf) { + Ok(size) => size, + Err(ec) => { + error!("Read Error: {}", ec.kind().to_string()); + 0 + } + }; + + let utf8string = String::from_utf8(rbuf[0..rsize].to_vec()).expect("could not encoded utf8"); + info!("Received message from Server ({rsize}): {utf8string}"); + + info!("Bye -"); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index fc8f7fd..42f4cd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,69 @@ extern crate pretty_env_logger; #[macro_use] extern crate log; -use std::env; +use clap::{Parser, ValueEnum}; use std::error::Error as StdError; use std::fs::File; use std::io::{BufReader, Read, Write}; use std::net::TcpListener; use std::sync::Arc; -fn initialize_log() { +#[derive(Parser)] +#[command(version = "1.0")] +struct Cli { + #[arg(short, long, value_name = "Certificate")] + /// fullchain certificate name. format PEM + cert: String, + #[arg(short, long, value_name = "PrivateKey")] + /// server privateKey name. format PEM + key: String, + /// set log level + #[arg(short, long, value_name = "Log Level", value_enum)] + level: Option, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum Level { + /// Log Level + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} + +impl Level { + fn to_level_filter(&self) -> log::LevelFilter { + match self { + Level::Off => log::LevelFilter::Off, + Level::Error => log::LevelFilter::Error, + Level::Warn => log::LevelFilter::Warn, + Level::Info => log::LevelFilter::Info, + Level::Debug => log::LevelFilter::Debug, + Level::Trace => log::LevelFilter::Trace, + } + } +} + +#[derive(Copy, Clone)] +enum KeyPath { + Cert = 0, + Pkey = 1, +} + +fn initialize_log(options: &Cli) { + let level = match options.level { + Some(level) => level.to_level_filter(), + None => log::LevelFilter::Debug, + }; + pretty_env_logger::formatted_timed_builder() .format(|buf, record| { // We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your @@ -27,24 +82,31 @@ fn initialize_log() { ) }) .format_timestamp_micros() - .filter_level(log::LevelFilter::Debug) + .filter_level(level) .init(); info!("Hi -"); } +fn parse_args() -> Vec { + let cli = Cli::parse(); + + let mut key_path = Vec::::new(); + key_path.push(cli.cert.clone()); + key_path.push(cli.key.clone()); + + initialize_log(&cli); + + key_path +} + fn main() -> Result<(), Box> { - initialize_log(); + let key_path = parse_args(); - let mut args = env::args(); - args.next(); - let cert_file = args.next().expect("missing certificate file argument"); - let private_key_file = args.next().expect("missing private key file argument"); - - let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(cert_file)?)) + let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(&key_path[KeyPath::Cert as usize][0..])?)) .collect::, _>>()?; let private_key = - rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(private_key_file)?))? + rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(&key_path[KeyPath::Pkey as usize][0..])?))? .unwrap(); let config = rustls::ServerConfig::builder() .with_no_client_auth() @@ -66,14 +128,17 @@ fn main() -> Result<(), Box> { Ok(size) => size, Err(ec) => { error!("Read Error: {}", ec.to_string()); - continue + continue; } }; - let utf8string = String::from_utf8(rbuf[0..rsize].to_vec()).expect("could not encoded utf8"); + let utf8string = + String::from_utf8(rbuf[0..rsize].to_vec()).expect("could not encoded utf8"); info!("Received message from client ({rsize}): {utf8string}"); - tls_conn.writer().write_fmt(format_args!("Echo, Client say: {}", utf8string))?; + tls_conn + .writer() + .write_fmt(format_args!("Echo, Client say: {}", utf8string))?; debug!("Send Data"); tls_conn.complete_io(&mut stream)?; // -> drop tls_conn