diff options
author | David Benjamin <davidben@chromium.org> | 2014-11-17 11:19:02 +0300 |
---|---|---|
committer | Adam Langley <agl@google.com> | 2014-11-22 00:35:39 +0300 |
commit | fe8eb9a6031aedf6426e2dda2ffcdb274fa5c9c1 (patch) | |
tree | 1cd4e3bd419e5c6eaefa8ab8500136f29c19ac79 /ssl/test | |
parent | ae3e487d51d3414769ed89b06683f3cbde5c7828 (diff) |
Add tests for session-ID-based resumption.
This implements session IDs in client and server in runner.go.
Change-Id: I26655f996b7b44c7eb56340ef6a415d3f2ac3503
Reviewed-on: https://boringssl-review.googlesource.com/2350
Reviewed-by: Adam Langley <agl@google.com>
Diffstat (limited to 'ssl/test')
-rw-r--r-- | ssl/test/runner/common.go | 124 | ||||
-rw-r--r-- | ssl/test/runner/handshake_client.go | 82 | ||||
-rw-r--r-- | ssl/test/runner/handshake_server.go | 53 | ||||
-rw-r--r-- | ssl/test/runner/runner.go | 73 |
4 files changed, 232 insertions, 100 deletions
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 01b7581d..476a2a4f 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -203,6 +203,7 @@ const ( // ClientSessionState contains the state needed by clients to resume TLS // sessions. type ClientSessionState struct { + sessionId []uint8 // Session ID supplied by the server. nil if the session has a ticket. 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 @@ -225,6 +226,19 @@ type ClientSessionCache interface { Put(sessionKey string, cs *ClientSessionState) } +// ServerSessionCache is a cache of sessionState objects that can be used by a +// client to resume a TLS session with a given server. ServerSessionCache +// implementations should expect to be called concurrently from different +// goroutines. +type ServerSessionCache interface { + // Get searches for a sessionState associated with the given session + // ID. On return, ok is true if one was found. + Get(sessionId string) (session *sessionState, ok bool) + + // Put adds the sessionState to the cache with the given session ID. + Put(sessionId string, session *sessionState) +} + // A Config structure is used to configure a TLS client or server. // After one has been passed to a TLS function it must not be // modified. A Config may be reused; the tls package will also not @@ -311,10 +325,14 @@ type Config struct { // connections using that key are compromised. SessionTicketKey [32]byte - // SessionCache is a cache of ClientSessionState entries for TLS session - // resumption. + // ClientSessionCache is a cache of ClientSessionState entries + // for TLS session resumption. ClientSessionCache ClientSessionCache + // ServerSessionCache is a cache of sessionState entries for TLS session + // resumption. + ServerSessionCache ServerSessionCache + // MinVersion contains the minimum SSL/TLS version that is acceptable. // If zero, then SSLv3 is taken as the minimum. MinVersion uint16 @@ -732,8 +750,8 @@ type handshakeMessage interface { unmarshal([]byte) bool } -// lruSessionCache is a ClientSessionCache implementation that uses an LRU -// caching strategy. +// lruSessionCache is a client or server session cache implementation +// that uses an LRU caching strategy. type lruSessionCache struct { sync.Mutex @@ -744,27 +762,11 @@ type lruSessionCache struct { type lruSessionCacheEntry struct { sessionKey string - state *ClientSessionState -} - -// NewLRUClientSessionCache returns a ClientSessionCache with the given -// capacity that uses an LRU strategy. If capacity is < 1, a default capacity -// is used instead. -func NewLRUClientSessionCache(capacity int) ClientSessionCache { - const defaultSessionCacheCapacity = 64 - - if capacity < 1 { - capacity = defaultSessionCacheCapacity - } - return &lruSessionCache{ - m: make(map[string]*list.Element), - q: list.New(), - capacity: capacity, - } + state interface{} } // Put adds the provided (sessionKey, cs) pair to the cache. -func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) { +func (c *lruSessionCache) Put(sessionKey string, cs interface{}) { c.Lock() defer c.Unlock() @@ -790,9 +792,9 @@ func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) { c.m[sessionKey] = elem } -// Get returns the ClientSessionState value associated with a given key. It -// returns (nil, false) if no value is found. -func (c *lruSessionCache) Get(sessionKey string) (*ClientSessionState, bool) { +// Get returns the value associated with a given key. It returns (nil, +// false) if no value is found. +func (c *lruSessionCache) Get(sessionKey string) (interface{}, bool) { c.Lock() defer c.Unlock() @@ -803,6 +805,78 @@ func (c *lruSessionCache) Get(sessionKey string) (*ClientSessionState, bool) { return nil, false } +// lruClientSessionCache is a ClientSessionCache implementation that +// uses an LRU caching strategy. +type lruClientSessionCache struct { + lruSessionCache +} + +func (c *lruClientSessionCache) Put(sessionKey string, cs *ClientSessionState) { + c.lruSessionCache.Put(sessionKey, cs) +} + +func (c *lruClientSessionCache) Get(sessionKey string) (*ClientSessionState, bool) { + cs, ok := c.lruSessionCache.Get(sessionKey) + if !ok { + return nil, false + } + return cs.(*ClientSessionState), true +} + +// lruServerSessionCache is a ServerSessionCache implementation that +// uses an LRU caching strategy. +type lruServerSessionCache struct { + lruSessionCache +} + +func (c *lruServerSessionCache) Put(sessionId string, session *sessionState) { + c.lruSessionCache.Put(sessionId, session) +} + +func (c *lruServerSessionCache) Get(sessionId string) (*sessionState, bool) { + cs, ok := c.lruSessionCache.Get(sessionId) + if !ok { + return nil, false + } + return cs.(*sessionState), true +} + +// NewLRUClientSessionCache returns a ClientSessionCache with the given +// capacity that uses an LRU strategy. If capacity is < 1, a default capacity +// is used instead. +func NewLRUClientSessionCache(capacity int) ClientSessionCache { + const defaultSessionCacheCapacity = 64 + + if capacity < 1 { + capacity = defaultSessionCacheCapacity + } + return &lruClientSessionCache{ + lruSessionCache{ + m: make(map[string]*list.Element), + q: list.New(), + capacity: capacity, + }, + } +} + +// NewLRUServerSessionCache returns a ServerSessionCache with the given +// capacity that uses an LRU strategy. If capacity is < 1, a default capacity +// is used instead. +func NewLRUServerSessionCache(capacity int) ServerSessionCache { + const defaultSessionCacheCapacity = 64 + + if capacity < 1 { + capacity = defaultSessionCacheCapacity + } + return &lruServerSessionCache{ + lruSessionCache{ + m: make(map[string]*list.Element), + q: list.New(), + capacity: capacity, + }, + } +} + // TODO(jsing): Make these available to both crypto/x509 and crypto/tls. type dsaSignature struct { R, S *big.Int diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index c4dff89a..1b156056 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -136,18 +136,17 @@ NextCipherSuite: var session *ClientSessionState var cacheKey string sessionCache := c.config.ClientSessionCache - if c.config.SessionTicketsDisabled { - sessionCache = nil - } if sessionCache != nil { - hello.ticketSupported = true + hello.ticketSupported = !c.config.SessionTicketsDisabled // Try to resume a previously negotiated TLS session, if // available. cacheKey = clientSessionCacheKey(c.conn.RemoteAddr(), c.config) candidateSession, ok := sessionCache.Get(cacheKey) if ok { + ticketOk := !c.config.SessionTicketsDisabled || candidateSession.sessionTicket == nil + // Check that the ciphersuite/version used for the // previous session are still valid. cipherSuiteOk := false @@ -160,36 +159,40 @@ NextCipherSuite: versOk := candidateSession.vers >= c.config.minVersion() && candidateSession.vers <= c.config.maxVersion() - if versOk && cipherSuiteOk { + if ticketOk && versOk && cipherSuiteOk { session = candidateSession } } } if session != nil { - hello.sessionTicket = session.sessionTicket - if c.config.Bugs.CorruptTicket { - hello.sessionTicket = make([]byte, len(session.sessionTicket)) - copy(hello.sessionTicket, session.sessionTicket) - if len(hello.sessionTicket) > 0 { - offset := 40 - if offset > len(hello.sessionTicket) { - offset = len(hello.sessionTicket) - 1 + if session.sessionTicket != nil { + hello.sessionTicket = session.sessionTicket + if c.config.Bugs.CorruptTicket { + hello.sessionTicket = make([]byte, len(session.sessionTicket)) + copy(hello.sessionTicket, session.sessionTicket) + if len(hello.sessionTicket) > 0 { + offset := 40 + if offset > len(hello.sessionTicket) { + offset = len(hello.sessionTicket) - 1 + } + hello.sessionTicket[offset] ^= 0x40 } - hello.sessionTicket[offset] ^= 0x40 } - } - // A random session ID is used to detect when the - // server accepted the ticket and is resuming a session - // (see RFC 5077). - sessionIdLen := 16 - if c.config.Bugs.OversizedSessionId { - sessionIdLen = 33 - } - hello.sessionId = make([]byte, sessionIdLen) - if _, err := io.ReadFull(c.config.rand(), hello.sessionId); err != nil { - c.sendAlert(alertInternalError) - return errors.New("tls: short read from Rand: " + err.Error()) + // A random session ID is used to detect when the + // server accepted the ticket and is resuming a session + // (see RFC 5077). + sessionIdLen := 16 + if c.config.Bugs.OversizedSessionId { + sessionIdLen = 33 + } + hello.sessionId = make([]byte, sessionIdLen) + if _, err := io.ReadFull(c.config.rand(), hello.sessionId); err != nil { + c.sendAlert(alertInternalError) + return errors.New("tls: short read from Rand: " + err.Error()) + } + } else { + hello.sessionId = session.sessionId } } @@ -730,11 +733,26 @@ func (hs *clientHandshakeState) readFinished() error { } func (hs *clientHandshakeState) readSessionTicket() error { + c := hs.c + + // Create a session with no server identifier. Either a + // session ID or session ticket will be attached. + session := &ClientSessionState{ + vers: c.vers, + cipherSuite: hs.suite.id, + masterSecret: hs.masterSecret, + handshakeHash: hs.finishedHash.server.Sum(nil), + serverCertificates: c.peerCertificates, + } + if !hs.serverHello.ticketSupported { + if hs.session == nil && len(hs.serverHello.sessionId) > 0 { + session.sessionId = hs.serverHello.sessionId + hs.session = session + } return nil } - c := hs.c msg, err := c.readHandshake() if err != nil { return err @@ -745,14 +763,8 @@ func (hs *clientHandshakeState) readSessionTicket() error { return unexpectedMessageError(sessionTicketMsg, msg) } - hs.session = &ClientSessionState{ - sessionTicket: sessionTicketMsg.ticket, - vers: c.vers, - cipherSuite: hs.suite.id, - masterSecret: hs.masterSecret, - handshakeHash: hs.finishedHash.server.Sum(nil), - serverCertificates: c.peerCertificates, - } + session.sessionTicket = sessionTicketMsg.ticket + hs.session = session hs.writeServerHash(sessionTicketMsg.marshal()) diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 32814d3b..b087b468 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -335,13 +335,25 @@ Curves: func (hs *serverHandshakeState) checkForResumption() bool { c := hs.c - if c.config.SessionTicketsDisabled { - return false - } + if len(hs.clientHello.sessionTicket) > 0 { + if c.config.SessionTicketsDisabled { + return false + } - var ok bool - if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok { - return false + var ok bool + if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok { + return false + } + } else { + if c.config.ServerSessionCache == nil { + return false + } + + var ok bool + sessionId := string(hs.clientHello.sessionId) + if hs.sessionState, ok = c.config.ServerSessionCache.Get(sessionId); !ok { + return false + } } // Never resume a session for a different SSL version. @@ -416,10 +428,19 @@ func (hs *serverHandshakeState) doFullHandshake() error { hs.hello.ocspStapling = true } - hs.hello.ticketSupported = hs.clientHello.ticketSupported && !config.SessionTicketsDisabled + hs.hello.ticketSupported = hs.clientHello.ticketSupported && !config.SessionTicketsDisabled && c.vers > VersionSSL30 hs.hello.cipherSuite = hs.suite.id c.extendedMasterSecret = hs.hello.extendedMasterSecret + // Generate a session ID if we're to save the session. + if !hs.hello.ticketSupported && config.ServerSessionCache != nil { + hs.hello.sessionId = make([]byte, 32) + if _, err := io.ReadFull(config.rand(), hs.hello.sessionId); err != nil { + c.sendAlert(alertInternalError) + return errors.New("tls: short read from Rand: " + err.Error()) + } + } + hs.finishedHash = newFinishedHash(c.vers, hs.suite) hs.writeClientHash(hs.clientHello.marshal()) hs.writeServerHash(hs.hello.marshal()) @@ -733,14 +754,7 @@ func (hs *serverHandshakeState) readFinished(isResume bool) error { } func (hs *serverHandshakeState) sendSessionTicket() error { - if !hs.hello.ticketSupported || hs.c.config.Bugs.SkipNewSessionTicket { - return nil - } - c := hs.c - m := new(newSessionTicketMsg) - - var err error state := sessionState{ vers: c.vers, cipherSuite: hs.suite.id, @@ -748,6 +762,17 @@ func (hs *serverHandshakeState) sendSessionTicket() error { certificates: hs.certsFromClient, handshakeHash: hs.finishedHash.server.Sum(nil), } + + if !hs.hello.ticketSupported || hs.c.config.Bugs.SkipNewSessionTicket { + if c.config.ServerSessionCache != nil && len(hs.hello.sessionId) != 0 { + c.config.ServerSessionCache.Put(string(hs.hello.sessionId), &state) + } + return nil + } + + m := new(newSessionTicketMsg) + + var err error m.ticket, err = c.encryptTicket(&state) if err != nil { return err diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index cf1b1f98..1ee63622 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -148,10 +148,14 @@ type testCase struct { // which attempts to resume the first session. resumeSession bool // resumeConfig, if not nil, points to a Config to be used on - // resumption. SessionTicketKey and ClientSessionCache are copied from - // the initial connection's config. If nil, the initial connection's - // config is used. + // resumption. Unless newSessionsOnResume is set, + // SessionTicketKey, ServerSessionCache, and + // ClientSessionCache are copied from the initial connection's + // config. If nil, the initial connection's config is used. resumeConfig *Config + // newSessionsOnResume, if true, will cause resumeConfig to + // use a different session resumption context. + newSessionsOnResume bool // sendPrefix sends a prefix on the socket before actually performing a // handshake. sendPrefix string @@ -787,6 +791,7 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { config := test.config config.ClientSessionCache = NewLRUClientSessionCache(1) + config.ServerSessionCache = NewLRUServerSessionCache(1) if test.testType == clientTest { if len(config.Certificates) == 0 { config.Certificates = []Certificate{getRSACertificate()} @@ -814,8 +819,11 @@ func runTest(test *testCase, buildDir string, mallocNumToFail int64) error { if len(resumeConfig.Certificates) == 0 { resumeConfig.Certificates = []Certificate{getRSACertificate()} } - resumeConfig.SessionTicketKey = config.SessionTicketKey - resumeConfig.ClientSessionCache = config.ClientSessionCache + if !test.newSessionsOnResume { + resumeConfig.SessionTicketKey = config.SessionTicketKey + resumeConfig.ClientSessionCache = config.ClientSessionCache + resumeConfig.ServerSessionCache = config.ServerSessionCache + } } else { resumeConfig = config } @@ -957,11 +965,6 @@ func addCipherSuiteTests() { continue } - // Go's TLS implementation only implements session - // resumption with tickets, so SSLv3 cannot resume - // sessions. - resumeSession := ver.version != VersionSSL30 - testCases = append(testCases, testCase{ testType: clientTest, name: ver.name + "-" + suite.name + "-client", @@ -974,7 +977,7 @@ func addCipherSuiteTests() { PreSharedKeyIdentity: pskIdentity, }, flags: flags, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ @@ -991,7 +994,7 @@ func addCipherSuiteTests() { certFile: certFile, keyFile: keyFile, flags: flags, - resumeSession: resumeSession, + resumeSession: true, }) // TODO(davidben): Fix DTLS 1.2 support and test that. @@ -1009,7 +1012,7 @@ func addCipherSuiteTests() { PreSharedKeyIdentity: pskIdentity, }, flags: flags, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ testType: serverTest, @@ -1026,7 +1029,7 @@ func addCipherSuiteTests() { certFile: certFile, keyFile: keyFile, flags: flags, - resumeSession: resumeSession, + resumeSession: true, }) } } @@ -1265,7 +1268,8 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) maxHandshakeRecordLength = 1 } - // Basic handshake, with resumption. Client and server. + // Basic handshake, with resumption. Client and server, + // session ID and session ticket. testCases = append(testCases, testCase{ protocol: protocol, name: "Basic-Client" + suffix, @@ -1291,6 +1295,18 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) }) testCases = append(testCases, testCase{ protocol: protocol, + name: "Basic-Client-NoTicket" + suffix, + config: Config{ + SessionTicketsDisabled: true, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: flags, + resumeSession: true, + }) + testCases = append(testCases, testCase{ + protocol: protocol, testType: serverTest, name: "Basic-Server" + suffix, config: Config{ @@ -1301,6 +1317,19 @@ func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) flags: flags, resumeSession: true, }) + testCases = append(testCases, testCase{ + protocol: protocol, + testType: serverTest, + name: "Basic-Server-NoTickets" + suffix, + config: Config{ + SessionTicketsDisabled: true, + Bugs: ProtocolBugs{ + MaxHandshakeRecordLength: maxHandshakeRecordLength, + }, + }, + flags: flags, + resumeSession: true, + }) // TLS client auth. testCases = append(testCases, testCase{ @@ -1854,15 +1883,7 @@ func addExtensionTests() { func addResumptionVersionTests() { // TODO(davidben): Once DTLS 1.2 is working, test that as well. for _, sessionVers := range tlsVersions { - // TODO(davidben): SSLv3 is omitted here because runner does not - // support resumption with session IDs. - if sessionVers.version == VersionSSL30 { - continue - } for _, resumeVers := range tlsVersions { - if resumeVers.version == VersionSSL30 { - continue - } suffix := "-" + sessionVers.name + "-" + resumeVers.name testCases = append(testCases, testCase{ @@ -1896,10 +1917,10 @@ func addResumptionVersionTests() { }, expectedVersion: sessionVers.version, resumeConfig: &Config{ - MaxVersion: resumeVers.version, - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, - SessionTicketsDisabled: true, + MaxVersion: resumeVers.version, + CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, }, + newSessionsOnResume: true, expectedResumeVersion: resumeVers.version, }) |