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
- Regenerate the certificate from the same private key you pass in (or vice versa) so the keypair matches
- Verify you are pointing at the correct key file for this host, not another host's key
- Check the curve argument matches the certificate's actual curve (Ed25519 vs P256)
- 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
- Always issue the certificate from the exact key file you deploy alongside it
- Deploy cert and key atomically from the same enrollment step
- Checksum and log both files at deployment to detect swaps
- Keep one keypair per host and never reuse keys across certificates
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
- key was not 64 bytes, is invalid ed25519 private key
- error while generating ed25519 keys: %s
- ErrBadFormat
- ErrRootExpired
- ErrExpired
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/8c6b79e55deb8496.
Report an issue: GitHub.