improve mkcert and tls
This commit is contained in:
parent
7437a34a55
commit
8f72b1dbb1
|
@ -1077,6 +1077,7 @@ dependencies = [
|
|||
"rustls",
|
||||
"serde",
|
||||
"temp-dir",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-tun",
|
||||
"toml",
|
||||
|
|
|
@ -23,3 +23,4 @@ tokio = { version = "1.31.0", features = ["full"] }
|
|||
tokio-tun = "0.9.0"
|
||||
toml = "0.7.6"
|
||||
boring = "^2.0.0"
|
||||
time = "*"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[client]
|
||||
endpoint = "alphamethyl.barr0w.net:9092"
|
||||
endpoint = "10.177.1.7:9092"
|
||||
server_name = "alphamethyl.barr0w.net"
|
||||
|
||||
[tls]
|
||||
ca_file = "tunnel_ca.crt"
|
||||
cert_file = "client_tls.crt"
|
||||
key_file = "client_tls.key"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[server]
|
||||
daemonize = true
|
||||
endpoint = "127.0.0.1:9092"
|
||||
daemonize = false
|
||||
endpoint = "10.177.1.7:9092"
|
||||
server_name = "alphamethyl.barr0w.net"
|
||||
user = "nobody"
|
||||
group = "daemon"
|
||||
|
@ -8,6 +8,7 @@ stdout = "/tmp/sleepyserver"
|
|||
stderr = "/tmp/sleepyserver.err"
|
||||
|
||||
[tls]
|
||||
ca_file = "tunnel_ca.crt"
|
||||
cert_file = "server_tls.crt"
|
||||
key_file = "server_tls.key"
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use rcgen::generate_simple_self_signed;
|
||||
use rcgen::{
|
||||
Certificate, CertificateParams, DistinguishedName, ExtendedKeyUsagePurpose, IsCa, KeyPair,
|
||||
KeyUsagePurpose, SanType,
|
||||
};
|
||||
use sleepytunny::config::Configuration;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
|
@ -13,42 +17,112 @@ use std::io::Write;
|
|||
#[command(about = "makes simple self-signed certificates", long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
name: Option<Vec<String>>,
|
||||
client: PathBuf,
|
||||
#[arg(short, long)]
|
||||
config: Option<String>,
|
||||
server: PathBuf,
|
||||
}
|
||||
|
||||
struct Config<'a> {
|
||||
client_sni: &'a String,
|
||||
server_sni: &'a String,
|
||||
client_tls: &'a sleepytunny::config::TLS,
|
||||
server_tls: &'a sleepytunny::config::TLS,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let config = match args.config {
|
||||
Some(c) => Configuration::load_config(&c)?,
|
||||
None => Configuration::load_config("config.toml")?,
|
||||
let client_cfg = Configuration::load_config(args.client)?;
|
||||
let server_cfg = Configuration::load_config(args.server)?;
|
||||
let config = Config {
|
||||
client_sni: client_cfg
|
||||
.client()?
|
||||
.server_name()?
|
||||
.context("client config is missing server_name")?,
|
||||
server_sni: server_cfg
|
||||
.server()?
|
||||
.server_name()?
|
||||
.context("server config is missing server_name")?,
|
||||
client_tls: client_cfg.tls()?,
|
||||
server_tls: server_cfg.tls()?,
|
||||
};
|
||||
match &config.tls {
|
||||
Some(t) => {
|
||||
let server_names: Option<String> = match config.server() {
|
||||
Ok(s) => Some(
|
||||
s.server_name()?
|
||||
.cloned()
|
||||
.unwrap_or(String::from("localhost")),
|
||||
),
|
||||
Err(_) => None,
|
||||
};
|
||||
// Lets vectorize it.
|
||||
let name = match server_names {
|
||||
Some(s) => vec![s],
|
||||
None => args.name.unwrap_or(vec![String::from("localhost")]),
|
||||
};
|
||||
let mut cert_file = File::create(&t.cert_file)?;
|
||||
let mut key_file = File::create(&t.key_file)?;
|
||||
let cert = generate_simple_self_signed(name)?;
|
||||
cert_file.write_all(cert.serialize_pem()?.as_bytes())?;
|
||||
key_file.write_all(cert.serialize_private_key_pem().as_bytes())?;
|
||||
|
||||
println!("Generating certificate authority...");
|
||||
let ca_cert = create_ca_cert()?;
|
||||
println!("Generating client certificate...");
|
||||
let client_cert = create_client_cert(&config)?;
|
||||
println!("Generating server certificate...");
|
||||
let server_cert = create_server_cert(&config)?;
|
||||
|
||||
println!("Serializing and signing...");
|
||||
let ca_pem = ca_cert.serialize_pem()?;
|
||||
let client_pem = client_cert.serialize_pem_with_signer(&ca_cert)?;
|
||||
let client_key = client_cert.serialize_private_key_pem();
|
||||
let server_pem = server_cert.serialize_pem_with_signer(&ca_cert)?;
|
||||
let server_key = server_cert.serialize_private_key_pem();
|
||||
|
||||
println!("Writing client certs...");
|
||||
fs::write(&config.client_tls.ca_file, &ca_pem)?;
|
||||
fs::write(&config.client_tls.cert_file, client_pem)?;
|
||||
fs::write(&config.client_tls.key_file, client_key)?;
|
||||
|
||||
println!("Writing server certs...");
|
||||
fs::write(&config.server_tls.ca_file, &ca_pem)?;
|
||||
fs::write(&config.server_tls.cert_file, server_pem)?;
|
||||
fs::write(&config.server_tls.key_file, server_key)?;
|
||||
|
||||
println!("Success");
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tls configuration specified in config.toml");
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_ca_cert() -> anyhow::Result<Certificate> {
|
||||
let key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
|
||||
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(rcgen::DnType::OrganizationName, "sleepytunny");
|
||||
dn.push(rcgen::DnType::CommonName, "rootca");
|
||||
|
||||
let mut params = CertificateParams::default();
|
||||
params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
|
||||
params.distinguished_name = dn;
|
||||
params.key_pair = Some(key_pair);
|
||||
params.not_before = time::OffsetDateTime::now_utc();
|
||||
params.not_after = time::OffsetDateTime::now_utc() + time::Duration::days(365);
|
||||
params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
|
||||
|
||||
let cert = Certificate::from_params(params)?;
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
fn create_client_cert(_config: &Config) -> anyhow::Result<Certificate> {
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(rcgen::DnType::OrganizationName, "sleepytunny");
|
||||
dn.push(rcgen::DnType::CommonName, "sleepytunny client");
|
||||
|
||||
let mut params = CertificateParams::default();
|
||||
params.distinguished_name = dn;
|
||||
params.not_before = time::OffsetDateTime::now_utc();
|
||||
params.not_after = time::OffsetDateTime::now_utc() + time::Duration::days(365);
|
||||
params.is_ca = IsCa::ExplicitNoCa;
|
||||
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
|
||||
let cert = Certificate::from_params(params)?;
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
fn create_server_cert(config: &Config) -> anyhow::Result<Certificate> {
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(rcgen::DnType::OrganizationName, "sleepytunny");
|
||||
dn.push(rcgen::DnType::CommonName, config.server_sni);
|
||||
|
||||
let mut params = CertificateParams::default();
|
||||
params.distinguished_name = dn;
|
||||
params.not_before = time::OffsetDateTime::now_utc();
|
||||
params.not_after = time::OffsetDateTime::now_utc() + time::Duration::days(365);
|
||||
params.subject_alt_names = vec![
|
||||
SanType::DnsName(config.server_sni.to_owned()),
|
||||
SanType::DnsName(config.client_sni.to_owned()),
|
||||
];
|
||||
params.is_ca = IsCa::ExplicitNoCa;
|
||||
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
|
||||
let cert = Certificate::from_params(params)?;
|
||||
Ok(cert)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use anyhow::Context;
|
||||
use boring::ssl::{SslContext, SslFiletype, SslMethod};
|
||||
use boring::pkey::PKey;
|
||||
use boring::ssl::{SslContext, SslMethod};
|
||||
use boring::x509::store::X509StoreBuilder;
|
||||
use boring::x509::X509;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
@ -115,19 +118,21 @@ impl Default for Client {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TLS {
|
||||
pub ca_file: String,
|
||||
pub cert_file: String,
|
||||
pub key_file: String,
|
||||
}
|
||||
impl TLS {
|
||||
/// Loads in the key and cert from files specified in config.
|
||||
pub fn load(&self) -> anyhow::Result<KeyPair> {
|
||||
KeyPair::new(&self.cert_file, &self.key_file)
|
||||
KeyPair::new(&self.ca_file, &self.cert_file, &self.key_file)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TLS {
|
||||
fn default() -> Self {
|
||||
TLS {
|
||||
ca_file: String::from("/etc/sleepytunny/ca.crt"),
|
||||
cert_file: String::from("/etc/sleepytunny/tls.crt"),
|
||||
key_file: String::from("/etc/sleepytunny/tls.key"),
|
||||
}
|
||||
|
@ -139,17 +144,37 @@ pub struct KeyPair {
|
|||
ssl_ctx: SslContext,
|
||||
}
|
||||
impl KeyPair {
|
||||
pub fn new(c: impl AsRef<Path>, k: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
pub fn new(
|
||||
ca: impl AsRef<Path>,
|
||||
c: impl AsRef<Path>,
|
||||
k: impl AsRef<Path>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut ctx = SslContext::builder(SslMethod::tls())?;
|
||||
ctx.set_default_verify_paths()?;
|
||||
ctx.set_certificate_chain_file(c.as_ref())?;
|
||||
ctx.set_private_key_file(k.as_ref(), SslFiletype::PEM)?;
|
||||
ctx.set_servername_callback(|sslref, sslalert| {
|
||||
let servername = sslref.servername(boring::ssl::NameType::HOST_NAME);
|
||||
eprintln!("SNI callback");
|
||||
eprintln!(" servername: {servername:?}");
|
||||
eprintln!(" alert: {sslalert:?}");
|
||||
Ok(())
|
||||
});
|
||||
let ca_cert = X509::from_pem(&fs::read(ca).context("failed to read ca certificate")?)?;
|
||||
let my_cert = X509::from_pem(&fs::read(c).context("failed to read tls certificate")?)?;
|
||||
let my_key = PKey::private_key_from_pem(&fs::read(k).context("failed to read tls key")?)?;
|
||||
let store = {
|
||||
let mut store = X509StoreBuilder::new()?;
|
||||
store.add_cert(ca_cert.clone())?;
|
||||
store.build()
|
||||
};
|
||||
ctx.set_verify_cert_store(store)?;
|
||||
ctx.set_certificate(&my_cert)?;
|
||||
ctx.add_extra_chain_cert(ca_cert)?;
|
||||
ctx.set_private_key(&my_key)?;
|
||||
Ok(Self {
|
||||
ssl_ctx: ctx.build(),
|
||||
})
|
||||
}
|
||||
pub fn ssl_ctx(&self) -> anyhow::Result<SslContext> {
|
||||
Ok(self.ssl_ctx.clone())
|
||||
pub fn into_ssl_ctx(self) -> anyhow::Result<SslContext> {
|
||||
Ok(self.ssl_ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +204,7 @@ pub struct Configuration {
|
|||
}
|
||||
impl Configuration {
|
||||
/// Loads configuration file f and returns a struct of all parsed values
|
||||
pub fn load_config(f: &str) -> anyhow::Result<Self> {
|
||||
pub fn load_config(f: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let config = fs::read_to_string(f);
|
||||
match config {
|
||||
Ok(c) => Ok(toml::from_str(&c)?),
|
||||
|
|
46
src/quic.rs
46
src/quic.rs
|
@ -99,11 +99,12 @@ sock.send_to(&tx_buffer[..tx_len], &send_info.to)?;
|
|||
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use boring::x509::X509;
|
||||
use ring::rand::*;
|
||||
use tokio::{net::UdpSocket, time::Instant};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
fn configure_server(tls: crate::config::KeyPair) -> anyhow::Result<quiche::Config> {
|
||||
let mut config = quiche::Config::with_boring_ssl_ctx(quiche::PROTOCOL_VERSION, tls.ssl_ctx()?)?;
|
||||
let mut config = quiche::Config::with_boring_ssl_ctx(quiche::PROTOCOL_VERSION, tls.into_ssl_ctx()?)?;
|
||||
config.set_application_protos(&[b"sleepytunny"])?; //change this to h3 eventually
|
||||
config.set_max_idle_timeout(5000);
|
||||
config.set_max_recv_udp_payload_size(1350);
|
||||
|
@ -115,12 +116,12 @@ fn configure_server(tls: crate::config::KeyPair) -> anyhow::Result<quiche::Confi
|
|||
config.set_initial_max_stream_data_bidi_remote(1_000_000);
|
||||
config.set_initial_max_stream_data_uni(1_000_000);
|
||||
config.set_disable_active_migration(true);
|
||||
config.verify_peer(false);
|
||||
config.verify_peer(true);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
fn configure_client(tls: crate::config::KeyPair) -> anyhow::Result<quiche::Config> {
|
||||
let mut config = quiche::Config::with_boring_ssl_ctx(quiche::PROTOCOL_VERSION, tls.ssl_ctx()?)?;
|
||||
let mut config = quiche::Config::with_boring_ssl_ctx(quiche::PROTOCOL_VERSION, tls.into_ssl_ctx()?)?;
|
||||
config.set_application_protos(&[b"sleepytunny"])?; //change this to h3 eventually
|
||||
config.set_max_idle_timeout(5000);
|
||||
config.set_max_recv_udp_payload_size(1350);
|
||||
|
@ -132,7 +133,7 @@ fn configure_client(tls: crate::config::KeyPair) -> anyhow::Result<quiche::Confi
|
|||
config.set_initial_max_stream_data_bidi_remote(1_000_000);
|
||||
config.set_initial_max_stream_data_uni(1_000_000);
|
||||
config.set_disable_active_migration(true);
|
||||
config.verify_peer(false);
|
||||
config.verify_peer(true);
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
|
@ -170,19 +171,32 @@ struct PartialSend {
|
|||
}
|
||||
pub struct ConnectedClient {
|
||||
pub conn: quiche::Connection,
|
||||
pub ka_last_tx: Instant,
|
||||
pub ka_last_rx: Instant,
|
||||
partial_sends: HashMap<u64, PartialSend>,
|
||||
debug_established: bool,
|
||||
}
|
||||
impl ConnectedClient {
|
||||
fn new(conn: quiche::Connection) -> Self {
|
||||
Self {
|
||||
conn,
|
||||
partial_sends: HashMap::new(),
|
||||
ka_last_tx: Instant::now(),
|
||||
ka_last_rx: Instant::now(),
|
||||
debug_established: false,
|
||||
}
|
||||
}
|
||||
fn debug_established(&mut self) -> anyhow::Result<()> {
|
||||
if !self.debug_established && self.conn.is_established() {
|
||||
self.debug_established = true;
|
||||
eprintln!("connection established! debug info:");
|
||||
eprintln!(" cert chain:");
|
||||
let chain = self.conn.peer_cert_chain().context("no peer cert chain")?;
|
||||
for (i, cert) in chain.into_iter().enumerate().rev() {
|
||||
eprintln!(" cert {i}:");
|
||||
let cert = X509::from_der(cert).context("failed to deserialize der")?;
|
||||
let subject_name = cert.subject_name().entries().map(|entry| entry.data().as_utf8().unwrap().to_string()).collect::<Vec<_>>().join("///");
|
||||
eprintln!(" subject name: {subject_name}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn handle_writable(&mut self, stream_id: u64) {
|
||||
let conn = &mut self.conn;
|
||||
// eprintln!("{} stream {} is writable", conn.trace_id(), stream_id);
|
||||
|
@ -321,6 +335,7 @@ pub async fn server_loop(
|
|||
None => client_map.get_mut(&conn_id).unwrap(),
|
||||
}
|
||||
};
|
||||
client.debug_established()?;
|
||||
let recv_info = quiche::RecvInfo {
|
||||
to: local_addr,
|
||||
from,
|
||||
|
@ -413,6 +428,7 @@ pub async fn client_loop(
|
|||
.context("initial send failed")?;
|
||||
let written = socket.send_to(&tx_buffer[..write], send_info.to).await?;
|
||||
eprintln!("wrote {written} bytes of {write} byte initiation");
|
||||
let mut debug_established = false;
|
||||
loop {
|
||||
'read: loop {
|
||||
let (len, from) = match socket.try_recv_from(&mut rx_buffer) {
|
||||
|
@ -442,6 +458,18 @@ pub async fn client_loop(
|
|||
eprintln!("connection closed");
|
||||
return Ok(());
|
||||
}
|
||||
if conn.is_established() && !debug_established {
|
||||
debug_established = true;
|
||||
eprintln!("connection established! debug info:");
|
||||
eprintln!(" cert chain:");
|
||||
let chain = conn.peer_cert_chain().context("no peer cert chain")?;
|
||||
for (i, cert) in chain.into_iter().enumerate().rev() {
|
||||
eprintln!(" cert {i}:");
|
||||
let cert = X509::from_der(cert).context("failed to deserialize der")?;
|
||||
let subject_name = cert.subject_name().entries().map(|entry| entry.data().as_utf8().unwrap().to_string()).collect::<Vec<_>>().join("///");
|
||||
eprintln!(" subject name: {}", subject_name);
|
||||
}
|
||||
}
|
||||
if conn.is_established() {
|
||||
f(&mut conn)?;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue