@@ -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 |
@@ -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) | |||
} |
@@ -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 | |||
@@ -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 | |||
@@ -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, | |||
} | |||
} | |||
@@ -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 | |||
@@ -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 | |||
@@ -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:] | |||
} | |||
@@ -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++ { | |||
@@ -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 { | |||
@@ -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...) | |||
masterSecret := make([]byte, masterSecretLength) | |||
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed) | |||
return masterSecret | |||
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 | |||
} | |||
} | |||
// keysFromMasterSecret generates the connection keys from the master | |||
@@ -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 | |||
@@ -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 { | |||
@@ -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) | |||
} | |||