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, nil

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Inspect the wrapped error (%w) to see the exact p256.Swap failure
  2. Re-export or re-issue the certificate so it carries a well-formed ECDSA signature
  3. Verify the certificate bytes were not truncated/corrupted in transit or in config
  4. 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

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.