improve mkcert and tls

This commit is contained in:
Ezra Barrow 2023-08-22 13:27:33 -05:00
parent 7437a34a55
commit 8f72b1dbb1
No known key found for this signature in database
GPG Key ID: 5EF8BA3CE9180419
7 changed files with 187 additions and 56 deletions

1
Cargo.lock generated
View File

@ -1077,6 +1077,7 @@ dependencies = [
"rustls",
"serde",
"temp-dir",
"time",
"tokio",
"tokio-tun",
"toml",

View File

@ -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 = "*"

View File

@ -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"

View File

@ -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"

View File

@ -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())?;
Ok(())
}
None => {
eprintln!("No tls configuration specified in config.toml");
panic!();
}
}
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(())
}
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)
}

View File

@ -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)?),

View File

@ -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)?;
}