Add: Client binary, Add Feature: command line arguments parser
This commit is contained in:
parent
eb30b7c2df
commit
dbfc557cc5
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.13", features = ["derive"] }
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
rustls = "0.23.12"
|
rustls = "0.23.12"
|
||||||
|
268
src/bin/client.rs
Normal file
268
src/bin/client.rs
Normal file
@ -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<String>,
|
||||||
|
/// set log level
|
||||||
|
#[arg(short, long, value_name = "Log Level", value_enum)]
|
||||||
|
level: Option<Level>,
|
||||||
|
/// 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<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||||
|
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(
|
||||||
|
&self,
|
||||||
|
_: &[u8],
|
||||||
|
_: &rustls::pki_types::CertificateDer<'_>,
|
||||||
|
_: &rustls::DigitallySignedStruct,
|
||||||
|
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||||
|
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<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||||
|
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<rustls::SignatureScheme> {
|
||||||
|
let mut ss = Vec::<rustls::SignatureScheme>::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::<Result<Vec<_>, _>>()
|
||||||
|
{
|
||||||
|
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<dyn StdError>> {
|
||||||
|
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(())
|
||||||
|
}
|
93
src/main.rs
93
src/main.rs
@ -1,14 +1,69 @@
|
|||||||
extern crate pretty_env_logger;
|
extern crate pretty_env_logger;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
use std::env;
|
use clap::{Parser, ValueEnum};
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::sync::Arc;
|
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<Level>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()
|
pretty_env_logger::formatted_timed_builder()
|
||||||
.format(|buf, record| {
|
.format(|buf, record| {
|
||||||
// We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your
|
// We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your
|
||||||
@ -27,24 +82,31 @@ fn initialize_log() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.format_timestamp_micros()
|
.format_timestamp_micros()
|
||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(level)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
info!("Hi -");
|
info!("Hi -");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Vec<String> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let mut key_path = Vec::<String>::new();
|
||||||
|
key_path.push(cli.cert.clone());
|
||||||
|
key_path.push(cli.key.clone());
|
||||||
|
|
||||||
|
initialize_log(&cli);
|
||||||
|
|
||||||
|
key_path
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn StdError>> {
|
fn main() -> Result<(), Box<dyn StdError>> {
|
||||||
initialize_log();
|
let key_path = parse_args();
|
||||||
|
|
||||||
let mut args = env::args();
|
let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(&key_path[KeyPath::Cert as usize][0..])?))
|
||||||
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)?))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let private_key =
|
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();
|
.unwrap();
|
||||||
let config = rustls::ServerConfig::builder()
|
let config = rustls::ServerConfig::builder()
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
@ -66,14 +128,17 @@ fn main() -> Result<(), Box<dyn StdError>> {
|
|||||||
Ok(size) => size,
|
Ok(size) => size,
|
||||||
Err(ec) => {
|
Err(ec) => {
|
||||||
error!("Read Error: {}", ec.to_string());
|
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}");
|
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");
|
debug!("Send Data");
|
||||||
tls_conn.complete_io(&mut stream)?;
|
tls_conn.complete_io(&mut stream)?;
|
||||||
// -> drop tls_conn
|
// -> drop tls_conn
|
||||||
|
Loading…
Reference in New Issue
Block a user