use std::{fmt::Display, sync::Arc};
use anyhow::Context;
use asn1_rs::{FromDer, nom::AsBytes};
use certs::{
EphemeralClientCert, EphemeralIssuingCaCert, EphemeralServerCert,
RevocableIssuingCaCert,
};
use lexe_common::{
api::revocable_clients::{GetRevocableClientStatus, RevocableClientStatus},
constants,
env::DeployEnv,
root_seed::RootSeed,
time::TimestampMs,
};
use lexe_crypto::{ed25519, rng::Crng};
use rustls::{
DigitallySignedStruct, DistinguishedName, RootCertStore,
client::{
WebPkiServerVerifier,
danger::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
},
},
pki_types::{CertificateDer, ServerName, UnixTime},
server::{
WebPkiClientVerifier,
danger::{ClientCertVerified, ClientCertVerifier},
},
};
use x509_parser::prelude::X509Certificate;
use crate::{
ed25519_ext::Ed25519PublicKeyExt,
lexe_ca,
types::{LxCertificateDer, LxPrivatePkcs8KeyDer},
};
pub mod certs;
pub fn node_run_server_config(
rng: &mut impl Crng,
eph_ca_cert: &EphemeralIssuingCaCert,
eph_ca_cert_der: &LxCertificateDer,
rev_ca_cert: &RevocableIssuingCaCert,
revocable_clients: Arc<dyn GetRevocableClientStatus>,
) -> anyhow::Result<(Arc<rustls::ServerConfig>, String)> {
let dns_name = constants::NODE_RUN_DNS;
let eph_server_cert = EphemeralServerCert::from_rng(rng, &[dns_name])
.context("Failed to generate ephemeral server cert")?;
let eph_server_cert_der = eph_server_cert
.serialize_der_ca_signed(eph_ca_cert)
.context("Failed to sign and serialize ephemeral server cert")?;
let eph_server_cert_key_der = eph_server_cert.serialize_key_der();
let client_cert_verifier = SharedSeedClientCertVerifier::new(
eph_ca_cert_der,
rev_ca_cert,
revocable_clients,
)
.context("Failed to build shared seed client cert verifier")?;
let mut config = lexe_tls_core::server_config_builder()
.with_client_cert_verifier(Arc::new(client_cert_verifier))
.with_single_cert(
vec![eph_server_cert_der.into()],
eph_server_cert_key_der.into(),
)
.context("Failed to build rustls::ServerConfig")?;
config
.alpn_protocols
.clone_from(&lexe_tls_core::LEXE_ALPN_PROTOCOLS);
Ok((Arc::new(config), dns_name.to_owned()))
}
pub fn user_node_run_root_seed_client_config(
rng: &mut impl Crng,
deploy_env: DeployEnv,
root_seed: &RootSeed,
) -> anyhow::Result<rustls::ClientConfig> {
let eph_ca_cert = EphemeralIssuingCaCert::from_root_seed(root_seed);
let eph_ca_cert_der = eph_ca_cert
.serialize_der_self_signed()
.context("Failed to sign and serialize ephemeral CA cert")?;
let ephemeral_ca_verifier = ephemeral_ca_verifier(&eph_ca_cert_der)
.context("Failed to build ephemeral CA verifier")?;
let lexe_server_verifier = lexe_ca::lexe_server_verifier(deploy_env);
let server_cert_verifier = UserNodeRunVerifier {
ephemeral_ca_verifier,
lexe_server_verifier,
};
let client_cert = EphemeralClientCert::generate_from_rng(rng);
let client_cert_der = client_cert
.serialize_der_ca_signed(&eph_ca_cert)
.context("Failed to sign and serialize ephemeral client cert")?;
let client_cert_key_der = client_cert.serialize_key_der();
let mut config = lexe_tls_core::client_config_builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(server_cert_verifier))
.with_client_auth_cert(
vec![client_cert_der.into()],
client_cert_key_der.into(),
)
.context("Failed to build rustls::ClientConfig")?;
config
.alpn_protocols
.clone_from(&lexe_tls_core::LEXE_ALPN_PROTOCOLS);
Ok(config)
}
pub fn user_node_run_revocable_client_config(
deploy_env: DeployEnv,
eph_ca_cert_der: &LxCertificateDer,
rev_client_cert_der: LxCertificateDer,
rev_client_cert_key_der: LxPrivatePkcs8KeyDer,
) -> anyhow::Result<rustls::ClientConfig> {
let ephemeral_ca_verifier = ephemeral_ca_verifier(eph_ca_cert_der)
.context("Failed to build ephemeral CA verifier")?;
let lexe_server_verifier = lexe_ca::lexe_server_verifier(deploy_env);
let server_cert_verifier = UserNodeRunVerifier {
ephemeral_ca_verifier,
lexe_server_verifier,
};
let mut config = lexe_tls_core::client_config_builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(server_cert_verifier))
.with_client_auth_cert(
vec![rev_client_cert_der.into()],
rev_client_cert_key_der.into(),
)
.context("Failed to build rustls::ClientConfig")?;
config
.alpn_protocols
.clone_from(&lexe_tls_core::LEXE_ALPN_PROTOCOLS);
Ok(config)
}
pub fn ephemeral_ca_verifier(
ephemeral_ca_cert_der: &LxCertificateDer,
) -> anyhow::Result<Arc<WebPkiServerVerifier>> {
let mut roots = RootCertStore::empty();
roots
.add(ephemeral_ca_cert_der.into())
.context("Failed to re-parse ephemeral CA cert")?;
let verifier = WebPkiServerVerifier::builder_with_provider(
Arc::new(roots),
lexe_tls_core::LEXE_CRYPTO_PROVIDER.clone(),
)
.build()
.context("Could not build ephemeral server verifier")?;
Ok(verifier)
}
#[derive(Debug)]
struct UserNodeRunVerifier {
ephemeral_ca_verifier: Arc<WebPkiServerVerifier>,
lexe_server_verifier: Arc<WebPkiServerVerifier>,
}
impl ServerCertVerifier for UserNodeRunVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer,
intermediates: &[CertificateDer],
server_name: &ServerName,
ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let maybe_dns_name = match server_name {
ServerName::DnsName(dns) => Some(dns.as_ref()),
_ => None,
};
match maybe_dns_name {
Some(constants::NODE_RUN_DNS) =>
self.ephemeral_ca_verifier.verify_server_cert(
end_entity,
intermediates,
server_name,
ocsp_response,
now,
),
_ => self.lexe_server_verifier.verify_server_cert(
end_entity,
intermediates,
server_name,
ocsp_response,
now,
),
}
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
let error = rustls::PeerIncompatible::ServerDoesNotSupportTls12Or13;
Err(rustls::Error::PeerIncompatible(error))
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&lexe_tls_core::LEXE_SIGNATURE_ALGORITHMS,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
lexe_tls_core::LEXE_SUPPORTED_VERIFY_SCHEMES.clone()
}
}
#[derive(Debug)]
pub struct SharedSeedClientCertVerifier {
ephemeral_ca_verifier: Arc<dyn ClientCertVerifier>,
revocable_ca_verifier: Arc<dyn ClientCertVerifier>,
revocable_clients: Arc<dyn GetRevocableClientStatus>,
}
impl SharedSeedClientCertVerifier {
pub fn new(
eph_ca_cert_der: &LxCertificateDer,
rev_ca_cert: &RevocableIssuingCaCert,
revocable_clients: Arc<dyn GetRevocableClientStatus>,
) -> anyhow::Result<Self> {
let ephemeral_ca_verifier = {
let mut eph_roots = rustls::RootCertStore::empty();
eph_roots
.add(eph_ca_cert_der.into())
.context("rustls failed to deserialize CA cert DER bytes")?;
WebPkiClientVerifier::builder_with_provider(
Arc::new(eph_roots),
lexe_tls_core::LEXE_CRYPTO_PROVIDER.clone(),
)
.build()
.context("Failed to build ephemeral CA verifier")?
};
let revocable_ca_verifier = {
let rev_ca_cert_der = rev_ca_cert
.serialize_der_self_signed()
.context("Failed to sign and serialize revocable CA cert")?;
let mut rev_roots = rustls::RootCertStore::empty();
rev_roots
.add(rev_ca_cert_der.into())
.context("rustls failed to deserialize CA cert DER bytes")?;
WebPkiClientVerifier::builder_with_provider(
Arc::new(rev_roots),
lexe_tls_core::LEXE_CRYPTO_PROVIDER.clone(),
)
.build()
.context("Failed to build ephemeral CA verifier")?
};
Ok(Self {
ephemeral_ca_verifier,
revocable_ca_verifier,
revocable_clients,
})
}
}
impl ClientCertVerifier for SharedSeedClientCertVerifier {
fn root_hint_subjects(&self) -> &[DistinguishedName] {
&[]
}
fn verify_client_cert(
&self,
end_entity_der: &CertificateDer,
intermediates: &[CertificateDer],
now: UnixTime,
) -> Result<ClientCertVerified, rustls::Error> {
fn rustls_err(s: impl Display) -> rustls::Error {
rustls::Error::General(s.to_string())
}
if let Ok(verified) = self.ephemeral_ca_verifier.verify_client_cert(
end_entity_der,
intermediates,
now,
) {
return Ok(verified);
}
self.revocable_ca_verifier.verify_client_cert(
end_entity_der,
intermediates,
now,
)?;
let (_remaining, end_entity) =
X509Certificate::from_der(end_entity_der.as_bytes())
.map_err(|_| rustls_err("Cert was not encoded correctly"))?;
let end_entity_pk = ed25519::PublicKey::try_from_spki(
&end_entity.tbs_certificate.subject_pki,
)
.map_err(|e| rustls_err(format!("Not an ed25519 pk: {e}")))?;
let now = TimestampMs::from_secs(now.as_secs())
.map_err(|_| rustls_err("Clock overflow"))?;
let status = self
.revocable_clients
.get_client_status(&end_entity_pk, now)
.ok_or_else(|| rustls_err("Unrecognized cert pk"))?;
match status {
RevocableClientStatus::Valid => {}
RevocableClientStatus::Revoked =>
return Err(rustls_err("Client was previously revoked")),
RevocableClientStatus::Expired =>
return Err(rustls_err("Client is expired")),
}
Ok(ClientCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
let error = rustls::PeerIncompatible::ServerDoesNotSupportTls12Or13;
Err(rustls::Error::PeerIncompatible(error))
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&lexe_tls_core::LEXE_SIGNATURE_ALGORITHMS,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
lexe_tls_core::LEXE_SUPPORTED_VERIFY_SCHEMES.clone()
}
}
#[cfg(test)]
mod test {
use std::sync::{Arc, RwLock};
use certs::RevocableClientCert;
use lexe_api_core::revocable_clients::{
RevocableClient, RevocableClients, RevocableClientsHandle,
};
use lexe_common::{
api::auth::LexeScope, env::DeployEnv, root_seed::RootSeed,
time::TimestampMs,
};
use lexe_crypto::rng::FastRng;
use secrecy::Secret;
use super::*;
use crate::test_utils;
#[tokio::test]
async fn user_node_run_handshake_succeeds() {
let client_seed = RootSeed::new(Secret::new([0x42; 32]));
let server_seed = RootSeed::new(Secret::new([0x42; 32]));
let [client_result, server_result] =
do_user_node_run_tls_handshake(&client_seed, &server_seed).await;
client_result.unwrap();
server_result.unwrap();
}
#[tokio::test]
async fn user_node_run_handshake_fails_with_different_seeds() {
let client_seed = RootSeed::new(Secret::new([0x42; 32]));
let server_seed = RootSeed::new(Secret::new([0x69; 32]));
let [client_result, server_result] =
do_user_node_run_tls_handshake(&client_seed, &server_seed).await;
assert!(client_result.unwrap_err().contains("Client didn't connect"));
assert!(server_result.unwrap_err().contains("Server didn't accept"));
}
async fn do_user_node_run_tls_handshake(
client_seed: &RootSeed,
server_seed: &RootSeed,
) -> [Result<(), String>; 2] {
let mut rng = FastRng::from_u64(20240514);
let deploy_env = DeployEnv::Dev;
let client_config = user_node_run_root_seed_client_config(
&mut rng,
deploy_env,
client_seed,
)
.map(Arc::new)
.unwrap();
let (server_config, server_dns) = {
let eph_ca_cert =
EphemeralIssuingCaCert::from_root_seed(server_seed);
let eph_ca_cert_der =
eph_ca_cert.serialize_der_self_signed().unwrap();
let rev_ca_cert =
RevocableIssuingCaCert::from_root_seed(server_seed);
let clients = Arc::new(RevocableClientsHandle(RwLock::new(
RevocableClients::default(),
)));
node_run_server_config(
&mut rng,
&eph_ca_cert,
&eph_ca_cert_der,
&rev_ca_cert,
clients,
)
.unwrap()
};
test_utils::do_tls_handshake(client_config, server_config, &server_dns)
.await
}
#[tokio::test]
async fn sdk_node_run_handshake() {
{
let expiration = None;
let is_revoked = false;
let [client_result, server_result] =
do_sdk_node_run_tls_handshake(expiration, is_revoked).await;
client_result.unwrap();
server_result.unwrap();
}
{
let expiration = Some(TimestampMs::MAX);
let is_revoked = false;
let [client_result, server_result] =
do_sdk_node_run_tls_handshake(expiration, is_revoked).await;
client_result.unwrap();
server_result.unwrap();
}
{
let expiration = Some(TimestampMs::MIN);
let is_revoked = false;
let [client_result, server_result] =
do_sdk_node_run_tls_handshake(expiration, is_revoked).await;
assert!(client_result.unwrap_err().contains("HandshakeFailure"));
assert!(server_result.unwrap_err().contains("Client is expired"));
}
{
let expiration = None;
let is_revoked = true;
let [client_result, server_result] =
do_sdk_node_run_tls_handshake(expiration, is_revoked).await;
assert!(client_result.unwrap_err().contains("HandshakeFailure"));
assert!(
server_result
.unwrap_err()
.contains("Client was previously revoked")
);
}
}
async fn do_sdk_node_run_tls_handshake(
expiration: Option<TimestampMs>,
is_revoked: bool,
) -> [Result<(), String>; 2] {
let server_seed = RootSeed::new(Secret::new([0x42; 32]));
let eph_ca_cert = EphemeralIssuingCaCert::from_root_seed(&server_seed);
let eph_ca_cert_der = eph_ca_cert.serialize_der_self_signed().unwrap();
let rev_ca_cert = RevocableIssuingCaCert::from_root_seed(&server_seed);
let mut rng = FastRng::from_u64(20250509);
let rev_client_cert = RevocableClientCert::generate_from_rng(&mut rng);
let rev_client_cert_der = rev_client_cert
.serialize_der_ca_signed(&rev_ca_cert)
.unwrap();
let rev_client_cert_key_der = rev_client_cert.serialize_key_der();
let rev_client_cert_pk = rev_client_cert.public_key();
let rev_client = RevocableClient {
pubkey: *rev_client_cert_pk,
created_at: TimestampMs::from_secs_u32(420),
expires_at: expiration,
label: Some("hullo".to_owned()),
scope: LexeScope::All,
is_revoked,
};
let rev_client_certs = {
let revocable_clients = RwLock::new(RevocableClients::default());
revocable_clients
.write()
.unwrap()
.clients
.insert(*rev_client_cert_pk, rev_client);
Arc::new(RevocableClientsHandle(revocable_clients))
};
let deploy_env = DeployEnv::Dev;
let client_config = user_node_run_revocable_client_config(
deploy_env,
&eph_ca_cert_der,
rev_client_cert_der,
rev_client_cert_key_der,
)
.map(Arc::new)
.unwrap();
let (server_config, server_dns) = {
let eph_ca_cert =
EphemeralIssuingCaCert::from_root_seed(&server_seed);
let rev_ca_cert =
RevocableIssuingCaCert::from_root_seed(&server_seed);
node_run_server_config(
&mut rng,
&eph_ca_cert,
&eph_ca_cert_der,
&rev_ca_cert,
rev_client_certs,
)
.unwrap()
};
test_utils::do_tls_handshake(client_config, server_config, &server_dns)
.await
}
}