slackhq/nebula · error

public key in cert and private key supplied don't match

Error message

public key in cert and private key supplied don't match

What it means

VerifyPrivateKey checks that the private key bytes supplied by the caller correspond to the public key embedded in the certificate. For Curve_Ed25519 it requires the key to be exactly ed25519.PrivateKeySize (64) bytes and derives the public key from it; when the derived public key does not match the certificate's public key, the certificate and key are not a pair, so verification fails with this error.

Source

Thrown at cert/cert_v1.go:148

func (c *certificateV1) Expired(t time.Time) bool {
	return c.details.notBefore.After(t) || c.details.notAfter.Before(t)
}

func (c *certificateV1) VerifyPrivateKey(curve Curve, key []byte) error {
	if curve != c.details.curve {
		return fmt.Errorf("curve in cert and private key supplied don't match")
	}
	if c.details.isCA {
		switch curve {
		case Curve_CURVE25519:
			// the call to PublicKey below will panic slice bounds out of range otherwise
			if len(key) != ed25519.PrivateKeySize {
				return fmt.Errorf("key was not 64 bytes, is invalid ed25519 private key")
			}

			if !ed25519.PublicKey(c.details.publicKey).Equal(ed25519.PrivateKey(key).Public()) {
				return fmt.Errorf("public key in cert and private key supplied don't match")
			}
		case Curve_P256:
			privkey, err := ecdh.P256().NewPrivateKey(key)
			if err != nil {
				return fmt.Errorf("cannot parse private key as P256: %w", err)
			}
			pub := privkey.PublicKey().Bytes()
			if !bytes.Equal(pub, c.details.publicKey) {
				return fmt.Errorf("public key in cert and private key supplied don't match")
			}
		default:
			return fmt.Errorf("invalid curve: %s", curve)
		}
		return nil
	}

	var pub []byte
	switch curve {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Regenerate the certificate from the same private key you pass in (or vice versa) so the keypair matches
  2. Verify you are pointing at the correct key file for this host, not another host's key
  3. Check the curve argument matches the certificate's actual curve (Ed25519 vs P256)
  4. Re-enroll the host or re-issue the certificate if the key was rotated

Example fix

// before
cert.VerifyPrivateKey(mismatchedKey, cert.Curve_Ed25519, keyBytes) // errors
// after
// ensure keyBytes is the private key whose ed25519.PublicKey equals cert's public key
cert.VerifyPrivateKey(mismatchedKey, cert.Curve_Ed25519, correctKeyBytes)
Defensive patterns

Strategy: validation

Validate before calling

func validEd25519KeyPair(cert *cert.CertificateV1, key []byte) bool {
    if len(key) != ed25519.PrivateKeySize {
        return false
    }
    pub, ok := ed25519.PrivateKey(key).Public().(ed25519.PublicKey)
    return ok && ed25519.PublicKey(certDetailsPublicKey(cert)).Equal(pub)
}
if !validEd25519KeyPair(c, keyBytes) {
    return fmt.Errorf("cert and key are not a matching pair")
}
err := c.VerifyPrivateKey(key, cert.Curve_Ed25519, keyBytes)

Type guard

func isEd25519PrivateKey(b []byte) bool { return len(b) == ed25519.PrivateKeySize }

Prevention

When it happens

Trigger: Calling (*certificateV1).VerifyPrivateKey(pubkey, curve=Curve_Ed25519, key) where key is 64 bytes but is the private key of a different keypair than the one that signed/backs this cert, or a corrupted/transposed key.

Common situations: Mixing up host.key and host.crt files across hosts during provisioning; rotating the CA or keypair and updating only one of cert/key on disk; copying config files between nodes; supplying a P256 private key while the cert is Ed25519.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/8c6b79e55deb8496. Report an issue: GitHub.