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 certificate

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Update the expected certificate (re-fetch the remote node's current TLS CA/cert) and reconnect
  2. Verify the endpoint resolves to the intended host (no wrong DNS/LB routing)
  3. 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

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

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/55cb50e6da798d95. Report an issue: GitHub.