mirror of https://github.com/ctz/rustls
Revert "Remove support for SCT stapling"
This reverts commit 4f0a7e0426
.
This commit is contained in:
parent
0018e7586c
commit
777cc07a4b
|
@ -20,9 +20,6 @@ If you'd like to help out, please see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|||
|
||||
## Release history
|
||||
|
||||
* Next release:
|
||||
- *Breaking change*: remove support for SCT stapling. Ecosystem support for this is rare compared to
|
||||
inclusion of SCTs in certificates.
|
||||
* Release 0.21.2 (2023-06-14)
|
||||
- Bump MSRV to 1.60 to track similar change in dependencies.
|
||||
- Differentiate between unexpected and expected EOF in `Stream` and `OwnedStream`.
|
||||
|
@ -112,6 +109,8 @@ obsolete cryptography.
|
|||
* Extended master secret support ([RFC7627](https://tools.ietf.org/html/rfc7627)).
|
||||
* Exporters ([RFC5705](https://tools.ietf.org/html/rfc5705)).
|
||||
* OCSP stapling by servers.
|
||||
* SCT stapling by servers.
|
||||
* SCT verification by clients.
|
||||
|
||||
## Possible future features
|
||||
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
"EmptyExtensions-ServerHello-TLS12": "",
|
||||
"Server-JDK11*": "workarounds for oracle engineering quality",
|
||||
"Client-RejectJDK11DowngradeRandom": "",
|
||||
"SendUnsolicitedSCTOnCertificate-TLS13": "SCT stapling not supported",
|
||||
"SignedCertificateTimestampListEmpty-Client-*": "",
|
||||
"SignedCertificateTimestampListEmptySCT-Client-*": "",
|
||||
"SendSCTListOnResume-TLS-TLS12": "",
|
||||
"IgnoreExtensionsOnIntermediates-TLS13": "assumes SCT support",
|
||||
"CBCRecordSplitting*": "insane ciphersuites",
|
||||
"*CBCPadding*": "",
|
||||
"RSAEphemeralKey": "",
|
||||
|
@ -327,6 +322,7 @@
|
|||
"NegotiatePSKResumption-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"PointFormat-Client-MissingUncompressed": ":PEER_MISBEHAVIOUR:",
|
||||
"SendUnsolicitedOCSPOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"SendUnsolicitedSCTOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"UnsolicitedServerNameAck-TLS-TLS12": ":PEER_MISBEHAVIOUR:",
|
||||
"UnsolicitedServerNameAck-TLS-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"TicketSessionIDLength-33-TLS-TLS12": ":BAD_HANDSHAKE_MSG:",
|
||||
|
@ -336,6 +332,10 @@
|
|||
"Ed25519DefaultDisable-NoAccept": ":PEER_MISBEHAVIOUR:",
|
||||
"SendUnknownExtensionOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"SendDuplicateExtensionsOnCerts-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"SignedCertificateTimestampListEmpty-Client-TLS-TLS12": ":PEER_MISBEHAVIOUR:",
|
||||
"SignedCertificateTimestampListEmpty-Client-TLS-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"SignedCertificateTimestampListEmptySCT-Client-TLS-TLS12": ":PEER_MISBEHAVIOUR:",
|
||||
"SignedCertificateTimestampListEmptySCT-Client-TLS-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"EMS-Forbidden-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
"Unclean-Shutdown": ":CLOSE_WITHOUT_CLOSE_NOTIFY:",
|
||||
"SendExtensionOnClientCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
|
||||
|
|
|
@ -344,6 +344,7 @@ mod danger {
|
|||
_end_entity: &rustls::Certificate,
|
||||
_intermediates: &[rustls::Certificate],
|
||||
_server_name: &rustls::ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp: &[u8],
|
||||
_now: std::time::SystemTime,
|
||||
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
|
||||
|
|
|
@ -597,7 +597,7 @@ fn make_config(args: &Args) -> Arc<rustls::ServerConfig> {
|
|||
.with_protocol_versions(&versions)
|
||||
.expect("inconsistent cipher-suites/versions specified")
|
||||
.with_client_cert_verifier(client_auth)
|
||||
.with_single_cert_with_ocsp(certs, privkey, ocsp)
|
||||
.with_single_cert_with_ocsp_and_sct(certs, privkey, ocsp, vec![])
|
||||
.expect("bad certificates/private key");
|
||||
|
||||
config.key_log = Arc::new(rustls::KeyLogFile::new());
|
||||
|
|
|
@ -18,6 +18,7 @@ rustversion = { version = "1.0.6", optional = true }
|
|||
[dependencies]
|
||||
log = { version = "0.4.4", optional = true }
|
||||
ring = "0.16.20"
|
||||
sct = "0.7.0"
|
||||
webpki = { package = "rustls-webpki", version = "0.100.0", features = ["alloc", "std"] }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -50,6 +50,7 @@ struct Options {
|
|||
check_close_notify: bool,
|
||||
host_name: String,
|
||||
use_sni: bool,
|
||||
send_sct: bool,
|
||||
key_file: String,
|
||||
cert_file: String,
|
||||
protocols: Vec<String>,
|
||||
|
@ -59,6 +60,7 @@ struct Options {
|
|||
min_version: Option<ProtocolVersion>,
|
||||
max_version: Option<ProtocolVersion>,
|
||||
server_ocsp_response: Vec<u8>,
|
||||
server_sct_list: Vec<u8>,
|
||||
use_signing_scheme: u16,
|
||||
curves: Option<Vec<u16>>,
|
||||
export_keying_material: usize,
|
||||
|
@ -89,6 +91,7 @@ impl Options {
|
|||
resume_with_tickets_disabled: false,
|
||||
host_name: "example.com".to_string(),
|
||||
use_sni: false,
|
||||
send_sct: false,
|
||||
queue_data: false,
|
||||
queue_data_on_resume: false,
|
||||
only_write_one_byte_after_handshake: false,
|
||||
|
@ -106,6 +109,7 @@ impl Options {
|
|||
min_version: None,
|
||||
max_version: None,
|
||||
server_ocsp_response: vec![],
|
||||
server_sct_list: vec![],
|
||||
use_signing_scheme: 0,
|
||||
curves: None,
|
||||
export_keying_material: 0,
|
||||
|
@ -211,7 +215,9 @@ impl server::ClientCertVerifier for DummyClientAuth {
|
|||
}
|
||||
}
|
||||
|
||||
struct DummyServerAuth {}
|
||||
struct DummyServerAuth {
|
||||
send_sct: bool,
|
||||
}
|
||||
|
||||
impl client::ServerCertVerifier for DummyServerAuth {
|
||||
fn verify_server_cert(
|
||||
|
@ -219,11 +225,16 @@ impl client::ServerCertVerifier for DummyServerAuth {
|
|||
_end_entity: &Certificate,
|
||||
_certs: &[Certificate],
|
||||
_hostname: &ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp: &[u8],
|
||||
_now: SystemTime,
|
||||
) -> Result<client::ServerCertVerified, Error> {
|
||||
Ok(client::ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn request_scts(&self) -> bool {
|
||||
self.send_sct
|
||||
}
|
||||
}
|
||||
|
||||
struct FixedSignatureSchemeSigningKey {
|
||||
|
@ -407,7 +418,12 @@ fn make_server_cfg(opts: &Options) -> Arc<ServerConfig> {
|
|||
.with_protocol_versions(&opts.supported_versions())
|
||||
.unwrap()
|
||||
.with_client_cert_verifier(client_auth)
|
||||
.with_single_cert_with_ocsp(cert.clone(), key, opts.server_ocsp_response.clone())
|
||||
.with_single_cert_with_ocsp_and_sct(
|
||||
cert.clone(),
|
||||
key,
|
||||
opts.server_ocsp_response.clone(),
|
||||
opts.server_sct_list.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay);
|
||||
|
@ -522,7 +538,9 @@ fn make_client_cfg(opts: &Options) -> Arc<ClientConfig> {
|
|||
.with_kx_groups(&kx_groups)
|
||||
.with_protocol_versions(&opts.supported_versions())
|
||||
.expect("inconsistent settings")
|
||||
.with_custom_certificate_verifier(Arc::new(DummyServerAuth {}));
|
||||
.with_custom_certificate_verifier(Arc::new(DummyServerAuth {
|
||||
send_sct: opts.send_sct,
|
||||
}));
|
||||
|
||||
let mut cfg = if !opts.cert_file.is_empty() && !opts.key_file.is_empty() {
|
||||
let cert = load_cert(&opts.cert_file);
|
||||
|
@ -973,7 +991,6 @@ fn main() {
|
|||
"-on-resume-expect-no-offer-early-data" |
|
||||
"-key-update" | //< we could implement an API for this
|
||||
"-expect-tls13-downgrade" |
|
||||
"-enable-signed-cert-timestamps" |
|
||||
"-expect-session-id" => {
|
||||
println!("not checking {}; NYI", arg);
|
||||
}
|
||||
|
@ -1003,6 +1020,16 @@ fn main() {
|
|||
opts.server_ocsp_response = BASE64_STANDARD.decode(args.remove(0).as_bytes())
|
||||
.expect("invalid base64");
|
||||
}
|
||||
"-signed-cert-timestamps" => {
|
||||
opts.server_sct_list = BASE64_STANDARD.decode(args.remove(0).as_bytes())
|
||||
.expect("invalid base64");
|
||||
|
||||
if opts.server_sct_list.len() == 2 &&
|
||||
opts.server_sct_list[0] == 0x00 &&
|
||||
opts.server_sct_list[1] == 0x00 {
|
||||
quit(":INVALID_SCT_LIST:");
|
||||
}
|
||||
}
|
||||
"-select-alpn" => {
|
||||
opts.protocols.push(args.remove(0));
|
||||
}
|
||||
|
@ -1038,6 +1065,9 @@ fn main() {
|
|||
"-use-null-client-ca-list" => {
|
||||
opts.offer_no_client_cas = true;
|
||||
}
|
||||
"-enable-signed-cert-timestamps" => {
|
||||
opts.send_sct = true;
|
||||
}
|
||||
"-enable-early-data" => {
|
||||
opts.tickets = false;
|
||||
opts.enable_early_data = true;
|
||||
|
@ -1170,7 +1200,6 @@ fn main() {
|
|||
"-wpa-202304" |
|
||||
"-srtp-profiles" |
|
||||
"-permute-extensions" |
|
||||
"-signed-cert-timestamps" |
|
||||
"-on-initial-expect-peer-cert-file" => {
|
||||
println!("NYI option {:?}", arg);
|
||||
process::exit(BOGO_NACK);
|
||||
|
|
|
@ -4,26 +4,27 @@ use crate::error::Error;
|
|||
use crate::key_log::NoKeyLog;
|
||||
use crate::kx::SupportedKxGroup;
|
||||
use crate::suites::SupportedCipherSuite;
|
||||
use crate::verify;
|
||||
use crate::verify::{self, CertificateTransparencyPolicy};
|
||||
use crate::{anchors, key, versions};
|
||||
|
||||
use super::client_conn::Resumption;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
|
||||
impl ConfigBuilder<ClientConfig, WantsVerifier> {
|
||||
/// Choose how to verify server certificates.
|
||||
pub fn with_root_certificates(
|
||||
self,
|
||||
root_store: anchors::RootCertStore,
|
||||
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
|
||||
) -> ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
|
||||
ConfigBuilder {
|
||||
state: WantsClientCert {
|
||||
state: WantsTransparencyPolicyOrClientCert {
|
||||
cipher_suites: self.state.cipher_suites,
|
||||
kx_groups: self.state.kx_groups,
|
||||
versions: self.state.versions,
|
||||
verifier: Arc::new(verify::WebPkiVerifier::new(root_store)),
|
||||
root_store,
|
||||
},
|
||||
side: PhantomData,
|
||||
}
|
||||
|
@ -47,6 +48,90 @@ impl ConfigBuilder<ClientConfig, WantsVerifier> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A config builder state where the caller needs to supply a certificate transparency policy or
|
||||
/// client certificate resolver.
|
||||
///
|
||||
/// In this state, the caller can optionally enable certificate transparency, or ignore CT and
|
||||
/// invoke one of the methods related to client certificates (as in the [`WantsClientCert`] state).
|
||||
///
|
||||
/// For more information, see the [`ConfigBuilder`] documentation.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WantsTransparencyPolicyOrClientCert {
|
||||
cipher_suites: Vec<SupportedCipherSuite>,
|
||||
kx_groups: Vec<&'static SupportedKxGroup>,
|
||||
versions: versions::EnabledVersions,
|
||||
root_store: anchors::RootCertStore,
|
||||
}
|
||||
|
||||
impl ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
|
||||
/// Set Certificate Transparency logs to use for server certificate validation.
|
||||
///
|
||||
/// Because Certificate Transparency logs are sharded on a per-year basis and can be trusted or
|
||||
/// distrusted relatively quickly, rustls stores a validation deadline. Server certificates will
|
||||
/// be validated against the configured CT logs until the deadline expires. After the deadline,
|
||||
/// certificates will no longer be validated, and a warning message will be logged. The deadline
|
||||
/// may vary depending on how often you deploy builds with updated dependencies.
|
||||
pub fn with_certificate_transparency_logs(
|
||||
self,
|
||||
logs: &'static [&'static sct::Log],
|
||||
validation_deadline: SystemTime,
|
||||
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
|
||||
self.with_logs(Some(CertificateTransparencyPolicy::new(
|
||||
logs,
|
||||
validation_deadline,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Sets a single certificate chain and matching private key for use
|
||||
/// in client authentication.
|
||||
///
|
||||
/// `cert_chain` is a vector of DER-encoded certificates.
|
||||
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
|
||||
///
|
||||
/// This function fails if `key_der` is invalid.
|
||||
pub fn with_single_cert(
|
||||
self,
|
||||
cert_chain: Vec<key::Certificate>,
|
||||
key_der: key::PrivateKey,
|
||||
) -> Result<ClientConfig, Error> {
|
||||
self.with_logs(None)
|
||||
.with_single_cert(cert_chain, key_der)
|
||||
}
|
||||
|
||||
/// Do not support client auth.
|
||||
pub fn with_no_client_auth(self) -> ClientConfig {
|
||||
self.with_logs(None)
|
||||
.with_client_cert_resolver(Arc::new(handy::FailResolveClientCert {}))
|
||||
}
|
||||
|
||||
/// Sets a custom [`ResolvesClientCert`].
|
||||
pub fn with_client_cert_resolver(
|
||||
self,
|
||||
client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
|
||||
) -> ClientConfig {
|
||||
self.with_logs(None)
|
||||
.with_client_cert_resolver(client_auth_cert_resolver)
|
||||
}
|
||||
|
||||
fn with_logs(
|
||||
self,
|
||||
ct_policy: Option<CertificateTransparencyPolicy>,
|
||||
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
|
||||
ConfigBuilder {
|
||||
state: WantsClientCert {
|
||||
cipher_suites: self.state.cipher_suites,
|
||||
kx_groups: self.state.kx_groups,
|
||||
versions: self.state.versions,
|
||||
verifier: Arc::new(verify::WebPkiVerifier::new(
|
||||
self.state.root_store,
|
||||
ct_policy,
|
||||
)),
|
||||
},
|
||||
side: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A config builder state where the caller needs to supply whether and how to provide a client
|
||||
/// certificate.
|
||||
///
|
||||
|
|
|
@ -2,8 +2,8 @@ use super::ResolvesClientCert;
|
|||
#[cfg(feature = "logging")]
|
||||
use crate::log::{debug, trace};
|
||||
use crate::msgs::enums::ExtensionType;
|
||||
use crate::msgs::handshake::ServerExtension;
|
||||
use crate::msgs::handshake::{CertificatePayload, DistinguishedName};
|
||||
use crate::msgs::handshake::{Sct, ServerExtension};
|
||||
use crate::{sign, SignatureScheme};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
@ -12,15 +12,29 @@ use std::sync::Arc;
|
|||
pub(super) struct ServerCertDetails {
|
||||
pub(super) cert_chain: CertificatePayload,
|
||||
pub(super) ocsp_response: Vec<u8>,
|
||||
pub(super) scts: Option<Vec<Sct>>,
|
||||
}
|
||||
|
||||
impl ServerCertDetails {
|
||||
pub(super) fn new(cert_chain: CertificatePayload, ocsp_response: Vec<u8>) -> Self {
|
||||
pub(super) fn new(
|
||||
cert_chain: CertificatePayload,
|
||||
ocsp_response: Vec<u8>,
|
||||
scts: Option<Vec<Sct>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cert_chain,
|
||||
ocsp_response,
|
||||
scts,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn scts(&self) -> impl Iterator<Item = &[u8]> {
|
||||
self.scts
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|payload| payload.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ClientHelloDetails {
|
||||
|
@ -34,6 +48,11 @@ impl ClientHelloDetails {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn server_may_send_sct_list(&self) -> bool {
|
||||
self.sent_extensions
|
||||
.contains(&ExtensionType::SCT)
|
||||
}
|
||||
|
||||
pub(super) fn server_sent_unsolicited_extensions(
|
||||
&self,
|
||||
received_exts: &[ServerExtension],
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::msgs::base::Payload;
|
|||
use crate::msgs::enums::{Compression, ExtensionType};
|
||||
use crate::msgs::enums::{ECPointFormat, PSKKeyExchangeMode};
|
||||
use crate::msgs::handshake::ConvertProtocolNameList;
|
||||
use crate::msgs::handshake::{CertificateStatusRequest, ClientSessionTicket};
|
||||
use crate::msgs::handshake::{CertificateStatusRequest, ClientSessionTicket, Sct};
|
||||
use crate::msgs::handshake::{ClientExtension, HasServerExtensions};
|
||||
use crate::msgs::handshake::{ClientHelloPayload, HandshakeMessagePayload, HandshakePayload};
|
||||
use crate::msgs::handshake::{HelloRetryRequest, KeyShareEntry};
|
||||
|
@ -141,11 +141,13 @@ pub(super) fn start_handshake(
|
|||
None => SessionId::random()?,
|
||||
};
|
||||
|
||||
let may_send_sct_list = config.verifier.request_scts();
|
||||
Ok(emit_client_hello_for_retry(
|
||||
transcript_buffer,
|
||||
None,
|
||||
key_share,
|
||||
extra_exts,
|
||||
may_send_sct_list,
|
||||
None,
|
||||
ClientHelloInput {
|
||||
config,
|
||||
|
@ -192,6 +194,7 @@ fn emit_client_hello_for_retry(
|
|||
retryreq: Option<&HelloRetryRequest>,
|
||||
key_share: Option<kx::KeyExchange>,
|
||||
extra_exts: Vec<ClientExtension>,
|
||||
may_send_sct_list: bool,
|
||||
suite: Option<SupportedCipherSuite>,
|
||||
mut input: ClientHelloInput,
|
||||
cx: &mut ClientContext<'_>,
|
||||
|
@ -235,6 +238,10 @@ fn emit_client_hello_for_retry(
|
|||
exts.push(ClientExtension::make_sni(sni_name));
|
||||
}
|
||||
|
||||
if may_send_sct_list {
|
||||
exts.push(ClientExtension::SignedCertificateTimestampRequest);
|
||||
}
|
||||
|
||||
if let Some(key_share) = &key_share {
|
||||
debug_assert!(support_tls13);
|
||||
let key_share = KeyShareEntry::new(key_share.group(), key_share.pubkey.as_ref());
|
||||
|
@ -472,6 +479,13 @@ pub(super) fn process_alpn_protocol(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn sct_list_is_invalid(scts: &[Sct]) -> bool {
|
||||
scts.is_empty()
|
||||
|| scts
|
||||
.iter()
|
||||
.any(|sct| sct.as_ref().is_empty())
|
||||
}
|
||||
|
||||
impl State<ClientConnectionData> for ExpectServerHello {
|
||||
fn handle(mut self: Box<Self>, cx: &mut ClientContext<'_>, m: Message) -> NextStateOrError {
|
||||
let server_hello =
|
||||
|
@ -786,6 +800,12 @@ impl ExpectServerHelloOrHelloRetryRequest {
|
|||
cx.data.early_data.rejected();
|
||||
}
|
||||
|
||||
let may_send_sct_list = self
|
||||
.next
|
||||
.input
|
||||
.hello
|
||||
.server_may_send_sct_list();
|
||||
|
||||
let key_share = match req_group {
|
||||
Some(group) if group != offered_key_share.group() => {
|
||||
let group = kx::KeyExchange::choose(group, &config.kx_groups).ok_or_else(|| {
|
||||
|
@ -804,6 +824,7 @@ impl ExpectServerHelloOrHelloRetryRequest {
|
|||
Some(hrr),
|
||||
Some(key_share),
|
||||
self.extra_exts,
|
||||
may_send_sct_list,
|
||||
Some(cs),
|
||||
self.next.input,
|
||||
cx,
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::msgs::base::{Payload, PayloadU8};
|
|||
use crate::msgs::ccs::ChangeCipherSpecPayload;
|
||||
use crate::msgs::codec::Codec;
|
||||
use crate::msgs::handshake::{
|
||||
CertificatePayload, HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload,
|
||||
CertificatePayload, HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, Sct,
|
||||
ServerECDHParams, SessionId,
|
||||
};
|
||||
use crate::msgs::message::{Message, MessagePayload};
|
||||
|
@ -102,6 +102,18 @@ mod server_hello {
|
|||
debug!("Server may staple OCSP response");
|
||||
}
|
||||
|
||||
// Save any sent SCTs for verification against the certificate.
|
||||
let server_cert_sct_list = if let Some(sct_list) = server_hello.get_sct_list() {
|
||||
debug!("Server sent {:?} SCTs", sct_list.len());
|
||||
|
||||
if hs::sct_list_is_invalid(sct_list) {
|
||||
return Err(PeerMisbehaved::InvalidSctList.into());
|
||||
}
|
||||
Some(sct_list.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// See if we're successfully resuming.
|
||||
if let Some(ref resuming) = self.resuming_session {
|
||||
if resuming.session_id == server_hello.session_id {
|
||||
|
@ -175,6 +187,7 @@ mod server_hello {
|
|||
suite,
|
||||
may_send_cert_status,
|
||||
must_issue_new_ticket,
|
||||
server_cert_sct_list,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +204,7 @@ struct ExpectCertificate {
|
|||
pub(super) suite: &'static Tls12CipherSuite,
|
||||
may_send_cert_status: bool,
|
||||
must_issue_new_ticket: bool,
|
||||
server_cert_sct_list: Option<Vec<Sct>>,
|
||||
}
|
||||
|
||||
impl State<ClientConnectionData> for ExpectCertificate {
|
||||
|
@ -216,11 +230,13 @@ impl State<ClientConnectionData> for ExpectCertificate {
|
|||
using_ems: self.using_ems,
|
||||
transcript: self.transcript,
|
||||
suite: self.suite,
|
||||
server_cert_sct_list: self.server_cert_sct_list,
|
||||
server_cert_chain,
|
||||
must_issue_new_ticket: self.must_issue_new_ticket,
|
||||
}))
|
||||
} else {
|
||||
let server_cert = ServerCertDetails::new(server_cert_chain, vec![]);
|
||||
let server_cert =
|
||||
ServerCertDetails::new(server_cert_chain, vec![], self.server_cert_sct_list);
|
||||
|
||||
Ok(Box::new(ExpectServerKx {
|
||||
config: self.config,
|
||||
|
@ -247,6 +263,7 @@ struct ExpectCertificateStatusOrServerKx {
|
|||
using_ems: bool,
|
||||
transcript: HandshakeHash,
|
||||
suite: &'static Tls12CipherSuite,
|
||||
server_cert_sct_list: Option<Vec<Sct>>,
|
||||
server_cert_chain: CertificatePayload,
|
||||
must_issue_new_ticket: bool,
|
||||
}
|
||||
|
@ -270,7 +287,11 @@ impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx {
|
|||
using_ems: self.using_ems,
|
||||
transcript: self.transcript,
|
||||
suite: self.suite,
|
||||
server_cert: ServerCertDetails::new(self.server_cert_chain, vec![]),
|
||||
server_cert: ServerCertDetails::new(
|
||||
self.server_cert_chain,
|
||||
vec![],
|
||||
self.server_cert_sct_list,
|
||||
),
|
||||
must_issue_new_ticket: self.must_issue_new_ticket,
|
||||
})
|
||||
.handle(cx, m),
|
||||
|
@ -290,6 +311,7 @@ impl State<ClientConnectionData> for ExpectCertificateStatusOrServerKx {
|
|||
using_ems: self.using_ems,
|
||||
transcript: self.transcript,
|
||||
suite: self.suite,
|
||||
server_cert_sct_list: self.server_cert_sct_list,
|
||||
server_cert_chain: self.server_cert_chain,
|
||||
must_issue_new_ticket: self.must_issue_new_ticket,
|
||||
})
|
||||
|
@ -315,6 +337,7 @@ struct ExpectCertificateStatus {
|
|||
using_ems: bool,
|
||||
transcript: HandshakeHash,
|
||||
suite: &'static Tls12CipherSuite,
|
||||
server_cert_sct_list: Option<Vec<Sct>>,
|
||||
server_cert_chain: CertificatePayload,
|
||||
must_issue_new_ticket: bool,
|
||||
}
|
||||
|
@ -338,7 +361,11 @@ impl State<ClientConnectionData> for ExpectCertificateStatus {
|
|||
&server_cert_ocsp_response
|
||||
);
|
||||
|
||||
let server_cert = ServerCertDetails::new(self.server_cert_chain, server_cert_ocsp_response);
|
||||
let server_cert = ServerCertDetails::new(
|
||||
self.server_cert_chain,
|
||||
server_cert_ocsp_response,
|
||||
self.server_cert_sct_list,
|
||||
);
|
||||
|
||||
Ok(Box::new(ExpectServerKx {
|
||||
config: self.config,
|
||||
|
@ -714,6 +741,7 @@ impl State<ClientConnectionData> for ExpectServerDone {
|
|||
end_entity,
|
||||
intermediates,
|
||||
&st.server_name,
|
||||
&mut st.server_cert.scts(),
|
||||
&st.server_cert.ocsp_response,
|
||||
now,
|
||||
)
|
||||
|
|
|
@ -448,6 +448,7 @@ impl State<ClientConnectionData> for ExpectEncryptedExtensions {
|
|||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
key_schedule: self.key_schedule,
|
||||
may_send_sct_list: self.hello.server_may_send_sct_list(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -460,6 +461,7 @@ struct ExpectCertificateOrCertReq {
|
|||
suite: &'static Tls13CipherSuite,
|
||||
transcript: HandshakeHash,
|
||||
key_schedule: KeyScheduleHandshake,
|
||||
may_send_sct_list: bool,
|
||||
}
|
||||
|
||||
impl State<ClientConnectionData> for ExpectCertificateOrCertReq {
|
||||
|
@ -479,6 +481,7 @@ impl State<ClientConnectionData> for ExpectCertificateOrCertReq {
|
|||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
key_schedule: self.key_schedule,
|
||||
may_send_sct_list: self.may_send_sct_list,
|
||||
client_auth: None,
|
||||
})
|
||||
.handle(cx, m),
|
||||
|
@ -496,6 +499,7 @@ impl State<ClientConnectionData> for ExpectCertificateOrCertReq {
|
|||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
key_schedule: self.key_schedule,
|
||||
may_send_sct_list: self.may_send_sct_list,
|
||||
})
|
||||
.handle(cx, m),
|
||||
payload => Err(inappropriate_handshake_message(
|
||||
|
@ -520,6 +524,7 @@ struct ExpectCertificateRequest {
|
|||
suite: &'static Tls13CipherSuite,
|
||||
transcript: HandshakeHash,
|
||||
key_schedule: KeyScheduleHandshake,
|
||||
may_send_sct_list: bool,
|
||||
}
|
||||
|
||||
impl State<ClientConnectionData> for ExpectCertificateRequest {
|
||||
|
@ -577,6 +582,7 @@ impl State<ClientConnectionData> for ExpectCertificateRequest {
|
|||
suite: self.suite,
|
||||
transcript: self.transcript,
|
||||
key_schedule: self.key_schedule,
|
||||
may_send_sct_list: self.may_send_sct_list,
|
||||
client_auth: Some(client_auth),
|
||||
}))
|
||||
}
|
||||
|
@ -589,6 +595,7 @@ struct ExpectCertificate {
|
|||
suite: &'static Tls13CipherSuite,
|
||||
transcript: HandshakeHash,
|
||||
key_schedule: KeyScheduleHandshake,
|
||||
may_send_sct_list: bool,
|
||||
client_auth: Option<ClientAuthDetails>,
|
||||
}
|
||||
|
||||
|
@ -618,8 +625,23 @@ impl State<ClientConnectionData> for ExpectCertificate {
|
|||
));
|
||||
}
|
||||
|
||||
let server_cert =
|
||||
ServerCertDetails::new(cert_chain.convert(), cert_chain.get_end_entity_ocsp());
|
||||
let server_cert = ServerCertDetails::new(
|
||||
cert_chain.convert(),
|
||||
cert_chain.get_end_entity_ocsp(),
|
||||
cert_chain
|
||||
.get_end_entity_scts()
|
||||
.map(|scts| scts.to_vec()),
|
||||
);
|
||||
|
||||
if let Some(sct_list) = server_cert.scts.as_ref() {
|
||||
if hs::sct_list_is_invalid(sct_list) {
|
||||
return Err(PeerMisbehaved::InvalidSctList.into());
|
||||
}
|
||||
|
||||
if !self.may_send_sct_list {
|
||||
return Err(PeerMisbehaved::UnsolicitedSctList.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(ExpectCertificateVerify {
|
||||
config: self.config,
|
||||
|
@ -670,6 +692,7 @@ impl State<ClientConnectionData> for ExpectCertificateVerify {
|
|||
end_entity,
|
||||
intermediates,
|
||||
&self.server_name,
|
||||
&mut self.server_cert.scts(),
|
||||
&self.server_cert.ocsp_response,
|
||||
now,
|
||||
)
|
||||
|
|
|
@ -66,6 +66,9 @@ pub enum Error {
|
|||
/// implementation.
|
||||
InvalidCertificate(CertificateError),
|
||||
|
||||
/// The presented SCT(s) were invalid.
|
||||
InvalidSct(sct::Error),
|
||||
|
||||
/// A catch-all error for unlikely errors.
|
||||
General(String),
|
||||
|
||||
|
@ -182,6 +185,7 @@ pub enum PeerMisbehaved {
|
|||
IncorrectBinder,
|
||||
InvalidMaxEarlyDataSize,
|
||||
InvalidKeyShare,
|
||||
InvalidSctList,
|
||||
KeyEpochWithPendingFragment,
|
||||
KeyUpdateReceivedInQuicConnection,
|
||||
MessageInterleavedWithHandshakeMessage,
|
||||
|
@ -422,6 +426,7 @@ impl fmt::Display for Error {
|
|||
Self::PeerSentOversizedRecord => write!(f, "peer sent excess record size"),
|
||||
Self::HandshakeNotComplete => write!(f, "handshake not complete"),
|
||||
Self::NoApplicationProtocol => write!(f, "peer doesn't support any known protocol"),
|
||||
Self::InvalidSct(ref err) => write!(f, "invalid certificate timestamp: {:?}", err),
|
||||
Self::FailedToGetCurrentTime => write!(f, "failed to get current time"),
|
||||
Self::FailedToGetRandomBytes => write!(f, "failed to get random bytes"),
|
||||
Self::BadMaxFragmentSize => {
|
||||
|
@ -475,6 +480,7 @@ mod tests {
|
|||
#[test]
|
||||
fn smoke() {
|
||||
use crate::enums::{AlertDescription, ContentType, HandshakeType};
|
||||
use sct;
|
||||
|
||||
let all = vec![
|
||||
Error::InappropriateMessage {
|
||||
|
@ -492,6 +498,7 @@ mod tests {
|
|||
super::PeerMisbehaved::UnsolicitedCertExtension.into(),
|
||||
Error::AlertReceived(AlertDescription::ExportRestriction),
|
||||
super::CertificateError::Expired.into(),
|
||||
Error::InvalidSct(sct::Error::MalformedSct),
|
||||
Error::General("undocumented error".to_string()),
|
||||
Error::FailedToGetCurrentTime,
|
||||
Error::FailedToGetRandomBytes,
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
//! * Extended master secret support ([RFC7627](https://tools.ietf.org/html/rfc7627)).
|
||||
//! * Exporters ([RFC5705](https://tools.ietf.org/html/rfc5705)).
|
||||
//! * OCSP stapling by servers.
|
||||
//! * SCT stapling by servers.
|
||||
//! * SCT verification by clients.
|
||||
//!
|
||||
//! ## Possible future features
|
||||
//!
|
||||
|
@ -415,7 +417,7 @@ pub mod client {
|
|||
mod tls13;
|
||||
|
||||
pub use crate::dns_name::InvalidDnsNameError;
|
||||
pub use builder::WantsClientCert;
|
||||
pub use builder::{WantsClientCert, WantsTransparencyPolicyOrClientCert};
|
||||
pub use client_conn::{
|
||||
ClientConfig, ClientConnection, ClientConnectionData, ClientSessionStore,
|
||||
ResolvesClientCert, Resumption, ServerName, Tls12Resumption, WriteEarlyData,
|
||||
|
@ -424,8 +426,9 @@ pub mod client {
|
|||
|
||||
#[cfg(feature = "dangerous_configuration")]
|
||||
pub use crate::verify::{
|
||||
verify_server_cert_signed_by_trust_anchor, verify_server_name, HandshakeSignatureValid,
|
||||
ServerCertVerified, ServerCertVerifier, WebPkiVerifier,
|
||||
verify_server_cert_signed_by_trust_anchor, verify_server_name,
|
||||
CertificateTransparencyPolicy, HandshakeSignatureValid, ServerCertVerified,
|
||||
ServerCertVerifier, WebPkiVerifier,
|
||||
};
|
||||
#[cfg(feature = "dangerous_configuration")]
|
||||
pub use client_conn::danger::DangerousClientConfig;
|
||||
|
|
|
@ -516,6 +516,15 @@ impl CertificateStatusRequest {
|
|||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// SCTs
|
||||
|
||||
wrapped_payload!(Sct, PayloadU16,);
|
||||
|
||||
impl TlsListElement for Sct {
|
||||
const SIZE_LEN: ListLength = ListLength::U16;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
impl TlsListElement for PSKKeyExchangeMode {
|
||||
|
@ -545,6 +554,7 @@ pub enum ClientExtension {
|
|||
Cookie(PayloadU16),
|
||||
ExtendedMasterSecretRequest,
|
||||
CertificateStatusRequest(CertificateStatusRequest),
|
||||
SignedCertificateTimestampRequest,
|
||||
TransportParameters(Vec<u8>),
|
||||
TransportParametersDraft(Vec<u8>),
|
||||
EarlyData,
|
||||
|
@ -567,6 +577,7 @@ impl ClientExtension {
|
|||
Self::Cookie(_) => ExtensionType::Cookie,
|
||||
Self::ExtendedMasterSecretRequest => ExtensionType::ExtendedMasterSecret,
|
||||
Self::CertificateStatusRequest(_) => ExtensionType::StatusRequest,
|
||||
Self::SignedCertificateTimestampRequest => ExtensionType::SCT,
|
||||
Self::TransportParameters(_) => ExtensionType::TransportParameters,
|
||||
Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft,
|
||||
Self::EarlyData => ExtensionType::EarlyData,
|
||||
|
@ -587,6 +598,7 @@ impl Codec for ClientExtension {
|
|||
Self::ServerName(ref r) => r.encode(&mut sub),
|
||||
Self::SessionTicket(ClientSessionTicket::Request)
|
||||
| Self::ExtendedMasterSecretRequest
|
||||
| Self::SignedCertificateTimestampRequest
|
||||
| Self::EarlyData => {}
|
||||
Self::SessionTicket(ClientSessionTicket::Offer(ref r)) => r.encode(&mut sub),
|
||||
Self::Protocols(ref r) => r.encode(&mut sub),
|
||||
|
@ -637,6 +649,7 @@ impl Codec for ClientExtension {
|
|||
let csr = CertificateStatusRequest::read(&mut sub)?;
|
||||
Self::CertificateStatusRequest(csr)
|
||||
}
|
||||
ExtensionType::SCT if !sub.any_left() => Self::SignedCertificateTimestampRequest,
|
||||
ExtensionType::TransportParameters => Self::TransportParameters(sub.rest().to_vec()),
|
||||
ExtensionType::TransportParametersDraft => {
|
||||
Self::TransportParametersDraft(sub.rest().to_vec())
|
||||
|
@ -694,6 +707,7 @@ pub enum ServerExtension {
|
|||
PresharedKey(u16),
|
||||
ExtendedMasterSecretAck,
|
||||
CertificateStatusAck,
|
||||
SignedCertificateTimestamp(Vec<Sct>),
|
||||
SupportedVersions(ProtocolVersion),
|
||||
TransportParameters(Vec<u8>),
|
||||
TransportParametersDraft(Vec<u8>),
|
||||
|
@ -713,6 +727,7 @@ impl ServerExtension {
|
|||
Self::PresharedKey(_) => ExtensionType::PreSharedKey,
|
||||
Self::ExtendedMasterSecretAck => ExtensionType::ExtendedMasterSecret,
|
||||
Self::CertificateStatusAck => ExtensionType::StatusRequest,
|
||||
Self::SignedCertificateTimestamp(_) => ExtensionType::SCT,
|
||||
Self::SupportedVersions(_) => ExtensionType::SupportedVersions,
|
||||
Self::TransportParameters(_) => ExtensionType::TransportParameters,
|
||||
Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft,
|
||||
|
@ -738,6 +753,7 @@ impl Codec for ServerExtension {
|
|||
Self::Protocols(ref r) => r.encode(&mut sub),
|
||||
Self::KeyShare(ref r) => r.encode(&mut sub),
|
||||
Self::PresharedKey(r) => r.encode(&mut sub),
|
||||
Self::SignedCertificateTimestamp(ref r) => r.encode(&mut sub),
|
||||
Self::SupportedVersions(ref r) => r.encode(&mut sub),
|
||||
Self::TransportParameters(ref r) | Self::TransportParametersDraft(ref r) => {
|
||||
sub.extend_from_slice(r);
|
||||
|
@ -764,6 +780,7 @@ impl Codec for ServerExtension {
|
|||
ExtensionType::KeyShare => Self::KeyShare(KeyShareEntry::read(&mut sub)?),
|
||||
ExtensionType::PreSharedKey => Self::PresharedKey(u16::read(&mut sub)?),
|
||||
ExtensionType::ExtendedMasterSecret => Self::ExtendedMasterSecretAck,
|
||||
ExtensionType::SCT => Self::SignedCertificateTimestamp(Vec::read(&mut sub)?),
|
||||
ExtensionType::SupportedVersions => {
|
||||
Self::SupportedVersions(ProtocolVersion::read(&mut sub)?)
|
||||
}
|
||||
|
@ -789,6 +806,11 @@ impl ServerExtension {
|
|||
let empty = Vec::new();
|
||||
Self::RenegotiationInfo(PayloadU8::new(empty))
|
||||
}
|
||||
|
||||
pub fn make_sct(sctl: Vec<u8>) -> Self {
|
||||
let scts = Vec::read_bytes(&sctl).expect("invalid SCT list");
|
||||
Self::SignedCertificateTimestamp(scts)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1242,6 +1264,14 @@ impl ServerHelloPayload {
|
|||
.is_some()
|
||||
}
|
||||
|
||||
pub fn get_sct_list(&self) -> Option<&[Sct]> {
|
||||
let ext = self.find_extension(ExtensionType::SCT)?;
|
||||
match *ext {
|
||||
ServerExtension::SignedCertificateTimestamp(ref sctl) => Some(sctl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_supported_versions(&self) -> Option<ProtocolVersion> {
|
||||
let ext = self.find_extension(ExtensionType::SupportedVersions)?;
|
||||
match *ext {
|
||||
|
@ -1264,6 +1294,7 @@ impl TlsListElement for key::Certificate {
|
|||
#[derive(Debug)]
|
||||
pub enum CertificateExtension {
|
||||
CertificateStatus(CertificateStatus),
|
||||
SignedCertificateTimestamp(Vec<Sct>),
|
||||
Unknown(UnknownExtension),
|
||||
}
|
||||
|
||||
|
@ -1271,16 +1302,29 @@ impl CertificateExtension {
|
|||
pub fn get_type(&self) -> ExtensionType {
|
||||
match *self {
|
||||
Self::CertificateStatus(_) => ExtensionType::StatusRequest,
|
||||
Self::SignedCertificateTimestamp(_) => ExtensionType::SCT,
|
||||
Self::Unknown(ref r) => r.typ,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_sct(sct_list: Vec<u8>) -> Self {
|
||||
let sctl = Vec::read_bytes(&sct_list).expect("invalid SCT list");
|
||||
Self::SignedCertificateTimestamp(sctl)
|
||||
}
|
||||
|
||||
pub fn get_cert_status(&self) -> Option<&Vec<u8>> {
|
||||
match *self {
|
||||
Self::CertificateStatus(ref cs) => Some(&cs.ocsp_response.0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sct_list(&self) -> Option<&[Sct]> {
|
||||
match *self {
|
||||
Self::SignedCertificateTimestamp(ref sctl) => Some(sctl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Codec for CertificateExtension {
|
||||
|
@ -1290,6 +1334,7 @@ impl Codec for CertificateExtension {
|
|||
let mut sub: Vec<u8> = Vec::new();
|
||||
match *self {
|
||||
Self::CertificateStatus(ref r) => r.encode(&mut sub),
|
||||
Self::SignedCertificateTimestamp(ref r) => r.encode(&mut sub),
|
||||
Self::Unknown(ref r) => r.encode(&mut sub),
|
||||
}
|
||||
|
||||
|
@ -1307,6 +1352,7 @@ impl Codec for CertificateExtension {
|
|||
let st = CertificateStatus::read(&mut sub)?;
|
||||
Self::CertificateStatus(st)
|
||||
}
|
||||
ExtensionType::SCT => Self::SignedCertificateTimestamp(Vec::read(&mut sub)?),
|
||||
_ => Self::Unknown(UnknownExtension::read(typ, &mut sub)),
|
||||
};
|
||||
|
||||
|
@ -1363,9 +1409,9 @@ impl CertificateEntry {
|
|||
}
|
||||
|
||||
pub fn has_unknown_extension(&self) -> bool {
|
||||
self.exts
|
||||
.iter()
|
||||
.any(|ext| ext.get_type() != ExtensionType::StatusRequest)
|
||||
self.exts.iter().any(|ext| {
|
||||
ext.get_type() != ExtensionType::StatusRequest && ext.get_type() != ExtensionType::SCT
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_ocsp_response(&self) -> Option<&Vec<u8>> {
|
||||
|
@ -1374,6 +1420,13 @@ impl CertificateEntry {
|
|||
.find(|ext| ext.get_type() == ExtensionType::StatusRequest)
|
||||
.and_then(CertificateExtension::get_cert_status)
|
||||
}
|
||||
|
||||
pub fn get_scts(&self) -> Option<&[Sct]> {
|
||||
self.exts
|
||||
.iter()
|
||||
.find(|ext| ext.get_type() == ExtensionType::SCT)
|
||||
.and_then(CertificateExtension::get_sct_list)
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsListElement for CertificateEntry {
|
||||
|
@ -1446,6 +1499,12 @@ impl CertificatePayloadTLS13 {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_end_entity_scts(&self) -> Option<&[Sct]> {
|
||||
self.entries
|
||||
.first()
|
||||
.and_then(CertificateEntry::get_scts)
|
||||
}
|
||||
|
||||
pub fn convert(&self) -> CertificatePayload {
|
||||
let mut ret = Vec::new();
|
||||
for entry in &self.entries {
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::msgs::handshake::{
|
|||
ECParameters, HandshakeMessagePayload, HandshakePayload, HasServerExtensions,
|
||||
HelloRetryExtension, HelloRetryRequest, KeyShareEntry, NewSessionTicketExtension,
|
||||
NewSessionTicketPayload, NewSessionTicketPayloadTLS13, PresharedKeyBinder,
|
||||
PresharedKeyIdentity, PresharedKeyOffer, ProtocolName, Random, ServerECDHParams,
|
||||
PresharedKeyIdentity, PresharedKeyOffer, ProtocolName, Random, Sct, ServerECDHParams,
|
||||
ServerExtension, ServerHelloPayload, ServerKeyExchangePayload, SessionId, UnknownExtension,
|
||||
};
|
||||
use crate::verify::DigitallySignedStruct;
|
||||
|
@ -131,7 +131,7 @@ fn refuses_server_ext_with_unparsed_bytes() {
|
|||
|
||||
#[test]
|
||||
fn refuses_certificate_ext_with_unparsed_bytes() {
|
||||
let bytes = [0x00u8, 0x05, 0x00, 0x03, 0x00, 0x00, 0x01];
|
||||
let bytes = [0x00u8, 0x12, 0x00, 0x03, 0x00, 0x00, 0x01];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
assert!(CertificateExtension::read(&mut rd).is_err());
|
||||
}
|
||||
|
@ -385,6 +385,7 @@ fn get_sample_clienthellopayload() -> ClientHelloPayload {
|
|||
ClientExtension::Cookie(PayloadU16(vec![1, 2, 3])),
|
||||
ClientExtension::ExtendedMasterSecretRequest,
|
||||
ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()),
|
||||
ClientExtension::SignedCertificateTimestampRequest,
|
||||
ClientExtension::TransportParameters(vec![1, 2, 3]),
|
||||
ClientExtension::Unknown(UnknownExtension {
|
||||
typ: ExtensionType::Unknown(12345),
|
||||
|
@ -703,6 +704,11 @@ fn server_get_ecpoints_extension() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_get_sct_list() {
|
||||
test_server_extension_getter(ExtensionType::SCT, |shp| shp.get_sct_list().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_get_supported_versions() {
|
||||
test_server_extension_getter(ExtensionType::SupportedVersions, |shp| {
|
||||
|
@ -736,6 +742,11 @@ fn certentry_get_ocsp_response() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn certentry_get_scts() {
|
||||
test_cert_extension_getter(ExtensionType::SCT, |ce| ce.get_scts().is_some());
|
||||
}
|
||||
|
||||
fn get_sample_serverhellopayload() -> ServerHelloPayload {
|
||||
ServerHelloPayload {
|
||||
legacy_version: ProtocolVersion::TLSv1_2,
|
||||
|
@ -753,6 +764,7 @@ fn get_sample_serverhellopayload() -> ServerHelloPayload {
|
|||
ServerExtension::PresharedKey(3),
|
||||
ServerExtension::ExtendedMasterSecretAck,
|
||||
ServerExtension::CertificateStatusAck,
|
||||
ServerExtension::SignedCertificateTimestamp(vec![Sct::from(vec![0])]),
|
||||
ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_2),
|
||||
ServerExtension::TransportParameters(vec![1, 2, 3]),
|
||||
ServerExtension::Unknown(UnknownExtension {
|
||||
|
@ -799,6 +811,7 @@ fn get_sample_certificatepayloadtls13() -> CertificatePayloadTLS13 {
|
|||
CertificateExtension::CertificateStatus(CertificateStatus {
|
||||
ocsp_response: PayloadU24(vec![1, 2, 3]),
|
||||
}),
|
||||
CertificateExtension::SignedCertificateTimestamp(vec![Sct::from(vec![0])]),
|
||||
CertificateExtension::Unknown(UnknownExtension {
|
||||
typ: ExtensionType::Unknown(12345),
|
||||
payload: Payload(vec![1, 2, 3]),
|
||||
|
|
|
@ -77,15 +77,19 @@ impl ConfigBuilder<ServerConfig, WantsServerCert> {
|
|||
/// `cert_chain` is a vector of DER-encoded certificates.
|
||||
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
|
||||
/// `ocsp` is a DER-encoded OCSP response. Ignored if zero length.
|
||||
/// `scts` is an `SignedCertificateTimestampList` encoding (see RFC6962)
|
||||
/// and is ignored if empty.
|
||||
///
|
||||
/// This function fails if `key_der` is invalid.
|
||||
pub fn with_single_cert_with_ocsp(
|
||||
pub fn with_single_cert_with_ocsp_and_sct(
|
||||
self,
|
||||
cert_chain: Vec<key::Certificate>,
|
||||
key_der: key::PrivateKey,
|
||||
ocsp: Vec<u8>,
|
||||
scts: Vec<u8>,
|
||||
) -> Result<ServerConfig, Error> {
|
||||
let resolver = handy::AlwaysResolvesChain::new_with_extras(cert_chain, &key_der, ocsp)?;
|
||||
let resolver =
|
||||
handy::AlwaysResolvesChain::new_with_extras(cert_chain, &key_der, ocsp, scts)?;
|
||||
Ok(self.with_cert_resolver(Arc::new(resolver)))
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::{key, sign};
|
|||
pub(super) struct ActiveCertifiedKey<'a> {
|
||||
key: &'a sign::CertifiedKey,
|
||||
ocsp: Option<&'a [u8]>,
|
||||
sct_list: Option<&'a [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> ActiveCertifiedKey<'a> {
|
||||
|
@ -12,6 +13,7 @@ impl<'a> ActiveCertifiedKey<'a> {
|
|||
ActiveCertifiedKey {
|
||||
key,
|
||||
ocsp: key.ocsp.as_deref(),
|
||||
sct_list: key.sct_list.as_deref(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,4 +33,9 @@ impl<'a> ActiveCertifiedKey<'a> {
|
|||
pub(super) fn get_ocsp(&self) -> Option<&[u8]> {
|
||||
self.ocsp
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn get_sct_list(&self) -> Option<&[u8]> {
|
||||
self.sct_list
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ impl AlwaysResolvesChain {
|
|||
chain: Vec<key::Certificate>,
|
||||
priv_key: &key::PrivateKey,
|
||||
ocsp: Vec<u8>,
|
||||
scts: Vec<u8>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut r = Self::new(chain, priv_key)?;
|
||||
|
||||
|
@ -120,6 +121,9 @@ impl AlwaysResolvesChain {
|
|||
if !ocsp.is_empty() {
|
||||
cert.ocsp = Some(ocsp);
|
||||
}
|
||||
if !scts.is_empty() {
|
||||
cert.sct_list = Some(scts);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(r)
|
||||
|
|
|
@ -67,6 +67,7 @@ impl ExtensionProcessing {
|
|||
config: &ServerConfig,
|
||||
cx: &mut ServerContext<'_>,
|
||||
ocsp_response: &mut Option<&[u8]>,
|
||||
sct_list: &mut Option<&[u8]>,
|
||||
hello: &ClientHelloPayload,
|
||||
resumedata: Option<&persist::ServerSessionValue>,
|
||||
extra_exts: Vec<ServerExtension>,
|
||||
|
@ -155,6 +156,24 @@ impl ExtensionProcessing {
|
|||
ocsp_response.take();
|
||||
}
|
||||
|
||||
if !for_resume
|
||||
&& hello
|
||||
.find_extension(ExtensionType::SCT)
|
||||
.is_some()
|
||||
{
|
||||
if !cx.common.is_tls13() {
|
||||
// Take the SCT list, if any, so we don't send it later,
|
||||
// and put it in the legacy extension.
|
||||
if let Some(sct_list) = sct_list.take() {
|
||||
self.exts
|
||||
.push(ServerExtension::make_sct(sct_list.to_vec()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Throw away any SCT list so we don't send it later.
|
||||
sct_list.take();
|
||||
}
|
||||
|
||||
self.exts.extend(extra_exts);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -193,7 +193,8 @@ mod client_hello {
|
|||
|
||||
debug_assert_eq!(ecpoint, ECPointFormat::Uncompressed);
|
||||
|
||||
let mut ocsp_response = server_key.get_ocsp();
|
||||
let (mut ocsp_response, mut sct_list) =
|
||||
(server_key.get_ocsp(), server_key.get_sct_list());
|
||||
|
||||
// If we're not offered a ticket or a potential session ID, allocate a session ID.
|
||||
if !self.config.session_storage.can_cache() {
|
||||
|
@ -210,6 +211,7 @@ mod client_hello {
|
|||
self.suite,
|
||||
self.using_ems,
|
||||
&mut ocsp_response,
|
||||
&mut sct_list,
|
||||
client_hello,
|
||||
None,
|
||||
&self.randoms,
|
||||
|
@ -281,6 +283,7 @@ mod client_hello {
|
|||
self.suite,
|
||||
self.using_ems,
|
||||
&mut None,
|
||||
&mut None,
|
||||
client_hello,
|
||||
Some(&resumedata),
|
||||
&self.randoms,
|
||||
|
@ -336,13 +339,22 @@ mod client_hello {
|
|||
suite: &'static Tls12CipherSuite,
|
||||
using_ems: bool,
|
||||
ocsp_response: &mut Option<&[u8]>,
|
||||
sct_list: &mut Option<&[u8]>,
|
||||
hello: &ClientHelloPayload,
|
||||
resumedata: Option<&persist::ServerSessionValue>,
|
||||
randoms: &ConnectionRandoms,
|
||||
extra_exts: Vec<ServerExtension>,
|
||||
) -> Result<bool, Error> {
|
||||
let mut ep = hs::ExtensionProcessing::new();
|
||||
ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?;
|
||||
ep.process_common(
|
||||
config,
|
||||
cx,
|
||||
ocsp_response,
|
||||
sct_list,
|
||||
hello,
|
||||
resumedata,
|
||||
extra_exts,
|
||||
)?;
|
||||
ep.process_tls12(config, hello, using_ems);
|
||||
|
||||
let sh = Message {
|
||||
|
|
|
@ -372,12 +372,14 @@ mod client_hello {
|
|||
emit_fake_ccs(cx.common);
|
||||
}
|
||||
|
||||
let mut ocsp_response = server_key.get_ocsp();
|
||||
let (mut ocsp_response, mut sct_list) =
|
||||
(server_key.get_ocsp(), server_key.get_sct_list());
|
||||
let doing_early_data = emit_encrypted_extensions(
|
||||
&mut self.transcript,
|
||||
self.suite,
|
||||
cx,
|
||||
&mut ocsp_response,
|
||||
&mut sct_list,
|
||||
client_hello,
|
||||
resumedata.as_ref(),
|
||||
self.extra_exts,
|
||||
|
@ -392,6 +394,7 @@ mod client_hello {
|
|||
cx.common,
|
||||
server_key.get_cert(),
|
||||
ocsp_response,
|
||||
sct_list,
|
||||
);
|
||||
emit_certificate_verify_tls13(
|
||||
&mut self.transcript,
|
||||
|
@ -663,13 +666,22 @@ mod client_hello {
|
|||
suite: &'static Tls13CipherSuite,
|
||||
cx: &mut ServerContext<'_>,
|
||||
ocsp_response: &mut Option<&[u8]>,
|
||||
sct_list: &mut Option<&[u8]>,
|
||||
hello: &ClientHelloPayload,
|
||||
resumedata: Option<&persist::ServerSessionValue>,
|
||||
extra_exts: Vec<ServerExtension>,
|
||||
config: &ServerConfig,
|
||||
) -> Result<EarlyDataDecision, Error> {
|
||||
let mut ep = hs::ExtensionProcessing::new();
|
||||
ep.process_common(config, cx, ocsp_response, hello, resumedata, extra_exts)?;
|
||||
ep.process_common(
|
||||
config,
|
||||
cx,
|
||||
ocsp_response,
|
||||
sct_list,
|
||||
hello,
|
||||
resumedata,
|
||||
extra_exts,
|
||||
)?;
|
||||
|
||||
let early_data = decide_if_early_data_allowed(cx, hello, resumedata, suite, config);
|
||||
if early_data == EarlyDataDecision::Accepted {
|
||||
|
@ -739,6 +751,7 @@ mod client_hello {
|
|||
common: &mut CommonState,
|
||||
cert_chain: &[Certificate],
|
||||
ocsp_response: Option<&[u8]>,
|
||||
sct_list: Option<&[u8]>,
|
||||
) {
|
||||
let mut cert_entries = vec![];
|
||||
for cert in cert_chain {
|
||||
|
@ -759,6 +772,13 @@ mod client_hello {
|
|||
.exts
|
||||
.push(CertificateExtension::CertificateStatus(cst));
|
||||
}
|
||||
|
||||
// Likewise, SCT
|
||||
if let Some(sct_list) = sct_list {
|
||||
end_entity_cert
|
||||
.exts
|
||||
.push(CertificateExtension::make_sct(sct_list.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
let cert_body = CertificatePayloadTLS13::new(cert_entries);
|
||||
|
|
|
@ -44,6 +44,11 @@ pub struct CertifiedKey {
|
|||
/// An optional OCSP response from the certificate issuer,
|
||||
/// attesting to its continued validity.
|
||||
pub ocsp: Option<Vec<u8>>,
|
||||
|
||||
/// An optional collection of SCTs from CT logs, proving the
|
||||
/// certificate is included on those logs. This must be
|
||||
/// a `SignedCertificateTimestampList` encoding; see RFC6962.
|
||||
pub sct_list: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl CertifiedKey {
|
||||
|
@ -56,6 +61,7 @@ impl CertifiedKey {
|
|||
cert,
|
||||
key,
|
||||
ocsp: None,
|
||||
sct_list: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::enums::SignatureScheme;
|
|||
use crate::error::{CertificateError, Error, InvalidMessage, PeerMisbehaved};
|
||||
use crate::key::{Certificate, ParsedCertificate};
|
||||
#[cfg(feature = "logging")]
|
||||
use crate::log::trace;
|
||||
use crate::log::{debug, trace, warn};
|
||||
use crate::msgs::base::PayloadU16;
|
||||
use crate::msgs::codec::{Codec, Reader};
|
||||
use crate::msgs::handshake::DistinguishedName;
|
||||
|
@ -108,12 +108,16 @@ pub trait ServerCertVerifier: Send + Sync {
|
|||
/// the implementor to handle invalid data. It is recommended that the implementor returns
|
||||
/// [`Error::InvalidCertificate(CertificateError::BadEncoding)`] when these cases are encountered.
|
||||
///
|
||||
/// `scts` contains the Signed Certificate Timestamps (SCTs) the server
|
||||
/// sent with the end-entity certificate, if any.
|
||||
///
|
||||
/// [Certificate]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &Certificate,
|
||||
intermediates: &[Certificate],
|
||||
server_name: &ServerName,
|
||||
scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
ocsp_response: &[u8],
|
||||
now: SystemTime,
|
||||
) -> Result<ServerCertVerified, Error>;
|
||||
|
@ -181,6 +185,16 @@ pub trait ServerCertVerifier: Send + Sync {
|
|||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
WebPkiVerifier::verification_schemes()
|
||||
}
|
||||
|
||||
/// Returns `true` if Rustls should ask the server to send SCTs.
|
||||
///
|
||||
/// Signed Certificate Timestamps (SCTs) are used for Certificate
|
||||
/// Transparency validation.
|
||||
///
|
||||
/// The default implementation of this function returns true.
|
||||
fn request_scts(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for dyn ServerCertVerifier {
|
||||
|
@ -376,6 +390,7 @@ impl ServerCertVerifier for WebPkiVerifier {
|
|||
end_entity: &Certificate,
|
||||
intermediates: &[Certificate],
|
||||
server_name: &ServerName,
|
||||
scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
ocsp_response: &[u8],
|
||||
now: SystemTime,
|
||||
) -> Result<ServerCertVerified, Error> {
|
||||
|
@ -383,6 +398,10 @@ impl ServerCertVerifier for WebPkiVerifier {
|
|||
|
||||
verify_server_cert_signed_by_trust_anchor(&cert, &self.roots, intermediates, now)?;
|
||||
|
||||
if let Some(policy) = &self.ct_policy {
|
||||
policy.verify(end_entity, now, scts)?;
|
||||
}
|
||||
|
||||
if !ocsp_response.is_empty() {
|
||||
trace!("Unvalidated OCSP response: {:?}", ocsp_response.to_vec());
|
||||
}
|
||||
|
@ -397,6 +416,7 @@ impl ServerCertVerifier for WebPkiVerifier {
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))]
|
||||
pub struct WebPkiVerifier {
|
||||
roots: RootCertStore,
|
||||
ct_policy: Option<CertificateTransparencyPolicy>,
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
|
@ -404,8 +424,12 @@ impl WebPkiVerifier {
|
|||
/// Constructs a new `WebPkiVerifier`.
|
||||
///
|
||||
/// `roots` is the set of trust anchors to trust for issuing server certs.
|
||||
pub fn new(roots: RootCertStore) -> Self {
|
||||
Self { roots }
|
||||
///
|
||||
/// `ct_logs` is the list of logs that are trusted for Certificate
|
||||
/// Transparency. Currently CT log enforcement is opportunistic; see
|
||||
/// <https://github.com/rustls/rustls/issues/479>.
|
||||
pub fn new(roots: RootCertStore, ct_policy: Option<CertificateTransparencyPolicy>) -> Self {
|
||||
Self { roots, ct_policy }
|
||||
}
|
||||
|
||||
/// Returns the signature verification methods supported by
|
||||
|
@ -425,6 +449,83 @@ impl WebPkiVerifier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Policy for enforcing Certificate Transparency.
|
||||
///
|
||||
/// Because Certificate Transparency logs are sharded on a per-year basis and can be trusted or
|
||||
/// distrusted relatively quickly, rustls stores a validation deadline. Server certificates will
|
||||
/// be validated against the configured CT logs until the deadline expires. After the deadline,
|
||||
/// certificates will no longer be validated, and a warning message will be logged. The deadline
|
||||
/// may vary depending on how often you deploy builds with updated dependencies.
|
||||
#[allow(unreachable_pub)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))]
|
||||
pub struct CertificateTransparencyPolicy {
|
||||
logs: &'static [&'static sct::Log<'static>],
|
||||
validation_deadline: SystemTime,
|
||||
}
|
||||
|
||||
impl CertificateTransparencyPolicy {
|
||||
/// Create a new policy.
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn new(
|
||||
logs: &'static [&'static sct::Log<'static>],
|
||||
validation_deadline: SystemTime,
|
||||
) -> Self {
|
||||
Self {
|
||||
logs,
|
||||
validation_deadline,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
cert: &Certificate,
|
||||
now: SystemTime,
|
||||
scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
) -> Result<(), Error> {
|
||||
if self.logs.is_empty() {
|
||||
return Ok(());
|
||||
} else if self
|
||||
.validation_deadline
|
||||
.duration_since(now)
|
||||
.is_err()
|
||||
{
|
||||
warn!("certificate transparency logs have expired, validation disabled");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let now = unix_time_millis(now)?;
|
||||
let mut last_sct_error = None;
|
||||
for sct in scts {
|
||||
#[cfg_attr(not(feature = "logging"), allow(unused_variables))]
|
||||
match sct::verify_sct(&cert.0, sct, now, self.logs) {
|
||||
Ok(index) => {
|
||||
debug!(
|
||||
"Valid SCT signed by {} on {}",
|
||||
self.logs[index].operated_by, self.logs[index].description
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
if e.should_be_fatal() {
|
||||
return Err(Error::InvalidSct(e));
|
||||
}
|
||||
debug!("SCT ignored because {:?}", e);
|
||||
last_sct_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we were supplied with some logs, and some SCTs,
|
||||
* but couldn't verify any of them, fail the handshake. */
|
||||
if let Some(last_sct_error) = last_sct_error {
|
||||
warn!("No valid SCTs provided");
|
||||
return Err(Error::InvalidSct(last_sct_error));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn intermediate_chain(intermediates: &[Certificate]) -> Vec<&[u8]> {
|
||||
intermediates
|
||||
.iter()
|
||||
|
@ -758,6 +859,16 @@ fn verify_tls13(
|
|||
.map(|_| HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn unix_time_millis(now: SystemTime) -> Result<u64, Error> {
|
||||
now.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|dur| dur.as_secs())
|
||||
.map_err(|_| Error::FailedToGetCurrentTime)
|
||||
.and_then(|secs| {
|
||||
secs.checked_mul(1000)
|
||||
.ok_or(Error::FailedToGetCurrentTime)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -213,7 +213,8 @@ impl Context {
|
|||
}
|
||||
|
||||
fn bench(&self, count: usize) {
|
||||
let verifier = verify::WebPkiVerifier::new(self.roots.clone());
|
||||
let verifier = verify::WebPkiVerifier::new(self.roots.clone(), None);
|
||||
const SCTS: &[&[u8]] = &[];
|
||||
const OCSP_RESPONSE: &[u8] = &[];
|
||||
let mut times = Vec::new();
|
||||
|
||||
|
@ -226,6 +227,7 @@ impl Context {
|
|||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut SCTS.iter().copied(),
|
||||
OCSP_RESPONSE,
|
||||
self.now,
|
||||
)
|
||||
|
|
|
@ -157,6 +157,7 @@ pub struct MockServerVerifier {
|
|||
cert_rejection_error: Option<Error>,
|
||||
tls12_signature_error: Option<Error>,
|
||||
tls13_signature_error: Option<Error>,
|
||||
wants_scts: bool,
|
||||
signature_schemes: Vec<SignatureScheme>,
|
||||
}
|
||||
|
||||
|
@ -166,12 +167,14 @@ impl ServerCertVerifier for MockServerVerifier {
|
|||
end_entity: &rustls::Certificate,
|
||||
intermediates: &[rustls::Certificate],
|
||||
server_name: &rustls::ServerName,
|
||||
scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
oscp_response: &[u8],
|
||||
now: std::time::SystemTime,
|
||||
) -> Result<ServerCertVerified, Error> {
|
||||
let scts: Vec<Vec<u8>> = scts.map(|x| x.to_owned()).collect();
|
||||
println!(
|
||||
"verify_server_cert({:?}, {:?}, {:?}, {:?}, {:?})",
|
||||
end_entity, intermediates, server_name, oscp_response, now
|
||||
"verify_server_cert({:?}, {:?}, {:?}, {:?}, {:?}, {:?})",
|
||||
end_entity, intermediates, server_name, scts, oscp_response, now
|
||||
);
|
||||
if let Some(error) = &self.cert_rejection_error {
|
||||
Err(error.clone())
|
||||
|
@ -217,6 +220,11 @@ impl ServerCertVerifier for MockServerVerifier {
|
|||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
self.signature_schemes.clone()
|
||||
}
|
||||
|
||||
fn request_scts(&self) -> bool {
|
||||
println!("request_scts? {:?}", self.wants_scts);
|
||||
self.wants_scts
|
||||
}
|
||||
}
|
||||
|
||||
impl MockServerVerifier {
|
||||
|
@ -262,6 +270,7 @@ impl Default for MockServerVerifier {
|
|||
cert_rejection_error: None,
|
||||
tls12_signature_error: None,
|
||||
tls13_signature_error: None,
|
||||
wants_scts: false,
|
||||
signature_schemes: WebPkiVerifier::verification_schemes(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue