hashicorp/packer · error
read signer %q: %w
Error message
read signer %q: %w
What it means
loadPEMSigner (reached via newPEMSigner) could not read the signer's private-key file because os.ReadFile failed. The OS error is wrapped with %w so the concrete reason (missing file, permission denied, is-a-directory) is preserved. This happens before any PEM parsing is attempted.
Source
Thrown at internal/attestation/sign_key.go:137
}
func LoadPEMVerifierBytes(contents []byte) (*pemVerifier, error) {
publicKey, rawVerifier, err := loadPEMPublicKey(contents)
if err != nil {
return nil, err
}
return &pemVerifier{
publicKey: publicKey,
keyID: sha256Hex(rawVerifier),
}, nil
}
func loadPEMSigner(path string) (crypto.Signer, *pemVerifier, error) {
contents, err := os.ReadFile(path)
if err != nil {
return nil, nil, fmt.Errorf("read signer %q: %w", path, err)
}
block, _ := pem.Decode(contents)
if block == nil {
return nil, nil, fmt.Errorf("decode signer %q: no PEM block found", path)
}
var signer crypto.Signer
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
var ok bool
signer, ok = key.(crypto.Signer)
if !ok {
return nil, nil, fmt.Errorf("signer %q does not implement crypto.Signer", path)
}
} else if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
signer = key
} else if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
signer = keyView on GitHub (pinned to eb36e3c3e4)
Solutions
- Check the path exists with os.Stat and correct any typo in the signer key path.
- Fix permissions so the process user can read the private key (e.g. chmod 600 key.pem + correct owner).
- If the key is mounted from a secret manager, verify the mount/sync completed before calling newPEMSigner.
- Inspect the wrapped OS error (errors.Is(err, fs.ErrNotExist) / fs.ErrPermission) for the exact cause.
Example fix
// before
signer, verifier, err := attestation.NewSigner("signing-key.pem") // ENOENT
// after
signer, verifier, err := attestation.NewSigner("/etc/packer/keys/signing-key.pem") // absolute, verified path Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(keyPath)
if err != nil {
return fmt.Errorf("signer key %q unavailable: %w", keyPath, err)
}
if info.Mode().Perm()&0o077 != 0 {
return fmt.Errorf("signer key %q too permissive: %v", keyPath, info.Mode().Perm())
} Try / catch
signer, verifier, err := attestation.NewSigner(keyPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("signing key not found: %s", keyPath)
}
if errors.Is(err, fs.ErrPermission) {
return fmt.Errorf("signing key %s: permission denied", keyPath)
}
return err
} Prevention
- Keep private-key paths in one config location with absolute paths.
- Set keys to 0600 owned by the service user; verify at deployment.
- Fail fast at startup by loading the signer once, not lazily on first signature.
- For containerized runs, assert secret mounts exist in the entrypoint script.
When it happens
Trigger: Calling newPEMSigner(path) where the private-key path does not exist, is unreadable by the process user, or is a directory/special file.
Common situations: Wrong path in signing config; key never mounted into the container; permissions tightened to 0600 under a different service account; secret-manager sync failed so the key file is absent at startup.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- read verifier %q: %w
- decode signer %q: no PEM block found
- signing_mode %q does not support Sigstore bundle emission
- load verifier %q: %w
- signer %q does not implement crypto.Signer
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/72450f4ae268600e.
Report an issue: GitHub.