slackhq/nebula · error
curve in cert and private key supplied don't match
Error message
curve in cert and private key supplied don't match
What it means
TBSCertificate.SignWith checks that the curve argument passed in matches the certificate's own t.Curve before delegating to the signer lambda. A mismatch means the private key/signer supplied corresponds to a different curve than the certificate declares (e.g. an Ed25519 key for a P256 cert), producing a signature that would not verify. The library fails fast instead.
Source
Thrown at cert/sign.go:79
return nil, err
}
sp := func(certBytes []byte) ([]byte, error) {
// We need to hash first for ECDSA
// - https://pkg.go.dev/crypto/ecdsa#SignASN1
hashed := sha256.Sum256(certBytes)
return ecdsa.SignASN1(rand.Reader, pk, hashed[:])
}
return t.SignWith(signer, curve, sp)
default:
return nil, fmt.Errorf("invalid curve: %s", t.Curve)
}
}
// SignWith does the same thing as sign, but uses the function in `sp` to calculate the signature.
// You should only use SignWith if you do not have direct access to your private key.
func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLambda) (Certificate, error) {
if curve != t.Curve {
return nil, fmt.Errorf("curve in cert and private key supplied don't match")
}
if signer != nil {
if t.IsCA {
return nil, fmt.Errorf("can not sign a CA certificate with another")
}
err := checkCAConstraints(signer, t.NotBefore, t.NotAfter, t.Groups, t.Networks, t.UnsafeNetworks)
if err != nil {
return nil, err
}
issuer, err := signer.Fingerprint()
if err != nil {
return nil, fmt.Errorf("error computing issuer: %v", err)
}
t.issuer = issuer
} else {View on GitHub (pinned to dd8f660c0a)
Solutions
- Pass the same curve the TBSCertificate was created with (check t.Curve and supply it as the curve argument)
- Use a signing key whose type matches the certificate curve (Ed25519 key for CURVE25519 certs, P256 key for P256 certs)
- If migrating curves, regenerate both the cert details and the signing key together
Example fix
// before cert, err := tbs.SignWith(signer, nebula.Curve_P256, sp) // tbs.Curve == Curve_CURVE25519 // after cert, err := tbs.SignWith(signer, tbs.Curve, sp)
Defensive patterns
Strategy: validation
Validate before calling
if curve != tbs.Curve {
return fmt.Errorf("signing key curve %v does not match certificate curve %v", curve, tbs.Curve)
} Type guard
func curvesMatch(tbs *nebula.TBSCertificate, c nebula.Curve) bool { return tbs.Curve == c } Try / catch
cert, err := tbs.SignWith(signer, curve, sp)
if err != nil {
return nil, fmt.Errorf("certificate curve and signing key curve disagree: %w", err)
} Prevention
- Derive the curve argument from the signing key type, not a hardcoded constant
- When rotating from Curve25519 to P256, regenerate cert details and keys together
- Keep a single source of truth for the curve in your cert-generation code
When it happens
Trigger: Call SignWith(signer, curve, sp) — directly or via Sign — where curve != t.Curve: e.g. a TBSCertificate built with Curve_P256 but signed with a Curve_CURVE25519 key, or a hardcoded curve argument that disagrees with the TBS struct.
Common situations: Mixing key types in test helpers (NewTestCert with mismatched key), migrating certificates from Curve25519 to P256 while reusing old signing keys, or copy/pasted signing code passing the wrong Curve constant.
Related errors
- ErrEmptySignature
- marshalling certificate details failed: %w
- invalid curve: %s
- recombine cert: %w
- ErrBadFormat
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/7cf957acc74fa729.
Report an issue: GitHub.