{"record":{"id":"c77814c5c219a8be","repo":"golang/go","slug":"tls-server-resumed-a-session-with-a-different-ems","errorCode":null,"errorMessage":"tls: server resumed a session with a different EMS extension","messagePattern":"tls: server resumed a session with a different EMS extension","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client.go","lineNumber":949,"sourceCode":"\n\tif !hs.serverResumedSession() {\n\t\treturn false, nil\n\t}\n\n\tif hs.session.version != c.vers {\n\t\tc.sendAlert(alertHandshakeFailure)\n\t\treturn false, errors.New(\"tls: server resumed a session with a different version\")\n\t}\n\n\tif hs.session.cipherSuite != hs.suite.id {\n\t\tc.sendAlert(alertHandshakeFailure)\n\t\treturn false, errors.New(\"tls: server resumed a session with a different cipher suite\")\n\t}\n\n\t// RFC 7627, Section 5.3\n\tif hs.session.extMasterSecret != hs.serverHello.extendedMasterSecret {\n\t\tc.sendAlert(alertHandshakeFailure)\n\t\treturn false, errors.New(\"tls: server resumed a session with a different EMS extension\")\n\t}\n\n\t// Restore master secret and certificates from previous state\n\ths.masterSecret = hs.session.secret\n\tc.extMasterSecret = hs.session.extMasterSecret\n\tc.peerCertificates = hs.session.peerCertificates\n\tc.verifiedChains = hs.session.verifiedChains\n\tc.ocspResponse = hs.session.ocspResponse\n\t// Let the ServerHello SCTs override the session SCTs from the original\n\t// connection, if any are provided.\n\tif len(c.scts) == 0 && len(hs.session.scts) != 0 {\n\t\tc.scts = hs.session.scts\n\t}\n\tc.curveID = hs.session.curveID\n\n\treturn true, nil\n}\n","sourceCodeStart":931,"sourceCodeEnd":967,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client.go#L931-L967","documentation":"Per RFC 7627 5.3, an Extended Master Secret session cannot be resumed as a non-EMS session and vice versa. If hs.session.extMasterSecret != hs.serverHello.extendedMasterSecret the resumption is rejected with alertHandshakeFailure, because EMS binds the master secret to the handshake transcript and downgrading it on resumption weakens that binding.","triggerScenarios":"Server resuming a session while downgrading EMS (a known triple-handshake class of attack); server bug flipping the EMS flag on resumption; downgrade attack on the resumed connection.","commonSituations":"TLS interception or a buggy server attempting to resume EMS-protected sessions without EMS; security-relevant.","solutions":["Ensure the server consistently enables Extended Master Secret for both the original and resumed handshake.","Upgrade the server TLS stack.","Disable client-side session resumption if the server is non-conformant."],"exampleFix":"cfg := &tls.Config{SessionTicketsDisabled: true}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isResumptionEMSMismatch(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"server resumed a session with a different EMS extension\")\n}","tryCatchPattern":"if _, err := tls.Dial(\"tcp\", addr, cfg); err != nil {\n    if isResumptionEMSMismatch(err) {\n        // Security-relevant: server is downgrading EMS on resumption. Disable resumption.\n        cfg.SessionTicketsDisabled = true\n        cfg.ClientSessionCache = nil\n        _, err = tls.Dial(\"tcp\", addr, cfg)\n    }\n}","preventionTips":["Ensure the server enables EMS consistently for both original and resumed handshakes.","Treat EMS downgrade on resumption as a security signal.","Disable resumption against non-conformant servers."],"tags":["tls","session","resumption","ems","security"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}