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

  1. Pass the same curve the TBSCertificate was created with (check t.Curve and supply it as the curve argument)
  2. Use a signing key whose type matches the certificate curve (Ed25519 key for CURVE25519 certs, P256 key for P256 certs)
  3. 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

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


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