slackhq/nebula · error
could not calculate alternate fingerprint to verify: %w
Error message
could not calculate alternate fingerprint to verify: %w
What it means
VerifyCertificate computes a second (alternate) fingerprint of the certificate by swapping the ECDSA signature's s value between high-s and low-s form, so certificates signed before nebula v1.10.3 (which allowed high-s signatures) can still be blocklist-checked. This wrapper error means the underlying CalculateAlternateFingerprint call failed while computing that alternate form; the original parse/swap error is wrapped via %w.
Source
Thrown at cert/ca_pool.go:177
return nil, fmt.Errorf("no certificate")
}
fp, err := c.Fingerprint()
if err != nil {
return nil, fmt.Errorf("could not calculate fingerprint to verify: %w", err)
}
signer, err := ncp.verify(c, now, fp, "")
if err != nil {
return nil, err
}
// Pre nebula v1.10.3 could generate signatures in either high or low s form and validation
// of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form
// but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we
// need to test both forms until such a time comes that we enforce low-s form on signature validation.
fp2, err := CalculateAlternateFingerprint(c)
if err != nil {
return nil, fmt.Errorf("could not calculate alternate fingerprint to verify: %w", err)
}
if fp2 != "" && ncp.IsBlocklisted(fp2) {
return nil, ErrBlockListed
}
cc := CachedCertificate{
Certificate: c,
InvertedGroups: make(map[string]struct{}),
Fingerprint: fp,
fingerprint2: fp2,
signerFingerprint: signer.Fingerprint,
}
for _, g := range c.Groups() {
cc.InvertedGroups[g] = struct{}{}
}
return &cc, nilView on GitHub (pinned to dd8f660c0a)
Solutions
- Inspect the wrapped error (%w) to see the exact p256.Swap failure
- Re-export or re-issue the certificate so it carries a well-formed ECDSA signature
- Verify the certificate bytes were not truncated/corrupted in transit or in config
- If the cert is not needed for blocklist checks, validate its signature directly with Certificate.CheckSignature instead of relying on VerifyCertificate's fingerprint path
Example fix
// before
cc, err := pool.VerifyCertificate(time.Now(), cert) // fails: could not calculate alternate fingerprint
// after
if err := cert.CheckSignature(caPubKey); err != nil { /* fix/reissue cert first */ }
cc, err := pool.VerifyCertificate(time.Now(), cert) Defensive patterns
Strategy: try-catch
Validate before calling
if cert.Curve() == cert.Curve_P256 {
// ensure signature parses before VerifyCertificate
if !cert.CheckSignature(caPubKey) {
return fmt.Errorf("certificate signature malformed; alternate fingerprint will fail")
}
} Try / catch
cc, err := pool.VerifyCertificate(now, c)
if err != nil {
var altFpErr bool
if strings.Contains(err.Error(), "alternate fingerprint") {
altFpErr = true // malformed P256 signature; treat cert as corrupt
}
return fmt.Errorf("verify failed (altFp=%v): %w", altFpErr, err)
} Prevention
- Only load certificates from trusted, intact sources
- Validate cert signatures with CheckSignature before relying on fingerprint paths
- Watch for wrapped errors with errors.Unwrap to find the root cause
- Pin to library versions >= v1.10.3 where signatures are canonical low-s
When it happens
Trigger: Calling CAPool.VerifyCertificate on a P256 certificate whose signature bytes cannot be swapped by p256.Swap (e.g. a malformed or truncated ECDSA signature in the cert). Non-P256 certificates short-circuit and return "", so only P256 certs with bad signature encoding reach this failure.
Common situations: Verifying a hand-crafted or corrupted certificate; a certificate produced by a third-party CA tool that emitted a non-canonical/malformed ECDSA signature; fuzzed or truncated certificate bytes loaded from a bad config source.
Related errors
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/15fda29a90f796eb.
Report an issue: GitHub.