More flexible and safer client authentication API.

Stop making "no client authentication" the silent default, because that
is not a safe default. Instead, require the user to make an explicit
choice of whether/how to do client authentication.

Previously, the default client authentication setting was the least
safe, so there's was no need to make the ability to plug in a client
auth implementation a “dangerous” feature. With this change, the
ability to provide one's own client authentication implementation
is available in the default configuration.

Some servers need more flexibility in doing client authentication than
was previously provided. Now all the choices for client authentication
are made by `ClientCertVerifier`.
This commit is contained in:
Brian Smith 2017-08-31 16:41:17 -10:00
parent 6940729011
commit 2cb3e6d5e3
9 changed files with 188 additions and 126 deletions

View File

@ -13,6 +13,7 @@ use rustls::{ClientConfig, ClientSession};
use rustls::{ServerConfig, ServerSession};
use rustls::ServerSessionMemoryCache;
use rustls::ClientSessionMemoryCache;
use rustls::{NoClientAuth, RootCertStore, WebPKIClientAuth};
use rustls::Session;
use rustls::Ticketer;
use rustls::internal::pemfile;
@ -118,17 +119,26 @@ impl Resumption {
}
fn make_server_config(version: rustls::ProtocolVersion,
clientauth: &ClientAuth,
client_auth: &ClientAuth,
resume: &Resumption)
-> ServerConfig {
let mut cfg = ServerConfig::new();
let client_auth = match client_auth {
&ClientAuth::Yes => {
let roots = get_chain();
let mut client_auth_roots = RootCertStore::empty();
for root in roots {
client_auth_roots.add(&root).unwrap();
}
WebPKIClientAuth::mandatory(client_auth_roots)
},
&ClientAuth::No => {
NoClientAuth::new()
}
};
let mut cfg = ServerConfig::new(client_auth);
cfg.set_single_cert(get_chain(), get_key());
if clientauth == &ClientAuth::Yes {
cfg.set_client_auth_roots(get_chain(), true);
}
if resume == &Resumption::SessionID {
cfg.set_persistence(ServerSessionMemoryCache::new(128));
} else if resume == &Resumption::Tickets {

View File

@ -127,17 +127,28 @@ fn split_protocols(protos: &str) -> Vec<String> {
ret
}
struct NoVerification {}
struct DummyClientAuth {
mandatory: bool,
}
impl rustls::ClientCertVerifier for DummyClientAuth {
fn offer_client_auth(&self) -> bool { true }
fn client_auth_mandatory(&self) -> bool { self.mandatory }
fn client_auth_root_subjects<'a>(&'a self) -> rustls::DistinguishedNames {
rustls::DistinguishedNames::new()
}
impl rustls::ClientCertVerifier for NoVerification {
fn verify_client_cert(&self,
_roots: &rustls::RootCertStore,
_certs: &[rustls::Certificate]) -> Result<rustls::ClientCertVerified, rustls::TLSError> {
Ok(rustls::ClientCertVerified::assertion())
}
}
impl rustls::ServerCertVerifier for NoVerification {
struct DummyServerAuth {}
impl rustls::ServerCertVerifier for DummyServerAuth {
fn verify_server_cert(&self,
_roots: &rustls::RootCertStore,
_certs: &[rustls::Certificate],
@ -148,7 +159,14 @@ impl rustls::ServerCertVerifier for NoVerification {
}
fn make_server_cfg(opts: &Options) -> Arc<rustls::ServerConfig> {
let mut cfg = rustls::ServerConfig::new();
let client_auth =
if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert {
Arc::new(DummyClientAuth { mandatory: opts.require_any_client_cert })
} else {
rustls::NoClientAuth::new()
};
let mut cfg = rustls::ServerConfig::new(client_auth);
let persist = rustls::ServerSessionMemoryCache::new(32);
cfg.set_persistence(persist);
@ -158,16 +176,6 @@ fn make_server_cfg(opts: &Options) -> Arc<rustls::ServerConfig> {
opts.server_ocsp_response.clone(),
opts.server_sct_list.clone());
if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert {
cfg.client_auth_offer = true;
cfg.dangerous()
.set_certificate_verifier(Arc::new(NoVerification {}));
}
if opts.require_any_client_cert {
cfg.client_auth_mandatory = true;
}
if opts.tickets {
cfg.ticketer = rustls::Ticketer::new();
}
@ -202,7 +210,7 @@ fn make_client_cfg(opts: &Options) -> Arc<rustls::ClientConfig> {
}
cfg.dangerous()
.set_certificate_verifier(Arc::new(NoVerification {}));
.set_certificate_verifier(Arc::new(DummyServerAuth {}));
if !opts.protocols.is_empty() {
cfg.set_protocols(&opts.protocols);

View File

@ -21,7 +21,7 @@ extern crate env_logger;
extern crate rustls;
use rustls::Session;
use rustls::{RootCertStore, Session, NoClientAuth, WebPKIClientAuth};
// Token for our listening socket.
const LISTENER: mio::Token = mio::Token(0);
@ -498,18 +498,28 @@ fn load_ocsp(filename: &Option<String>) -> Vec<u8> {
}
fn make_config(args: &Args) -> Arc<rustls::ServerConfig> {
let mut config = rustls::ServerConfig::new();
let client_auth = if args.flag_auth.is_some() {
let roots = load_certs(args.flag_auth.as_ref().unwrap());
let mut client_auth_roots = RootCertStore::empty();
for root in roots {
client_auth_roots.add(&root).unwrap();
}
if args.flag_require_auth {
WebPKIClientAuth::mandatory(client_auth_roots)
} else {
WebPKIClientAuth::optional(client_auth_roots)
}
} else {
NoClientAuth::new()
};
let mut config = rustls::ServerConfig::new(client_auth);
let certs = load_certs(args.flag_certs.as_ref().expect("--certs option missing"));
let privkey = load_private_key(args.flag_key.as_ref().expect("--key option missing"));
let ocsp = load_ocsp(&args.flag_ocsp);
config.set_single_cert_with_ocsp_and_sct(certs, privkey, ocsp, vec![]);
if args.flag_auth.is_some() {
let client_auth_roots = load_certs(args.flag_auth.as_ref().unwrap());
config.set_client_auth_roots(client_auth_roots, args.flag_require_auth);
}
if !args.flag_suite.is_empty() {
config.ciphersuites = lookup_suites(&args.flag_suite);
}

View File

@ -1,7 +1,7 @@
use webpki;
use untrusted;
use msgs::handshake::{DistinguishedName, DistinguishedNames};
pub use msgs::handshake::{DistinguishedName, DistinguishedNames};
use pemfile;
use x509;
use key;

View File

@ -265,7 +265,7 @@ pub use msgs::enums::SignatureScheme;
pub use error::TLSError;
pub use session::Session;
pub use stream::Stream;
pub use anchors::RootCertStore;
pub use anchors::{DistinguishedNames, RootCertStore};
pub use client::{StoresClientSessions, ClientSessionMemoryCache};
pub use client::{ClientConfig, ClientSession};
pub use client::ResolvesClientCert;
@ -274,6 +274,7 @@ pub use server::{ServerConfig, ServerSession};
pub use server::ResolvesServerCert;
pub use server::ProducesTickets;
pub use ticketer::Ticketer;
pub use verify::{NoClientAuth, WebPKIClientAuth};
pub use suites::{ALL_CIPHERSUITES, SupportedCipherSuite};
pub use key::{Certificate, PrivateKey};
@ -285,5 +286,4 @@ pub use verify::{ServerCertVerifier, ServerCertVerified,
ClientCertVerifier, ClientCertVerified};
#[cfg(feature = "dangerous_configuration")]
pub use client::danger::DangerousClientConfig;
#[cfg(feature = "dangerous_configuration")]
pub use server::danger::DangerousServerConfig;

View File

@ -425,11 +425,11 @@ impl ExpectClientHello {
}
fn emit_certificate_req_tls13(&mut self, sess: &mut ServerSessionImpl) -> bool {
if !sess.config.client_auth_offer {
if !sess.config.verifier.offer_client_auth() {
return false;
}
let names = sess.config.client_auth_roots.get_subjects();
let names = sess.config.verifier.client_auth_root_subjects();
let cr = CertificateRequestPayloadTLS13 {
context: PayloadU8::empty(),
@ -692,11 +692,13 @@ impl ExpectClientHello {
}
fn emit_certificate_req(&mut self, sess: &mut ServerSessionImpl) -> bool {
if !sess.config.client_auth_offer {
let client_auth = &sess.config.verifier;
if !client_auth.offer_client_auth() {
return false;
}
let names = sess.config.client_auth_roots.get_subjects();
let names = client_auth.client_auth_root_subjects();
let cr = CertificateRequestPayload {
certtypes: vec![ ClientCertificateType::RSASign,
@ -1184,7 +1186,8 @@ impl State for ExpectTLS12Certificate {
let cert_chain = extract_handshake!(m, HandshakePayload::Certificate).unwrap();
self.handshake.transcript.add_message(&m);
if cert_chain.is_empty() && !sess.config.client_auth_mandatory {
if cert_chain.is_empty() &&
!sess.config.verifier.client_auth_mandatory() {
info!("client auth requested but no certificate supplied");
self.handshake.transcript.abandon_client_auth();
return Ok(self.into_expect_tls12_client_kx(None));
@ -1192,8 +1195,7 @@ impl State for ExpectTLS12Certificate {
debug!("certs {:?}", cert_chain);
sess.config.get_verifier().verify_client_cert(&sess.config.client_auth_roots,
cert_chain)
sess.config.verifier.verify_client_cert(cert_chain)
.or_else(|err| {
incompatible(sess, "certificate invalid");
Err(err)
@ -1246,7 +1248,7 @@ impl State for ExpectTLS13Certificate {
let cert_chain = certp.convert();
if cert_chain.is_empty() {
if !sess.config.client_auth_mandatory {
if !sess.config.verifier.client_auth_mandatory() {
info!("client auth requested but no certificate supplied");
self.handshake.transcript.abandon_client_auth();
return Ok(self.into_expect_tls13_finished());
@ -1256,8 +1258,7 @@ impl State for ExpectTLS13Certificate {
return Err(TLSError::NoCertificatesPresented);
}
sess.config.get_verifier().verify_client_cert(&sess.config.client_auth_roots,
&cert_chain)?;
sess.config.get_verifier().verify_client_cert(&cert_chain)?;
let cert = ClientCertDetails::new(cert_chain);
Ok(self.into_expect_tls13_certificate_verify(cert))

View File

@ -9,7 +9,6 @@ use error::TLSError;
use rand;
use sign;
use verify;
use anchors;
use key;
use webpki;
@ -124,16 +123,6 @@ pub struct ServerConfig {
/// If empty we don't do ALPN at all.
pub alpn_protocols: Vec<String>,
/// List of client authentication root certificates.
pub client_auth_roots: anchors::RootCertStore,
/// Whether to attempt client auth.
pub client_auth_offer: bool,
/// Whether to complete handshakes with clients which
/// don't do client auth.
pub client_auth_mandatory: bool,
/// Supported protocol versions, in no particular order.
/// The default is all supported versions.
pub versions: Vec<ProtocolVersion>,
@ -283,9 +272,17 @@ impl ResolvesServerCert for AlwaysResolvesChain {
impl ServerConfig {
/// Make a `ServerConfig` with a default set of ciphersuites,
/// no keys/certificates, no ALPN protocols, no client auth, and
/// no session persistence.
pub fn new() -> ServerConfig {
/// no keys/certificates, no ALPN protocols, and no session persistence.
///
/// Publicly-available web servers on the internet generally don't do client
/// authentication; for this use case, `client_cert_verifier` should be a
/// `NoClientAuth`. Otherwise, use `WebPKIClientAuth` or another
/// implementation to enforce client authentication.
//
// We don't provide a default for `client_cert_verifier` because the safest
// default, requiring client authentication, requires additional
// configuration that we cannot provide reasonable defaults for.
pub fn new(client_cert_verifier: Arc<verify::ClientCertVerifier>) -> ServerConfig {
ServerConfig {
ciphersuites: ALL_CIPHERSUITES.to_vec(),
ignore_client_order: false,
@ -293,11 +290,8 @@ impl ServerConfig {
ticketer: Arc::new(NeverProducesTickets {}),
alpn_protocols: Vec::new(),
cert_resolver: Arc::new(FailResolveChain {}),
client_auth_roots: anchors::RootCertStore::empty(),
client_auth_offer: false,
client_auth_mandatory: false,
versions: vec![ ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2 ],
verifier: Arc::new(verify::WebPKIVerifier::new()),
verifier: client_cert_verifier,
}
}
@ -352,49 +346,6 @@ impl ServerConfig {
self.alpn_protocols.clear();
self.alpn_protocols.extend_from_slice(protocols);
}
/// Enables client authentication. The server will ask for
/// and validate certificates to the given list of root
/// `certs`. If `mandatory` is true, the server will fail
/// to handshake with a client if it does not do client auth.
pub fn set_client_auth_roots(&mut self, certs: Vec<key::Certificate>, mandatory: bool) {
for cert in certs {
self.client_auth_roots
.add(&cert)
.unwrap()
}
self.client_auth_offer = true;
self.client_auth_mandatory = mandatory;
}
/// Access configuration options whose use is dangerous and requires
/// extra care.
#[cfg(feature = "dangerous_configuration")]
pub fn dangerous(&mut self) -> danger::DangerousServerConfig {
danger::DangerousServerConfig { cfg: self }
}
}
/// Container for unsafe APIs
#[cfg(feature = "dangerous_configuration")]
pub mod danger {
use super::ServerConfig;
use super::verify::ClientCertVerifier;
use super::Arc;
/// Accessor for dangerous configuration options.
pub struct DangerousServerConfig<'a> {
/// The underlying ServerConfig
pub cfg: &'a mut ServerConfig
}
impl<'a> DangerousServerConfig<'a> {
/// Overrides the default `ClientCertVerifier` with something else.
pub fn set_certificate_verifier(&mut self,
verifier: Arc<ClientCertVerifier>) {
self.cfg.verifier = verifier;
}
}
}
pub struct ServerSessionImpl {
@ -416,7 +367,7 @@ impl fmt::Debug for ServerSessionImpl {
impl ServerSessionImpl {
pub fn new(server_config: &Arc<ServerConfig>) -> ServerSessionImpl {
let perhaps_client_auth = server_config.client_auth_offer;
let perhaps_client_auth = server_config.verifier.offer_client_auth();
ServerSessionImpl {
config: server_config.clone(),

View File

@ -2,13 +2,14 @@ use webpki;
use untrusted;
use sct;
use std;
use std::sync::Arc;
use key::Certificate;
use msgs::handshake::DigitallySignedStruct;
use msgs::handshake::SCTList;
use msgs::enums::SignatureScheme;
use error::TLSError;
use anchors::RootCertStore;
use anchors::{DistinguishedNames, RootCertStore};
type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
@ -70,10 +71,21 @@ pub trait ServerCertVerifier : Send + Sync {
/// Something that can verify a client certificate chain
pub trait ClientCertVerifier : Send + Sync {
/// Returns `true` to enable the server to request a client certificate and
/// `false` to skip requesting a client certificate. Defaults to `true`.
fn offer_client_auth(&self) -> bool { true }
/// Returns `true` to require a client certificate and `false` to make client
/// authentication optional. Defaults to `self.offer_client_auth()`.
fn client_auth_mandatory(&self) -> bool { self.offer_client_auth() }
/// Returns the subject names of the client authentication trust anchors to
/// share with the client when requesting client authentication.
fn client_auth_root_subjects<'a>(&'a self) -> DistinguishedNames;
/// Verify a certificate chain `presented_certs` is rooted in `roots`.
/// Does no further checking of the certificate.
fn verify_client_cert(&self,
roots: &RootCertStore,
presented_certs: &[Certificate]) -> Result<ClientCertVerified, TLSError>;
}
@ -99,15 +111,6 @@ impl ServerCertVerifier for WebPKIVerifier {
}
}
impl ClientCertVerifier for WebPKIVerifier {
fn verify_client_cert(&self,
roots: &RootCertStore,
presented_certs: &[Certificate]) -> Result<ClientCertVerified, TLSError> {
self.verify_common_cert(roots, presented_certs)
.map(|_| ClientCertVerified::assertion())
}
}
impl WebPKIVerifier {
pub fn new() -> WebPKIVerifier {
WebPKIVerifier {
@ -152,6 +155,76 @@ impl WebPKIVerifier {
}
}
/// Client certificate verification using the webpki crate.
pub struct WebPKIClientAuth {
roots: RootCertStore,
mandatory: bool,
}
impl WebPKIClientAuth {
/// Construct a new `WebPKIClientAuth` that will ensure that every client
/// provides a trusted certificate.
///
/// `roots` is the list of trust anchors to use for certificate validation.
pub fn mandatory(roots: RootCertStore) -> Arc<ClientCertVerifier> {
Arc::new(WebPKIClientAuth {
roots: roots,
mandatory: true,
})
}
/// Construct a new `WebPKIClientAuth` that will allow both anonymous and
/// authenticated clients.
///
/// If the client presents a certificate then it must be valid.
///
/// `roots` is the list of trust anchors to use for certificate validation.
pub fn optional(roots: RootCertStore) -> Arc<ClientCertVerifier> {
Arc::new(WebPKIClientAuth {
roots: roots,
mandatory: false,
})
}
}
impl ClientCertVerifier for WebPKIClientAuth {
fn offer_client_auth(&self) -> bool { true }
fn client_auth_mandatory(&self) -> bool { self.mandatory }
fn client_auth_root_subjects<'a>(&'a self) -> DistinguishedNames {
self.roots.get_subjects()
}
fn verify_client_cert(&self, presented_certs: &[Certificate])
-> Result<ClientCertVerified, TLSError> {
WebPKIVerifier::new().verify_common_cert(&self.roots, presented_certs)
.map(|_| ClientCertVerified::assertion())
}
}
/// Turns off client authentication.
pub struct NoClientAuth;
impl NoClientAuth {
/// Constructs a `NoClientAuth` and wraps it in an `Arc`.
pub fn new() -> Arc<ClientCertVerifier> { Arc::new(NoClientAuth) }
}
impl ClientCertVerifier for NoClientAuth {
fn offer_client_auth(&self) -> bool { false }
fn client_auth_root_subjects<'a>(&'a self) -> DistinguishedNames {
unimplemented!();
}
fn verify_client_cert(&self, _presented_certs: &[Certificate])
-> Result<ClientCertVerified, TLSError> {
unimplemented!();
}
}
static ECDSA_SHA256: SignatureAlgorithms = &[&webpki::ECDSA_P256_SHA256,
&webpki::ECDSA_P384_SHA256];
static ECDSA_SHA384: SignatureAlgorithms = &[&webpki::ECDSA_P256_SHA384,

View File

@ -15,6 +15,7 @@ use rustls::TLSError;
use rustls::sign;
use rustls::{Certificate, PrivateKey};
use rustls::internal::pemfile;
use rustls::{RootCertStore, NoClientAuth, WebPKIClientAuth};
extern crate webpki;
@ -50,7 +51,20 @@ fn get_key() -> PrivateKey {
}
fn make_server_config() -> ServerConfig {
let mut cfg = ServerConfig::new();
let mut cfg = ServerConfig::new(NoClientAuth::new());
cfg.set_single_cert(get_chain(), get_key());
cfg
}
fn make_server_config_with_mandatory_client_auth() -> ServerConfig {
let roots = get_chain();
let mut client_auth_roots = RootCertStore::empty();
for root in roots {
client_auth_roots.add(&root).unwrap();
}
let mut cfg = ServerConfig::new(WebPKIClientAuth::mandatory(client_auth_roots));
cfg.set_single_cert(get_chain(), get_key());
cfg
@ -279,9 +293,7 @@ fn client_can_get_server_cert() {
#[test]
fn server_can_get_client_cert() {
let mut client_config = make_client_config();
let mut server_config = make_server_config();
server_config.set_client_auth_roots(get_chain(), true);
let server_config = make_server_config_with_mandatory_client_auth();
client_config.set_single_client_cert(get_chain(), get_key());
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
@ -308,9 +320,8 @@ fn check_read_and_close(reader: &mut io::Read, expect: &[u8]) {
#[test]
fn server_close_notify() {
let mut client_config = make_client_config();
let mut server_config = make_server_config();
let server_config = make_server_config_with_mandatory_client_auth();
server_config.set_client_auth_roots(get_chain(), true);
client_config.set_single_client_cert(get_chain(), get_key());
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
@ -336,9 +347,8 @@ fn server_close_notify() {
#[test]
fn client_close_notify() {
let mut client_config = make_client_config();
let mut server_config = make_server_config();
let server_config = make_server_config_with_mandatory_client_auth();
server_config.set_client_auth_roots(get_chain(), true);
client_config.set_single_client_cert(get_chain(), get_key());
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
@ -522,10 +532,9 @@ impl ResolvesClientCert for ClientCheckCertResolve {
#[test]
fn client_cert_resolve() {
let mut client_config = make_client_config();
let mut server_config = make_server_config();
let server_config = make_server_config_with_mandatory_client_auth();
client_config.client_auth_cert_resolver = Arc::new(ClientCheckCertResolve::new(1));
server_config.set_client_auth_roots(get_chain(), true);
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
let mut client = ClientSession::new(&Arc::new(client_config), dns_name);