On-the-fly certificate forgery
This commit is contained in:
parent
191a05236f
commit
28d623fc3d
8 changed files with 920 additions and 447 deletions
876
Cargo.lock
generated
876
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
|
@ -10,8 +10,6 @@ env_logger = "0.11.8"
|
|||
futures-util = "0.3.31"
|
||||
log = "0.4.28"
|
||||
rustls-platform-verifier = "0.6.2"
|
||||
#sslrelay = { path = "../sslrelay", optional = true }
|
||||
sslrelay = { git = "https://git.zoai.re/tuxmain/sslrelay.git", rev = "11a4777cfebfc41695583c23c545478dc50bdbe0", optional = true }
|
||||
static_cell = "2.1.1"
|
||||
tokio = { version = "1.49.0", features = ["io-util", "macros", "net", "rt-multi-thread", "sync", "time"]}
|
||||
tokio-rustls = "0.26.4"
|
||||
|
|
@ -29,13 +27,20 @@ rustls-openssl = { version = "0.3.0", default-features = false, features = ["tls
|
|||
rustls-post-quantum = { version = "0.2.4", optional = true }
|
||||
rustls-symcrypt = { version = "0.2.1", optional = true, features = ["chacha", "x25519"] }
|
||||
|
||||
# Record
|
||||
dashmap = { version = "6.2.1", optional = true }
|
||||
dns-lookup = { version = "3.0.1", optional = true }
|
||||
openssl = { version = "0.10.81", optional = true }
|
||||
#sslrelay = { path = "../sslrelay", optional = true }
|
||||
sslrelay = { git = "https://git.zoai.re/tuxmain/sslrelay.git", rev = "5e0007fab63a11ab0ca52586c8fc35cb560116bf", optional = true }
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"aws-lc",# Change this to the wanted cryptographic backend (list below)
|
||||
#"record",# It may be needed to remove the record feature when building with openssl
|
||||
"record",# It may be needed to remove the record feature when building with openssl
|
||||
]
|
||||
|
||||
record = ["sslrelay"]
|
||||
record = ["dashmap", "dns-lookup", "dep:openssl", "sslrelay"]
|
||||
|
||||
# Available cryptographic backends
|
||||
aws-lc = ["tokio-rustls/aws-lc-rs", "rustls-post-quantum", "rustls-post-quantum/aws-lc-rs-unstable", "aws-lc-rs"]
|
||||
|
|
@ -48,3 +53,7 @@ symcrypt = ["rustls-symcrypt"]
|
|||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
[patch.crates-io]
|
||||
#openssl = { path = "../rust-openssl/openssl" }
|
||||
#openssl-sys = { path = "../rust-openssl/openssl-sys" }
|
||||
|
|
|
|||
|
|
@ -210,10 +210,12 @@ pub async fn play(
|
|||
.await
|
||||
.unwrap();
|
||||
let mut stream = crate::codec::StreamCodec::new(stream);
|
||||
let mut last_req_id = 0;
|
||||
for (direction, reqs) in ResponseStreamer::new(records.iter()) {
|
||||
match direction {
|
||||
Direction::ClientToServer => {
|
||||
for (req_id, len) in reqs {
|
||||
last_req_id = req_id;
|
||||
//println!("[CLT] ({conn_id}) >> {}", len);
|
||||
let mut data = dummy_bytes[0..len as usize].to_vec();
|
||||
data[0..4].copy_from_slice(&(len as u32).to_be_bytes());
|
||||
|
|
@ -226,7 +228,9 @@ pub async fn play(
|
|||
{
|
||||
Ok(_v) => {}
|
||||
Err(_e) => {
|
||||
println!("client timeout {conn_id} (sending)");
|
||||
println!(
|
||||
"client timeout {conn_id} ({req_id}) (sending)"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -244,8 +248,14 @@ pub async fn play(
|
|||
Err(_e) => {
|
||||
// TODO fix
|
||||
println!(
|
||||
"client timeout {}: {} / {}",
|
||||
conn_id, total_recv, expected_total_len
|
||||
"client timeout {} ({} -> {:?}): {} / {}",
|
||||
conn_id,
|
||||
last_req_id,
|
||||
reqs.iter()
|
||||
.map(|(req_id, _len)| req_id)
|
||||
.collect::<Vec<_>>(),
|
||||
total_recv,
|
||||
expected_total_len
|
||||
);
|
||||
//print_bin(data);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ impl<S: AsyncRead + Unpin> StreamCodec<S> {
|
|||
if expected_len < 8 || expected_len > 16 * 1024 * 1024 {
|
||||
return Err(std::io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
//println!("Expecting {expected_len}");
|
||||
buf.resize(expected_len, 0);
|
||||
self.stream.read_exact(&mut buf[8..expected_len]).await?;
|
||||
Ok(buf)
|
||||
|
|
|
|||
17
src/main.rs
17
src/main.rs
|
|
@ -106,7 +106,14 @@ struct OptPrint {
|
|||
#[cfg(feature = "record")]
|
||||
#[derive(FromArgs)]
|
||||
#[argp(subcommand, name = "record")]
|
||||
struct OptRecord {}
|
||||
struct OptRecord {
|
||||
/// Path to the CAs directory
|
||||
#[argp(positional)]
|
||||
ca_dir: String,
|
||||
/// Path to the directory to store certificates
|
||||
#[argp(positional)]
|
||||
certs_dir: String,
|
||||
}
|
||||
|
||||
/// Record traffic
|
||||
#[derive(FromArgs)]
|
||||
|
|
@ -179,7 +186,13 @@ async fn main() {
|
|||
record::print_records(&records, subopt.number);
|
||||
}
|
||||
#[cfg(feature = "record")]
|
||||
Subcommand::Record(_subopt) => {
|
||||
Subcommand::Record(subopt) => {
|
||||
record::CERTIFICATES
|
||||
.set(record::LyingCertificateRegistry::new(
|
||||
&subopt.ca_dir,
|
||||
&subopt.certs_dir,
|
||||
))
|
||||
.unwrap();
|
||||
record::make_record(&opt.record_file);
|
||||
}
|
||||
Subcommand::Remove(subopt) => {
|
||||
|
|
|
|||
351
src/record.rs
351
src/record.rs
|
|
@ -1,7 +1,16 @@
|
|||
use crate::util::SigAlg;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use log::error;
|
||||
use openssl::{pkey::PKey, x509::X509};
|
||||
use std::{
|
||||
collections::{BTreeMap, btree_map},
|
||||
collections::{BTreeMap, HashMap, btree_map},
|
||||
io::{Read, Write},
|
||||
sync::mpsc::{Receiver, Sender, channel},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
OnceLock,
|
||||
mpsc::{Receiver, Sender, channel},
|
||||
},
|
||||
};
|
||||
|
||||
const CLIENT_TO_SERVER: u8 = b'C';
|
||||
|
|
@ -28,6 +37,327 @@ static TEST_RECORD: &[(u64, &str, Direction, &[u8])] =
|
|||
//(1, "upload.wikimedia.org", Direction::ServerToClient, b"0\r\n\r\n"),
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LyingCertificateRegistry {
|
||||
by_name: DashMap<String, &'static openssl::ssl::SslContext>,
|
||||
cas: HashMap<SigAlg, (X509, PKey<openssl::pkey::Private>)>,
|
||||
certs_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl LyingCertificateRegistry {
|
||||
pub fn new(ca_dir: &str, certs_dir: &str) -> Self {
|
||||
Self {
|
||||
by_name: DashMap::new(),
|
||||
cas: crate::util::load_cas(ca_dir),
|
||||
certs_dir: PathBuf::from(certs_dir),
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch(&self, servername: &str) -> Option<&'static openssl::ssl::SslContext> {
|
||||
if let Some(entry) = self.by_name.get(servername) {
|
||||
// Exact match
|
||||
return Some(entry.value());
|
||||
}
|
||||
// Iteratively remove subdomains, replacing them with wildcards.
|
||||
let mut name = servername.to_string();
|
||||
loop {
|
||||
if !name.contains('.') {
|
||||
// We made it to the top level!
|
||||
break;
|
||||
}
|
||||
if name.as_bytes()[0] == b'*' {
|
||||
if !name[2..].contains('.') {
|
||||
// We made it to the top level!
|
||||
break;
|
||||
}
|
||||
name = format!("*{}", &name[name[2..].find('.').unwrap() + 2..]);
|
||||
} else {
|
||||
name = format!("*{}", &name[name.find('.').unwrap()..]);
|
||||
}
|
||||
if let Some(entry) = self.by_name.get(&name) {
|
||||
return Some(entry.value());
|
||||
}
|
||||
}
|
||||
// If we are still here, it means the certificate is not in the registry yet.
|
||||
|
||||
// Fetch certificate chain from the server
|
||||
if let Some(cert_chain) = crate::util::fetch_server_cert_chain(servername) {
|
||||
let mut parent = None;
|
||||
let mut new_chain = Vec::new();
|
||||
for cert in cert_chain.iter().rev() {
|
||||
//println!(
|
||||
// "Old: {}",
|
||||
// String::from_utf8(cert.to_text().unwrap()).unwrap()
|
||||
//);
|
||||
// Authority key type is not explicit in certificates, so we have to guess from vague signature algorithm and signature size.
|
||||
let sig_type = cert.signature_algorithm();
|
||||
let sig = cert.signature();
|
||||
let sig_alg = match (sig_type.object().nid(), sig.len()) {
|
||||
(openssl::nid::Nid::X9_62_PRIME256V1, _) => SigAlg::P256,
|
||||
(openssl::nid::Nid::SECP384R1, _) => SigAlg::P384,
|
||||
(openssl::nid::Nid::ECDSA_WITH_SHA256, _) => SigAlg::P256,
|
||||
(openssl::nid::Nid::ECDSA_WITH_SHA384, _) => SigAlg::P384,
|
||||
(
|
||||
openssl::nid::Nid::SHA1WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA224WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA256WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA384WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA512WITHRSAENCRYPTION,
|
||||
256,
|
||||
) => SigAlg::Rsa2048,
|
||||
(
|
||||
openssl::nid::Nid::SHA1WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA224WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA256WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA384WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA512WITHRSAENCRYPTION,
|
||||
384,
|
||||
) => SigAlg::Rsa3072,
|
||||
(
|
||||
openssl::nid::Nid::SHA1WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA224WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA256WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA384WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::SHA512WITHRSAENCRYPTION,
|
||||
512,
|
||||
) => SigAlg::Rsa4096,
|
||||
key => {
|
||||
panic!("Unknown key type {key:?}");
|
||||
}
|
||||
};
|
||||
let sig_hash = match sig_type.object().nid() {
|
||||
openssl::nid::Nid::SHA1WITHRSAENCRYPTION => {
|
||||
openssl::hash::MessageDigest::sha1()
|
||||
}
|
||||
openssl::nid::Nid::SHA224WITHRSAENCRYPTION => {
|
||||
openssl::hash::MessageDigest::sha224()
|
||||
}
|
||||
openssl::nid::Nid::SHA256WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::ECDSA_WITH_SHA256 => openssl::hash::MessageDigest::sha256(),
|
||||
openssl::nid::Nid::SHA384WITHRSAENCRYPTION
|
||||
| openssl::nid::Nid::ECDSA_WITH_SHA384 => openssl::hash::MessageDigest::sha384(),
|
||||
openssl::nid::Nid::SHA512WITHRSAENCRYPTION => {
|
||||
openssl::hash::MessageDigest::sha512()
|
||||
}
|
||||
o => panic!("Unknown hash {o:?}"),
|
||||
};
|
||||
|
||||
let original_pk = cert.public_key().unwrap();
|
||||
let new_key = match (original_pk.id(), original_pk.bits()) {
|
||||
(openssl::pkey::Id::EC, 256) => {
|
||||
let key = openssl::ec::EcKey::generate(
|
||||
&openssl::ec::EcGroup::from_curve_name(
|
||||
openssl::nid::Nid::X9_62_PRIME256V1,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
openssl::pkey::PKey::from_ec_key(key).unwrap()
|
||||
}
|
||||
(openssl::pkey::Id::EC, 384) => {
|
||||
let key = openssl::ec::EcKey::generate(
|
||||
&openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::SECP384R1)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
openssl::pkey::PKey::from_ec_key(key).unwrap()
|
||||
}
|
||||
(openssl::pkey::Id::RSA, 2048) => {
|
||||
let key = openssl::rsa::Rsa::generate(2048).unwrap();
|
||||
openssl::pkey::PKey::from_rsa(key).unwrap()
|
||||
}
|
||||
(openssl::pkey::Id::RSA, 3072) => {
|
||||
let key = openssl::rsa::Rsa::generate(3072).unwrap();
|
||||
openssl::pkey::PKey::from_rsa(key).unwrap()
|
||||
}
|
||||
(openssl::pkey::Id::RSA, 4096) => {
|
||||
let key = openssl::rsa::Rsa::generate(4096).unwrap();
|
||||
openssl::pkey::PKey::from_rsa(key).unwrap()
|
||||
}
|
||||
key => {
|
||||
panic!("Unknown key type {key:?}");
|
||||
}
|
||||
};
|
||||
|
||||
if parent.is_none() {
|
||||
// Highest certificate in the chain
|
||||
// Sign it with our CA
|
||||
let (ca_cert, ca_key) = self.cas.get(&sig_alg).unwrap();
|
||||
parent = Some((ca_cert.clone(), ca_key.clone()));
|
||||
}
|
||||
|
||||
let mut cert_builder = openssl::x509::X509::builder().unwrap();
|
||||
cert_builder
|
||||
.set_issuer_name(parent.as_ref().unwrap().0.subject_name())
|
||||
.unwrap();
|
||||
cert_builder
|
||||
.set_not_after(
|
||||
openssl::asn1::Asn1Time::days_from_now(900)
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
)
|
||||
.unwrap();
|
||||
cert_builder.set_not_before(cert.not_before()).unwrap();
|
||||
cert_builder
|
||||
.set_serial_number(cert.serial_number())
|
||||
.unwrap();
|
||||
cert_builder.set_subject_name(cert.subject_name()).unwrap();
|
||||
cert_builder.set_version(cert.version()).unwrap();
|
||||
cert_builder.set_pubkey(new_key.as_ref()).unwrap();
|
||||
let x509_ctx =
|
||||
cert_builder.x509v3_context(Some(parent.as_ref().unwrap().0.as_ref()), None); // TODO issuer
|
||||
let mut new_exts = Vec::new();
|
||||
// rust-openssl cannot get extensions from X509
|
||||
let der = cert.to_der().unwrap();
|
||||
let (_, parsed_cert) = x509_parser::parse_x509_certificate(&der).unwrap();
|
||||
for ext in parsed_cert.extensions() {
|
||||
match ext.parsed_extension() {
|
||||
x509_parser::extensions::ParsedExtension::KeyUsage(v) => {
|
||||
// Worst interface ever
|
||||
let mut x = openssl::x509::extension::KeyUsage::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
if v.digital_signature() {
|
||||
x.digital_signature();
|
||||
}
|
||||
if v.key_cert_sign() {
|
||||
x.key_cert_sign();
|
||||
}
|
||||
if v.crl_sign() {
|
||||
x.crl_sign();
|
||||
}
|
||||
new_exts.push(x.build().unwrap());
|
||||
}
|
||||
x509_parser::extensions::ParsedExtension::ExtendedKeyUsage(v) => {
|
||||
let mut x = openssl::x509::extension::ExtendedKeyUsage::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
if v.server_auth {
|
||||
x.server_auth();
|
||||
}
|
||||
new_exts.push(x.build().unwrap());
|
||||
}
|
||||
x509_parser::extensions::ParsedExtension::BasicConstraints(v) => {
|
||||
let mut x = openssl::x509::extension::BasicConstraints::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
if v.ca {
|
||||
x.ca();
|
||||
}
|
||||
new_exts.push(x.build().unwrap());
|
||||
}
|
||||
x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(_v) => {
|
||||
let mut x = openssl::x509::extension::SubjectKeyIdentifier::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
new_exts.push(x.build(&x509_ctx).unwrap());
|
||||
}
|
||||
x509_parser::extensions::ParsedExtension::AuthorityKeyIdentifier(v) => {
|
||||
let mut x = openssl::x509::extension::AuthorityKeyIdentifier::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
x.issuer(v.authority_cert_issuer.is_some());
|
||||
x.keyid(v.key_identifier.is_some());
|
||||
new_exts.push(x.build(&x509_ctx).unwrap());
|
||||
}
|
||||
x509_parser::extensions::ParsedExtension::SubjectAlternativeName(v) => {
|
||||
let mut x = openssl::x509::extension::SubjectAlternativeName::new();
|
||||
if ext.critical {
|
||||
x.critical();
|
||||
}
|
||||
for name in &v.general_names {
|
||||
match name {
|
||||
x509_parser::extensions::GeneralName::DNSName(n) => {
|
||||
x.dns(n);
|
||||
}
|
||||
other => panic!("Unknown alternative name {other:?}"),
|
||||
}
|
||||
}
|
||||
new_exts.push(x.build(&x509_ctx).unwrap());
|
||||
}
|
||||
_ => { /* ¯\_(ツ)_/¯ */ }
|
||||
}
|
||||
//cert_builder.append_extension(ext).unwrap();
|
||||
}
|
||||
for ext in new_exts {
|
||||
cert_builder.append_extension(ext).unwrap();
|
||||
}
|
||||
|
||||
if let Some((_parent_cert, parent_key)) = &parent {
|
||||
cert_builder.sign(parent_key, sig_hash).unwrap();
|
||||
}
|
||||
|
||||
let new_cert = cert_builder.build();
|
||||
|
||||
new_chain.push(new_cert.clone());
|
||||
parent = Some((new_cert, new_key));
|
||||
}
|
||||
|
||||
let mut cert_file = std::fs::OpenOptions::new()
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(PathBuf::from(&self.certs_dir).join(format!("{servername}.crt")))
|
||||
.unwrap();
|
||||
for cert in new_chain.iter().rev() {
|
||||
//println!(
|
||||
// "New: {}",
|
||||
// String::from_utf8(cert.to_text().unwrap()).unwrap()
|
||||
//);
|
||||
cert_file.write_all(&cert.to_pem().unwrap()).unwrap();
|
||||
}
|
||||
|
||||
let mut ctx_builder =
|
||||
openssl::ssl::SslContext::builder(openssl::ssl::SslMethod::tls_server()).unwrap();
|
||||
let (new_cert, new_key) = parent.unwrap();
|
||||
ctx_builder.set_certificate(new_cert.as_ref()).unwrap();
|
||||
ctx_builder.set_private_key(new_key.as_ref()).unwrap();
|
||||
for cert in new_chain.into_iter().rev().skip(1) {
|
||||
ctx_builder.add_extra_chain_cert(cert).unwrap();
|
||||
}
|
||||
let ctx = ctx_builder.build();
|
||||
|
||||
let mut names: Vec<String> = new_cert
|
||||
.subject_name()
|
||||
.entries()
|
||||
.map(|e| {
|
||||
let s: &str = &e.data().to_string().unwrap();
|
||||
s.to_string()
|
||||
})
|
||||
.collect();
|
||||
if let Some(subject_alt_names) = new_cert.subject_alt_names() {
|
||||
for alt_name in subject_alt_names {
|
||||
let alt_name = alt_name.dnsname().unwrap();
|
||||
names.push(alt_name.to_string());
|
||||
}
|
||||
}
|
||||
Some(self.add_cert(names, ctx))
|
||||
} else {
|
||||
error!("Fetching cert for `{servername}`: no certificate");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn add_cert(
|
||||
&self,
|
||||
names: Vec<String>,
|
||||
ctx: openssl::ssl::SslContext,
|
||||
) -> &'static openssl::ssl::SslContext {
|
||||
let ctx: &'static _ = Box::leak(Box::new(ctx));
|
||||
for name in names {
|
||||
self.by_name.insert(name, ctx);
|
||||
}
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
pub static CERTIFICATES: OnceLock<LyingCertificateRegistry> = OnceLock::new();
|
||||
|
||||
fn write_record(
|
||||
file: &mut std::fs::File,
|
||||
direction: Direction,
|
||||
|
|
@ -135,6 +465,18 @@ impl sslrelay::HandlerCallbacks for Handler {
|
|||
}
|
||||
}
|
||||
|
||||
fn servername_callback(
|
||||
ssl: &mut openssl::ssl::SslRef,
|
||||
_alert: &mut openssl::ssl::SslAlert,
|
||||
) -> Result<(), openssl::ssl::SniError> {
|
||||
if let Some(servername) = ssl.servername(openssl::ssl::NameType::HOST_NAME)
|
||||
&& let Some(ctx) = CERTIFICATES.get().unwrap().fetch(servername)
|
||||
{
|
||||
ssl.set_ssl_context(ctx.as_ref()).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "record")]
|
||||
pub fn make_record(path: &str) {
|
||||
let (mut recorder, sender) = Recorder::new(path);
|
||||
|
|
@ -154,9 +496,8 @@ pub fn make_record(path: &str) {
|
|||
.unwrap_or_else(|| String::from("www.apple.com"))
|
||||
},
|
||||
remote_port: "443".to_string(),
|
||||
tls_config: sslrelay::TLSConfig::FILE {
|
||||
certificate_path: "/dev/shm/exp/certs/prime256v1/all.crt".to_string(),
|
||||
private_key_path: "/dev/shm/exp/certs/prime256v1/all.key".to_string(),
|
||||
tls_config: sslrelay::TLSConfig::CALLBACK {
|
||||
servername_callback,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -216,14 +216,20 @@ pub async fn play(
|
|||
let mut break_next = false;
|
||||
//let mut previous = Vec::new();
|
||||
loop {
|
||||
let Ok(req) =
|
||||
tokio::time::timeout(tokio::time::Duration::from_secs(5), stream.next())
|
||||
.await
|
||||
else {
|
||||
if break_next {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
let req = match tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(5),
|
||||
stream.next(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
println!("Error receiving for `{server_name}`: {e:?}");
|
||||
if break_next {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
let req = req.unwrap();
|
||||
|
|
|
|||
83
src/util.rs
83
src/util.rs
|
|
@ -1,6 +1,8 @@
|
|||
use crate::record::Direction;
|
||||
|
||||
use log::info;
|
||||
#[cfg(feature = "record")]
|
||||
use std::collections::HashMap;
|
||||
use std::iter::Peekable;
|
||||
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||
|
||||
|
|
@ -429,3 +431,84 @@ pub fn init_provider() {
|
|||
_provider = CryptoProvider::install_default(prov);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "record")]
|
||||
pub fn fetch_server_cert_chain(servername: &str) -> Option<Vec<openssl::x509::X509>> {
|
||||
use log::error;
|
||||
let addr = dns_lookup::lookup_host(servername)
|
||||
.inspect_err(|e| error!("Fetching cert for (DNS) `{servername}`: {e:?}"))
|
||||
.ok()?
|
||||
.next()?;
|
||||
let stream = std::net::TcpStream::connect((addr, 443))
|
||||
.inspect_err(|e| error!("Fetching cert (connect) for `{servername}`: {e:?}"))
|
||||
.ok()?;
|
||||
let connector = openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls_client())
|
||||
.unwrap()
|
||||
.build();
|
||||
let mut stream = connector
|
||||
.connect(servername, stream)
|
||||
.inspect_err(|e| error!("Fetching cert (TLS connect) for `{servername}`: {e:?}"))
|
||||
.ok()?;
|
||||
stream
|
||||
.do_handshake()
|
||||
.inspect_err(|e| error!("Fetching cert (handshake) for `{servername}`: {e:?}"))
|
||||
.ok()?;
|
||||
//stream.connect()
|
||||
if let Some(cert_chain) = stream.ssl().peer_cert_chain() {
|
||||
//println!("Chain:");
|
||||
//for cert in cert_chain {
|
||||
// println!("{}", String::from_utf8(cert.to_text().unwrap()).unwrap());
|
||||
//}
|
||||
Some(cert_chain.iter().map(|cert| cert.to_owned()).collect())
|
||||
} else {
|
||||
use log::warn;
|
||||
warn!("no certificate");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum SigAlg {
|
||||
P256,
|
||||
P384,
|
||||
Rsa2048,
|
||||
Rsa3072,
|
||||
Rsa4096,
|
||||
}
|
||||
|
||||
#[cfg(feature = "record")]
|
||||
pub fn load_cas(
|
||||
ca_dir: &str,
|
||||
) -> HashMap<
|
||||
SigAlg,
|
||||
(
|
||||
openssl::x509::X509,
|
||||
openssl::pkey::PKey<openssl::pkey::Private>,
|
||||
),
|
||||
> {
|
||||
let mut cas = HashMap::new();
|
||||
for f in std::fs::read_dir(ca_dir).unwrap() {
|
||||
let f = f.unwrap();
|
||||
if f.file_name().as_encoded_bytes().ends_with(b".crt") {
|
||||
let cert = openssl::x509::X509::from_pem(&std::fs::read(f.path()).unwrap()).unwrap();
|
||||
let pk = cert.public_key().unwrap();
|
||||
let alg = match (pk.id(), pk.bits()) {
|
||||
(openssl::pkey::Id::EC, 256) => SigAlg::P256,
|
||||
(openssl::pkey::Id::EC, 384) => SigAlg::P384,
|
||||
(openssl::pkey::Id::RSA, 2048) => SigAlg::Rsa2048,
|
||||
(openssl::pkey::Id::RSA, 3072) => SigAlg::Rsa3072,
|
||||
(openssl::pkey::Id::RSA, 4096) => SigAlg::Rsa4096,
|
||||
key => {
|
||||
panic!("Unknown key type {key:?}");
|
||||
}
|
||||
};
|
||||
let mut key_path = f.path().to_str().unwrap().to_string();
|
||||
key_path.truncate(key_path.len() - 3);
|
||||
key_path.push_str("key");
|
||||
let key = openssl::pkey::PKey::private_key_from_pem(&std::fs::read(key_path).unwrap())
|
||||
.unwrap();
|
||||
cas.insert(alg, (cert, key));
|
||||
}
|
||||
}
|
||||
cas
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue