Add ALPN offer to server cert resolver

Breaking API change
This commit is contained in:
Joseph Birr-Pixton 2019-09-07 10:32:57 +01:00 committed by ctz
parent e92323fca5
commit 13d2a90235
9 changed files with 105 additions and 49 deletions

View File

@ -19,6 +19,13 @@ If you'd like to help out, please see [CONTRIBUTING.md](CONTRIBUTING.md).
## Release history:
* Next release:
- *Breaking API change*: ALPN protocols offered by the client are passed
to the server certificate resolution trait (`ResolvesServerCert`).
- Signature schemes offered by the client are now filtered to those
compatible with the client-offered ciphersuites. Prior to this change
it was likely that server key type switching would not work for clients
that offer signature schemes mismatched with their ciphersuites.
* 0.16.0 (2019-08-10):
- Optimisation of read path for polled non-blocking IO.
- Correct an omission in TLS1.3 middlebox compatibility mode, causing

View File

@ -217,8 +217,10 @@ struct FixedSignatureSchemeServerCertResolver {
impl rustls::ResolvesServerCert for FixedSignatureSchemeServerCertResolver {
fn resolve(&self,
server_name: Option<webpki::DNSNameRef<'_>>,
sigschemes: &[rustls::SignatureScheme]) -> Option<rustls::sign::CertifiedKey> {
let mut certkey = self.resolver.resolve(server_name, sigschemes)?;
sigschemes: &[rustls::SignatureScheme],
alpn: Option<&[&[u8]]>
) -> Option<rustls::sign::CertifiedKey> {
let mut certkey = self.resolver.resolve(server_name, sigschemes, alpn)?;
certkey.key = Arc::new(Box::new(FixedSignatureSchemeSigningKey {
key: certkey.key.clone(),
scheme: self.scheme,

View File

@ -333,7 +333,7 @@ pub type ProtocolNameList = VecU16OfPayloadU8;
pub trait ConvertProtocolNameList {
fn from_slices(names: &[&[u8]]) -> Self;
fn to_vecs(&self) -> Vec<Vec<u8>>;
fn to_slices(&self) -> Vec<&[u8]>;
fn as_single_slice(&self) -> Option<&[u8]>;
}
@ -348,12 +348,10 @@ impl ConvertProtocolNameList for ProtocolNameList {
ret
}
fn to_vecs(&self) -> Vec<Vec<u8>> {
let mut ret = Vec::new();
for proto in self {
ret.push(proto.0.clone());
}
ret
fn to_slices(&self) -> Vec<&[u8]> {
self.iter()
.map(|proto| -> &[u8] { &proto.0 })
.collect::<Vec<&[u8]>>()
}
fn as_single_slice(&self) -> Option<&[u8]> {

View File

@ -297,8 +297,8 @@ fn can_roundtrip_multi_proto() {
match ext {
ClientExtension::Protocols(prot) => {
assert_eq!(2, prot.len());
assert_eq!(vec![b"hi".to_vec(), b"lo".to_vec()],
prot.to_vecs());
assert_eq!(vec![b"hi", b"lo"],
prot.to_slices());
assert_eq!(prot.as_single_slice(), None);
}
_ => unreachable!()
@ -323,7 +323,7 @@ fn can_roundtrip_single_proto() {
match ext {
ClientExtension::Protocols(prot) => {
assert_eq!(1, prot.len());
assert_eq!(vec![b"hi".to_vec()], prot.to_vecs());
assert_eq!(vec![b"hi"], prot.to_slices());
assert_eq!(prot.as_single_slice(), Some(&b"hi"[..]));
}
_ => unreachable!()

View File

@ -97,7 +97,8 @@ pub struct FailResolveChain {}
impl server::ResolvesServerCert for FailResolveChain {
fn resolve(&self,
_server_name: Option<webpki::DNSNameRef>,
_sigschemes: &[SignatureScheme])
_sigschemes: &[SignatureScheme],
_alpn_protocols: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey> {
None
}
@ -138,7 +139,8 @@ impl AlwaysResolvesChain {
impl server::ResolvesServerCert for AlwaysResolvesChain {
fn resolve(&self,
_server_name: Option<webpki::DNSNameRef>,
_sigschemes: &[SignatureScheme])
_sigschemes: &[SignatureScheme],
_alpn_protocols: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey> {
Some(self.0.clone())
}
@ -174,7 +176,8 @@ impl ResolvesServerCertUsingSNI {
impl server::ResolvesServerCert for ResolvesServerCertUsingSNI {
fn resolve(&self,
server_name: Option<webpki::DNSNameRef>,
_sigschemes: &[SignatureScheme])
_sigschemes: &[SignatureScheme],
_alpn_protocols: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey> {
if let Some(name) = server_name {
self.by_name.get(name.into())

View File

@ -154,15 +154,15 @@ impl ExtensionProcessing {
let our_protocols = &sess.config.alpn_protocols;
let maybe_their_protocols = hello.get_alpn_extension();
if let Some(their_protocols) = maybe_their_protocols {
let their_proto_vecs = their_protocols.to_vecs();
let their_protocols = their_protocols.to_slices();
if their_proto_vecs.iter().any(Vec::is_empty) {
if their_protocols.iter().any(|protocol| protocol.is_empty()) {
return Err(TLSError::PeerMisbehavedError("client offered empty ALPN protocol"
.to_string()));
}
sess.alpn_protocol = our_protocols.iter()
.filter(|protocol| their_proto_vecs.contains(protocol))
.filter(|protocol| their_protocols.contains(&protocol.as_slice()))
.nth(0)
.cloned();
if let Some(ref selected_protocol) = sess.alpn_protocol {
@ -600,12 +600,24 @@ impl State for ExpectClientHello {
.unwrap_or_else(SupportedSignatureSchemes::default);
sigschemes_ext.retain(|scheme| suites::compatible_sigscheme_for_suites(*scheme, &common_suites));
let alpn_protocols = client_hello.get_alpn_extension()
.map(|protos| protos.to_slices());
// Choose a certificate.
let mut certkey = {
let sni_ref = sni.as_ref().map(webpki::DNSName::as_ref);
trace!("sni {:?}", sni_ref);
trace!("sig schemes {:?}", sigschemes_ext);
let certkey = sess.config.cert_resolver.resolve(sni_ref, &sigschemes_ext);
trace!("alpn protocols {:?}", alpn_protocols);
let alpn_slices = match alpn_protocols {
Some(ref vec) => Some(vec.as_slice()),
None => None,
};
let certkey = sess.config.cert_resolver.resolve(sni_ref,
&sigschemes_ext,
alpn_slices);
certkey.ok_or_else(|| {
sess.common.send_fatal_alert(AlertDescription::AccessDenied);
TLSError::General("no server certificate chain resolved".to_string())

View File

@ -96,13 +96,24 @@ pub trait ProducesTickets : Send + Sync {
/// in server authentication.
pub trait ResolvesServerCert : Send + Sync {
/// Choose a certificate chain and matching key given any server DNS
/// name provided via SNI, and signature schemes.
/// name provided via SNI, any ALPN protocol names, and signature schemes
/// advertised by the client.
///
/// The certificate chain is returned as a vec of `Certificate`s,
/// the key is inside a `SigningKey`.
/// `server_name` is `None` if the client did not supply an SNI name.
///
/// `sigschemes` is replaced with a standard-specified default if the client
/// omitted this extension.
///
/// `alpn_protocols` is `None` if the client did not include an ALPN extension.
///
/// Return a `CertifiedKey` to continue the handshake. A `CertifiedKey`
/// binds together a signing key and a certificate chain.
///
/// Return `None` to abort the handshake.
fn resolve(&self,
server_name: Option<webpki::DNSNameRef>,
sigschemes: &[SignatureScheme])
sigschemes: &[SignatureScheme],
alpn_protocols: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey>;
}

View File

@ -30,7 +30,7 @@ pub trait Signer : Send + Sync {
}
/// A packaged-together certificate chain, matching `SigningKey` and
/// optional stapled OCSP response and/or SCT.
/// optional stapled OCSP response and/or SCT list.
#[derive(Clone)]
pub struct CertifiedKey {
/// The certificate chain.

View File

@ -310,39 +310,28 @@ fn client_close_notify() {
}
}
#[derive(Default)]
struct ServerCheckCertResolve {
expected_sni: String,
expected_sni: Option<String>,
expected_sigalgs: Option<Vec<SignatureScheme>>,
}
impl ServerCheckCertResolve {
fn new(expected_sni: &str) -> ServerCheckCertResolve {
ServerCheckCertResolve {
expected_sni: expected_sni.to_string(),
expected_sigalgs: None,
}
}
expected_alpn: Option<Vec<Vec<u8>>>,
}
impl ResolvesServerCert for ServerCheckCertResolve {
fn resolve(&self,
server_name: Option<webpki::DNSNameRef<'_>>,
sigschemes: &[SignatureScheme])
sigschemes: &[SignatureScheme],
alpn: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey> {
if let Some(got_dns_name) = server_name {
let got: &str = got_dns_name.into();
if got != self.expected_sni {
panic!("unexpected dns name (wanted '{}' got '{:?}')",
&self.expected_sni, got_dns_name);
}
} else {
panic!("dns name not provided (wanted '{}')", &self.expected_sni);
}
if sigschemes.len() == 0 {
panic!("no signature schemes shared by client");
}
if let Some(expected_sni) = &self.expected_sni {
let sni: &str = server_name.expect("sni unexpectedly absent").into();
assert_eq!(expected_sni, sni);
}
if let Some(expected_sigalgs) = &self.expected_sigalgs {
if expected_sigalgs != &sigschemes {
panic!("unexpected signature schemes (wanted {:?} got {:?})",
@ -350,6 +339,15 @@ impl ResolvesServerCert for ServerCheckCertResolve {
}
}
if let Some(expected_alpn) = &self.expected_alpn {
let alpn = alpn.expect("alpn unexpectedly absent");
assert_eq!(alpn.len(), expected_alpn.len());
for (got, wanted) in alpn.iter().zip(expected_alpn.iter()) {
assert_eq!(got, &wanted.as_slice());
}
}
None
}
}
@ -360,7 +358,10 @@ fn server_cert_resolve_with_sni() {
let client_config = make_client_config(*kt);
let mut server_config = make_server_config(*kt);
server_config.cert_resolver = Arc::new(ServerCheckCertResolve::new("the-value-from-sni"));
server_config.cert_resolver = Arc::new(ServerCheckCertResolve {
expected_sni: Some("the-value-from-sni".into()),
..Default::default()
});
let mut client = ClientSession::new(&Arc::new(client_config), dns_name("the-value-from-sni"));
let mut server = ServerSession::new(&Arc::new(server_config));
@ -370,6 +371,27 @@ fn server_cert_resolve_with_sni() {
}
}
#[test]
fn server_cert_resolve_with_alpn() {
for kt in ALL_KEY_TYPES.iter() {
let mut client_config = make_client_config(*kt);
client_config.alpn_protocols = vec!["foo".into(), "bar".into()];
let mut server_config = make_server_config(*kt);
server_config.cert_resolver = Arc::new(ServerCheckCertResolve {
expected_alpn: Some(vec![ b"foo".to_vec(), b"bar".to_vec() ]),
..Default::default()
});
let mut client = ClientSession::new(&Arc::new(client_config), dns_name("sni-value"));
let mut server = ServerSession::new(&Arc::new(server_config));
let err = do_handshake_until_error(&mut client, &mut server);
assert_eq!(err.is_err(), true);
}
}
fn check_sigalgs_reduced_by_ciphersuite(kt: KeyType, suite: CipherSuite,
expected_sigalgs: Vec<SignatureScheme>) {
let mut client_config = make_client_config(kt);
@ -378,11 +400,11 @@ fn check_sigalgs_reduced_by_ciphersuite(kt: KeyType, suite: CipherSuite,
let mut server_config = make_server_config(kt);
server_config.cert_resolver = Arc::new(ServerCheckCertResolve {
expected_sni: "sni-value".into(),
expected_sigalgs: Some(expected_sigalgs),
..Default::default()
});
let mut client = ClientSession::new(&Arc::new(client_config), dns_name("sni-value"));
let mut client = ClientSession::new(&Arc::new(client_config), dns_name("localhost"));
let mut server = ServerSession::new(&Arc::new(server_config));
let err = do_handshake_until_error(&mut client, &mut server);
@ -422,7 +444,8 @@ struct ServerCheckNoSNI {}
impl ResolvesServerCert for ServerCheckNoSNI {
fn resolve(&self,
server_name: Option<webpki::DNSNameRef<'_>>,
_sigschemes: &[SignatureScheme])
_sigschemes: &[SignatureScheme],
_alpn: Option<&[&[u8]]>)
-> Option<sign::CertifiedKey> {
assert!(server_name.is_none());