mirror of
https://github.com/golang/go
synced 2024-11-27 02:01:23 -07:00
crypto/tls: implement TLS 1.3 PSK authentication (client side)
Also check original certificate validity when resuming TLS 1.0–1.2. Will refuse to resume a session if the certificate is expired or if the original connection had InsecureSkipVerify and the resumed one doesn't. Support only PSK+DHE to protect forward secrecy even with lack of a strong session ticket rotation story. Tested with NSS because s_server does not provide any way of getting the same session ticket key across invocations. Will self-test like TLS 1.0–1.2 once server side is implemented. Incorporates CL 128477 by @santoshankr. Fixes #24919 Updates #9671 Change-Id: Id3eaa5b6c77544a1357668bf9ff255f3420ecc34 Reviewed-on: https://go-review.googlesource.com/c/147420 Reviewed-by: Adam Langley <agl@golang.org>
This commit is contained in:
parent
dc0be727dc
commit
d669cc47ad
@ -399,12 +399,16 @@ func ecdheRSAKA(version uint16) keyAgreement {
|
||||
func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
for _, suite := range cipherSuites {
|
||||
if suite.id == want {
|
||||
return suite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return cipherSuiteByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteByID(id uint16) *cipherSuite {
|
||||
for _, cipherSuite := range cipherSuites {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -413,12 +417,16 @@ func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
|
||||
func mutualCipherSuiteTLS13(have []uint16, want uint16) *cipherSuiteTLS13 {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
for _, suite := range cipherSuitesTLS13 {
|
||||
if suite.id == want {
|
||||
return suite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return cipherSuiteTLS13ByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 {
|
||||
for _, cipherSuite := range cipherSuitesTLS13 {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -240,22 +240,32 @@ type ClientSessionState struct {
|
||||
sessionTicket []uint8 // Encrypted ticket used for session resumption with server
|
||||
vers uint16 // SSL/TLS version negotiated for the session
|
||||
cipherSuite uint16 // Ciphersuite negotiated for the session
|
||||
masterSecret []byte // MasterSecret generated by client on a full handshake
|
||||
masterSecret []byte // Full handshake MasterSecret, or TLS 1.3 resumption_master_secret
|
||||
serverCertificates []*x509.Certificate // Certificate chain presented by the server
|
||||
verifiedChains [][]*x509.Certificate // Certificate chains we built for verification
|
||||
receivedAt time.Time // When the session ticket was received from the server
|
||||
|
||||
// TLS 1.3 fields.
|
||||
nonce []byte // Ticket nonce sent by the server, to derive PSK
|
||||
useBy time.Time // Expiration of the ticket lifetime as set by the server
|
||||
ageAdd uint32 // Random obfuscation factor for sending the ticket age
|
||||
}
|
||||
|
||||
// ClientSessionCache is a cache of ClientSessionState objects that can be used
|
||||
// by a client to resume a TLS session with a given server. ClientSessionCache
|
||||
// implementations should expect to be called concurrently from different
|
||||
// goroutines. Only ticket-based resumption is supported, not SessionID-based
|
||||
// resumption.
|
||||
// goroutines. Up to TLS 1.2, only ticket-based resumption is supported, not
|
||||
// SessionID-based resumption. In TLS 1.3 they were merged into PSK modes, which
|
||||
// are supported via this interface.
|
||||
type ClientSessionCache interface {
|
||||
// Get searches for a ClientSessionState associated with the given key.
|
||||
// On return, ok is true if one was found.
|
||||
Get(sessionKey string) (session *ClientSessionState, ok bool)
|
||||
|
||||
// Put adds the ClientSessionState to the cache with the given key.
|
||||
// Put adds the ClientSessionState to the cache with the given key. It might
|
||||
// get called multiple times in a connection if a TLS 1.3 server provides
|
||||
// more than one session ticket. If called with a nil *ClientSessionState,
|
||||
// it should remove the cache entry.
|
||||
Put(sessionKey string, cs *ClientSessionState)
|
||||
}
|
||||
|
||||
@ -502,19 +512,19 @@ type Config struct {
|
||||
// the order of elements in CipherSuites, is used.
|
||||
PreferServerCipherSuites bool
|
||||
|
||||
// SessionTicketsDisabled may be set to true to disable session ticket
|
||||
// (resumption) support. Note that on clients, session ticket support is
|
||||
// SessionTicketsDisabled may be set to true to disable session ticket and
|
||||
// PSK (resumption) support. Note that on clients, session ticket support is
|
||||
// also disabled if ClientSessionCache is nil.
|
||||
SessionTicketsDisabled bool
|
||||
|
||||
// SessionTicketKey is used by TLS servers to provide session
|
||||
// resumption. See RFC 5077. If zero, it will be filled with
|
||||
// random data before the first server handshake.
|
||||
// SessionTicketKey is used by TLS servers to provide session resumption.
|
||||
// See RFC 5077 and the PSK mode of RFC 8446. If zero, it will be filled
|
||||
// with random data before the first server handshake.
|
||||
//
|
||||
// If multiple servers are terminating connections for the same host
|
||||
// they should all have the same SessionTicketKey. If the
|
||||
// SessionTicketKey leaks, previously recorded and future TLS
|
||||
// connections using that key are compromised.
|
||||
// connections using that key might be compromised.
|
||||
SessionTicketKey [32]byte
|
||||
|
||||
// ClientSessionCache is a cache of ClientSessionState entries for TLS
|
||||
@ -937,15 +947,21 @@ func NewLRUClientSessionCache(capacity int) ClientSessionCache {
|
||||
}
|
||||
}
|
||||
|
||||
// Put adds the provided (sessionKey, cs) pair to the cache.
|
||||
// Put adds the provided (sessionKey, cs) pair to the cache. If cs is nil, the entry
|
||||
// corresponding to sessionKey is removed from the cache instead.
|
||||
func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if elem, ok := c.m[sessionKey]; ok {
|
||||
entry := elem.Value.(*lruSessionCacheEntry)
|
||||
entry.state = cs
|
||||
c.q.MoveToFront(elem)
|
||||
if cs == nil {
|
||||
c.q.Remove(elem)
|
||||
delete(c.m, sessionKey)
|
||||
} else {
|
||||
entry := elem.Value.(*lruSessionCacheEntry)
|
||||
entry.state = cs
|
||||
c.q.MoveToFront(elem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,9 @@ type Conn struct {
|
||||
secureRenegotiation bool
|
||||
// ekm is a closure for exporting keying material.
|
||||
ekm func(label string, context []byte, length int) ([]byte, error)
|
||||
// resumptionSecret is the resumption_master_secret for generating or
|
||||
// handling NewSessionTicket messages. nil if config.SessionTicketsDisabled.
|
||||
resumptionSecret []byte
|
||||
|
||||
// clientFinishedIsFirst is true if the client sent the first Finished
|
||||
// message during the most recent handshake. This is recorded because
|
||||
@ -1169,10 +1172,15 @@ func (c *Conn) handlePostHandshakeMessage() error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.retryCount++
|
||||
if c.retryCount > maxUselessRecords {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *newSessionTicketMsgTLS13:
|
||||
// TODO(filippo): TLS 1.3 session ticket not implemented.
|
||||
return nil
|
||||
return c.handleNewSessionTicket(msg)
|
||||
case *keyUpdateMsg:
|
||||
return c.handleKeyUpdate(msg)
|
||||
default:
|
||||
@ -1182,19 +1190,7 @@ func (c *Conn) handlePostHandshakeMessage() error {
|
||||
}
|
||||
|
||||
func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
|
||||
c.retryCount++
|
||||
if c.retryCount > maxUselessRecords {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
||||
}
|
||||
|
||||
var cipherSuite *cipherSuiteTLS13
|
||||
for _, suite := range cipherSuitesTLS13 {
|
||||
if suite.id == c.cipherSuite {
|
||||
cipherSuite = suite
|
||||
break
|
||||
}
|
||||
}
|
||||
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||
if cipherSuite == nil {
|
||||
return c.in.setErrorLocked(c.sendAlert(alertInternalError))
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type clientHandshakeState struct {
|
||||
@ -134,7 +135,7 @@ NextCipherSuite:
|
||||
return hello, params, nil
|
||||
}
|
||||
|
||||
func (c *Conn) clientHandshake() error {
|
||||
func (c *Conn) clientHandshake() (err error) {
|
||||
if c.config == nil {
|
||||
c.config = defaultConfig()
|
||||
}
|
||||
@ -148,8 +149,20 @@ func (c *Conn) clientHandshake() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var newSession *ClientSessionState
|
||||
cacheKey, session := c.loadSession(hello)
|
||||
cacheKey, session, earlySecret, binderKey := c.loadSession(hello)
|
||||
if cacheKey != "" && session != nil {
|
||||
defer func() {
|
||||
// If we got a handshake failure when resuming a session, throw away
|
||||
// the session ticket. See RFC 5077, Section 3.2.
|
||||
//
|
||||
// RFC 8446 makes no mention of dropping tickets on failure, but it
|
||||
// does require servers to abort on invalid binders, so we need to
|
||||
// delete tickets to recover from a corrupted PSK.
|
||||
if err != nil {
|
||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if _, err := c.writeRecord(recordTypeHandshake, hello.marshal()); err != nil {
|
||||
return err
|
||||
@ -177,81 +190,147 @@ func (c *Conn) clientHandshake() error {
|
||||
hello: hello,
|
||||
ecdheParams: ecdheParams,
|
||||
session: session,
|
||||
earlySecret: earlySecret,
|
||||
binderKey: binderKey,
|
||||
}
|
||||
|
||||
if err := hs.handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
// In TLS 1.3, session tickets are delivered after the handshake.
|
||||
return hs.handshake()
|
||||
}
|
||||
|
||||
newSession = hs.session
|
||||
} else {
|
||||
hs := &clientHandshakeState{
|
||||
c: c,
|
||||
serverHello: serverHello,
|
||||
hello: hello,
|
||||
session: session,
|
||||
}
|
||||
hs := &clientHandshakeState{
|
||||
c: c,
|
||||
serverHello: serverHello,
|
||||
hello: hello,
|
||||
session: session,
|
||||
}
|
||||
|
||||
if err := hs.handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newSession = hs.session
|
||||
if err := hs.handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we had a successful handshake and hs.session is different from
|
||||
// the one already cached - cache a new one.
|
||||
if hello.ticketSupported && newSession != nil && session != newSession {
|
||||
c.config.ClientSessionCache.Put(cacheKey, newSession)
|
||||
if cacheKey != "" && hs.session != nil && session != hs.session {
|
||||
c.config.ClientSessionCache.Put(cacheKey, hs.session)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string, session *ClientSessionState) {
|
||||
func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
|
||||
session *ClientSessionState, earlySecret, binderKey []byte) {
|
||||
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
||||
return
|
||||
return "", nil, nil, nil
|
||||
}
|
||||
|
||||
hello.ticketSupported = true
|
||||
|
||||
if hello.supportedVersions[0] == VersionTLS13 {
|
||||
// Require DHE on resumption as it guarantees forward secrecy against
|
||||
// compromise of the session ticket key. See RFC 8446, Section 4.2.9.
|
||||
hello.pskModes = []uint8{pskModeDHE}
|
||||
}
|
||||
|
||||
// Session resumption is not allowed if renegotiating because
|
||||
// renegotiation is primarily used to allow a client to send a client
|
||||
// certificate, which would be skipped if session resumption occurred.
|
||||
if c.handshakes != 0 {
|
||||
return
|
||||
return "", nil, nil, nil
|
||||
}
|
||||
|
||||
// Try to resume a previously negotiated TLS session, if available.
|
||||
cacheKey = clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
|
||||
candidateSession, ok := c.config.ClientSessionCache.Get(cacheKey)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the ciphersuite and version used for the previous session
|
||||
// are still valid.
|
||||
cipherSuiteOk := false
|
||||
for _, id := range hello.cipherSuites {
|
||||
if id == candidateSession.cipherSuite {
|
||||
cipherSuiteOk = true
|
||||
break
|
||||
}
|
||||
session, ok := c.config.ClientSessionCache.Get(cacheKey)
|
||||
if !ok || session == nil {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
|
||||
// Check that version used for the previous session is still valid.
|
||||
versOk := false
|
||||
for _, v := range hello.supportedVersions {
|
||||
if v == candidateSession.vers {
|
||||
if v == session.vers {
|
||||
versOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if versOk && cipherSuiteOk {
|
||||
session = candidateSession
|
||||
hello.sessionTicket = session.sessionTicket
|
||||
if !versOk {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
|
||||
// Check that the cached server certificate is not expired, and that it's
|
||||
// valid for the ServerName. This should be ensured by the cache key, but
|
||||
// protect the application from a faulty ClientSessionCache implementation.
|
||||
if !c.config.InsecureSkipVerify {
|
||||
if len(session.verifiedChains) == 0 {
|
||||
// The original connection had InsecureSkipVerify, while this doesn't.
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
serverCert := session.serverCertificates[0]
|
||||
if c.config.time().After(serverCert.NotAfter) {
|
||||
// Expired certificate, delete the entry.
|
||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
if err := serverCert.VerifyHostname(c.config.ServerName); err != nil {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
if session.vers != VersionTLS13 {
|
||||
// In TLS 1.2 the cipher suite must match the resumed session. Ensure we
|
||||
// are still offering it.
|
||||
if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) == nil {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
|
||||
hello.sessionTicket = session.sessionTicket
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the session ticket is not expired.
|
||||
if c.config.time().After(session.useBy) {
|
||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
|
||||
// In TLS 1.3 the KDF hash must match the resumed session. Ensure we
|
||||
// offer at least one cipher suite with that hash.
|
||||
cipherSuite := cipherSuiteTLS13ByID(session.cipherSuite)
|
||||
if cipherSuite == nil {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
cipherSuiteOk := false
|
||||
for _, offeredID := range hello.cipherSuites {
|
||||
offeredSuite := cipherSuiteTLS13ByID(offeredID)
|
||||
if offeredSuite != nil && offeredSuite.hash == cipherSuite.hash {
|
||||
cipherSuiteOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !cipherSuiteOk {
|
||||
return cacheKey, nil, nil, nil
|
||||
}
|
||||
|
||||
// Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
|
||||
ticketAge := uint32(c.config.time().Sub(session.receivedAt) / time.Millisecond)
|
||||
identity := pskIdentity{
|
||||
label: session.sessionTicket,
|
||||
obfuscatedTicketAge: ticketAge + session.ageAdd,
|
||||
}
|
||||
hello.pskIdentities = []pskIdentity{identity}
|
||||
hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())}
|
||||
|
||||
// Compute the PSK binders. See RFC 8446, Section 4.2.11.2.
|
||||
psk := cipherSuite.expandLabel(session.masterSecret, "resumption",
|
||||
session.nonce, cipherSuite.hash.Size())
|
||||
earlySecret = cipherSuite.extract(psk, nil)
|
||||
binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil)
|
||||
transcript := cipherSuite.hash.New()
|
||||
transcript.Write(hello.marshalWithoutBinders())
|
||||
pskBinders := [][]byte{cipherSuite.finishedHash(binderKey, transcript)}
|
||||
hello.updateBinders(pskBinders)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -275,8 +354,8 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Does the handshake, either a full one or resumes old session.
|
||||
// Requires hs.c, hs.hello, and, optionally, hs.session to be set.
|
||||
// Does the handshake, either a full one or resumes old session. Requires hs.c,
|
||||
// hs.hello, hs.serverHello, and, optionally, hs.session to be set.
|
||||
func (hs *clientHandshakeState) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
@ -692,6 +771,7 @@ func (hs *clientHandshakeState) readSessionTicket() error {
|
||||
masterSecret: hs.masterSecret,
|
||||
serverCertificates: c.peerCertificates,
|
||||
verifiedChains: c.verifiedChains,
|
||||
receivedAt: c.config.time(),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -529,7 +529,7 @@ func runClientTestForVersion(t *testing.T, template *clientTest, version, option
|
||||
|
||||
test.name = version + "-" + test.name
|
||||
if len(test.command) == 0 {
|
||||
test.command = defaultClientCommand
|
||||
test.command = defaultServerCommand
|
||||
}
|
||||
test.command = append([]string(nil), test.command...)
|
||||
test.command = append(test.command, option)
|
||||
@ -746,10 +746,9 @@ func TestHandshakeClientCHACHA20SHA256(t *testing.T) {
|
||||
|
||||
func TestHandshakeClientECDSATLS13(t *testing.T) {
|
||||
test := &clientTest{
|
||||
name: "ECDSA",
|
||||
command: []string{"openssl", "s_server"},
|
||||
cert: testECDSACertificate,
|
||||
key: testECDSAPrivateKey,
|
||||
name: "ECDSA",
|
||||
cert: testECDSACertificate,
|
||||
key: testECDSAPrivateKey,
|
||||
}
|
||||
runClientTestTLS13(t, test)
|
||||
}
|
||||
@ -871,6 +870,9 @@ func TestClientKeyUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientResumption(t *testing.T) {
|
||||
// TODO(filippo): update to test both TLS 1.3 and 1.2 once PSK are
|
||||
// supported server-side.
|
||||
|
||||
serverConfig := &Config{
|
||||
CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA},
|
||||
Certificates: testConfig.Certificates,
|
||||
@ -907,6 +909,10 @@ func TestClientResumption(t *testing.T) {
|
||||
getTicket := func() []byte {
|
||||
return clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).state.sessionTicket
|
||||
}
|
||||
deleteTicket := func() {
|
||||
ticketKey := clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).sessionKey
|
||||
clientConfig.ClientSessionCache.Put(ticketKey, nil)
|
||||
}
|
||||
randomKey := func() [32]byte {
|
||||
var k [32]byte
|
||||
if _, err := io.ReadFull(serverConfig.rand(), k[:]); err != nil {
|
||||
@ -951,6 +957,28 @@ func TestClientResumption(t *testing.T) {
|
||||
testResumeState("DifferentCipherSuite", false)
|
||||
testResumeState("DifferentCipherSuiteRecovers", true)
|
||||
|
||||
deleteTicket()
|
||||
testResumeState("WithoutSessionTicket", false)
|
||||
|
||||
// Session resumption should work when using client certificates
|
||||
deleteTicket()
|
||||
serverConfig.ClientCAs = rootCAs
|
||||
serverConfig.ClientAuth = RequireAndVerifyClientCert
|
||||
clientConfig.Certificates = serverConfig.Certificates
|
||||
testResumeState("InitialHandshake", false)
|
||||
testResumeState("WithClientCertificates", true)
|
||||
|
||||
// Tickets should be removed from the session cache on TLS handshake failure
|
||||
farFuture := func() time.Time { return time.Unix(16725225600, 0) }
|
||||
serverConfig.Time = farFuture
|
||||
_, _, err = testHandshake(t, clientConfig, serverConfig)
|
||||
if err == nil {
|
||||
t.Fatalf("handshake did not fail after client certificate expiry")
|
||||
}
|
||||
serverConfig.Time = nil
|
||||
testResumeState("AfterHandshakeFailure", false)
|
||||
serverConfig.ClientAuth = NoClientCert
|
||||
|
||||
clientConfig.ClientSessionCache = nil
|
||||
testResumeState("WithoutSessionCache", false)
|
||||
}
|
||||
@ -994,10 +1022,21 @@ func TestLRUClientSessionCache(t *testing.T) {
|
||||
t.Fatalf("session cache failed update for key 0")
|
||||
}
|
||||
|
||||
// Adding a nil entry is valid.
|
||||
// Calling Put with a nil entry deletes the key.
|
||||
cache.Put(keys[0], nil)
|
||||
if s, ok := cache.Get(keys[0]); !ok || s != nil {
|
||||
t.Fatalf("failed to add nil entry to cache")
|
||||
if _, ok := cache.Get(keys[0]); ok {
|
||||
t.Fatalf("session cache failed to delete key 0")
|
||||
}
|
||||
|
||||
// Delete entry 2. LRU should keep 4 and 5
|
||||
cache.Put(keys[2], nil)
|
||||
if _, ok := cache.Get(keys[2]); ok {
|
||||
t.Fatalf("session cache failed to delete key 4")
|
||||
}
|
||||
for i := 4; i < 6; i++ {
|
||||
if s, ok := cache.Get(keys[i]); !ok || s != &cs[i] {
|
||||
t.Fatalf("session cache should not have deleted key: %s", keys[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1128,11 +1167,10 @@ func TestHandshakClientSCTs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Note that this needs OpenSSL 1.0.2 because that is the first
|
||||
// version that supports the -serverinfo flag.
|
||||
test := &clientTest{
|
||||
name: "SCT",
|
||||
// Note that this needs OpenSSL 1.0.2 because that is the first
|
||||
// version that supports the -serverinfo flag.
|
||||
command: []string{"openssl", "s_server"},
|
||||
name: "SCT",
|
||||
config: config,
|
||||
extensions: [][]byte{scts},
|
||||
validate: func(state ConnectionState) error {
|
||||
@ -1237,9 +1275,8 @@ func TestRenegotiateTwiceRejected(t *testing.T) {
|
||||
|
||||
func TestHandshakeClientExportKeyingMaterial(t *testing.T) {
|
||||
test := &clientTest{
|
||||
name: "ExportKeyingMaterial",
|
||||
command: []string{"openssl", "s_server"},
|
||||
config: testConfig.Clone(),
|
||||
name: "ExportKeyingMaterial",
|
||||
config: testConfig.Clone(),
|
||||
validate: func(state ConnectionState) error {
|
||||
if km, err := state.ExportKeyingMaterial("test", nil, 42); err != nil {
|
||||
return fmt.Errorf("ExportKeyingMaterial failed: %v", err)
|
||||
|
@ -11,22 +11,30 @@ import (
|
||||
"errors"
|
||||
"hash"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type clientHandshakeStateTLS13 struct {
|
||||
c *Conn
|
||||
serverHello *serverHelloMsg
|
||||
hello *clientHelloMsg
|
||||
c *Conn
|
||||
serverHello *serverHelloMsg
|
||||
hello *clientHelloMsg
|
||||
ecdheParams ecdheParameters
|
||||
|
||||
session *ClientSessionState
|
||||
earlySecret []byte
|
||||
binderKey []byte
|
||||
|
||||
certReq *certificateRequestMsgTLS13
|
||||
usingPSK bool
|
||||
sentDummyCCS bool
|
||||
ecdheParams ecdheParameters
|
||||
suite *cipherSuiteTLS13
|
||||
transcript hash.Hash
|
||||
masterSecret []byte
|
||||
trafficSecret []byte // client_application_traffic_secret_0
|
||||
session *ClientSessionState
|
||||
}
|
||||
|
||||
// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheParams, and,
|
||||
// optionally, hs.session, hs.earlySecret and hs.binderKey to be set.
|
||||
func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
c := hs.c
|
||||
|
||||
@ -50,22 +58,12 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
hs.transcript.Write(hs.hello.marshal())
|
||||
|
||||
if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
|
||||
// The first ClientHello gets double-hashed into the transcript upon a
|
||||
// HelloRetryRequest. See RFC 8446, Section 4.4.1.
|
||||
chHash := hs.transcript.Sum(nil)
|
||||
hs.transcript.Reset()
|
||||
hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
hs.transcript.Write(chHash)
|
||||
hs.transcript.Write(hs.serverHello.marshal())
|
||||
|
||||
if err := hs.sendDummyChangeCipherSpec(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.processHelloRetryRequest(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.transcript.Write(hs.hello.marshal())
|
||||
}
|
||||
|
||||
hs.transcript.Write(hs.serverHello.marshal())
|
||||
@ -83,7 +81,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
|
||||
if err := hs.readServerParameters(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.doFullHandshake(); err != nil {
|
||||
if err := hs.readServerCertificate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hs.readServerFinished(); err != nil {
|
||||
@ -178,6 +176,14 @@ func (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
|
||||
func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
c := hs.c
|
||||
|
||||
// The first ClientHello gets double-hashed into the transcript upon a
|
||||
// HelloRetryRequest. See RFC 8446, Section 4.4.1.
|
||||
chHash := hs.transcript.Sum(nil)
|
||||
hs.transcript.Reset()
|
||||
hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
hs.transcript.Write(chHash)
|
||||
hs.transcript.Write(hs.serverHello.marshal())
|
||||
|
||||
if hs.serverHello.serverShare.group != 0 {
|
||||
c.sendAlert(alertDecodeError)
|
||||
return errors.New("tls: received malformed key_share extension")
|
||||
@ -218,6 +224,31 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
||||
hs.hello.cookie = hs.serverHello.cookie
|
||||
|
||||
hs.hello.raw = nil
|
||||
if len(hs.hello.pskIdentities) > 0 {
|
||||
pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
|
||||
if pskSuite == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if pskSuite.hash == hs.suite.hash {
|
||||
// Update binders and obfuscated_ticket_age.
|
||||
ticketAge := uint32(c.config.time().Sub(hs.session.receivedAt) / time.Millisecond)
|
||||
hs.hello.pskIdentities[0].obfuscatedTicketAge = ticketAge + hs.session.ageAdd
|
||||
|
||||
transcript := hs.suite.hash.New()
|
||||
transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||
transcript.Write(chHash)
|
||||
transcript.Write(hs.serverHello.marshal())
|
||||
transcript.Write(hs.hello.marshalWithoutBinders())
|
||||
pskBinders := [][]byte{hs.suite.finishedHash(hs.binderKey, transcript)}
|
||||
hs.hello.updateBinders(pskBinders)
|
||||
} else {
|
||||
// Server selected a cipher suite incompatible with the PSK.
|
||||
hs.hello.pskIdentities = nil
|
||||
hs.hello.pskBinders = nil
|
||||
}
|
||||
}
|
||||
|
||||
hs.transcript.Write(hs.hello.marshal())
|
||||
if _, err := c.writeRecord(recordTypeHandshake, hs.hello.marshal()); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -259,11 +290,40 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
||||
return errors.New("tls: malformed key_share extension")
|
||||
}
|
||||
|
||||
if hs.serverHello.serverShare.group == 0 {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server did not send a key share")
|
||||
}
|
||||
if hs.serverHello.serverShare.group != hs.ecdheParams.CurveID() {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected unsupported group")
|
||||
}
|
||||
|
||||
if !hs.serverHello.selectedIdentityPresent {
|
||||
return nil
|
||||
}
|
||||
|
||||
if int(hs.serverHello.selectedIdentity) >= len(hs.hello.pskIdentities) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected an invalid PSK")
|
||||
}
|
||||
|
||||
if len(hs.hello.pskIdentities) != 1 || hs.session == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
|
||||
if pskSuite == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
if pskSuite.hash != hs.suite.hash {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: server selected an invalid PSK and cipher suite pair")
|
||||
}
|
||||
|
||||
hs.usingPSK = true
|
||||
c.didResume = true
|
||||
c.peerCertificates = hs.session.serverCertificates
|
||||
c.verifiedChains = hs.session.verifiedChains
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -276,7 +336,10 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
|
||||
return errors.New("tls: invalid server key share")
|
||||
}
|
||||
|
||||
earlySecret := hs.suite.extract(nil, nil)
|
||||
earlySecret := hs.earlySecret
|
||||
if !hs.usingPSK {
|
||||
earlySecret = hs.suite.extract(nil, nil)
|
||||
}
|
||||
handshakeSecret := hs.suite.extract(sharedKey,
|
||||
hs.suite.deriveSecret(earlySecret, "derived", nil))
|
||||
|
||||
@ -328,9 +391,15 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) doFullHandshake() error {
|
||||
func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
|
||||
c := hs.c
|
||||
|
||||
// Either a PSK or a certificate is always used, but not both.
|
||||
// See RFC 8446, Section 4.1.1.
|
||||
if hs.usingPSK {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg, err := c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -419,13 +488,10 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error {
|
||||
return unexpectedMessageError(finished, msg)
|
||||
}
|
||||
|
||||
// See RFC 8446, sections 4.4.4 and 4.4.
|
||||
finishedKey := hs.suite.expandLabel(c.in.trafficSecret, "finished", nil, hs.suite.hash.Size())
|
||||
expectedMAC := hmac.New(hs.suite.hash.New, finishedKey)
|
||||
expectedMAC.Write(hs.transcript.Sum(nil))
|
||||
if !hmac.Equal(expectedMAC.Sum(nil), finished.verifyData) {
|
||||
expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
|
||||
if !hmac.Equal(expectedMAC, finished.verifyData) {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid finished hash")
|
||||
return errors.New("tls: invalid server finished hash")
|
||||
}
|
||||
|
||||
hs.transcript.Write(finished.marshal())
|
||||
@ -465,11 +531,8 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
|
||||
func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
|
||||
c := hs.c
|
||||
|
||||
finishedKey := hs.suite.expandLabel(c.out.trafficSecret, "finished", nil, hs.suite.hash.Size())
|
||||
verifyData := hmac.New(hs.suite.hash.New, finishedKey)
|
||||
verifyData.Write(hs.transcript.Sum(nil))
|
||||
finished := &finishedMsg{
|
||||
verifyData: verifyData.Sum(nil),
|
||||
verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
|
||||
}
|
||||
|
||||
hs.transcript.Write(finished.marshal())
|
||||
@ -479,5 +542,58 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
|
||||
|
||||
c.out.setTrafficSecret(hs.suite, hs.trafficSecret)
|
||||
|
||||
if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
|
||||
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||
resumptionLabel, hs.transcript)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
||||
if !c.isClient {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return errors.New("tls: received new session ticket from a client")
|
||||
}
|
||||
|
||||
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// See RFC 8446, Section 4.6.1.
|
||||
if msg.lifetime == 0 {
|
||||
return nil
|
||||
}
|
||||
lifetime := time.Duration(msg.lifetime) * time.Second
|
||||
if lifetime > 7*24*time.Hour {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: received a session ticket with invalid lifetime")
|
||||
}
|
||||
|
||||
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||
if cipherSuite == nil || c.resumptionSecret == nil {
|
||||
return c.sendAlert(alertInternalError)
|
||||
}
|
||||
|
||||
// Save the resumption_master_secret and nonce instead of deriving the PSK
|
||||
// to do the least amount of work on NewSessionTicket messages before we
|
||||
// know if the ticket will be used. Forward secrecy of resumed connections
|
||||
// is guaranteed by the requirement for pskModeDHE.
|
||||
session := &ClientSessionState{
|
||||
sessionTicket: msg.label,
|
||||
vers: c.vers,
|
||||
cipherSuite: c.cipherSuite,
|
||||
masterSecret: c.resumptionSecret,
|
||||
serverCertificates: c.peerCertificates,
|
||||
verifiedChains: c.verifiedChains,
|
||||
receivedAt: c.config.time(),
|
||||
nonce: msg.nonce,
|
||||
useBy: c.config.time().Add(lifetime),
|
||||
ageAdd: msg.ageAdd,
|
||||
}
|
||||
|
||||
cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
|
||||
c.config.ClientSessionCache.Put(cacheKey, session)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -288,6 +288,50 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
return m.raw
|
||||
}
|
||||
|
||||
// marshalWithoutBinders returns the ClientHello through the
|
||||
// PreSharedKeyExtension.identities field, according to RFC 8446, Section
|
||||
// 4.2.11.2. Note that m.pskBinders must be set to slices of the correct length.
|
||||
func (m *clientHelloMsg) marshalWithoutBinders() []byte {
|
||||
bindersLen := 2 // uint16 length prefix
|
||||
for _, binder := range m.pskBinders {
|
||||
bindersLen += 1 // uint8 length prefix
|
||||
bindersLen += len(binder)
|
||||
}
|
||||
|
||||
fullMessage := m.marshal()
|
||||
return fullMessage[:len(fullMessage)-bindersLen]
|
||||
}
|
||||
|
||||
// updateBinders updates the m.pskBinders field, if necessary updating the
|
||||
// cached marshalled representation. The supplied binders must have the same
|
||||
// length as the current m.pskBinders.
|
||||
func (m *clientHelloMsg) updateBinders(pskBinders [][]byte) {
|
||||
if len(pskBinders) != len(m.pskBinders) {
|
||||
panic("tls: internal error: pskBinders length mismatch")
|
||||
}
|
||||
for i := range m.pskBinders {
|
||||
if len(pskBinders[i]) != len(m.pskBinders[i]) {
|
||||
panic("tls: internal error: pskBinders length mismatch")
|
||||
}
|
||||
}
|
||||
m.pskBinders = pskBinders
|
||||
if m.raw != nil {
|
||||
lenWithoutBinders := len(m.marshalWithoutBinders())
|
||||
// TODO(filippo): replace with NewFixedBuilder once CL 148882 is imported.
|
||||
b := cryptobyte.NewBuilder(m.raw[:lenWithoutBinders])
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, binder := range m.pskBinders {
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(binder)
|
||||
})
|
||||
}
|
||||
})
|
||||
if len(b.BytesOrPanic()) != len(m.raw) {
|
||||
panic("tls: internal error: failed to update binders")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
||||
*m = clientHelloMsg{raw: data}
|
||||
s := cryptobyte.String(data)
|
||||
|
@ -756,14 +756,7 @@ func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (c
|
||||
func (hs *serverHandshakeState) setCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16) bool {
|
||||
for _, supported := range supportedCipherSuites {
|
||||
if id == supported {
|
||||
var candidate *cipherSuite
|
||||
|
||||
for _, s := range cipherSuites {
|
||||
if s.id == id {
|
||||
candidate = s
|
||||
break
|
||||
}
|
||||
}
|
||||
candidate := cipherSuiteByID(id)
|
||||
if candidate == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -434,12 +434,8 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
|
||||
func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
|
||||
c := hs.c
|
||||
|
||||
// See RFC 8446, sections 4.4.4 and 4.4.
|
||||
finishedKey := hs.suite.expandLabel(c.out.trafficSecret, "finished", nil, hs.suite.hash.Size())
|
||||
verifyData := hmac.New(hs.suite.hash.New, finishedKey)
|
||||
verifyData.Write(hs.transcript.Sum(nil))
|
||||
finished := &finishedMsg{
|
||||
verifyData: verifyData.Sum(nil),
|
||||
verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
|
||||
}
|
||||
|
||||
hs.transcript.Write(finished.marshal())
|
||||
@ -488,10 +484,8 @@ func (hs *serverHandshakeStateTLS13) readClientFinished() error {
|
||||
return unexpectedMessageError(finished, msg)
|
||||
}
|
||||
|
||||
finishedKey := hs.suite.expandLabel(c.in.trafficSecret, "finished", nil, hs.suite.hash.Size())
|
||||
expectedMAC := hmac.New(hs.suite.hash.New, finishedKey)
|
||||
expectedMAC.Write(hs.transcript.Sum(nil))
|
||||
if !hmac.Equal(expectedMAC.Sum(nil), finished.verifyData) {
|
||||
expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
|
||||
if !hmac.Equal(expectedMAC, finished.verifyData) {
|
||||
c.sendAlert(alertDecryptError)
|
||||
return errors.New("tls: invalid client finished hash")
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package tls
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"golang_org/x/crypto/cryptobyte"
|
||||
"golang_org/x/crypto/curve25519"
|
||||
@ -77,6 +78,16 @@ func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// finishedHash generates the Finished verify_data or PskBinderEntry according
|
||||
// to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey
|
||||
// selection.
|
||||
func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {
|
||||
finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size())
|
||||
verifyData := hmac.New(c.hash.New, finishedKey)
|
||||
verifyData.Write(transcript.Sum(nil))
|
||||
return verifyData.Sum(nil)
|
||||
}
|
||||
|
||||
// exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to
|
||||
// RFC 8446, Section 7.5.
|
||||
func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
|
||||
|
@ -87,20 +87,11 @@ func TestKeysFromPreMasterSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func cipherSuiteById(id uint16) *cipherSuite {
|
||||
for _, cipherSuite := range cipherSuites {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
panic("ciphersuite not found")
|
||||
}
|
||||
|
||||
// These test vectors were generated from GnuTLS using `gnutls-cli --insecure -d 9 `
|
||||
var testKeysFromTests = []testKeysFromTest{
|
||||
{
|
||||
VersionTLS10,
|
||||
cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
|
||||
cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
|
||||
"0302cac83ad4b1db3b9ab49ad05957de2a504a634a386fc600889321e1a971f57479466830ac3e6f468e87f5385fa0c5",
|
||||
"4ae66303755184a3917fcb44880605fcc53baa01912b22ed94473fc69cebd558",
|
||||
"4ae663020ec16e6bb5130be918cfcafd4d765979a3136a5d50c593446e4e44db",
|
||||
@ -116,7 +107,7 @@ var testKeysFromTests = []testKeysFromTest{
|
||||
},
|
||||
{
|
||||
VersionTLS10,
|
||||
cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
|
||||
cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
|
||||
"03023f7527316bc12cbcd69e4b9e8275d62c028f27e65c745cfcddc7ce01bd3570a111378b63848127f1c36e5f9e4890",
|
||||
"4ae66364b5ea56b20ce4e25555aed2d7e67f42788dd03f3fee4adae0459ab106",
|
||||
"4ae66363ab815cbf6a248b87d6b556184e945e9b97fbdf247858b0bdafacfa1c",
|
||||
@ -132,7 +123,7 @@ var testKeysFromTests = []testKeysFromTest{
|
||||
},
|
||||
{
|
||||
VersionTLS10,
|
||||
cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
|
||||
cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
|
||||
"832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
|
||||
"4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
|
||||
"4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",
|
||||
@ -148,7 +139,7 @@ var testKeysFromTests = []testKeysFromTest{
|
||||
},
|
||||
{
|
||||
VersionSSL30,
|
||||
cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
|
||||
cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
|
||||
"832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
|
||||
"4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
|
||||
"4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",
|
||||
|
Loading…
Reference in New Issue
Block a user