Support RFC7627 extended master secret

This commit is contained in:
Joseph Birr-Pixton 2017-02-17 02:10:39 +00:00
parent 42800fd492
commit 466ed6381a
9 changed files with 151 additions and 29 deletions

View File

@ -14,8 +14,6 @@
"ConflictingVersionNegotiation": "",
"ConflictingVersionNegotiation-2": "",
"PointFormat-Server-Missing": "we require ecc",
"EMS-Forbidden-TLS13": "TODO: unsupported but should be",
"ExtendedMasterSecret-*": "",
"ECDSAKeyUsage-*": "TODO: we don't do anything with key usages",
"CheckRecordVersion-*": "we don't look at record version",
"TLS13-WrongOuterRecord": "we're lax on this",
@ -76,6 +74,7 @@
"Renegotiate-Client-*": "no reneg",
"Renegotiate-Server-*": "",
"SendHalfHelloRequest-*": "",
"ExtendedMasterSecret-Renego-*": "",
"Agree-Digest-SHA1": "the remainder are tests that are UNIMPLEMENTED (ie that need an option the shim doesn't support). including them here makes running the tests quicker under kcov",
"Agree-Digest-SHA256": "delete these when doing development of the shim or upgrading bogo!",
@ -324,8 +323,6 @@
"NoCommonAlgorithms": "",
"NoCommonAlgorithms-Digests": "",
"NoCommonAlgorithms-TLS13": "",
"NoExtendedMasterSecret-TLS13-Client": "",
"NoExtendedMasterSecret-TLS13-Server": "",
"NoFalseStart-DHE_RSA": "",
"NoFalseStart-NoAEAD": "",
"NoFalseStart-NoALPN": "",
@ -688,6 +685,10 @@
"SendUnsolicitedOCSPOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
"SendUnsolicitedSCTOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
"SendUnknownExtensionOnCertificate-TLS13": ":PEER_MISBEHAVIOUR:",
"LargePlaintext": ":PEER_MISBEHAVIOUR:"
"LargePlaintext": ":PEER_MISBEHAVIOUR:",
"EMS-Forbidden-TLS13": ":PEER_MISBEHAVIOUR:",
"ExtendedMasterSecret-NoToYes-Client": ":PEER_MISBEHAVIOUR:",
"ExtendedMasterSecret-YesToNo-Server": ":PEER_MISBEHAVIOUR:",
"ExtendedMasterSecret-YesToNo-Client": ":PEER_MISBEHAVIOUR:"
}
}

View File

@ -336,6 +336,7 @@ fn main() {
}
"-expect-no-session" |
"-expect-session-miss" |
"-expect-extended-master-secret" |
"-expect-ticket-renewal" => {}
"-select-alpn" => {
@ -407,7 +408,6 @@ fn main() {
"-use-ticket-callback" |
"-enable-grease" |
"-enable-channel-id" |
"-expect-extended-master-secret" |
"-expect-resume-curve-id" |
"-resumption-delay" |
"-expect-early-data-info" |

View File

@ -262,6 +262,7 @@ pub struct ClientHandshakeData {
pub resuming_session: Option<persist::ClientSessionValue>,
pub randoms: SessionRandoms,
pub must_issue_new_ticket: bool,
pub using_ems: bool,
pub new_ticket: Vec<u8>,
pub new_ticket_lifetime: u32,
pub doing_client_auth: bool,
@ -285,6 +286,7 @@ impl ClientHandshakeData {
resuming_session: None,
randoms: SessionRandoms::for_client(),
must_issue_new_ticket: false,
using_ems: false,
new_ticket: Vec::new(),
new_ticket_lifetime: 0,
doing_client_auth: false,

View File

@ -230,6 +230,7 @@ fn emit_client_hello_for_retry(sess: &mut ClientSessionImpl,
exts.push(ClientExtension::ECPointFormats(ECPointFormatList::supported()));
exts.push(ClientExtension::NamedGroups(NamedGroups::supported()));
exts.push(ClientExtension::SignatureAlgorithms(SupportedSignatureSchemes::supported_verify()));
exts.push(ClientExtension::ExtendedMasterSecretRequest);
if support_tls13 {
exts.push(ClientExtension::KeyShare(key_shares));
@ -381,7 +382,8 @@ static ALLOWED_PLAINTEXT_EXTS: &'static [ExtensionType] = &[
static DISALLOWED_TLS13_EXTS: &'static [ExtensionType] = &[
ExtensionType::ECPointFormats,
ExtensionType::SessionTicket,
ExtensionType::RenegotiationInfo
ExtensionType::RenegotiationInfo,
ExtensionType::ExtendedMasterSecret,
];
fn validate_server_hello_tls13(sess: &mut ClientSessionImpl,
@ -561,6 +563,11 @@ fn handle_server_hello(sess: &mut ClientSessionImpl, m: Message) -> StateResult
server_hello.random.write_slice(&mut sess.handshake_data.randoms.server);
sess.handshake_data.session_id = server_hello.session_id.clone();
// Doing EMS?
if server_hello.ems_support_acked() {
sess.handshake_data.using_ems = true;
}
// Might the server send a ticket?
if server_hello.find_extension(ExtensionType::SessionTicket).is_some() {
info!("Server supports tickets");
@ -580,6 +587,12 @@ fn handle_server_hello(sess: &mut ClientSessionImpl, m: Message) -> StateResult
return Err(TLSError::PeerMisbehavedError(error_msg));
}
// And about EMS support?
if resuming.extended_ms != sess.handshake_data.using_ems {
let error_msg = "server varied ems support over resume".to_string();
return Err(TLSError::PeerMisbehavedError(error_msg));
}
sess.secrets = Some(SessionSecrets::new_resume(&sess.handshake_data.randoms,
scs.unwrap().get_hash(),
&resuming.master_secret.0));
@ -1127,6 +1140,8 @@ fn handle_server_hello_done(sess: &mut ClientSessionImpl,
// 4b.
emit_clientkx(sess, &kxd);
// nb. EMS handshake hash only runs up to ClientKeyExchange.
let handshake_hash = sess.handshake_data.transcript.get_current_hash();
// 4c.
if sess.handshake_data.doing_client_auth {
@ -1138,8 +1153,16 @@ fn handle_server_hello_done(sess: &mut ClientSessionImpl,
// 4e. Now commit secrets.
let hashalg = sess.common.get_suite().get_hash();
sess.secrets =
Some(SessionSecrets::new(&sess.handshake_data.randoms, hashalg, &kxd.premaster_secret));
if sess.handshake_data.using_ems {
sess.secrets = Some(SessionSecrets::new_ems(&sess.handshake_data.randoms,
&handshake_hash,
hashalg,
&kxd.premaster_secret));
} else {
sess.secrets = Some(SessionSecrets::new(&sess.handshake_data.randoms,
hashalg,
&kxd.premaster_secret));
}
sess.start_encryption_tls12();
// 5.
@ -1253,6 +1276,9 @@ fn save_session(sess: &mut ClientSessionImpl) {
value.set_times(ticket_timebase(),
sess.handshake_data.new_ticket_lifetime,
0);
if sess.handshake_data.using_ems {
value.set_extended_ms_used();
}
let mut persist = sess.config.session_persistence.lock().unwrap();
let worked = persist.put(key.get_encoding(), value.get_encoding());

View File

@ -555,6 +555,7 @@ pub enum ClientExtension {
PresharedKeyModes(PSKKeyExchangeModes),
PresharedKey(PresharedKeyOffer),
Cookie(PayloadU16),
ExtendedMasterSecretRequest,
Unknown(UnknownExtension),
}
@ -574,6 +575,7 @@ impl ClientExtension {
ClientExtension::PresharedKeyModes(_) => ExtensionType::PSKKeyExchangeModes,
ClientExtension::PresharedKey(_) => ExtensionType::PreSharedKey,
ClientExtension::Cookie(_) => ExtensionType::Cookie,
ClientExtension::ExtendedMasterSecretRequest => ExtensionType::ExtendedMasterSecret,
ClientExtension::Unknown(ref r) => r.typ,
}
}
@ -598,6 +600,7 @@ impl Codec for ClientExtension {
ClientExtension::PresharedKeyModes(ref r) => r.encode(&mut sub),
ClientExtension::PresharedKey(ref r) => r.encode(&mut sub),
ClientExtension::Cookie(ref r) => r.encode(&mut sub),
ClientExtension::ExtendedMasterSecretRequest => (),
ClientExtension::Unknown(ref r) => r.encode(&mut sub),
}
@ -617,8 +620,10 @@ impl Codec for ClientExtension {
ExtensionType::EllipticCurves => {
ClientExtension::NamedGroups(try_ret!(NamedGroups::read(&mut sub)))
}
ExtensionType::SignatureAlgorithms =>
ClientExtension::SignatureAlgorithms(try_ret!(SupportedSignatureSchemes::read(&mut sub))),
ExtensionType::SignatureAlgorithms => {
let schemes = try_ret!(SupportedSignatureSchemes::read(&mut sub));
ClientExtension::SignatureAlgorithms(schemes)
}
ExtensionType::Heartbeat => {
ClientExtension::Heartbeat(try_ret!(HeartbeatMode::read(&mut sub)))
}
@ -648,6 +653,9 @@ impl Codec for ClientExtension {
ClientExtension::PresharedKey(try_ret!(PresharedKeyOffer::read(&mut sub)))
}
ExtensionType::Cookie => ClientExtension::Cookie(try_ret!(PayloadU16::read(&mut sub))),
ExtensionType::ExtendedMasterSecret if !sub.any_left() => {
ClientExtension::ExtendedMasterSecretRequest
}
_ => ClientExtension::Unknown(try_ret!(UnknownExtension::read(typ, &mut sub))),
})
}
@ -669,12 +677,13 @@ impl ClientExtension {
pub enum ServerExtension {
ECPointFormats(ECPointFormatList),
Heartbeat(HeartbeatMode),
ServerNameAcknowledgement,
SessionTicketAcknowledgement,
ServerNameAck,
SessionTicketAck,
RenegotiationInfo(PayloadU8),
Protocols(ProtocolNameList),
KeyShare(KeyShareEntry),
PresharedKey(u16),
ExtendedMasterSecretAck,
Unknown(UnknownExtension),
}
@ -683,12 +692,13 @@ impl ServerExtension {
match *self {
ServerExtension::ECPointFormats(_) => ExtensionType::ECPointFormats,
ServerExtension::Heartbeat(_) => ExtensionType::Heartbeat,
ServerExtension::ServerNameAcknowledgement => ExtensionType::ServerName,
ServerExtension::SessionTicketAcknowledgement => ExtensionType::SessionTicket,
ServerExtension::ServerNameAck => ExtensionType::ServerName,
ServerExtension::SessionTicketAck => ExtensionType::SessionTicket,
ServerExtension::RenegotiationInfo(_) => ExtensionType::RenegotiationInfo,
ServerExtension::Protocols(_) => ExtensionType::ALProtocolNegotiation,
ServerExtension::KeyShare(_) => ExtensionType::KeyShare,
ServerExtension::PresharedKey(_) => ExtensionType::PreSharedKey,
ServerExtension::ExtendedMasterSecretAck => ExtensionType::ExtendedMasterSecret,
ServerExtension::Unknown(ref r) => r.typ,
}
}
@ -702,12 +712,13 @@ impl Codec for ServerExtension {
match *self {
ServerExtension::ECPointFormats(ref r) => r.encode(&mut sub),
ServerExtension::Heartbeat(ref r) => r.encode(&mut sub),
ServerExtension::ServerNameAcknowledgement => (),
ServerExtension::SessionTicketAcknowledgement => (),
ServerExtension::ServerNameAck => (),
ServerExtension::SessionTicketAck => (),
ServerExtension::RenegotiationInfo(ref r) => r.encode(&mut sub),
ServerExtension::Protocols(ref r) => r.encode(&mut sub),
ServerExtension::KeyShare(ref r) => r.encode(&mut sub),
ServerExtension::PresharedKey(r) => codec::encode_u16(r, &mut sub),
ServerExtension::ExtendedMasterSecretAck => (),
ServerExtension::Unknown(ref r) => r.encode(&mut sub),
}
@ -727,8 +738,8 @@ impl Codec for ServerExtension {
ExtensionType::Heartbeat => {
ServerExtension::Heartbeat(try_ret!(HeartbeatMode::read(&mut sub)))
}
ExtensionType::ServerName => ServerExtension::ServerNameAcknowledgement,
ExtensionType::SessionTicket => ServerExtension::SessionTicketAcknowledgement,
ExtensionType::ServerName => ServerExtension::ServerNameAck,
ExtensionType::SessionTicket => ServerExtension::SessionTicketAck,
ExtensionType::RenegotiationInfo => {
ServerExtension::RenegotiationInfo(try_ret!(PayloadU8::read(&mut sub)))
}
@ -741,6 +752,7 @@ impl Codec for ServerExtension {
ExtensionType::PreSharedKey => {
ServerExtension::PresharedKey(try_ret!(codec::read_u16(&mut sub)))
}
ExtensionType::ExtendedMasterSecret => ServerExtension::ExtendedMasterSecretAck,
_ => ServerExtension::Unknown(try_ret!(UnknownExtension::read(typ, &mut sub))),
})
}
@ -941,6 +953,11 @@ impl ClientHelloPayload {
_ => {}
};
}
pub fn ems_support_offered(&self) -> bool {
self.find_extension(ExtensionType::ExtendedMasterSecret)
.is_some()
}
}
#[derive(Debug)]
@ -1151,6 +1168,11 @@ impl ServerHelloPayload {
_ => None,
}
}
pub fn ems_support_acked(&self) -> bool {
self.find_extension(ExtensionType::ExtendedMasterSecret)
.is_some()
}
}
pub type ASN1Cert = key::Certificate; // PayloadU24;

View File

@ -56,7 +56,8 @@ pub struct ClientSessionValue {
pub master_secret: PayloadU8,
pub epoch: u64,
pub lifetime: u32,
pub age_add: u32
pub age_add: u32,
pub extended_ms: bool,
}
impl Codec for ClientSessionValue {
@ -69,6 +70,7 @@ impl Codec for ClientSessionValue {
codec::encode_u64(self.epoch, bytes);
codec::encode_u32(self.lifetime, bytes);
codec::encode_u32(self.age_add, bytes);
codec::encode_u8(if self.extended_ms { 1u8 } else { 0u8 }, bytes);
}
fn read(r: &mut Reader) -> Option<ClientSessionValue> {
@ -80,6 +82,7 @@ impl Codec for ClientSessionValue {
let epoch = try_ret!(codec::read_u64(r));
let lifetime = try_ret!(codec::read_u32(r));
let age_add = try_ret!(codec::read_u32(r));
let extended_ms = try_ret!(codec::read_u8(r));
Some(ClientSessionValue {
version: v,
@ -89,7 +92,8 @@ impl Codec for ClientSessionValue {
master_secret: ms,
epoch: epoch,
lifetime: lifetime,
age_add: age_add
age_add: age_add,
extended_ms: extended_ms == 1u8,
})
}
}
@ -111,10 +115,15 @@ impl ClientSessionValue {
master_secret: PayloadU8::new(ms),
epoch: 0,
lifetime: 0,
age_add: 0
age_add: 0,
extended_ms: false,
}
}
pub fn set_extended_ms_used(&mut self) {
self.extended_ms = true;
}
pub fn set_times(&mut self, receipt_time_secs: u64,
lifetime_secs: u32, age_add: u32) {
self.epoch = receipt_time_secs;
@ -147,6 +156,7 @@ pub struct ServerSessionValue {
pub version: ProtocolVersion,
pub cipher_suite: CipherSuite,
pub master_secret: PayloadU8,
pub extended_ms: bool,
pub client_cert_chain: Option<CertificatePayload>,
}
@ -155,6 +165,7 @@ impl Codec for ServerSessionValue {
self.version.encode(bytes);
self.cipher_suite.encode(bytes);
self.master_secret.encode(bytes);
codec::encode_u8(if self.extended_ms { 1u8 } else { 0u8 }, bytes);
if self.client_cert_chain.is_some() {
self.client_cert_chain.as_ref().unwrap().encode(bytes);
}
@ -164,6 +175,7 @@ impl Codec for ServerSessionValue {
let v = try_ret!(ProtocolVersion::read(r));
let cs = try_ret!(CipherSuite::read(r));
let ms = try_ret!(PayloadU8::read(r));
let ems = try_ret!(codec::read_u8(r));
let ccert = if r.any_left() {
CertificatePayload::read(r)
} else {
@ -174,6 +186,7 @@ impl Codec for ServerSessionValue {
version: v,
cipher_suite: cs,
master_secret: ms,
extended_ms: ems == 1u8,
client_cert_chain: ccert,
})
}
@ -189,7 +202,12 @@ impl ServerSessionValue {
version: v,
cipher_suite: cs,
master_secret: PayloadU8::new(ms),
extended_ms: false,
client_cert_chain: cert_chain.clone(),
}
}
pub fn set_extended_ms_used(&mut self) {
self.extended_ms = true;
}
}

View File

@ -319,6 +319,7 @@ pub struct ServerHandshakeData {
pub kx_data: Option<KeyExchange>,
pub doing_resume: bool,
pub send_ticket: bool,
pub using_ems: bool,
pub doing_client_auth: bool,
pub done_retry: bool,
pub valid_client_cert_chain: Option<Vec<key::Certificate>>,
@ -334,6 +335,7 @@ impl ServerHandshakeData {
hash_at_server_fin: vec![],
kx_data: None,
send_ticket: false,
using_ems: false,
doing_resume: false,
doing_client_auth: false,
done_retry: false,

View File

@ -87,7 +87,7 @@ fn process_extensions(sess: &mut ServerSessionImpl,
// SNI
if hello.get_sni_extension().is_some() {
ret.push(ServerExtension::ServerNameAcknowledgement);
ret.push(ServerExtension::ServerNameAck);
}
if !sess.common.is_tls13() {
@ -107,7 +107,12 @@ fn process_extensions(sess: &mut ServerSessionImpl,
if hello.find_extension(ExtensionType::SessionTicket).is_some() &&
sess.config.ticketer.enabled() {
sess.handshake_data.send_ticket = true;
ret.push(ServerExtension::SessionTicketAcknowledgement);
ret.push(ServerExtension::SessionTicketAck);
}
// Confirm use of EMS if offered.
if sess.handshake_data.using_ems {
ret.push(ServerExtension::ExtendedMasterSecretAck);
}
}
@ -270,8 +275,13 @@ fn can_resume(sess: &ServerSessionImpl,
resumedata: &Option<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.
resumedata.is_some() &&
resumedata.as_ref().unwrap().cipher_suite == sess.common.get_suite().suite
if let Some(ref resume) = *resumedata {
resume.cipher_suite == sess.common.get_suite().suite &&
(resume.extended_ms == sess.handshake_data.using_ems ||
(resume.extended_ms && !sess.handshake_data.using_ems))
} else {
false
}
}
fn start_resumption(sess: &mut ServerSessionImpl,
@ -281,6 +291,10 @@ fn start_resumption(sess: &mut ServerSessionImpl,
-> StateResult {
info!("Resuming session");
if resumedata.extended_ms && !sess.handshake_data.using_ems {
return Err(illegal_param(sess, "refusing to resume without ems"));
}
sess.handshake_data.session_id = id.clone();
try!(emit_server_hello(sess, client_hello));
@ -785,6 +799,10 @@ fn handle_client_hello(sess: &mut ServerSessionImpl, m: Message) -> StateResult
// Save their Random.
client_hello.random.write_slice(&mut sess.handshake_data.randoms.client);
if client_hello.ems_support_offered() {
sess.handshake_data.using_ems = true;
}
let groups_ext = try!(client_hello.get_namedgroups_extension()
.ok_or_else(|| incompatible(sess, "client didn't describe groups")));
let ecpoints_ext = try!(client_hello.get_ecpoints_extension()
@ -989,8 +1007,17 @@ fn handle_client_kx(sess: &mut ServerSessionImpl, m: Message) -> StateResult {
};
let hashalg = sess.common.get_suite().get_hash();
sess.secrets =
Some(SessionSecrets::new(&sess.handshake_data.randoms, hashalg, &kxd.premaster_secret));
if sess.handshake_data.using_ems {
let handshake_hash = sess.handshake_data.transcript.get_current_hash();
sess.secrets = Some(SessionSecrets::new_ems(&sess.handshake_data.randoms,
&handshake_hash,
hashalg,
&kxd.premaster_secret));
} else {
sess.secrets = Some(SessionSecrets::new(&sess.handshake_data.randoms,
hashalg,
&kxd.premaster_secret));
}
sess.start_encryption_tls12();
if sess.handshake_data.doing_client_auth {
@ -1171,7 +1198,13 @@ fn get_server_session_value(sess: &ServerSessionImpl) -> persist::ServerSessionV
(ProtocolVersion::TLSv1_2, sess.secrets.as_ref().unwrap().get_master_secret())
};
persist::ServerSessionValue::new(version, scs.suite, secret, client_certs)
let mut v = persist::ServerSessionValue::new(version, scs.suite, secret, client_certs);
if sess.handshake_data.using_ems {
v.set_extended_ms_used();
}
v
}
fn handle_finished(sess: &mut ServerSessionImpl, m: Message) -> StateResult {

View File

@ -150,6 +150,24 @@ impl SessionSecrets {
ret
}
pub fn new_ems(randoms: &SessionRandoms,
hs_hash: &[u8],
hashalg: &'static ring::digest::Algorithm,
pms: &[u8]) -> SessionSecrets {
let mut ret = SessionSecrets {
randoms: randoms.clone(),
hash: hashalg,
master_secret: [0u8; 48]
};
prf::prf(&mut ret.master_secret,
ret.hash,
pms,
b"extended master secret",
hs_hash);
ret
}
pub fn new_resume(randoms: &SessionRandoms,
hashalg: &'static ring::digest::Algorithm,
master_secret: &[u8])