Add EMS support to tls-tris

see RFC7627
This commit is contained in:
Watson Ladd 2017-05-18 16:33:09 -07:00 committato da Kris Kwiatkowski
parent 58c559ba00
commit 7e1760cc7c
14 ha cambiato i file con 200 aggiunte e 21 eliminazioni

Vedi File

@ -0,0 +1,7 @@
# Used to build examples. Must be compiled from current directory and after "make build-all"
build:
GOROOT=../GOROOT/linux_amd64 go build ems_client.go
run:
./ems_client google.com:443

Vedi File

@ -0,0 +1,75 @@
package main
import (
"bufio"
"crypto/tls"
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
)
var tlsVersionToName = map[string]uint16{
"tls10": tls.VersionTLS10,
"tls11": tls.VersionTLS11,
"tls12": tls.VersionTLS12,
"tls13": tls.VersionTLS13,
}
// Usage client args host:port
func main() {
var version string
var addr string
var enableEMS bool
var resume bool
var config tls.Config
var cache tls.ClientSessionCache
cache = tls.NewLRUClientSessionCache(0)
flag.StringVar(&version, "version", "tls12", "Version of TLS to use")
flag.BoolVar(&enableEMS, "m", false, "Enable EMS")
flag.BoolVar(&resume, "r", false, "Attempt Resumption")
flag.Parse()
config.MinVersion = tlsVersionToName[version]
config.MaxVersion = tlsVersionToName[version]
config.InsecureSkipVerify = true
config.UseExtendedMasterSecret = !enableEMS
config.ClientSessionCache = cache
var iters int
if resume {
iters = 2
} else {
iters = 1
}
addr = flag.Arg(0)
for ; iters > 0; iters-- {
conn, err := tls.Dial("tcp", addr, &config)
if err != nil {
fmt.Println("Error %s", err)
os.Exit(1)
}
var req http.Request
var response *http.Response
req.Method = "GET"
req.URL, err = url.Parse("https://" + addr + "/")
if err != nil {
fmt.Println("Failed to parse url")
os.Exit(1)
}
req.Write(conn)
reader := bufio.NewReader(conn)
response, err = http.ReadResponse(reader, nil)
if err != nil {
fmt.Println("HTTP problem")
fmt.Println(err)
os.Exit(1)
}
io.Copy(os.Stdout, response.Body)
conn.Close()
if resume && iters == 2 {
fmt.Println("Attempting resumption")
}
}
os.Exit(0)
}

Vedi File

@ -92,6 +92,7 @@ func result() {
}
}
// Usage client args host:port
func main() {
var keylog_file string
var enable_rsa, enable_ecdsa, client_auth bool

Vedi File

@ -38,6 +38,7 @@ const (
alertInappropriateFallback alert = 86
alertUserCanceled alert = 90
alertNoRenegotiation alert = 100
alertUnsupportedExtension alert = 110
alertCertificateRequired alert = 116
alertNoApplicationProtocol alert = 120
alertSuccess alert = 255 // dummy value returned by unmarshal functions

Vedi File

@ -84,6 +84,7 @@ const (
extensionSignatureAlgorithms uint16 = 13
extensionALPN uint16 = 16
extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
extensionEMS uint16 = 23
extensionSessionTicket uint16 = 35
extensionPreSharedKey uint16 = 41
extensionEarlyData uint16 = 42
@ -259,6 +260,7 @@ type ClientSessionState struct {
masterSecret []byte // MasterSecret generated by client on a full handshake
serverCertificates []*x509.Certificate // Certificate chain presented by the server
verifiedChains [][]*x509.Certificate // Certificate chains we built for verification
useEMS bool // State of extended master secret
}
// ClientSessionCache is a cache of ClientSessionState objects that can be used
@ -641,6 +643,10 @@ type Config struct {
// for new tickets and any subsequent keys can be used to decrypt old
// tickets.
sessionTicketKeys []ticketKey
// UseExtendedMasterSecret indicates whether or not the connection
// should use the extended master secret computation if available
UseExtendedMasterSecret bool
}
// ticketKeyNameLen is the number of bytes of identifier that is prepended to
@ -711,6 +717,7 @@ func (c *Config) Clone() *Config {
AcceptDelegatedCredential: c.AcceptDelegatedCredential,
GetDelegatedCredential: c.GetDelegatedCredential,
sessionTicketKeys: sessionTicketKeys,
UseExtendedMasterSecret: c.UseExtendedMasterSecret,
}
}

Vedi File

@ -66,6 +66,8 @@ type Conn struct {
// renegotiation extension. (This is meaningless as a server because
// renegotiation is not supported in that case.)
secureRenegotiation bool
// indicates wether extended MasterSecret extension is used (see RFC7627)
useEMS bool
// clientFinishedIsFirst is true if the client sent the first Finished
// message during the most recent handshake. This is recorded because

Vedi File

@ -67,6 +67,7 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) {
secureRenegotiationSupported: true,
delegatedCredential: config.AcceptDelegatedCredential,
alpnProtocols: config.NextProtos,
extendedMSSupported: config.UseExtendedMasterSecret,
}
possibleCipherSuites := config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
@ -589,6 +590,13 @@ func (hs *clientHandshakeState) doFullHandshake() error {
return err
}
}
c.useEMS = hs.serverHello.extendedMSSupported
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random, hs.finishedHash, c.useEMS)
if err := c.config.writeKeyLog("CLIENT_RANDOM", hs.hello.random, hs.masterSecret); err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: failed to write to key log: " + err.Error())
}
if chainToSend != nil && len(chainToSend.Certificate) > 0 {
certVerify := &certificateVerifyMsg{
@ -631,12 +639,6 @@ func (hs *clientHandshakeState) doFullHandshake() error {
}
}
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
if err := c.config.writeKeyLog("CLIENT_RANDOM", hs.hello.random, hs.masterSecret); err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: failed to write to key log: " + err.Error())
}
hs.finishedHash.discardHandshakeBuffer()
return nil
@ -697,6 +699,16 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
}
}
if hs.serverHello.extendedMSSupported {
if hs.hello.extendedMSSupported {
c.useEMS = true
} else {
// server wants to calculate master secret in a different way than client
c.sendAlert(alertUnsupportedExtension)
return false, errors.New("tls: unexpected extension (EMS) received in SH")
}
}
clientDidNPN := hs.hello.nextProtoNeg
clientDidALPN := len(hs.hello.alpnProtocols) > 0
serverHasNPN := hs.serverHello.nextProtoNeg
@ -727,6 +739,10 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
return false, nil
}
if hs.session.useEMS != c.useEMS {
return false, errors.New("differing EMS state")
}
if hs.session.vers != c.vers {
c.sendAlert(alertHandshakeFailure)
return false, errors.New("tls: server resumed a session with a different version")
@ -797,6 +813,7 @@ func (hs *clientHandshakeState) readSessionTicket() error {
masterSecret: hs.masterSecret,
serverCertificates: c.peerCertificates,
verifiedChains: c.verifiedChains,
useEMS: c.useEMS,
}
return nil

Vedi File

@ -50,6 +50,7 @@ type clientHelloMsg struct {
pskKeyExchangeModes []uint8
earlyData bool
delegatedCredential bool
extendedMSSupported bool // RFC7627
}
// Function used for signature_algorithms and signature_algorithrms_cert
@ -129,7 +130,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
eqKeyShares(m.keyShares, m1.keyShares) &&
eqUint16s(m.supportedVersions, m1.supportedVersions) &&
m.earlyData == m1.earlyData &&
m.delegatedCredential == m1.delegatedCredential
m.delegatedCredential == m1.delegatedCredential &&
m.extendedMSSupported == m1.extendedMSSupported
}
func (m *clientHelloMsg) marshal() []byte {
@ -141,7 +143,6 @@ func (m *clientHelloMsg) marshal() []byte {
numExtensions := 0
extensionsLength := 0
// Indicates wether to send signature_algorithms_cert extension
if m.nextProtoNeg {
numExtensions++
}
@ -208,6 +209,9 @@ func (m *clientHelloMsg) marshal() []byte {
if m.delegatedCredential {
numExtensions++
}
if m.extendedMSSupported {
numExtensions++
}
if numExtensions > 0 {
extensionsLength += 4 * numExtensions
length += 2 + extensionsLength
@ -429,6 +433,10 @@ func (m *clientHelloMsg) marshal() []byte {
binary.BigEndian.PutUint16(z, extensionDelegatedCredential)
z = z[4:]
}
if m.extendedMSSupported {
binary.BigEndian.PutUint16(z, extensionEMS)
z = z[4:]
}
m.raw = x
@ -494,6 +502,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert {
m.pskKeyExchangeModes = nil
m.earlyData = false
m.delegatedCredential = false
m.extendedMSSupported = false
if len(data) == 0 {
// ClientHello is optionally followed by extension data
@ -761,6 +770,12 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert {
case extensionDelegatedCredential:
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02
m.delegatedCredential = true
case extensionEMS:
// RFC 7627
m.extendedMSSupported = true
if length != 0 {
return alertDecodeError
}
}
data = data[length:]
bindersOffset += length
@ -793,6 +808,9 @@ type serverHelloMsg struct {
keyShare keyShare
psk bool
pskIdentity uint16
// RFC7627
extendedMSSupported bool
}
func (m *serverHelloMsg) equal(i interface{}) bool {
@ -826,7 +844,8 @@ func (m *serverHelloMsg) equal(i interface{}) bool {
m.keyShare.group == m1.keyShare.group &&
bytes.Equal(m.keyShare.data, m1.keyShare.data) &&
m.psk == m1.psk &&
m.pskIdentity == m1.pskIdentity
m.pskIdentity == m1.pskIdentity &&
m.extendedMSSupported == m1.extendedMSSupported
}
func (m *serverHelloMsg) marshal() []byte {
@ -857,6 +876,9 @@ func (m *serverHelloMsg) marshal() []byte {
extensionsLength += 1 + len(m.secureRenegotiation)
numExtensions++
}
if m.extendedMSSupported {
numExtensions++
}
if alpnLen := len(m.alpnProtocol); alpnLen > 0 {
if alpnLen >= 256 {
panic("invalid ALPN protocol")
@ -1017,6 +1039,10 @@ func (m *serverHelloMsg) marshal() []byte {
z[5] = byte(m.pskIdentity)
z = z[6:]
}
if m.extendedMSSupported {
binary.BigEndian.PutUint16(z, extensionEMS)
z = z[4:]
}
m.raw = x
@ -1053,6 +1079,7 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert {
m.keyShare.data = nil
m.psk = false
m.pskIdentity = 0
m.extendedMSSupported = false
if len(data) == 0 {
// ServerHello is optionally followed by extension data
@ -1194,6 +1221,8 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert {
}
m.psk = true
m.pskIdentity = uint16(data[0])<<8 | uint16(data[1])
case extensionEMS:
m.extendedMSSupported = true
}
data = data[length:]
}

Vedi File

@ -171,7 +171,12 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value {
if rand.Intn(10) > 5 {
m.earlyData = true
}
if rand.Intn(10) > 5 {
m.delegatedCredential = true
}
if rand.Intn(10) > 5 {
m.extendedMSSupported = true
}
return reflect.ValueOf(m)
}
@ -199,6 +204,9 @@ func (*serverHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value {
if rand.Intn(10) > 5 {
m.ticketSupported = true
}
if rand.Intn(10) > 5 {
m.extendedMSSupported = true
}
m.alpnProtocol = randomString(rand.Intn(32)+1, rand)
if rand.Intn(10) > 5 {
@ -344,6 +352,9 @@ func (*sessionState) Generate(rand *rand.Rand, size int) reflect.Value {
s.vers = uint16(rand.Intn(10000))
s.cipherSuite = uint16(rand.Intn(10000))
s.masterSecret = randomBytes(rand.Intn(100), rand)
if rand.Intn(10) > 5 {
s.usedEMS = true
}
numCerts := rand.Intn(20)
s.certificates = make([][]byte, numCerts)
for i := 0; i < numCerts; i++ {

Vedi File

@ -409,6 +409,11 @@ func (hs *serverHandshakeState) checkForResumption() bool {
return false
}
// Do not resume connections where client support for EMS has changed
if (hs.clientHello.extendedMSSupported && c.config.UseExtendedMasterSecret) != hs.sessionState.usedEMS {
return false
}
cipherSuiteOk := false
// Check that the client is still offering the ciphersuite in the session.
for _, id := range hs.clientHello.cipherSuites {
@ -446,6 +451,7 @@ func (hs *serverHandshakeState) doResumeHandshake() error {
// that we're doing a resumption.
hs.hello.sessionId = hs.clientHello.sessionId
hs.hello.ticketSupported = hs.sessionState.usedOldKey
hs.hello.extendedMSSupported = hs.clientHello.extendedMSSupported && c.config.UseExtendedMasterSecret
hs.finishedHash = newFinishedHash(c.vers, hs.suite)
hs.finishedHash.discardHandshakeBuffer()
hs.finishedHash.Write(hs.clientHello.marshal())
@ -461,6 +467,7 @@ func (hs *serverHandshakeState) doResumeHandshake() error {
}
hs.masterSecret = hs.sessionState.masterSecret
c.useEMS = hs.sessionState.usedEMS
return nil
}
@ -474,6 +481,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
hs.hello.ticketSupported = hs.clientHello.ticketSupported && !c.config.SessionTicketsDisabled
hs.hello.cipherSuite = hs.suite.id
hs.hello.extendedMSSupported = hs.clientHello.extendedMSSupported && c.config.UseExtendedMasterSecret
hs.finishedHash = newFinishedHash(hs.c.vers, hs.suite)
if c.config.ClientAuth == NoClientCert {
@ -607,7 +615,8 @@ func (hs *serverHandshakeState) doFullHandshake() error {
}
return err
}
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
c.useEMS = hs.hello.extendedMSSupported
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random, hs.finishedHash, c.useEMS)
if err := c.config.writeKeyLog("CLIENT_RANDOM", hs.clientHello.random, hs.masterSecret); err != nil {
c.sendAlert(alertInternalError)
return err
@ -737,6 +746,7 @@ func (hs *serverHandshakeState) sendSessionTicket() error {
cipherSuite: hs.suite.id,
masterSecret: hs.masterSecret,
certificates: hs.certsFromClient,
usedEMS: c.useEMS,
}
m.ticket, err = c.encryptTicket(state.marshal())
if err != nil {

22
prf.go
Vedi File

@ -117,6 +117,7 @@ var masterSecretLabel = []byte("master secret")
var keyExpansionLabel = []byte("key expansion")
var clientFinishedLabel = []byte("client finished")
var serverFinishedLabel = []byte("server finished")
var extendedMasterSecretLabel = []byte("extended master secret")
func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) {
switch version {
@ -141,14 +142,21 @@ func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, labe
// masterFromPreMasterSecret generates the master secret from the pre-master
// secret. See http://tools.ietf.org/html/rfc5246#section-8.1
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
seed = append(seed, clientRandom...)
seed = append(seed, serverRandom...)
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte, fin finishedHash, ems bool) []byte {
if ems {
session_hash := fin.Sum()
masterSecret := make([]byte, masterSecretLength)
prfForVersion(version, suite)(masterSecret, preMasterSecret, extendedMasterSecretLabel, session_hash)
return masterSecret
} else {
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
seed = append(seed, clientRandom...)
seed = append(seed, serverRandom...)
masterSecret := make([]byte, masterSecretLength)
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed)
return masterSecret
masterSecret := make([]byte, masterSecretLength)
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed)
return masterSecret
}
}
// keysFromMasterSecret generates the connection keys from the master

Vedi File

@ -49,8 +49,9 @@ func TestKeysFromPreMasterSecret(t *testing.T) {
in, _ := hex.DecodeString(test.preMasterSecret)
clientRandom, _ := hex.DecodeString(test.clientRandom)
serverRandom, _ := hex.DecodeString(test.serverRandom)
fin := newFinishedHash(test.version, test.suite)
masterSecret := masterFromPreMasterSecret(test.version, test.suite, in, clientRandom, serverRandom)
masterSecret := masterFromPreMasterSecret(test.version, test.suite, in, clientRandom, serverRandom, fin, false)
if s := hex.EncodeToString(masterSecret); s != test.masterSecret {
t.Errorf("#%d: bad master secret %s, want %s", i, s, test.masterSecret)
continue

Vedi File

@ -37,6 +37,7 @@ type SessionTicketSealer interface {
type sessionState struct {
vers uint16
cipherSuite uint16
usedEMS bool
masterSecret []byte
certificates [][]byte
// usedOldKey is true if the ticket from which this session came from
@ -51,6 +52,7 @@ func (s *sessionState) equal(i interface{}) bool {
}
if s.vers != s1.vers ||
s.usedEMS != s1.usedEMS ||
s.cipherSuite != s1.cipherSuite ||
!bytes.Equal(s.masterSecret, s1.masterSecret) {
return false
@ -77,7 +79,12 @@ func (s *sessionState) marshal() []byte {
ret := make([]byte, length)
x := ret
x[0] = byte(s.vers >> 8)
was_used := byte(0)
if s.usedEMS {
was_used = byte(0x80)
}
x[0] = byte(s.vers>>8) | byte(was_used)
x[1] = byte(s.vers)
x[2] = byte(s.cipherSuite >> 8)
x[3] = byte(s.cipherSuite)
@ -108,8 +115,9 @@ func (s *sessionState) unmarshal(data []byte) alert {
return alertDecodeError
}
s.vers = uint16(data[0])<<8 | uint16(data[1])
s.vers = (uint16(data[0])<<8 | uint16(data[1])) & 0x7fff
s.cipherSuite = uint16(data[2])<<8 | uint16(data[3])
s.usedEMS = (data[0] & 0x80) == 0x80
masterSecretLen := int(data[4])<<8 | int(data[5])
data = data[6:]
if len(data) < masterSecretLen {

Vedi File

@ -715,6 +715,8 @@ func TestCloneNonFuncFields(t *testing.T) {
// TODO
case "AcceptDelegatedCredential":
f.Set(reflect.ValueOf(false))
case "UseExtendedMasterSecret":
f.Set(reflect.ValueOf(false))
default:
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
}