netbirdio/netbird · critical
artifact key verification failed: %w
Error message
artifact key verification failed: %w
What it means
reposign.ValidateArtifactKeys rejected the root-to-artifact-key chain; this is a security decision in verify-artifact-key. Refusals in reposign/artifact.go:166-219: signature timestamp more than 5 minutes in the future, signature older than 10 years, root signature verification failure (wrong root key, or the key data was modified after signing), PEM bundle parse failure of the artifact keys, and all keys in the bundle being expired or revoked ("all N artifact keys are revoked").
Source
Thrown at client/cmd/signer/artifactsign.go:250
// Read optional revocation list
var revocationList *reposign.RevocationList
if revocationFile != "" {
revData, err := os.ReadFile(revocationFile)
if err != nil {
return fmt.Errorf("read revocation file: %w", err)
}
revocationList, err = reposign.ParseRevocationList(revData)
if err != nil {
return fmt.Errorf("failed to parse revocation list: %w", err)
}
}
// Validate artifact key(s)
validKeys, err := reposign.ValidateArtifactKeys(rootPublicKeys, artifactKeyData, *signature, revocationList)
if err != nil {
return fmt.Errorf("artifact key verification failed: %w", err)
}
cmd.Println("✅ Artifact key(s) verified successfully")
cmd.Printf("Signed by root key: %s\n", signature.KeyID)
cmd.Printf("Signature timestamp: %s\n", signature.Timestamp.Format("2006-01-02 15:04:05 MST"))
cmd.Printf("\nValid artifact keys (%d):\n", len(validKeys))
for i, key := range validKeys {
cmd.Printf(" [%d] Key ID: %s\n", i+1, key.Metadata.ID)
cmd.Printf(" Created: %s\n", key.Metadata.CreatedAt.Format("2006-01-02 15:04:05 MST"))
if !key.Metadata.ExpiresAt.IsZero() {
cmd.Printf(" Expires: %s\n", key.Metadata.ExpiresAt.Format("2006-01-02 15:04:05 MST"))
} else {
cmd.Printf(" Expires: Never\n")
}
}
return nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Confirm --root-key-file is the root public key generation that signed this bundle (check KeyID in the .sig matches a known root key)
- Re-fetch the matching artifact-key-pub.pem and artifact-key-pub.pem.sig pair from the key store — they must be the exact pair produced together
- Sync the clock (5-minute skew budget) and re-verify
- If keys are expired or revoked, generate a new artifact key with generate-artifact-key and publish the new root-signed bundle
Defensive patterns
Strategy: try-catch
Try / catch
if _, err := reposign.ValidateArtifactKeys(rootPublicKeys, artifactKeyData, *signature, revocationList); err != nil {
switch {
case strings.Contains(err.Error(), "failed to verify signature of artifact keys"):
log.Fatalf("wrong root key or modified bundle: %v", err)
case strings.Contains(err.Error(), "revoked"):
log.Fatalf("all keys revoked/expired: %v", err)
default:
log.Fatalf("KEY CHAIN VERIFICATION FAILED: %v", err)
}
} Prevention
- Pin the root key generation that signed each bundle; publish bundle and .sig as an atomic pair
- Rotate artifact keys before expiry and after any suspected compromise, then extend the revocation list
- Sync verifier clocks; the future-timestamp tolerance is 5 minutes
When it happens
Trigger: Passing the wrong root public key to --root-key-file; the key bundle bytes were modified after the root key signed them; every artifact key in the bundle is past ExpiresAt or listed in the supplied revocation list; verifier clock more than 5 minutes fast; reusing a .sig from a previous bundle rotation.
Common situations: Root key rotation where the verifier still holds the old root; artifact keys expired because the release cadence outlived the key lifetime; the supplied revocation file legitimately revoked the leaked key being checked.
Related errors
- artifact verification failed: %w
- failed to parse artifact private key: %w
- sign artifact: %w
- failed to parse artifact public key: %w
- failed to parse root public key(s): %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/c922685f6d31420f.
Report an issue: GitHub.