Support connecting to the uplink over SPKI-pinned TLS
All checks were successful
CI / check (push) Successful in 5m26s
All checks were successful
CI / check (push) Successful in 5m26s
This commit is contained in:
parent
3b76454586
commit
cb552c04b0
6 changed files with 242 additions and 7 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
|
@ -290,9 +290,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid",
|
"const-oid",
|
||||||
|
"der_derive",
|
||||||
|
"flagset",
|
||||||
|
"pem-rfc7468",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "der_derive"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -369,6 +383,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ureq",
|
"ureq",
|
||||||
|
"x509-cert",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -573,6 +588,12 @@ version = "0.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flagset"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
@ -1068,6 +1089,15 @@ dependencies = [
|
||||||
"hmac",
|
"hmac",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem-rfc7468"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -1687,6 +1717,27 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tls_codec"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
|
||||||
|
dependencies = [
|
||||||
|
"tls_codec_derive",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tls_codec_derive"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.3"
|
version = "1.52.3"
|
||||||
|
|
@ -2150,6 +2201,18 @@ version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x509-cert"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
|
||||||
|
dependencies = [
|
||||||
|
"const-oid",
|
||||||
|
"der",
|
||||||
|
"spki",
|
||||||
|
"tls_codec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -2219,6 +2282,20 @@ name = "zeroize"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
tokio-rustls = "0.26.4"
|
tokio-rustls = "0.26.4"
|
||||||
rustls-pemfile = "2.2.0"
|
rustls-pemfile = "2.2.0"
|
||||||
|
x509-cert = "0.2"
|
||||||
tonic = { version = "0.12", features = ["tls"] }
|
tonic = { version = "0.12", features = ["tls"] }
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
axum-server = { version = "0.7", features = ["tls-rustls"] }
|
axum-server = { version = "0.7", features = ["tls-rustls"] }
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,13 @@ pub struct Uplink {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
// Connect to the uplink over TLS, authenticated by pinning the server's SPKI
|
||||||
|
// fingerprint (base64 SHA256 of its SubjectPublicKeyInfo) rather than a CA — the
|
||||||
|
// link cert is typically self-signed. Off by default (plaintext, e.g. loopback).
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub spki_fingerprint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
58
src/link.rs
58
src/link.rs
|
|
@ -1,15 +1,57 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, ReadBuf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
|
use tokio_rustls::client::TlsStream;
|
||||||
|
use tokio_rustls::rustls::pki_types::ServerName;
|
||||||
|
use tokio_rustls::TlsConnector;
|
||||||
|
|
||||||
use crate::engine::db::Db;
|
use crate::engine::db::Db;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::proto::{NetAction, Protocol};
|
use crate::proto::{NetAction, Protocol};
|
||||||
|
|
||||||
|
// The uplink socket, plaintext or TLS. Both inner types are Unpin, so projecting
|
||||||
|
// the pin is a plain re-pin of the inner stream.
|
||||||
|
enum UplinkStream {
|
||||||
|
Plain(TcpStream),
|
||||||
|
Tls(Box<TlsStream<TcpStream>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for UplinkStream {
|
||||||
|
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
UplinkStream::Plain(s) => Pin::new(s).poll_read(cx, buf),
|
||||||
|
UplinkStream::Tls(s) => Pin::new(s.as_mut()).poll_read(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for UplinkStream {
|
||||||
|
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<std::io::Result<usize>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
UplinkStream::Plain(s) => Pin::new(s).poll_write(cx, buf),
|
||||||
|
UplinkStream::Tls(s) => Pin::new(s.as_mut()).poll_write(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
UplinkStream::Plain(s) => Pin::new(s).poll_flush(cx),
|
||||||
|
UplinkStream::Tls(s) => Pin::new(s.as_mut()).poll_flush(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
UplinkStream::Plain(s) => Pin::new(s).poll_shutdown(cx),
|
||||||
|
UplinkStream::Tls(s) => Pin::new(s.as_mut()).poll_shutdown(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cap on one uplink line (well above IRC's 512 + generous room for message tags),
|
// Cap on one uplink line (well above IRC's 512 + generous room for message tags),
|
||||||
// so a misbehaving uplink can't grow an unbounded read buffer.
|
// so a misbehaving uplink can't grow an unbounded read buffer.
|
||||||
const MAX_UPLINK_LINE: usize = 16 * 1024;
|
const MAX_UPLINK_LINE: usize = 16 * 1024;
|
||||||
|
|
@ -100,13 +142,19 @@ fn redact(line: &str) -> Cow<'_, str> {
|
||||||
// The engine is shared with the gossip layer, so it is locked per operation and
|
// The engine is shared with the gossip layer, so it is locked per operation and
|
||||||
// never held across the registration key-stretching await.
|
// never held across the registration key-stretching await.
|
||||||
#[allow(clippy::too_many_arguments)] // the link driver legitimately wires up many collaborators
|
#[allow(clippy::too_many_arguments)] // the link driver legitimately wires up many collaborators
|
||||||
pub async fn run(mut proto: Box<dyn Protocol>, engine: Arc<Mutex<Engine>>, addr: &str, mut irc_rx: mpsc::UnboundedReceiver<NetAction>, irc_tx: mpsc::UnboundedSender<NetAction>, email: Option<crate::config::Email>, keycard: Option<crate::config::Keycard>, dict_server: Option<String>) -> Result<()> {
|
pub async fn run(mut proto: Box<dyn Protocol>, engine: Arc<Mutex<Engine>>, addr: &str, tls: Option<(TlsConnector, ServerName<'static>)>, mut irc_rx: mpsc::UnboundedReceiver<NetAction>, irc_tx: mpsc::UnboundedSender<NetAction>, email: Option<crate::config::Email>, keycard: Option<crate::config::Keycard>, dict_server: Option<String>) -> Result<()> {
|
||||||
let stream = TcpStream::connect(addr).await?;
|
let tcp = TcpStream::connect(addr).await?;
|
||||||
// Disable Nagle: service replies are small multi-line bursts, and without this
|
// Disable Nagle: service replies are small multi-line bursts, and without this
|
||||||
// the last segment of a reply is held ~40ms waiting on a delayed ACK, so a
|
// the last segment of a reply is held ~40ms waiting on a delayed ACK, so a
|
||||||
// HELP visibly lags before it lands. Every ircd sets this on every socket.
|
// HELP visibly lags before it lands. Every ircd sets this on every socket.
|
||||||
stream.set_nodelay(true)?;
|
tcp.set_nodelay(true)?;
|
||||||
let (read, mut write) = stream.into_split();
|
// Wrap in TLS (SPKI-pinned) when configured; otherwise stay plaintext.
|
||||||
|
let stream = match tls {
|
||||||
|
Some((connector, name)) => UplinkStream::Tls(Box::new(connector.connect(name, tcp).await?)),
|
||||||
|
None => UplinkStream::Plain(tcp),
|
||||||
|
};
|
||||||
|
let (read, write) = tokio::io::split(stream);
|
||||||
|
let mut write = write;
|
||||||
let mut reader = BufReader::new(read);
|
let mut reader = BufReader::new(read);
|
||||||
|
|
||||||
for line in proto.handshake() {
|
for line in proto.handshake() {
|
||||||
|
|
|
||||||
12
src/main.rs
12
src/main.rs
|
|
@ -11,6 +11,7 @@ mod health;
|
||||||
mod jsonrpc;
|
mod jsonrpc;
|
||||||
mod keycard;
|
mod keycard;
|
||||||
mod link;
|
mod link;
|
||||||
|
mod uplink_tls;
|
||||||
mod migrate;
|
mod migrate;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
@ -377,13 +378,20 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = format!("{}:{}", cfg.uplink.host, cfg.uplink.port);
|
let addr = format!("{}:{}", cfg.uplink.host, cfg.uplink.port);
|
||||||
tracing::info!(server = %cfg.server.name, %addr, "linking to uplink");
|
let uplink_tls = match uplink_tls::connector(&cfg.uplink) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(%e, "uplink TLS misconfigured");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tracing::info!(server = %cfg.server.name, %addr, tls = uplink_tls.is_some(), "linking to uplink");
|
||||||
// Run until the uplink loop ends or the process is asked to stop. Every
|
// Run until the uplink loop ends or the process is asked to stop. Every
|
||||||
// committed change is already fsync'd, so a clean stop loses nothing; this
|
// committed change is already fsync'd, so a clean stop loses nothing; this
|
||||||
// just lets systemd stop us without waiting out the kill timeout.
|
// just lets systemd stop us without waiting out the kill timeout.
|
||||||
let shutdown_engine = engine.clone();
|
let shutdown_engine = engine.clone();
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = link::run(proto, engine, &addr, irc_rx, irc_tx, cfg.email.clone(), cfg.keycard.clone(), cfg.dictserv.as_ref().map(|d| d.server.clone())) => res,
|
res = link::run(proto, engine, &addr, uplink_tls, irc_rx, irc_tx, cfg.email.clone(), cfg.keycard.clone(), cfg.dictserv.as_ref().map(|d| d.server.clone())) => res,
|
||||||
_ = shutdown_signal() => {
|
_ = shutdown_signal() => {
|
||||||
// Flush stat counters + the incident ring so a clean stop/restart keeps
|
// Flush stat counters + the incident ring so a clean stop/restart keeps
|
||||||
// StatServ history and OperServ LOGSEARCH.
|
// StatServ history and OperServ LOGSEARCH.
|
||||||
|
|
|
||||||
94
src/uplink_tls.rs
Normal file
94
src/uplink_tls.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
//! Optional TLS for the ircd uplink (#370). The link cert is typically self-signed,
|
||||||
|
//! so instead of a CA we authenticate the server by pinning its SPKIFP — the base64
|
||||||
|
//! SHA256 of its SubjectPublicKeyInfo (which, unlike a whole-cert fingerprint,
|
||||||
|
//! survives a certificate renewal that keeps the same key). The normal PASS
|
||||||
|
//! handshake still runs inside the TLS channel.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use base64::Engine;
|
||||||
|
use tokio_rustls::rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||||
|
use tokio_rustls::rustls::crypto::{self, CryptoProvider};
|
||||||
|
use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
|
use tokio_rustls::rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme};
|
||||||
|
use tokio_rustls::TlsConnector;
|
||||||
|
|
||||||
|
use crate::config::Uplink;
|
||||||
|
|
||||||
|
/// The SPKIFP of a certificate: base64(SHA256(SubjectPublicKeyInfo DER)).
|
||||||
|
pub fn spki_fingerprint(cert: &CertificateDer) -> Result<String> {
|
||||||
|
use x509_cert::der::{Decode, Encode};
|
||||||
|
let parsed = x509_cert::Certificate::from_der(cert.as_ref())?;
|
||||||
|
let spki = parsed.tbs_certificate.subject_public_key_info.to_der()?;
|
||||||
|
let digest = <sha2::Sha256 as sha2::Digest>::digest(&spki);
|
||||||
|
Ok(base64::engine::general_purpose::STANDARD.encode(digest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept the server iff its SPKIFP matches the pinned value; the handshake
|
||||||
|
// signature is still verified against that key by the crypto provider, so only the
|
||||||
|
// holder of the pinned key can complete the connection.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SpkiPin {
|
||||||
|
expected: String,
|
||||||
|
provider: Arc<CryptoProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerCertVerifier for SpkiPin {
|
||||||
|
fn verify_server_cert(&self, end_entity: &CertificateDer, _intermediates: &[CertificateDer], _server_name: &ServerName, _ocsp: &[u8], _now: UnixTime) -> Result<ServerCertVerified, Error> {
|
||||||
|
let got = spki_fingerprint(end_entity).map_err(|e| Error::General(format!("uplink cert unreadable: {e}")))?;
|
||||||
|
if got == self.expected {
|
||||||
|
Ok(ServerCertVerified::assertion())
|
||||||
|
} else {
|
||||||
|
Err(Error::General("uplink SPKI fingerprint does not match the pinned value".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer, dss: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
|
||||||
|
crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer, dss: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
|
||||||
|
crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||||
|
self.provider.signature_verification_algorithms.supported_schemes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A TLS connector + server name for the uplink, or `None` when TLS is off.
|
||||||
|
pub fn connector(uplink: &Uplink) -> Result<Option<(TlsConnector, ServerName<'static>)>> {
|
||||||
|
if !uplink.tls {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if uplink.spki_fingerprint.is_empty() {
|
||||||
|
return Err(anyhow!("uplink.tls is set but uplink.spki_fingerprint is empty"));
|
||||||
|
}
|
||||||
|
// Install a process-default provider if nothing else did yet (idempotent).
|
||||||
|
let _ = crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
let provider = Arc::new(crypto::aws_lc_rs::default_provider());
|
||||||
|
let verifier = Arc::new(SpkiPin { expected: uplink.spki_fingerprint.clone(), provider });
|
||||||
|
let config = ClientConfig::builder()
|
||||||
|
.dangerous()
|
||||||
|
.with_custom_certificate_verifier(verifier)
|
||||||
|
.with_no_client_auth();
|
||||||
|
let name = ServerName::try_from(uplink.host.clone())?;
|
||||||
|
Ok(Some((TlsConnector::from(Arc::new(config)), name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// The SPKIFP matches `openssl x509 -pubkey | openssl pkey -pubin -outform DER |
|
||||||
|
// openssl dgst -sha256 -binary | base64` for this fixed self-signed cert.
|
||||||
|
#[test]
|
||||||
|
fn spki_fingerprint_matches_openssl() {
|
||||||
|
let der = base64::engine::general_purpose::STANDARD.decode(
|
||||||
|
"MIIDCTCCAfGgAwIBAgIUJf3uwnHdwpCANK0wddShifEEProwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJZWNoby10ZXN0MB4XDTI2MDcyMTEzNDIxNFoXDTI2MDcyMzEzNDIxNFowFDESMBAGA1UEAwwJZWNoby10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu/L8m2F/7ELMezgktDchrlVGh6VQpT+uatXG0IvVPOS9BNm60xLAaijEvv7oI1paWCBM4EKpRPhZH5Of5xyBNtvfpyUdLKKeS5NiRNrGQJwXU0wz8j1Wrwe6qyffWPBO3RlBd+PvRLgCGlE1oV5gyw+TkjCo+9eLNgwIVIJP5/HCmEohSgtpOjqOthzVt/DjwfaH8VUbHC9r718cNfj1stfL46wW/sevQThmqEwFshOWxGKPbcJNhlpcC/LXr6YO2RwlzGnF2AEkvC7ZzG4dKCDZNYYgOgR7sKFcUArBE+4mvKQBcG1qDj4GBLeH82n0jRZSf0f6EpHjKCPTkvnJswIDAQABo1MwUTAdBgNVHQ4EFgQU6cgHWvusLXkexbuZ/9KOtzexegMwHwYDVR0jBBgwFoAU6cgHWvusLXkexbuZ/9KOtzexegMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAbuujq+xvRbRlq+FXC2PHhWlyCqHPEn7rKC7vr77rdXIMVxAzY3rlAfHgyTgL41XrDsx58dfKswUp3g2VxJZHwKXQhoCKRWo+fAxJ6bHVWXrPkJJYZtI/JtggKNcORWw6VT0081UhXaRANDlGHMQ+MfTr3NaNz+aIAMhMtL4BSobSocjn4eAUaFxXVF0yznT8+U8MiGMgThOLrnyvzg9NK7D4iNf3gC1XRyD1RHP6w7ji/tpmLR4CQ6A3i2ywYD8ZQilBQhBa2abzP7zxHbQe8wHbopWPaz5QQ34bxpZ8MLKgY2OqzNndqt8+ZUYv9uk8AllnBT/fQbWC1b02B8gNMQ==",
|
||||||
|
).unwrap();
|
||||||
|
let cert = CertificateDer::from(der);
|
||||||
|
assert_eq!(spki_fingerprint(&cert).unwrap(), "Wh/ag/dtBCpXhiYPhRWV66kx3FutmTyNw1dysTbWvao=");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue