Implement HelloRetryRequest in Go.

Change-Id: Ibde837040d2332bc8570589ba5be9b32e774bfcf
Reviewed-on: https://boringssl-review.googlesource.com/8811
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
This commit is contained in:
Nick Harper 2016-07-16 17:47:31 +02:00 committed by CQ bot account: commit-bot@chromium.org
parent c7fe3b9ac5
commit dcfbc67d1c
5 changed files with 183 additions and 13 deletions

View File

@ -364,6 +364,12 @@ type Config struct {
// be used.
CurvePreferences []CurveID
// DefaultCurves contains the elliptic curves for which public values will
// be sent in the ClientHello's KeyShare extension. If this value is nil,
// all supported curves will have public values sent. This field is ignored
// on servers.
DefaultCurves []CurveID
// ChannelID contains the ECDSA key for the client to use as
// its TLS Channel ID.
ChannelID *ecdsa.PrivateKey
@ -1041,6 +1047,18 @@ func (c *Config) curvePreferences() []CurveID {
return c.CurvePreferences
}
func (c *Config) defaultCurves() map[CurveID]bool {
defaultCurves := make(map[CurveID]bool)
curves := c.DefaultCurves
if c == nil || c.DefaultCurves == nil {
curves = c.curvePreferences()
}
for _, curveID := range curves {
defaultCurves[curveID] = true
}
return defaultCurves
}
// mutualVersion returns the protocol version to use given the advertised
// version of the peer.
func (c *Config) mutualVersion(vers uint16, isDTLS bool) (uint16, bool) {

View File

@ -1137,6 +1137,8 @@ func (c *Conn) readHandshake() (interface{}, error) {
m = &serverHelloMsg{
isDTLS: c.isDTLS,
}
case typeHelloRetryRequest:
m = new(helloRetryRequestMsg)
case typeNewSessionTicket:
m = new(newSessionTicketMsg)
case typeEncryptedExtensions:

View File

@ -105,13 +105,13 @@ func (c *Conn) clientHandshake() error {
var keyShares map[CurveID]ecdhCurve
if hello.vers >= VersionTLS13 {
// Offer every supported curve in the initial ClientHello.
//
// TODO(davidben): For real code, default to a more conservative
// set like P-256 and X25519. Make it configurable for tests to
// stress the HelloRetryRequest logic when implemented.
keyShares = make(map[CurveID]ecdhCurve)
hello.hasKeyShares = true
curvesToSend := c.config.defaultCurves()
for _, curveID := range hello.supportedCurves {
if !curvesToSend[curveID] {
continue
}
curve, ok := curveForCurveID(curveID)
if !ok {
continue
@ -314,19 +314,78 @@ NextCipherSuite:
}
}
// TODO(davidben): Handle HelloRetryRequest.
var serverVersion uint16
switch m := msg.(type) {
case *helloRetryRequestMsg:
serverVersion = m.vers
case *serverHelloMsg:
serverVersion = m.vers
default:
c.sendAlert(alertUnexpectedMessage)
return fmt.Errorf("tls: received unexpected message of type %T when waiting for HelloRetryRequest or ServerHello", msg)
}
var ok bool
c.vers, ok = c.config.mutualVersion(serverVersion, c.isDTLS)
if !ok {
c.sendAlert(alertProtocolVersion)
return fmt.Errorf("tls: server selected unsupported protocol version %x", c.vers)
}
c.haveVers = true
helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
var secondHelloBytes []byte
if haveHelloRetryRequest {
var hrrCurveFound bool
group := helloRetryRequest.selectedGroup
for _, curveID := range hello.supportedCurves {
if group == curveID {
hrrCurveFound = true
break
}
}
if !hrrCurveFound || keyShares[group] != nil {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: received invalid HelloRetryRequest")
}
curve, ok := curveForCurveID(group)
if !ok {
return errors.New("tls: Unable to get curve requested in HelloRetryRequest")
}
publicKey, err := curve.offer(c.config.rand())
if err != nil {
return err
}
keyShares[group] = curve
hello.keyShares = append(hello.keyShares, keyShareEntry{
group: group,
keyExchange: publicKey,
})
hello.hasEarlyData = false
hello.earlyDataContext = nil
hello.raw = nil
secondHelloBytes = hello.marshal()
c.writeRecord(recordTypeHandshake, secondHelloBytes)
c.flushHandshake()
msg, err = c.readHandshake()
if err != nil {
return err
}
}
serverHello, ok := msg.(*serverHelloMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(serverHello, msg)
}
c.vers, ok = c.config.mutualVersion(serverHello.vers, c.isDTLS)
if !ok {
if c.vers != serverHello.vers {
c.sendAlert(alertProtocolVersion)
return fmt.Errorf("tls: server selected unsupported protocol version %x", serverHello.vers)
return fmt.Errorf("tls: server sent non-matching version %x vs %x", serverHello.vers, c.vers)
}
c.haveVers = true
// Check for downgrade signals in the server random, per
// draft-ietf-tls-tls13-14, section 6.3.1.2.
@ -349,6 +408,11 @@ NextCipherSuite:
return fmt.Errorf("tls: server selected an unsupported cipher suite")
}
if haveHelloRetryRequest && (helloRetryRequest.cipherSuite != serverHello.cipherSuite || helloRetryRequest.selectedGroup != serverHello.keyShare.group) {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: ServerHello parameters did not match HelloRetryRequest")
}
hs := &clientHandshakeState{
c: c,
serverHello: serverHello,
@ -360,6 +424,10 @@ NextCipherSuite:
}
hs.writeHash(helloBytes, hs.c.sendHandshakeSeq-1)
if haveHelloRetryRequest {
hs.writeServerHash(helloRetryRequest.marshal())
hs.writeClientHash(secondHelloBytes)
}
hs.writeServerHash(hs.serverHello.marshal())
if c.vers >= VersionTLS13 {

View File

@ -126,6 +126,7 @@ type clientHelloMsg struct {
ocspStapling bool
supportedCurves []CurveID
supportedPoints []uint8
hasKeyShares bool
keyShares []keyShareEntry
pskIdentities [][]uint8
hasEarlyData bool
@ -164,6 +165,7 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
m.ocspStapling == m1.ocspStapling &&
eqCurveIDs(m.supportedCurves, m1.supportedCurves) &&
bytes.Equal(m.supportedPoints, m1.supportedPoints) &&
m.hasKeyShares == m1.hasKeyShares &&
eqKeyShareEntryLists(m.keyShares, m1.keyShares) &&
eqByteSlices(m.pskIdentities, m1.pskIdentities) &&
m.hasEarlyData == m1.hasEarlyData &&
@ -274,7 +276,7 @@ func (m *clientHelloMsg) marshal() []byte {
supportedPoints.addU8(pointFormat)
}
}
if len(m.keyShares) > 0 {
if m.hasKeyShares {
extensions.addU16(extensionKeyShare)
keyShareList := extensions.addU16LengthPrefixed()
@ -549,6 +551,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
return false
}
d := data[2:length]
m.hasKeyShares = true
for len(d) > 0 {
// The next KeyShareEntry contains a NamedGroup (2 bytes) and a
// key_exchange (2-byte length prefix with at least 1 byte of content).
@ -1142,6 +1145,47 @@ func (m *serverExtensions) unmarshal(data []byte, version uint16) bool {
return true
}
type helloRetryRequestMsg struct {
raw []byte
vers uint16
cipherSuite uint16
selectedGroup CurveID
}
func (m *helloRetryRequestMsg) marshal() []byte {
if m.raw != nil {
return m.raw
}
retryRequestMsg := newByteBuilder()
retryRequestMsg.addU8(typeHelloRetryRequest)
retryRequest := retryRequestMsg.addU24LengthPrefixed()
retryRequest.addU16(m.vers)
retryRequest.addU16(m.cipherSuite)
retryRequest.addU16(uint16(m.selectedGroup))
// Extensions field. We have none to send.
retryRequest.addU16(0)
m.raw = retryRequestMsg.finish()
return m.raw
}
func (m *helloRetryRequestMsg) unmarshal(data []byte) bool {
m.raw = data
if len(data) < 12 {
return false
}
m.vers = uint16(data[4])<<8 | uint16(data[5])
m.cipherSuite = uint16(data[6])<<8 | uint16(data[7])
m.selectedGroup = CurveID(data[8])<<8 | CurveID(data[9])
extLen := int(data[10])<<8 | int(data[11])
data = data[12:]
if len(data) != extLen {
return false
}
return true
}
type certificateMsg struct {
raw []byte
hasRequestContext bool

View File

@ -353,8 +353,46 @@ Curves:
}
if selectedKeyShare == nil {
// TODO(davidben,nharper): Implement HelloRetryRequest.
return errors.New("tls: HelloRetryRequest not implemented")
// Send HelloRetryRequest.
helloRetryRequestMsg := helloRetryRequestMsg{
vers: c.vers,
cipherSuite: hs.hello.cipherSuite,
selectedGroup: selectedCurve,
}
hs.writeServerHash(helloRetryRequestMsg.marshal())
c.writeRecord(recordTypeHandshake, helloRetryRequestMsg.marshal())
// Read new ClientHello.
newMsg, err := c.readHandshake()
if err != nil {
return err
}
newClientHello, ok := newMsg.(*clientHelloMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(newClientHello, newMsg)
}
hs.writeClientHash(newClientHello.marshal())
// Check that the new ClientHello matches the old ClientHello, except for
// the addition of the new KeyShareEntry at the end of the list, and
// removing the EarlyDataIndication extension (if present).
newKeyShares := newClientHello.keyShares
if len(newKeyShares) == 0 || newKeyShares[len(newKeyShares)-1].group != selectedCurve {
return errors.New("tls: KeyShare from HelloRetryRequest not present in new ClientHello")
}
oldClientHelloCopy := *hs.clientHello
oldClientHelloCopy.raw = nil
oldClientHelloCopy.hasEarlyData = false
oldClientHelloCopy.earlyDataContext = nil
newClientHelloCopy := *newClientHello
newClientHelloCopy.raw = nil
newClientHelloCopy.keyShares = newKeyShares[:len(newKeyShares)-1]
if !oldClientHelloCopy.equal(&newClientHelloCopy) {
return errors.New("tls: new ClientHello does not match")
}
selectedKeyShare = &newKeyShares[len(newKeyShares)-1]
}
// Once a curve has been selected and a key share identified,