123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package tls
- import (
- "bytes"
- "context"
- "crypto"
- "crypto/hmac"
- "crypto/rsa"
- "errors"
- "hash"
- "sync/atomic"
- "time"
- )
- type clientHandshakeStateTLS13 struct {
- c *Conn
- ctx context.Context
- serverHello *serverHelloMsg
- hello *clientHelloMsg
- ecdheParams ecdheParameters
- session *ClientSessionState
- earlySecret []byte
- binderKey []byte
- certReq *certificateRequestMsgTLS13
- usingPSK bool
- sentDummyCCS bool
- suite *cipherSuiteTLS13
- transcript hash.Hash
- masterSecret []byte
- trafficSecret []byte // client_application_traffic_secret_0
- }
- // 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
- // The server must not select TLS 1.3 in a renegotiation. See RFC 8446,
- // sections 4.1.2 and 4.1.3.
- if c.handshakes > 0 {
- c.sendAlert(alertProtocolVersion)
- return errors.New("tls: server selected TLS 1.3 in a renegotiation")
- }
- // Consistency check on the presence of a keyShare and its parameters.
- if hs.ecdheParams == nil || len(hs.hello.keyShares) != 1 {
- return c.sendAlert(alertInternalError)
- }
- if err := hs.checkServerHelloOrHRR(); err != nil {
- return err
- }
- hs.transcript = hs.suite.hash.New()
- hs.transcript.Write(hs.hello.marshal())
- if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
- if err := hs.sendDummyChangeCipherSpec(); err != nil {
- return err
- }
- if err := hs.processHelloRetryRequest(); err != nil {
- return err
- }
- }
- hs.transcript.Write(hs.serverHello.marshal())
- c.buffering = true
- if err := hs.processServerHello(); err != nil {
- return err
- }
- if err := hs.sendDummyChangeCipherSpec(); err != nil {
- return err
- }
- if err := hs.establishHandshakeKeys(); err != nil {
- return err
- }
- if err := hs.readServerParameters(); err != nil {
- return err
- }
- if err := hs.readServerCertificate(); err != nil {
- return err
- }
- if err := hs.readServerFinished(); err != nil {
- return err
- }
- if err := hs.sendClientCertificate(); err != nil {
- return err
- }
- if err := hs.sendClientFinished(); err != nil {
- return err
- }
- if _, err := c.flush(); err != nil {
- return err
- }
- atomic.StoreUint32(&c.handshakeStatus, 1)
- return nil
- }
- // checkServerHelloOrHRR does validity checks that apply to both ServerHello and
- // HelloRetryRequest messages. It sets hs.suite.
- func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error {
- c := hs.c
- if hs.serverHello.supportedVersion == 0 {
- c.sendAlert(alertMissingExtension)
- return errors.New("tls: server selected TLS 1.3 using the legacy version field")
- }
- if hs.serverHello.supportedVersion != VersionTLS13 {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server selected an invalid version after a HelloRetryRequest")
- }
- if hs.serverHello.vers != VersionTLS12 {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server sent an incorrect legacy version")
- }
- if hs.serverHello.ocspStapling ||
- hs.serverHello.ticketSupported ||
- hs.serverHello.secureRenegotiationSupported ||
- len(hs.serverHello.secureRenegotiation) != 0 ||
- len(hs.serverHello.alpnProtocol) != 0 ||
- len(hs.serverHello.scts) != 0 {
- c.sendAlert(alertUnsupportedExtension)
- return errors.New("tls: server sent a ServerHello extension forbidden in TLS 1.3")
- }
- if !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server did not echo the legacy session ID")
- }
- if hs.serverHello.compressionMethod != compressionNone {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server selected unsupported compression format")
- }
- selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite)
- if hs.suite != nil && selectedSuite != hs.suite {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server changed cipher suite after a HelloRetryRequest")
- }
- if selectedSuite == nil {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server chose an unconfigured cipher suite")
- }
- hs.suite = selectedSuite
- c.cipherSuite = hs.suite.id
- return nil
- }
- // sendDummyChangeCipherSpec sends a ChangeCipherSpec record for compatibility
- // with middleboxes that didn't implement TLS correctly. See RFC 8446, Appendix D.4.
- func (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
- if hs.sentDummyCCS {
- return nil
- }
- hs.sentDummyCCS = true
- _, err := hs.c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
- return err
- }
- // processHelloRetryRequest handles the HRR in hs.serverHello, modifies and
- // resends hs.hello, and reads the new ServerHello into hs.serverHello.
- func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
- c := hs.c
- // The first ClientHello gets double-hashed into the transcript upon a
- // HelloRetryRequest. (The idea is that the server might offload transcript
- // storage to the client in the cookie.) 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())
- // The only HelloRetryRequest extensions we support are key_share and
- // cookie, and clients must abort the handshake if the HRR would not result
- // in any change in the ClientHello.
- if hs.serverHello.selectedGroup == 0 && hs.serverHello.cookie == nil {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server sent an unnecessary HelloRetryRequest message")
- }
- if hs.serverHello.cookie != nil {
- hs.hello.cookie = hs.serverHello.cookie
- }
- if hs.serverHello.serverShare.group != 0 {
- c.sendAlert(alertDecodeError)
- return errors.New("tls: received malformed key_share extension")
- }
- // If the server sent a key_share extension selecting a group, ensure it's
- // a group we advertised but did not send a key share for, and send a key
- // share for it this time.
- if curveID := hs.serverHello.selectedGroup; curveID != 0 {
- curveOK := false
- for _, id := range hs.hello.supportedCurves {
- if id == curveID {
- curveOK = true
- break
- }
- }
- if !curveOK {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server selected unsupported group")
- }
- if hs.ecdheParams.CurveID() == curveID {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share")
- }
- if _, ok := curveForCurveID(curveID); curveID != X25519 && !ok {
- c.sendAlert(alertInternalError)
- return errors.New("tls: CurvePreferences includes unsupported curve")
- }
- params, err := generateECDHEParameters(c.config.rand(), curveID)
- if err != nil {
- c.sendAlert(alertInternalError)
- return err
- }
- hs.ecdheParams = params
- hs.hello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}}
- }
- 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
- }
- msg, err := c.readHandshake()
- if err != nil {
- return err
- }
- serverHello, ok := msg.(*serverHelloMsg)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(serverHello, msg)
- }
- hs.serverHello = serverHello
- if err := hs.checkServerHelloOrHRR(); err != nil {
- return err
- }
- return nil
- }
- func (hs *clientHandshakeStateTLS13) processServerHello() error {
- c := hs.c
- if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
- c.sendAlert(alertUnexpectedMessage)
- return errors.New("tls: server sent two HelloRetryRequest messages")
- }
- if len(hs.serverHello.cookie) != 0 {
- c.sendAlert(alertUnsupportedExtension)
- return errors.New("tls: server sent a cookie in a normal ServerHello")
- }
- if hs.serverHello.selectedGroup != 0 {
- c.sendAlert(alertDecodeError)
- 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
- c.ocspResponse = hs.session.ocspResponse
- c.scts = hs.session.scts
- return nil
- }
- func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
- c := hs.c
- sharedKey := hs.ecdheParams.SharedKey(hs.serverHello.serverShare.data)
- if sharedKey == nil {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: invalid server key share")
- }
- earlySecret := hs.earlySecret
- if !hs.usingPSK {
- earlySecret = hs.suite.extract(nil, nil)
- }
- handshakeSecret := hs.suite.extract(sharedKey,
- hs.suite.deriveSecret(earlySecret, "derived", nil))
- clientSecret := hs.suite.deriveSecret(handshakeSecret,
- clientHandshakeTrafficLabel, hs.transcript)
- c.out.setTrafficSecret(hs.suite, clientSecret)
- serverSecret := hs.suite.deriveSecret(handshakeSecret,
- serverHandshakeTrafficLabel, hs.transcript)
- c.in.setTrafficSecret(hs.suite, serverSecret)
- err := c.config.writeKeyLog(keyLogLabelClientHandshake, hs.hello.random, clientSecret)
- if err != nil {
- c.sendAlert(alertInternalError)
- return err
- }
- err = c.config.writeKeyLog(keyLogLabelServerHandshake, hs.hello.random, serverSecret)
- if err != nil {
- c.sendAlert(alertInternalError)
- return err
- }
- hs.masterSecret = hs.suite.extract(nil,
- hs.suite.deriveSecret(handshakeSecret, "derived", nil))
- return nil
- }
- func (hs *clientHandshakeStateTLS13) readServerParameters() error {
- c := hs.c
- msg, err := c.readHandshake()
- if err != nil {
- return err
- }
- encryptedExtensions, ok := msg.(*encryptedExtensionsMsg)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(encryptedExtensions, msg)
- }
- hs.transcript.Write(encryptedExtensions.marshal())
- if err := checkALPN(hs.hello.alpnProtocols, encryptedExtensions.alpnProtocol); err != nil {
- c.sendAlert(alertUnsupportedExtension)
- return err
- }
- c.clientProtocol = encryptedExtensions.alpnProtocol
- return nil
- }
- 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 {
- // Make sure the connection is still being verified whether or not this
- // is a resumption. Resumptions currently don't reverify certificates so
- // they don't call verifyServerCertificate. See Issue 31641.
- if c.config.VerifyConnection != nil {
- if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
- c.sendAlert(alertBadCertificate)
- return err
- }
- }
- return nil
- }
- msg, err := c.readHandshake()
- if err != nil {
- return err
- }
- certReq, ok := msg.(*certificateRequestMsgTLS13)
- if ok {
- hs.transcript.Write(certReq.marshal())
- hs.certReq = certReq
- msg, err = c.readHandshake()
- if err != nil {
- return err
- }
- }
- certMsg, ok := msg.(*certificateMsgTLS13)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(certMsg, msg)
- }
- if len(certMsg.certificate.Certificate) == 0 {
- c.sendAlert(alertDecodeError)
- return errors.New("tls: received empty certificates message")
- }
- hs.transcript.Write(certMsg.marshal())
- c.scts = certMsg.certificate.SignedCertificateTimestamps
- c.ocspResponse = certMsg.certificate.OCSPStaple
- if err := c.verifyServerCertificate(certMsg.certificate.Certificate); err != nil {
- return err
- }
- msg, err = c.readHandshake()
- if err != nil {
- return err
- }
- certVerify, ok := msg.(*certificateVerifyMsg)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(certVerify, msg)
- }
- // See RFC 8446, Section 4.4.3.
- if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms) {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: certificate used with invalid signature algorithm")
- }
- sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm)
- if err != nil {
- return c.sendAlert(alertInternalError)
- }
- if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 {
- c.sendAlert(alertIllegalParameter)
- return errors.New("tls: certificate used with invalid signature algorithm")
- }
- signed := signedMessage(sigHash, serverSignatureContext, hs.transcript)
- if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey,
- sigHash, signed, certVerify.signature); err != nil {
- c.sendAlert(alertDecryptError)
- return errors.New("tls: invalid signature by the server certificate: " + err.Error())
- }
- hs.transcript.Write(certVerify.marshal())
- return nil
- }
- func (hs *clientHandshakeStateTLS13) readServerFinished() error {
- c := hs.c
- msg, err := c.readHandshake()
- if err != nil {
- return err
- }
- finished, ok := msg.(*finishedMsg)
- if !ok {
- c.sendAlert(alertUnexpectedMessage)
- return unexpectedMessageError(finished, msg)
- }
- expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
- if !hmac.Equal(expectedMAC, finished.verifyData) {
- c.sendAlert(alertDecryptError)
- return errors.New("tls: invalid server finished hash")
- }
- hs.transcript.Write(finished.marshal())
- // Derive secrets that take context through the server Finished.
- hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret,
- clientApplicationTrafficLabel, hs.transcript)
- serverSecret := hs.suite.deriveSecret(hs.masterSecret,
- serverApplicationTrafficLabel, hs.transcript)
- c.in.setTrafficSecret(hs.suite, serverSecret)
- err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)
- if err != nil {
- c.sendAlert(alertInternalError)
- return err
- }
- err = c.config.writeKeyLog(keyLogLabelServerTraffic, hs.hello.random, serverSecret)
- if err != nil {
- c.sendAlert(alertInternalError)
- return err
- }
- c.ekm = hs.suite.exportKeyingMaterial(hs.masterSecret, hs.transcript)
- return nil
- }
- func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
- c := hs.c
- if hs.certReq == nil {
- return nil
- }
- cert, err := c.getClientCertificate(&CertificateRequestInfo{
- AcceptableCAs: hs.certReq.certificateAuthorities,
- SignatureSchemes: hs.certReq.supportedSignatureAlgorithms,
- Version: c.vers,
- ctx: hs.ctx,
- })
- if err != nil {
- return err
- }
- certMsg := new(certificateMsgTLS13)
- certMsg.certificate = *cert
- certMsg.scts = hs.certReq.scts && len(cert.SignedCertificateTimestamps) > 0
- certMsg.ocspStapling = hs.certReq.ocspStapling && len(cert.OCSPStaple) > 0
- hs.transcript.Write(certMsg.marshal())
- if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil {
- return err
- }
- // If we sent an empty certificate message, skip the CertificateVerify.
- if len(cert.Certificate) == 0 {
- return nil
- }
- certVerifyMsg := new(certificateVerifyMsg)
- certVerifyMsg.hasSignatureAlgorithm = true
- certVerifyMsg.signatureAlgorithm, err = selectSignatureScheme(c.vers, cert, hs.certReq.supportedSignatureAlgorithms)
- if err != nil {
- // getClientCertificate returned a certificate incompatible with the
- // CertificateRequestInfo supported signature algorithms.
- c.sendAlert(alertHandshakeFailure)
- return err
- }
- sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerifyMsg.signatureAlgorithm)
- if err != nil {
- return c.sendAlert(alertInternalError)
- }
- signed := signedMessage(sigHash, clientSignatureContext, hs.transcript)
- signOpts := crypto.SignerOpts(sigHash)
- if sigType == signatureRSAPSS {
- signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
- }
- sig, err := cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts)
- if err != nil {
- c.sendAlert(alertInternalError)
- return errors.New("tls: failed to sign handshake: " + err.Error())
- }
- certVerifyMsg.signature = sig
- hs.transcript.Write(certVerifyMsg.marshal())
- if _, err := c.writeRecord(recordTypeHandshake, certVerifyMsg.marshal()); err != nil {
- return err
- }
- return nil
- }
- func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
- c := hs.c
- finished := &finishedMsg{
- verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
- }
- hs.transcript.Write(finished.marshal())
- if _, err := c.writeRecord(recordTypeHandshake, finished.marshal()); err != nil {
- return err
- }
- 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 > maxSessionTicketLifetime {
- 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,
- ocspResponse: c.ocspResponse,
- scts: c.scts,
- }
- cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
- c.config.ClientSessionCache.Put(cacheKey, session)
- return nil
- }
|