golang/go · error

tls: server resumed a session with a different EMS extension

Error message

tls: server resumed a session with a different EMS extension

What it means

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.

Source

Thrown at src/crypto/tls/handshake_client.go:949

	if !hs.serverResumedSession() {
		return false, nil
	}

	if hs.session.version != c.vers {
		c.sendAlert(alertHandshakeFailure)
		return false, errors.New("tls: server resumed a session with a different version")
	}

	if hs.session.cipherSuite != hs.suite.id {
		c.sendAlert(alertHandshakeFailure)
		return false, errors.New("tls: server resumed a session with a different cipher suite")
	}

	// RFC 7627, Section 5.3
	if hs.session.extMasterSecret != hs.serverHello.extendedMasterSecret {
		c.sendAlert(alertHandshakeFailure)
		return false, errors.New("tls: server resumed a session with a different EMS extension")
	}

	// Restore master secret and certificates from previous state
	hs.masterSecret = hs.session.secret
	c.extMasterSecret = hs.session.extMasterSecret
	c.peerCertificates = hs.session.peerCertificates
	c.verifiedChains = hs.session.verifiedChains
	c.ocspResponse = hs.session.ocspResponse
	// Let the ServerHello SCTs override the session SCTs from the original
	// connection, if any are provided.
	if len(c.scts) == 0 && len(hs.session.scts) != 0 {
		c.scts = hs.session.scts
	}
	c.curveID = hs.session.curveID

	return true, nil
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure the server consistently enables Extended Master Secret for both the original and resumed handshake.
  2. Upgrade the server TLS stack.
  3. Disable client-side session resumption if the server is non-conformant.

Example fix

cfg := &tls.Config{SessionTicketsDisabled: true}
Defensive patterns

Strategy: fallback

Type guard

func isResumptionEMSMismatch(err error) bool {
    return err != nil && strings.Contains(err.Error(), "server resumed a session with a different EMS extension")
}

Try / catch

if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    if isResumptionEMSMismatch(err) {
        // Security-relevant: server is downgrading EMS on resumption. Disable resumption.
        cfg.SessionTicketsDisabled = true
        cfg.ClientSessionCache = nil
        _, err = tls.Dial("tcp", addr, cfg)
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: TLS interception or a buggy server attempting to resume EMS-protected sessions without EMS; security-relevant.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/c77814c5c219a8be. Report an issue: GitHub.