mirror of https://github.com/ctz/rustls
Add ALPN offer to server cert resolver
Breaking API change
This commit is contained in:
parent
e92323fca5
commit
13d2a90235
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]> {
|
||||
|
|
|
@ -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!()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Reference in New Issue