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 = key

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Check the path exists with os.Stat and correct any typo in the signer key path.
  2. Fix permissions so the process user can read the private key (e.g. chmod 600 key.pem + correct owner).
  3. If the key is mounted from a secret manager, verify the mount/sync completed before calling newPEMSigner.
  4. 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

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


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/72450f4ae268600e. Report an issue: GitHub.