diff options
Diffstat (limited to 'vendor/rustls/src/server/hs.rs')
| -rw-r--r-- | vendor/rustls/src/server/hs.rs | 763 |
1 files changed, 763 insertions, 0 deletions
diff --git a/vendor/rustls/src/server/hs.rs b/vendor/rustls/src/server/hs.rs new file mode 100644 index 00000000..d98336e4 --- /dev/null +++ b/vendor/rustls/src/server/hs.rs @@ -0,0 +1,763 @@ +use alloc::borrow::ToOwned; +use alloc::boxed::Box; +use alloc::vec::Vec; + +use pki_types::DnsName; + +use super::server_conn::ServerConnectionData; +#[cfg(feature = "tls12")] +use super::tls12; +use crate::common_state::{KxState, Protocol, State}; +use crate::conn::ConnectionRandoms; +use crate::crypto::SupportedKxGroup; +use crate::enums::{ + AlertDescription, CertificateType, CipherSuite, HandshakeType, ProtocolVersion, + SignatureAlgorithm, SignatureScheme, +}; +use crate::error::{Error, PeerIncompatible, PeerMisbehaved}; +use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; +use crate::log::{debug, trace}; +use crate::msgs::enums::{Compression, ExtensionType, NamedGroup}; +#[cfg(feature = "tls12")] +use crate::msgs::handshake::SessionId; +use crate::msgs::handshake::{ + ClientHelloPayload, HandshakePayload, KeyExchangeAlgorithm, ProtocolName, Random, + ServerExtensions, ServerExtensionsInput, ServerNamePayload, SingleProtocolName, + TransportParameters, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::server::common::ActiveCertifiedKey; +use crate::server::{ClientHello, ServerConfig, tls13}; +use crate::sync::Arc; +use crate::{SupportedCipherSuite, suites}; + +pub(super) type NextState<'a> = Box<dyn State<ServerConnectionData> + 'a>; +pub(super) type NextStateOrError<'a> = Result<NextState<'a>, Error>; +pub(super) type ServerContext<'a> = crate::common_state::Context<'a, ServerConnectionData>; + +pub(super) fn can_resume( + suite: SupportedCipherSuite, + sni: &Option<DnsName<'_>>, + using_ems: bool, + resumedata: &persist::ServerSessionValue, +) -> bool { + // The RFCs underspecify what happens if we try to resume to + // an unoffered/varying suite. We merely don't resume in weird cases. + // + // RFC 6066 says "A server that implements this extension MUST NOT accept + // the request to resume the session if the server_name extension contains + // a different name. Instead, it proceeds with a full handshake to + // establish a new session." + // + // RFC 8446: "The server MUST ensure that it selects + // a compatible PSK (if any) and cipher suite." + resumedata.cipher_suite == suite.suite() + && (resumedata.extended_ms == using_ems || (resumedata.extended_ms && !using_ems)) + && &resumedata.sni == sni +} + +#[derive(Default)] +pub(super) struct ExtensionProcessing { + // extensions to reply with + pub(super) extensions: Box<ServerExtensions<'static>>, + #[cfg(feature = "tls12")] + pub(super) send_ticket: bool, +} + +impl ExtensionProcessing { + pub(super) fn new(extra_exts: ServerExtensionsInput<'static>) -> Self { + let ServerExtensionsInput { + transport_parameters, + } = extra_exts; + + let mut extensions = Box::new(ServerExtensions::default()); + match transport_parameters { + Some(TransportParameters::Quic(v)) => extensions.transport_parameters = Some(v), + Some(TransportParameters::QuicDraft(v)) => { + extensions.transport_parameters_draft = Some(v) + } + None => {} + } + + Self { + extensions, + #[cfg(feature = "tls12")] + send_ticket: false, + } + } + + pub(super) fn process_common( + &mut self, + config: &ServerConfig, + cx: &mut ServerContext<'_>, + ocsp_response: &mut Option<&[u8]>, + hello: &ClientHelloPayload, + resumedata: Option<&persist::ServerSessionValue>, + ) -> Result<(), Error> { + // ALPN + let our_protocols = &config.alpn_protocols; + if let Some(their_protocols) = &hello.protocols { + cx.common.alpn_protocol = our_protocols + .iter() + .find(|ours| { + their_protocols + .iter() + .any(|theirs| theirs.as_ref() == ours.as_slice()) + }) + .map(|bytes| ProtocolName::from(bytes.clone())); + if let Some(selected_protocol) = &cx.common.alpn_protocol { + debug!("Chosen ALPN protocol {selected_protocol:?}"); + + self.extensions.selected_protocol = + Some(SingleProtocolName::new(selected_protocol.clone())); + } else if !our_protocols.is_empty() { + return Err(cx.common.send_fatal_alert( + AlertDescription::NoApplicationProtocol, + Error::NoApplicationProtocol, + )); + } + } + + if cx.common.is_quic() { + // QUIC has strict ALPN, unlike TLS's more backwards-compatible behavior. RFC 9001 + // says: "The server MUST treat the inability to select a compatible application + // protocol as a connection error of type 0x0178". We judge that ALPN was desired + // (rather than some out-of-band protocol negotiation mechanism) if and only if any ALPN + // protocols were configured locally or offered by the client. This helps prevent + // successful establishment of connections between peers that can't understand + // each other. + if cx.common.alpn_protocol.is_none() + && (!our_protocols.is_empty() || hello.protocols.is_some()) + { + return Err(cx.common.send_fatal_alert( + AlertDescription::NoApplicationProtocol, + Error::NoApplicationProtocol, + )); + } + + let transport_params = hello + .transport_parameters + .as_ref() + .or(hello + .transport_parameters_draft + .as_ref()); + match transport_params { + Some(params) => cx.common.quic.params = Some(params.to_owned().into_vec()), + None => { + return Err(cx + .common + .missing_extension(PeerMisbehaved::MissingQuicTransportParameters)); + } + } + } + + let for_resume = resumedata.is_some(); + // SNI + if let (false, Some(ServerNamePayload::SingleDnsName(_))) = (for_resume, &hello.server_name) + { + self.extensions.server_name_ack = Some(()); + } + + // Send status_request response if we have one. This is not allowed + // if we're resuming, and is only triggered if we have an OCSP response + // to send. + if !for_resume + && hello + .certificate_status_request + .is_some() + { + if ocsp_response.is_some() && !cx.common.is_tls13() { + // Only TLS1.2 sends confirmation in ServerHello + self.extensions + .certificate_status_request_ack = Some(()); + } + } else { + // Throw away any OCSP response so we don't try to send it later. + ocsp_response.take(); + } + + self.validate_server_cert_type_extension(hello, config, cx)?; + self.validate_client_cert_type_extension(hello, config, cx)?; + + Ok(()) + } + + #[cfg(feature = "tls12")] + pub(super) fn process_tls12( + &mut self, + config: &ServerConfig, + hello: &ClientHelloPayload, + using_ems: bool, + ) { + // Renegotiation. + // (We don't do reneg at all, but would support the secure version if we did.) + + use crate::msgs::base::PayloadU8; + let secure_reneg_offered = hello.renegotiation_info.is_some() + || hello + .cipher_suites + .contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + + if secure_reneg_offered { + self.extensions.renegotiation_info = Some(PayloadU8::new(Vec::new())); + } + + // Tickets: + // If we get any SessionTicket extension and have tickets enabled, + // we send an ack. + if hello.session_ticket.is_some() && config.ticketer.enabled() { + self.send_ticket = true; + self.extensions.session_ticket_ack = Some(()); + } + + // Confirm use of EMS if offered. + if using_ems { + self.extensions + .extended_master_secret_ack = Some(()); + } + } + + fn validate_server_cert_type_extension( + &mut self, + hello: &ClientHelloPayload, + config: &ServerConfig, + cx: &mut ServerContext<'_>, + ) -> Result<(), Error> { + let client_supports = hello + .server_certificate_types + .as_deref() + .unwrap_or_default(); + + self.process_cert_type_extension( + client_supports, + config + .cert_resolver + .only_raw_public_keys(), + ExtensionType::ServerCertificateType, + cx, + ) + } + + fn validate_client_cert_type_extension( + &mut self, + hello: &ClientHelloPayload, + config: &ServerConfig, + cx: &mut ServerContext<'_>, + ) -> Result<(), Error> { + let client_supports = hello + .client_certificate_types + .as_deref() + .unwrap_or_default(); + + self.process_cert_type_extension( + client_supports, + config + .verifier + .requires_raw_public_keys(), + ExtensionType::ClientCertificateType, + cx, + ) + } + + fn process_cert_type_extension( + &mut self, + client_supports: &[CertificateType], + requires_raw_keys: bool, + extension_type: ExtensionType, + cx: &mut ServerContext<'_>, + ) -> Result<(), Error> { + debug_assert!( + extension_type == ExtensionType::ClientCertificateType + || extension_type == ExtensionType::ServerCertificateType + ); + let raw_key_negotation_result = match ( + requires_raw_keys, + client_supports.contains(&CertificateType::RawPublicKey), + client_supports.contains(&CertificateType::X509), + ) { + (true, true, _) => Ok((extension_type, CertificateType::RawPublicKey)), + (false, _, true) => Ok((extension_type, CertificateType::X509)), + (false, true, false) => Err(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension, + )), + (true, false, _) => Err(Error::PeerIncompatible( + PeerIncompatible::IncorrectCertificateTypeExtension, + )), + (false, false, false) => return Ok(()), + }; + + match raw_key_negotation_result { + Ok((ExtensionType::ClientCertificateType, cert_type)) => { + self.extensions.client_certificate_type = Some(cert_type); + } + Ok((ExtensionType::ServerCertificateType, cert_type)) => { + self.extensions.server_certificate_type = Some(cert_type); + } + Err(err) => { + return Err(cx + .common + .send_fatal_alert(AlertDescription::HandshakeFailure, err)); + } + Ok((_, _)) => unreachable!(), + } + Ok(()) + } +} + +pub(super) struct ExpectClientHello { + pub(super) config: Arc<ServerConfig>, + pub(super) extra_exts: ServerExtensionsInput<'static>, + pub(super) transcript: HandshakeHashOrBuffer, + #[cfg(feature = "tls12")] + pub(super) session_id: SessionId, + #[cfg(feature = "tls12")] + pub(super) using_ems: bool, + pub(super) done_retry: bool, + pub(super) send_tickets: usize, +} + +impl ExpectClientHello { + pub(super) fn new( + config: Arc<ServerConfig>, + extra_exts: ServerExtensionsInput<'static>, + ) -> Self { + let mut transcript_buffer = HandshakeHashBuffer::new(); + + if config.verifier.offer_client_auth() { + transcript_buffer.set_client_auth_enabled(); + } + + Self { + config, + extra_exts, + transcript: HandshakeHashOrBuffer::Buffer(transcript_buffer), + #[cfg(feature = "tls12")] + session_id: SessionId::empty(), + #[cfg(feature = "tls12")] + using_ems: false, + done_retry: false, + send_tickets: 0, + } + } + + /// Continues handling of a `ClientHello` message once config and certificate are available. + pub(super) fn with_certified_key( + self, + mut sig_schemes: Vec<SignatureScheme>, + client_hello: &ClientHelloPayload, + m: &Message<'_>, + cx: &mut ServerContext<'_>, + ) -> NextStateOrError<'static> { + let tls13_enabled = self + .config + .supports_version(ProtocolVersion::TLSv1_3); + let tls12_enabled = self + .config + .supports_version(ProtocolVersion::TLSv1_2); + + // Are we doing TLS1.3? + let version = if let Some(versions) = &client_hello.supported_versions { + if versions.tls13 && tls13_enabled { + ProtocolVersion::TLSv1_3 + } else if !versions.tls12 || !tls12_enabled { + return Err(cx.common.send_fatal_alert( + AlertDescription::ProtocolVersion, + PeerIncompatible::Tls12NotOfferedOrEnabled, + )); + } else if cx.common.is_quic() { + return Err(cx.common.send_fatal_alert( + AlertDescription::ProtocolVersion, + PeerIncompatible::Tls13RequiredForQuic, + )); + } else { + ProtocolVersion::TLSv1_2 + } + } else if u16::from(client_hello.client_version) < u16::from(ProtocolVersion::TLSv1_2) { + return Err(cx.common.send_fatal_alert( + AlertDescription::ProtocolVersion, + PeerIncompatible::Tls12NotOffered, + )); + } else if !tls12_enabled && tls13_enabled { + return Err(cx.common.send_fatal_alert( + AlertDescription::ProtocolVersion, + PeerIncompatible::SupportedVersionsExtensionRequired, + )); + } else if cx.common.is_quic() { + return Err(cx.common.send_fatal_alert( + AlertDescription::ProtocolVersion, + PeerIncompatible::Tls13RequiredForQuic, + )); + } else { + ProtocolVersion::TLSv1_2 + }; + + cx.common.negotiated_version = Some(version); + + // We communicate to the upper layer what kind of key they should choose + // via the sigschemes value. Clients tend to treat this extension + // orthogonally to offered ciphersuites (even though, in TLS1.2 it is not). + // So: reduce the offered sigschemes to those compatible with the + // intersection of ciphersuites. + let client_suites = self + .config + .provider + .cipher_suites + .iter() + .copied() + .filter(|scs| { + client_hello + .cipher_suites + .contains(&scs.suite()) + }) + .collect::<Vec<_>>(); + + sig_schemes + .retain(|scheme| suites::compatible_sigscheme_for_suites(*scheme, &client_suites)); + + // We adhere to the TLS 1.2 RFC by not exposing this to the cert resolver if TLS version is 1.2 + let certificate_authorities = match version { + ProtocolVersion::TLSv1_2 => None, + _ => client_hello + .certificate_authority_names + .as_deref(), + }; + // Choose a certificate. + let certkey = { + let client_hello = ClientHello { + server_name: &cx.data.sni, + signature_schemes: &sig_schemes, + alpn: client_hello.protocols.as_ref(), + client_cert_types: client_hello + .client_certificate_types + .as_deref(), + server_cert_types: client_hello + .server_certificate_types + .as_deref(), + cipher_suites: &client_hello.cipher_suites, + certificate_authorities, + named_groups: client_hello.named_groups.as_deref(), + }; + trace!("Resolving server certificate: {client_hello:#?}"); + + let certkey = self + .config + .cert_resolver + .resolve(client_hello); + + certkey.ok_or_else(|| { + cx.common.send_fatal_alert( + AlertDescription::AccessDenied, + Error::General("no server certificate chain resolved".to_owned()), + ) + })? + }; + let certkey = ActiveCertifiedKey::from_certified_key(&certkey); + + let (suite, skxg) = self + .choose_suite_and_kx_group( + version, + certkey.get_key().algorithm(), + cx.common.protocol, + client_hello + .named_groups + .as_deref() + .unwrap_or_default(), + &client_hello.cipher_suites, + ) + .map_err(|incompat| { + cx.common + .send_fatal_alert(AlertDescription::HandshakeFailure, incompat) + })?; + + debug!("decided upon suite {suite:?}"); + cx.common.suite = Some(suite); + cx.common.kx_state = KxState::Start(skxg); + + // Start handshake hash. + let starting_hash = suite.hash_provider(); + let transcript = match self.transcript { + HandshakeHashOrBuffer::Buffer(inner) => inner.start_hash(starting_hash), + HandshakeHashOrBuffer::Hash(inner) + if inner.algorithm() == starting_hash.algorithm() => + { + inner + } + _ => { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::HandshakeHashVariedAfterRetry, + )); + } + }; + + // Save their Random. + let randoms = ConnectionRandoms::new( + client_hello.random, + Random::new(self.config.provider.secure_random)?, + ); + match suite { + SupportedCipherSuite::Tls13(suite) => tls13::CompleteClientHelloHandling { + config: self.config, + transcript, + suite, + randoms, + done_retry: self.done_retry, + send_tickets: self.send_tickets, + extra_exts: self.extra_exts, + } + .handle_client_hello(cx, certkey, m, client_hello, skxg, sig_schemes), + #[cfg(feature = "tls12")] + SupportedCipherSuite::Tls12(suite) => tls12::CompleteClientHelloHandling { + config: self.config, + transcript, + session_id: self.session_id, + suite, + using_ems: self.using_ems, + randoms, + send_ticket: self.send_tickets > 0, + extra_exts: self.extra_exts, + } + .handle_client_hello( + cx, + certkey, + m, + client_hello, + skxg, + sig_schemes, + tls13_enabled, + ), + } + } + + fn choose_suite_and_kx_group( + &self, + selected_version: ProtocolVersion, + sig_key_algorithm: SignatureAlgorithm, + protocol: Protocol, + client_groups: &[NamedGroup], + client_suites: &[CipherSuite], + ) -> Result<(SupportedCipherSuite, &'static dyn SupportedKxGroup), PeerIncompatible> { + // Determine which `KeyExchangeAlgorithm`s are theoretically possible, based + // on the offered and supported groups. + let mut ecdhe_possible = false; + let mut ffdhe_possible = false; + let mut ffdhe_offered = false; + let mut supported_groups = Vec::with_capacity(client_groups.len()); + + for offered_group in client_groups { + let supported = self + .config + .provider + .kx_groups + .iter() + .find(|skxg| { + skxg.usable_for_version(selected_version) && skxg.name() == *offered_group + }); + + match offered_group.key_exchange_algorithm() { + KeyExchangeAlgorithm::DHE => { + ffdhe_possible |= supported.is_some(); + ffdhe_offered = true; + } + KeyExchangeAlgorithm::ECDHE => { + ecdhe_possible |= supported.is_some(); + } + } + + supported_groups.push(supported); + } + + let first_supported_dhe_kxg = if selected_version == ProtocolVersion::TLSv1_2 { + // https://datatracker.ietf.org/doc/html/rfc7919#section-4 (paragraph 2) + let first_supported_dhe_kxg = self + .config + .provider + .kx_groups + .iter() + .find(|skxg| skxg.name().key_exchange_algorithm() == KeyExchangeAlgorithm::DHE); + ffdhe_possible |= !ffdhe_offered && first_supported_dhe_kxg.is_some(); + first_supported_dhe_kxg + } else { + // In TLS1.3, the server may only directly negotiate a group. + None + }; + + if !ecdhe_possible && !ffdhe_possible { + return Err(PeerIncompatible::NoKxGroupsInCommon); + } + + let mut suitable_suites_iter = self + .config + .provider + .cipher_suites + .iter() + .filter(|suite| { + // Reduce our supported ciphersuites by the certified key's algorithm. + suite.usable_for_signature_algorithm(sig_key_algorithm) + // And version + && suite.version().version == selected_version + // And protocol + && suite.usable_for_protocol(protocol) + // And support one of key exchange groups + && (ecdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::ECDHE) + || ffdhe_possible && suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE)) + }); + + // RFC 7919 (https://datatracker.ietf.org/doc/html/rfc7919#section-4) requires us to send + // the InsufficientSecurity alert in case we don't recognize client's FFDHE groups (i.e., + // `suitable_suites` becomes empty). But that does not make a lot of sense (e.g., client + // proposes FFDHE4096 and we only support FFDHE2048), so we ignore that requirement here, + // and continue to send HandshakeFailure. + + let suite = if self.config.ignore_client_order { + suitable_suites_iter.find(|suite| client_suites.contains(&suite.suite())) + } else { + let suitable_suites = suitable_suites_iter.collect::<Vec<_>>(); + client_suites + .iter() + .find_map(|client_suite| { + suitable_suites + .iter() + .find(|x| *client_suite == x.suite()) + }) + .copied() + } + .ok_or(PeerIncompatible::NoCipherSuitesInCommon)?; + + // Finally, choose a key exchange group that is compatible with the selected cipher + // suite. + let maybe_skxg = supported_groups + .iter() + .find_map(|maybe_skxg| match maybe_skxg { + Some(skxg) => suite + .usable_for_kx_algorithm(skxg.name().key_exchange_algorithm()) + .then_some(*skxg), + None => None, + }); + + if selected_version == ProtocolVersion::TLSv1_3 { + // This unwrap is structurally guaranteed by the early return for `!ffdhe_possible && !ecdhe_possible` + return Ok((*suite, *maybe_skxg.unwrap())); + } + + // For TLS1.2, the server can unilaterally choose a DHE group if it has one and + // there was no better option. + match maybe_skxg { + Some(skxg) => Ok((*suite, *skxg)), + None if suite.usable_for_kx_algorithm(KeyExchangeAlgorithm::DHE) => { + // If kx for the selected cipher suite is DHE and no DHE groups are specified in the extension, + // the server is free to choose DHE params, we choose the first DHE kx group of the provider. + if let Some(server_selected_ffdhe_skxg) = first_supported_dhe_kxg { + Ok((*suite, *server_selected_ffdhe_skxg)) + } else { + Err(PeerIncompatible::NoKxGroupsInCommon) + } + } + None => Err(PeerIncompatible::NoKxGroupsInCommon), + } + } +} + +impl State<ServerConnectionData> for ExpectClientHello { + fn handle<'m>( + self: Box<Self>, + cx: &mut ServerContext<'_>, + m: Message<'m>, + ) -> NextStateOrError<'m> + where + Self: 'm, + { + let (client_hello, sig_schemes) = process_client_hello(&m, self.done_retry, cx)?; + self.with_certified_key(sig_schemes, client_hello, &m, cx) + } + + fn into_owned(self: Box<Self>) -> NextState<'static> { + self + } +} + +/// Configuration-independent validation of a `ClientHello` message. +/// +/// This represents the first part of the `ClientHello` handling, where we do all validation that +/// doesn't depend on a `ServerConfig` being available and extract everything needed to build a +/// [`ClientHello`] value for a [`ResolvesServerCert`]. +/// +/// Note that this will modify `data.sni` even if config or certificate resolution fail. +/// +/// [`ResolvesServerCert`]: crate::server::ResolvesServerCert +pub(super) fn process_client_hello<'m>( + m: &'m Message<'m>, + done_retry: bool, + cx: &mut ServerContext<'_>, +) -> Result<(&'m ClientHelloPayload, Vec<SignatureScheme>), Error> { + let client_hello = + require_handshake_msg!(m, HandshakeType::ClientHello, HandshakePayload::ClientHello)?; + trace!("we got a clienthello {client_hello:?}"); + + if !client_hello + .compression_methods + .contains(&Compression::Null) + { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerIncompatible::NullCompressionRequired, + )); + } + + // No handshake messages should follow this one in this flight. + cx.common.check_aligned_handshake()?; + + // Extract and validate the SNI DNS name, if any, before giving it to + // the cert resolver. In particular, if it is invalid then we should + // send an Illegal Parameter alert instead of the Internal Error alert + // (or whatever) that we'd send if this were checked later or in a + // different way. + // + // [RFC6066][] specifies that literal IP addresses are illegal in + // `ServerName`s with a `name_type` of `host_name`. + // + // Some clients incorrectly send such extensions: we choose to + // successfully parse these (into `ServerNamePayload::IpAddress`) + // but then act like the client sent no `server_name` extension. + // + // [RFC6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-3 + let sni = match &client_hello.server_name { + Some(ServerNamePayload::SingleDnsName(dns_name)) => Some(dns_name.to_lowercase_owned()), + Some(ServerNamePayload::IpAddress) => None, + Some(ServerNamePayload::Invalid) => { + return Err(cx.common.send_fatal_alert( + AlertDescription::IllegalParameter, + PeerMisbehaved::ServerNameMustContainOneHostName, + )); + } + None => None, + }; + + // save only the first SNI + if let (Some(sni), false) = (&sni, done_retry) { + // Save the SNI into the session. + // The SNI hostname is immutable once set. + assert!(cx.data.sni.is_none()); + cx.data.sni = Some(sni.clone()); + } else if cx.data.sni != sni { + return Err(PeerMisbehaved::ServerNameDifferedOnRetry.into()); + } + + let sig_schemes = client_hello + .signature_schemes + .as_ref() + .ok_or_else(|| { + cx.common.send_fatal_alert( + AlertDescription::HandshakeFailure, + PeerIncompatible::SignatureAlgorithmsExtensionRequired, + ) + })?; + + Ok((client_hello, sig_schemes.to_owned())) +} + +pub(crate) enum HandshakeHashOrBuffer { + Buffer(HandshakeHashBuffer), + Hash(HandshakeHash), +} |
