hyperledger/fabric · critical
public key of server certificate presented by %s doesn't mat
Error message
public key of server certificate presented by %s doesn't match the expected public key
What it means
verifyHandshake builds a TLS RemoteVerifier that compares the certificate presented during the handshake against the expected server certificate using crypto.CertificatesWithSamePublicKey. If the public keys differ, the server at the endpoint is not the one we pinned, so the connection is rejected.
Source
Thrown at orderer/common/cluster/connections.go:64
connMapping := &ConnectionStore{
Connections: &connMapperReporter{
ConnectionMapper: make(ConnByCertMap),
tlsConnectionCountMetrics: tlsConnectionCount,
},
dialer: dialer,
}
return connMapping
}
// verifyHandshake returns a predicate that verifies that the remote node authenticates
// itself with the given TLS certificate
func (c *ConnectionStore) verifyHandshake(endpoint string, certificate []byte) RemoteVerifier {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
err := crypto.CertificatesWithSamePublicKey(certificate, rawCerts[0])
if err == nil {
return nil
}
return errors.Errorf("public key of server certificate presented by %s doesn't match the expected public key",
endpoint)
}
}
// Disconnect closes the gRPC connection that is mapped to the given certificate
func (c *ConnectionStore) Disconnect(expectedServerCert []byte) {
c.lock.Lock()
defer c.lock.Unlock()
conn, connected := c.Connections.Lookup(expectedServerCert)
if !connected {
return
}
conn.Close()
c.Connections.Remove(expectedServerCert)
}
// Connection obtains a connection to the given endpoint and expects the given server certificateView on GitHub (pinned to 2736b63f8f)
Solutions
- Update the expected certificate (re-fetch the remote node's current TLS CA/cert) and reconnect
- Verify the endpoint resolves to the intended host (no wrong DNS/LB routing)
- If certs were rotated, redistribute the new TLS certificates across the cluster and restart the affected orderers
Example fix
// before conn, err := mgr.Connect(endpoint, staleCert) // after newCert := fetchCurrentTLSCert(endpoint) // re-pull rotated cert conn, err := mgr.Connect(endpoint, newCert)
Defensive patterns
Strategy: validation
Validate before calling
certs, err := x509.ParseCertificates(rawCerts)
if err != nil || len(rawCerts) == 0 {
return fmt.Errorf("no valid server certificate")
}
if err := crypto.CertificatesWithSamePublicKey(expectedCert, rawCerts[0]); err != nil {
return fmt.Errorf("unexpected server cert at endpoint")
} Type guard
func certMatches(expected, presented []byte) bool {
return crypto.CertificatesWithSamePublicKey(expected, presented) == nil
} Try / catch
verifier := store.verifyHandshake(endpoint, expectedCert)
if err := verifier(rawCerts, nil); err != nil {
log.Errorf("TLS pinning failed for %s: %v", endpoint, err)
return err
} Prevention
- Distribute TLS cert updates cluster-wide before expiry/rotation
- Pin certificates, not just CAs, when using verifyHandshake
- Monitor for cert rotation and refresh pinned certs automatically
- Verify DNS/LB so the endpoint always reaches the intended node
When it happens
Trigger: The remote orderer at endpoint presented a TLS certificate whose public key does not match the certificate bytes captured earlier (certificate passed into ConnectionStore), during gRPC handshake verification.
Common situations: The remote node rotated/reissued its TLS cert; DNS or load balancer routes the endpoint to a different node; cluster TLS certs regenerated while peers cached the old public key.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to add ca-file PEM to cert pool
- loading client cert/key pair: %s
- %s: wrong PEM encoding
- subjectKeyIdentifier not found in certificate
- session binding read failed
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/55cb50e6da798d95.
Report an issue: GitHub.