feat(tls/server-extract): extract server-side TLS code into alknet-tls

Phase 1, Task 2 of crate extraction. Extracts server-side TLS setup code
from alknet-core/endpoint.rs into alknet-tls:
- server.rs: TlsServerConfig, build_rustls_server_config, RawKeyCertResolver,
  AcceptAnyCertVerifier, SelfSignedCert, generate_self_signed_cert
- signing.rs: Ed25519SigningKey (shared, used by both server and client)
- pem.rs: load_cert_chain, load_private_key (shared)
- lib.rs: TlsError enum (Config, Io, Cert variants)
- Cargo.toml: added futures dependency for ACME feature

Old code in endpoint.rs stays (duplicated). No breakage.
This commit is contained in:
deepseek-v4-pro committed 2026-07-17 09:59:18 +00:00
1 parent 44f5e32740
commit 114cd73a21
5 files changed
+434 -9

No files matched your search

+1
View File
@@ -26,6 +26,7 @@ webpki-roots = "1"
rcgen = "0.13"
tracing = "0.1"
thiserror = "2"
futures = "0.3"
quinn = { version = "0.11", optional = true }
tokio-rustls = { version = "0.26", optional = true }
rustls-acme = { version = "0.12", optional = true, features = ["aws-lc-rs"] }
+12
View File
@@ -9,3 +9,15 @@ pub mod client;
pub mod pem;
pub mod server;
pub mod signing;
use std::io;
#[derive(Debug, thiserror::Error)]
pub enum TlsError {
#[error("TLS config error: {0}")]
Config(String),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("certificate error: {0}")]
Cert(String),
}
+30 -2
View File
@@ -1,4 +1,32 @@
//! PEM loading helpers: `load_cert_chain`, `load_private_key`.
//! Consolidated — one copy used by both server and client.
//!
//! TODO: implement
use std::io;
use std::path::Path;
use crate::TlsError;
pub fn load_cert_chain(
path: &Path,
) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>, TlsError> {
let bytes = std::fs::read(path).map_err(TlsError::Io)?;
let mut reader = io::BufReader::new(bytes.as_slice());
rustls_pemfile::certs(&mut reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| TlsError::Io(io::Error::other(e)))
}
pub fn load_private_key(
path: &Path,
) -> Result<rustls::pki_types::PrivateKeyDer<'static>, TlsError> {
let bytes = std::fs::read(path).map_err(TlsError::Io)?;
let mut reader = io::BufReader::new(bytes.as_slice());
match rustls_pemfile::private_key(&mut reader) {
Ok(Some(key)) => Ok(key),
Ok(None) => Err(TlsError::Io(io::Error::new(
io::ErrorKind::InvalidData,
"no private key found in file",
))),
Err(e) => Err(TlsError::Io(io::Error::other(e))),
}
}
+336 -5
View File
@@ -1,5 +1,336 @@
//! Server-side TLS configuration: `TlsServerConfig`, `TlsSetup`,
//! `RawKeyCertResolver`, `AcceptAnyCertVerifier`, `SelfSignedCert`,
//! `generate_self_signed_cert`, and `build_iroh_endpoint`.
//!
//! TODO: implement
//! Server-side TLS configuration: `TlsServerConfig`, `RawKeyCertResolver`,
//! `AcceptAnyCertVerifier`, `SelfSignedCert`, `generate_self_signed_cert`.
use std::sync::Arc;
use alknet_core::config::{Ed25519SecretKey, TlsIdentity};
#[cfg(feature = "acme")]
use alknet_core::config::AcmeDirectory;
#[cfg(feature = "acme")]
use tracing::{debug, error, warn};
use crate::signing::Ed25519SigningKey;
use crate::TlsError;
/// Server-side TLS configuration, transport-agnostic.
/// Wraps a `rustls::ServerConfig` plus optional ACME state.
#[allow(dead_code)]
pub struct TlsServerConfig {
pub(crate) rustls_config: rustls::ServerConfig,
#[cfg(feature = "acme")]
pub(crate) acme_state_handle: Option<tokio::task::JoinHandle<()>>,
}
impl TlsServerConfig {
/// Build a server config from a `TlsIdentity` and ALPN list.
/// ACME identities spawn a background cert-renewal task.
pub async fn new(
tls_identity: &TlsIdentity,
alpns: &[Vec<u8>],
) -> Result<Self, TlsError> {
match tls_identity {
TlsIdentity::Acme {
domains,
cache_dir,
directory,
contact,
} => {
#[cfg(feature = "acme")]
{
Self::new_acme(domains, cache_dir, directory, contact, alpns).await
}
#[cfg(not(feature = "acme"))]
{
let _ = (domains, cache_dir, directory, contact, alpns);
Err(TlsError::Config(
"ACME feature not enabled but TlsIdentity::Acme configured".to_string(),
))
}
}
_ => {
let server_config = build_rustls_server_config(tls_identity, alpns)?;
Ok(Self {
rustls_config: server_config,
#[cfg(feature = "acme")]
acme_state_handle: None,
})
}
}
}
#[cfg(feature = "acme")]
async fn new_acme(
domains: &[String],
cache_dir: &std::path::Path,
directory: &AcmeDirectory,
contact: &[String],
alpns: &[Vec<u8>],
) -> Result<Self, TlsError> {
use rustls_acme::caches::DirCache;
use rustls_acme::{AcmeConfig, EventError, EventOk};
let acme_config = AcmeConfig::new(domains.to_vec())
.cache(DirCache::new(cache_dir.to_path_buf()))
.directory(directory.url())
.contact(contact.iter().map(|c| c.as_str()));
let state = acme_config.state();
let resolver = state.resolver();
let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
let mut config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| TlsError::Config(e.to_string()))?
.with_client_cert_verifier(Arc::new(AcceptAnyCertVerifier))
.with_cert_resolver(resolver);
config.max_early_data_size = u32::MAX;
let mut alpn = alpns.to_vec();
alpn.push(b"acme-tls/1".to_vec());
config.alpn_protocols = alpn;
let domains_owned: Vec<String> = domains.to_vec();
let handle = tokio::spawn(async move {
use futures::StreamExt;
let mut state = state;
while let Some(event) = state.next().await {
match event {
Ok(EventOk::DeployedCachedCert) => {
debug!(domains = ?domains_owned, "ACME: deployed cached certificate");
}
Ok(EventOk::DeployedNewCert) => {
debug!(domains = ?domains_owned, "ACME: deployed new certificate");
}
Ok(EventOk::CertCacheStore) => {
debug!(domains = ?domains_owned, "ACME: certificate stored to cache");
}
Ok(EventOk::AccountCacheStore) => {
debug!(domains = ?domains_owned, "ACME: account stored to cache");
}
Err(EventError::CertCacheLoad(e)) => {
error!(domains = ?domains_owned, error = ?e, "ACME: certificate cache load failed");
}
Err(EventError::AccountCacheLoad(e)) => {
error!(domains = ?domains_owned, error = ?e, "ACME: account cache load failed");
}
Err(EventError::CertCacheStore(e)) => {
warn!(domains = ?domains_owned, error = ?e, "ACME: certificate cache store failed");
}
Err(EventError::AccountCacheStore(e)) => {
warn!(domains = ?domains_owned, error = ?e, "ACME: account cache store failed");
}
Err(EventError::CachedCertParse(e)) => {
error!(domains = ?domains_owned, error = ?e, "ACME: cached certificate parse failed");
}
Err(EventError::Order(e)) => {
warn!(domains = ?domains_owned, error = ?e, "ACME: certificate order failed, will retry");
}
Err(EventError::NewCertParse(e)) => {
error!(domains = ?domains_owned, error = ?e, "ACME: new certificate parse failed");
}
}
}
debug!(domains = ?domains_owned, "ACME: state machine ended");
});
Ok(Self {
rustls_config: config,
acme_state_handle: Some(handle),
})
}
/// Convert to a `quinn::ServerConfig` for QUIC transport.
#[cfg(feature = "quinn")]
pub fn for_quinn(self) -> Result<quinn::ServerConfig, TlsError> {
use quinn::crypto::rustls::QuicServerConfig;
let quic_server_config = QuicServerConfig::try_from(self.rustls_config)
.map_err(|e| TlsError::Config(e.to_string()))?;
Ok(quinn::ServerConfig::with_crypto(Arc::new(
quic_server_config,
)))
}
}
fn build_rustls_server_config(
tls_identity: &TlsIdentity,
alpns: &[Vec<u8>],
) -> Result<rustls::ServerConfig, TlsError> {
let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
let client_verifier = Arc::new(AcceptAnyCertVerifier);
match tls_identity {
TlsIdentity::X509 { cert, key } => {
let cert_chain = crate::pem::load_cert_chain(cert)?;
let private_key = crate::pem::load_private_key(key)?;
let mut config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| TlsError::Config(e.to_string()))?
.with_client_cert_verifier(client_verifier)
.with_single_cert(cert_chain, private_key)
.map_err(|e| TlsError::Config(e.to_string()))?;
config.alpn_protocols = alpns.to_vec();
config.max_early_data_size = u32::MAX;
Ok(config)
}
TlsIdentity::RawKey(secret_key) => {
let resolver = Arc::new(RawKeyCertResolver::new(secret_key));
let mut config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| TlsError::Config(e.to_string()))?
.with_client_cert_verifier(client_verifier)
.with_cert_resolver(resolver);
config.alpn_protocols = alpns.to_vec();
config.max_early_data_size = u32::MAX;
Ok(config)
}
TlsIdentity::SelfSigned => {
let cert = generate_self_signed_cert()?;
let mut config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| TlsError::Config(e.to_string()))?
.with_client_cert_verifier(client_verifier)
.with_single_cert(cert.cert_chain, cert.private_key)
.map_err(|e| TlsError::Config(e.to_string()))?;
config.alpn_protocols = alpns.to_vec();
config.max_early_data_size = u32::MAX;
Ok(config)
}
TlsIdentity::Acme { .. } => {
unreachable!(
"TlsIdentity::Acme is handled by TlsServerConfig::new_acme, not build_rustls_server_config"
)
}
}
}
struct SelfSignedCert {
cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>,
private_key: rustls::pki_types::PrivateKeyDer<'static>,
}
fn generate_self_signed_cert() -> Result<SelfSignedCert, TlsError> {
use rcgen::{CertificateParams, KeyPair};
let key_pair =
KeyPair::generate().map_err(|e| TlsError::Config(e.to_string()))?;
let params = CertificateParams::default();
let cert = params
.self_signed(&key_pair)
.map_err(|e| TlsError::Config(e.to_string()))?;
let cert_der = cert.der().clone();
let key_der = rustls::pki_types::PrivateKeyDer::Pkcs8(
rustls::pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der()),
);
Ok(SelfSignedCert {
cert_chain: vec![cert_der],
private_key: key_der,
})
}
/// Server-side "request-but-don't-require" client cert verifier (ADR-034).
///
/// Asks for a client TLS cert (X.509 or RFC 7250 raw key) so the endpoint can
/// extract the fingerprint via `peer_identity()`, but does not require one
/// and does not verify the presented cert against a CA. The fingerprint is
/// matched against `PeerEntry.fingerprints` by
/// `IdentityProvider::resolve_from_fingerprint()`.
///
/// **Server-side only.** This must not be reused as a client-side
/// `ServerCertVerifier` — the client-side verifier is selected by `PeerEntry`
/// presence (ADR-034 §3): CA verification for unknown X.509 remotes,
/// fingerprint pinning for known peers.
pub struct AcceptAnyCertVerifier;
impl std::fmt::Debug for AcceptAnyCertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AcceptAnyCertVerifier").finish()
}
}
impl rustls::server::danger::ClientCertVerifier for AcceptAnyCertVerifier {
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> bool {
false
}
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
&[]
}
fn verify_client_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
Ok(rustls::server::danger::ClientCertVerified::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
]
}
}
pub struct RawKeyCertResolver {
key: Arc<rustls::sign::CertifiedKey>,
}
impl RawKeyCertResolver {
pub fn new(secret_key: &Ed25519SecretKey) -> Self {
let signing_key = Arc::new(Ed25519SigningKey::new(secret_key.clone()));
let public_key = signing_key.spki_public_key();
let cert = rustls::pki_types::CertificateDer::from(public_key.to_vec());
let certified_key = rustls::sign::CertifiedKey::new(vec![cert], signing_key);
Self {
key: Arc::new(certified_key),
}
}
}
impl rustls::server::ResolvesServerCert for RawKeyCertResolver {
fn resolve(
&self,
_client_hello: rustls::server::ClientHello<'_>,
) -> Option<Arc<rustls::sign::CertifiedKey>> {
Some(Arc::clone(&self.key))
}
fn only_raw_public_keys(&self) -> bool {
true
}
}
impl std::fmt::Debug for RawKeyCertResolver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawKeyCertResolver").finish()
}
}
+55 -2
View File
@@ -1,5 +1,58 @@
//! Ed25519 signing key usable as both a rustls `SigningKey` and `Signer`.
//! Consolidated — one copy used by both server (`RawKeyCertResolver`) and
//! client (`RawKeyClientCertResolver`).
//!
//! TODO: implement
#[derive(Clone)]
pub struct Ed25519SigningKey {
key: alknet_core::config::Ed25519SecretKey,
}
impl std::fmt::Debug for Ed25519SigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Ed25519SigningKey").finish()
}
}
impl Ed25519SigningKey {
pub fn new(key: alknet_core::config::Ed25519SecretKey) -> Self {
Self { key }
}
pub fn spki_public_key(&self) -> rustls::pki_types::SubjectPublicKeyInfoDer<'static> {
rustls::sign::public_key_to_spki(
&rustls::pki_types::alg_id::ED25519,
self.key.public().as_bytes(),
)
}
}
impl rustls::sign::SigningKey for Ed25519SigningKey {
fn choose_scheme(
&self,
offered: &[rustls::SignatureScheme],
) -> Option<Box<dyn rustls::sign::Signer>> {
if offered.contains(&rustls::SignatureScheme::ED25519) {
Some(Box::new(self.clone()))
} else {
None
}
}
fn algorithm(&self) -> rustls::SignatureAlgorithm {
rustls::SignatureAlgorithm::ED25519
}
fn public_key(&self) -> Option<rustls::pki_types::SubjectPublicKeyInfoDer<'_>> {
Some(self.spki_public_key())
}
}
impl rustls::sign::Signer for Ed25519SigningKey {
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, rustls::Error> {
Ok(self.key.sign(message).to_bytes().to_vec())
}
fn scheme(&self) -> rustls::SignatureScheme {
rustls::SignatureScheme::ED25519
}
}