parazyd
ee62ec7bd2
net/tls: Implement server certificate verification.
...
The system works such that the server creates a new TLS certificate
from an ed25519 keypair. The public key is also encoded with base32
and placed into the altName of the certificate so it can easily be
used with other things.
The server certificate verification will parse the certificate, then
look for the altName extension, attempt to parse the public key from
there, make sure that it is the same as the actual certificate pubkey,
and finally it will verify the certificate signature.
We also remove "pem" dependencies and use binary DER encoding where
applicable.
This is a breaking change for existing deployments.
diff --git a/Cargo.lock b/Cargo.lock
index ff543ddedd..893bfa2380 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -177,6 +177,45 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index "
checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c"
+[[package]]
+name = "asn1-rs"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror",
+ "time 0.3.22",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "async-attributes"
version = "1.1.2"
@@ -1332,6 +1371,7 @@ dependencies = [
"wasmer",
"wasmer-compiler-singlepass",
"wasmer-middlewares",
+ "x509-parser",
]
[[package]]
@@ -1735,6 +1775,20 @@ dependencies = [
"url",
]
+[[package]]
+name = "der-parser"
+version = "8.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
[[package]]
name = "derivative"
version = "2.2.0"
@@ -1829,6 +1883,17 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "displaydoc"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
[[package]]
name = "dlib"
version = "0.5.2"
@@ -3372,6 +3437,15 @@ dependencies = [
"cc",
]
+[[package]]
+name = "oid-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff"
+dependencies = [
+ "asn1-rs",
+]
+
[[package]]
name = "once_cell"
version = "1.18.0"
@@ -4097,6 +4171,15 @@ dependencies = [
"semver 0.11.0",
]
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "rustix"
version = "0.37.20"
@@ -4734,6 +4817,18 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "unicode-xid",
+]
+
[[package]]
name = "tabbycat"
version = "0.1.2"
@@ -5163,6 +5258,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index "
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
[[package]]
name = "unicode_categories"
version = "0.1.1"
@@ -5877,6 +5978,24 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "x509-parser"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index "
+checksum = "bab0c2f54ae1d92f4fcb99c0b7ccf0b1e3451cbd395e5f115ccbdbcb18d4f634"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "ring",
+ "rusticata-macros",
+ "thiserror",
+ "time 0.3.22",
+]
+
[[package]]
name = "xsalsa20poly1305"
version = "0.9.1"
diff --git a/Cargo.toml b/Cargo.toml
index 1aaf3c1641..4fb39d2603 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -77,9 +77,10 @@ ipnet = {version = "2.7.2", optional = true}
socket2 = {version = "0.5.3", optional = true, features = ["all"]}
# TLS cert utilities
-ed25519-compact = {version = "2.0.4", features = ["pem"], optional = true}
-rcgen = {version = "0.10.0", features = ["pem"], optional = true}
+ed25519-compact = {version = "2.0.4", optional = true}
+rcgen = {version = "0.10.0", optional = true}
rustls-pemfile = {version = "1.0.2", optional = true}
+x509-parser = {version = "0.15.0", features = ["validate", "verify"], optional = true}
# Encoding
bs58 = {version = "0.5.0", optional = true}
@@ -213,6 +214,7 @@ net = [
"rand",
"rcgen",
"rustls-pemfile",
+ "x509-parser",
"serde",
"serde_json",
"socket2",
diff --git a/src/net/transport/upgrade_tls.rs b/src/net/transport/upgrade_tls.rs
index 570d5d3b01..8674c25938 100644
--- a/src/net/transport/upgrade_tls.rs
+++ b/src/net/transport/upgrade_tls.rs
@@ -31,9 +31,16 @@ use futures_rustls::{
},
TlsAcceptor, TlsConnector, TlsStream,
};
+use log::error;
use rustls_pemfile::pkcs8_private_keys;
+use x509_parser::{
+ extensions::{GeneralName, ParsedExtension},
+ parse_x509_certificate,
+ prelude::FromDer,
+ x509::SubjectPublicKeyInfo,
+};
-use crate::Result;
+use crate::{util::encoding::base32, Result};
const CIPHER_SUITE: &str = "TLS13_CHACHA20_POLY1305_SHA256";
@@ -53,14 +60,82 @@ struct ServerCertificateVerifier;
impl ServerCertVerifier for ServerCertificateVerifier {
fn verify_server_cert(
&self,
- _end_entity: &Certificate,
+ end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &ServerName,
_scrs: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: SystemTime,
) -> std::result::Result<ServerCertVerified, rustls::Error> {
- // TODO: upsycle
+ // Parse the actual end_entity certificate
+ let Ok((_, cert)) = parse_x509_certificate(&end_entity.0) else {
+ error!(target: "net::tls", "Failed parsing server TLS certificate");
+ return Err(rustls::CertificateError::BadEncoding.into())
+ };
+
+ // We keep a public key in the altName, so we need to grab it.
+ // We compare that the actual public key of the certificate is
+ // the same as that one, and we then verify the signature of the
+ // provided certificate.
+ #[rustfmt::skip]
+ let oid = x509_parser::oid_registry::asn1_rs::oid!(2.5.29.17);
+ let Ok(Some(extension)) = cert.get_extension_unique(&oid) else {
+ error!(target: "net::tls", "Could not find OID extension for subjectAltName");
+ return Err(rustls::CertificateError::BadSignature.into())
+ };
+
+ // Parse the actual extension
+ // (ノಠ益ಠ)ノ彡┻━┻
+ let pubkey_bytes = match extension.parsed_extension() {
+ ParsedExtension::SubjectAlternativeName(altname) => {
+ if altname.general_names.len() != 1 {
+ return Err(rustls::CertificateError::BadEncoding.into())
+ }
+
+ match altname.general_names[0] {
+ GeneralName::DNSName(a) => base32::decode(a),
+ _ => return Err(rustls::CertificateError::BadEncoding.into()),
+ }
+ }
+ _ => return Err(rustls::CertificateError::BadEncoding.into()),
+ };
+ let Some(pubkey_bytes) = pubkey_bytes else {
+ error!(target: "net::tls", "Could not decode server pubkey from altName");
+ return Err(rustls::CertificateError::BadSignature.into())
+ };
+ if pubkey_bytes.len() != 32 {
+ error!(target: "net::tls", "Could not decode server pubkey from altName");
+ return Err(rustls::CertificateError::BadSignature.into())
+ }
+ let pubkey_der = ed25519_compact::PublicKey::new(pubkey_bytes.try_into().unwrap()).to_der();
+ let Ok((_, parsed_pubkey)) = SubjectPublicKeyInfo::from_der(&pubkey_der) else {
+ error!(target: "net::tls", "Could not decode server pubkey from altName");
+ return Err(rustls::CertificateError::BadSignature.into())
+ };
+
+ let Ok(parsed_name_pubkey) = parsed_pubkey.parsed() else {
+ error!(target: "net::tls", "Could not parse server altName pubkey");
+ return Err(rustls::CertificateError::BadEncoding.into())
+ };
+
+ let Ok(parsed_cert_pubkey) = cert.public_key().parsed() else {
+ error!(target: "net::tls", "Could not parse server certificate pubkey");
+ return Err(rustls::CertificateError::BadEncoding.into())
+ };
+
+ if parsed_name_pubkey != parsed_cert_pubkey {
+ error!(target: "net::tls", "Server altName pubkey does not match certificate key");
+ return Err(rustls::CertificateError::BadSignature.into())
+ }
+
+ // Finally verify the signature. By passing `None`, it should use
+ // the certificate pubkey, but we also verified that it matches
+ // the one in altNames above.
+ if let Err(e) = cert.verify_signature(None) {
+ error!(target: "net::tls", "Failed verifying server certificate signature: {}", e);
+ return Err(rustls::CertificateError::BadSignature.into())
+ }
+
Ok(ServerCertVerified::assertion())
}
}
@@ -92,11 +167,13 @@ pub struct TlsUpgrade {
impl TlsUpgrade {
pub fn new() -> Self {
// On each instantiation, generate a new keypair and certificate.
- let keypair_pem = ed25519_compact::KeyPair::generate().to_pem();
+ let keypair = ed25519_compact::KeyPair::generate();
+ let keypair_pem = keypair.to_pem();
let secret_key = pkcs8_private_keys(&mut keypair_pem.as_bytes()).unwrap();
let secret_key = rustls::PrivateKey(secret_key[0].clone());
- let altnames = vec![String::from("dark.fi")];
+ let altname = base32::encode(false, keypair.pk.as_slice()).to_ascii_lowercase();
+ let altnames = vec![altname];
let mut cert_params = rcgen::CertificateParams::new(altnames);
cert_params.alg = &rcgen::PKCS_ED25519;
cert_params.key_pair = Some(rcgen::KeyPair::from_pem(&keypair_pem).unwrap());
2023-06-29 13:06:54 +02:00