Skip to content

Commit e144092

Browse files
authored
Merge pull request #310 from OverOrion/feat/multiple-cas-upstream
feat(server/tls): add support for multiple CAs
2 parents fd5cf96 + 5337200 commit e144092

File tree

2 files changed

+107
-20
lines changed

2 files changed

+107
-20
lines changed

server/src/lib.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ use tokio_util::sync::CancellationToken;
7070

7171
use crate::logging::ACCESS_LOGGER;
7272
use crate::proxy_protocol::read_proxy_header;
73-
use crate::tls::{make_config, subject_from_cert};
73+
use crate::tls::{find_matching_ca, issuer_from_cert, make_config, subject_from_cert};
7474

7575
pub enum RequestCategory {
7676
Enumerate(String),
@@ -957,7 +957,8 @@ fn create_tls_server(
957957
let svc_monitoring_settings = monitoring_settings.clone();
958958
let subscriptions = collector_subscriptions.clone();
959959
let collector_heartbeat_tx = collector_heartbeat_tx.clone();
960-
let thumbprint = tls_config.thumbprint.clone();
960+
let ca_thumbprints = tls_config.thumbprints.clone();
961+
961962
let tls_acceptor = tls_acceptor.clone();
962963

963964
// Create a "rx" channel end for the task
@@ -998,21 +999,37 @@ fn create_tls_server(
998999
}
9991000
};
10001001

1001-
// get peer certificate
1002-
let cert = stream
1002+
// get peer certificates (full chain)
1003+
let certificate_chain = stream
10031004
.get_ref()
10041005
.1
10051006
.peer_certificates()
1006-
.expect("Peer certificate should exist") // client auth has to happen, so this should not fail
1007+
.expect("Peer certificate should exist"); // client auth has to happen, so this should not fail
1008+
let cert = certificate_chain
10071009
.first()
10081010
.expect("Peer certificate should not be empty") // client cert cannot be empty if authentication succeeded
10091011
.clone();
10101012

10111013
let subject =
10121014
subject_from_cert(cert.as_ref()).expect("Could not parse client certificate");
1015+
debug!("Incoming TLS connection from subject '{}'", &subject);
1016+
1017+
let issuer = issuer_from_cert(cert.as_ref())
1018+
.expect("Could not parse issuer from client certificate");
1019+
debug!("Client certificate issued by '{}'", &issuer);
1020+
debug!("Known CAs: {:?}", ca_thumbprints);
1021+
1022+
// Try to find a trusted CA in the certificate chain
1023+
let matching_ca_entry = match find_matching_ca(certificate_chain, &ca_thumbprints) {
1024+
Ok(entry) => entry,
1025+
Err(e) => {
1026+
warn!( "No trusted CA found in certificate chain for connection from '{}' (subject: '{}'): {:?}", real_client_addr, subject, e);
1027+
return;
1028+
}
1029+
};
10131030

10141031
// Initialize Authentication context once for each TCP connection
1015-
let auth_ctx = AuthenticationContext::Tls(subject, thumbprint);
1032+
let auth_ctx = AuthenticationContext::Tls(subject.clone(), matching_ca_entry);
10161033

10171034
// Hyper needs a wrapper for the stream
10181035
let io = TokioIo::new(stream);

server/src/tls.rs

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use anyhow::{bail, Context, Result};
22
use common::encoding::encode_utf16le;
33
use hex::ToHex;
4-
use log::{debug, info};
4+
use log::{debug, info, warn};
55
use sha1::{Digest, Sha1};
6+
use std::collections::HashMap;
67
use std::fs;
78
use std::io::BufReader;
89
use std::sync::Arc;
@@ -66,7 +67,7 @@ pub fn compute_thumbprint(cert_content: &[u8]) -> String {
6667

6768
pub struct TlsConfig {
6869
pub server: Arc<ServerConfig>,
69-
pub thumbprint: String,
70+
pub thumbprints: HashMap<String, String>,
7071
}
7172

7273
/// Create configuration for TLS connection
@@ -77,18 +78,35 @@ pub fn make_config(args: &common::settings::Tls) -> Result<TlsConfig> {
7778
load_priv_key(args.server_private_key()).context("Could not load private key")?;
7879
let ca_certs = load_certs(args.ca_certificate()).context("Could not load CA certificate")?;
7980

80-
let ca_cert_content: &[u8] = ca_certs
81-
.first()
82-
.context("CA certificate should contain at least one certificate")?
83-
.as_ref();
84-
let thumbprint = compute_thumbprint(ca_cert_content);
85-
86-
debug!("CA Thumbprint from certificate : {}", thumbprint);
81+
// Ensure at least one CA is present
82+
if ca_certs.is_empty() {
83+
bail!("CA certificate should contain at least one certificate");
84+
}
8785

8886
let mut client_auth_roots = RootCertStore::empty();
87+
let mut ca_thumbprints = HashMap::new();
8988

90-
// Put all certificates from given CA certificate file into certificate store
89+
// Compute thumbprint for each CA and add to trust store
9190
for root in ca_certs {
91+
let subject = subject_from_cert(root.as_ref())?;
92+
let thumbprint = compute_thumbprint(root.as_ref());
93+
debug!("CA Thumbprint from certificate: {}", &thumbprint);
94+
if let Some(existing) = ca_thumbprints.get(&subject) {
95+
if existing != &thumbprint {
96+
bail!(
97+
"Duplicate CA subject with different thumbprints: {}",
98+
&subject
99+
);
100+
}
101+
// already present, skip, but warn
102+
warn!(
103+
"Duplicate CA certificate found for subject: {}, thumbprint: {}",
104+
&subject, &thumbprint
105+
);
106+
continue;
107+
}
108+
ca_thumbprints.insert(subject, thumbprint);
109+
92110
client_auth_roots
93111
.add(root)
94112
.context("Could not add certificate to root of trust")?;
@@ -107,20 +125,21 @@ pub fn make_config(args: &common::settings::Tls) -> Result<TlsConfig> {
107125
.with_protocol_versions(ALL_VERSIONS)
108126
.context("Could not build configuration defaults")?
109127
.with_client_cert_verifier(client_cert_verifier) // add verifier
110-
.with_single_cert(cert, priv_key) // add server vertification
128+
.with_single_cert(cert, priv_key) // add server verification
111129
.context("Bad configuration certificate or key")?;
112130

113131
// any http version is ok
114132
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
115133

116134
info!(
117-
"Loaded TLS configuration with server certificate {}",
118-
args.server_certificate()
135+
"Loaded TLS configuration with server certificate {} and {} CA(s)",
136+
args.server_certificate(),
137+
ca_thumbprints.len()
119138
);
120139

121140
Ok(TlsConfig {
122141
server: Arc::new(config),
123-
thumbprint,
142+
thumbprints: ca_thumbprints,
124143
})
125144
}
126145

@@ -158,6 +177,57 @@ pub fn subject_from_cert(cert: &[u8]) -> Result<String> {
158177
bail!("CommonName not found")
159178
}
160179

180+
pub fn issuer_from_cert(cert: &[u8]) -> Result<String> {
181+
// load certificate to decompose its content
182+
let cert = X509Certificate::from_der(cert)?.1;
183+
184+
let oid_registry = OidRegistry::default().with_x509(); // registry of OIDs we will need
185+
let rdn_iter = cert.issuer.iter_rdn(); // iterator on RDNs
186+
187+
for subject_attribute in rdn_iter {
188+
// Each entry contains a list of AttributeTypeAndValue objects,
189+
// so we fetch their attribute type (= the OID representing each of them)
190+
let sn = subject_attribute.iter();
191+
192+
for set in sn {
193+
// OID of the sub-entry
194+
let typ = set.attr_type();
195+
196+
// get the SN corresponding to the OID (None if it does not exist)
197+
let oid_reg = oid_registry.get(typ).map(|oid| oid.sn());
198+
199+
// the value we are interested in is only contained where the commonName is
200+
if oid_reg == Some("commonName") {
201+
// get data as text => FQDN of the client
202+
if let Ok(name) = set.as_str() {
203+
return Ok(name.to_string());
204+
} else {
205+
bail!("CommonName is empty")
206+
}
207+
}
208+
}
209+
}
210+
bail!("CommonName not found")
211+
}
212+
213+
pub fn find_matching_ca(
214+
peer_certs: &[CertificateDer],
215+
ca_thumbprints: &HashMap<String, String>,
216+
) -> Result<String> {
217+
peer_certs
218+
.iter()
219+
.find_map(|cert| {
220+
let issuer = issuer_from_cert(cert.as_ref()).ok()?;
221+
debug!("Checking issuer '{}'", &issuer);
222+
223+
ca_thumbprints.get(&issuer).map(|ca_entry| {
224+
debug!("Found matching CA for issuer '{}'", &issuer);
225+
ca_entry.clone()
226+
})
227+
})
228+
.context("No trusted CA found in certificate chain")
229+
}
230+
161231
/// Read and decode request payload
162232
pub async fn get_request_payload(
163233
parts: hyper::http::request::Parts,

0 commit comments

Comments
 (0)