hashicorp/terraform · error
failed to create cert signer
Error message
failed to create cert signer %q: %s
What it means
ssh.NewCertSigner combines the parsed certificate and signer. This fails if the certificate was not signed by the private key provided (key/cert mismatch), if the certificate is expired or structurally invalid for signer construction, or if the type assertion pcert.(*ssh.Certificate) panics because ParseAuthorizedKey returned a non-certificate key type. SECURITY NOTE: the error interpolates usigner via %q.
Solutions
- Ensure the private_key and certificate are a matched pair — the certificate must be signed by the corresponding CA, and the private key must be the one the certificate identifies.
- Regenerate both the key pair and certificate together: ssh-keygen -t rsa -f id_rsa then ssh-keygen -s ca_key -I identity id_rsa.pub.
- Verify the certificate's serial and key ID match the private key: ssh-keygen -L -f id_rsa-cert.pub.
- If hitting a panic (not an error) from the type assertion, ensure the certificate value is actually a certificate, not a plain public key.
Example fix
# before — mismatched key and cert from different sources
connection {
private_key = file("~/.ssh/key_a")
certificate = file("~/.ssh/key_b-cert.pub") # signed for a different key
}
# after — matched pair
cd ~/.ssh
ssh-keygen -t rsa -f tf_key
ssh-keygen -s ca_key -I tf-identity tf_key.pub
connection {
private_key = file("~/.ssh/tf_key")
certificate = file("~/.ssh/tf_key-cert.pub")
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the key/cert pair is consistent before calling NewCertSigner
func validateKeyCertPair(pk, cert string) error {
rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
if err != nil {
return err
}
signer, err := ssh.NewSignerFromKey(rawPk)
if err != nil {
return err
}
parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(cert))
if err != nil {
return err
}
sshCert, ok := parsedKey.(*ssh.Certificate)
if !ok {
return errors.New("not a certificate")
}
// Check the certificate's signature key matches the public key of the signer
certPubKey := sshCert.SignatureKey.Marshal()
signerPubKey := signer.PublicKey().Marshal()
if !bytes.Equal(certPubKey, signerPubKey) {
return errors.New("certificate was signed for a different key than the private key provided")
}
return nil
} Type guard
// Safe type assertion guard to prevent the panic in the original code
func asCertificate(key ssh.PublicKey) (*ssh.Certificate, bool) {
c, ok := key.(*ssh.Certificate)
return c, ok
} Try / catch
// Replace the unsafe assertion pcert.(*ssh.Certificate) with:
sshCert, ok := pcert.(*ssh.Certificate)
if !ok {
return nil, errors.New("parsed key is not an SSH certificate")
}
ucertSigner, err := ssh.NewCertSigner(sshCert, usigner)
if err != nil {
return nil, fmt.Errorf("cert signer creation failed (key/cert mismatch?): %w", err)
} Prevention
- Always generate the key and certificate as a matched pair from one CA.
- Use a safe type assertion (comma-ok form) instead of a single-return assertion to avoid panics.
- Verify the pair with ssh-keygen -L -f cert-file and compare the key ID.
- Do not log signer or key objects in error messages.
When it happens
Trigger: The private key and certificate do not correspond — the certificate was signed by a different CA key. Also triggered when the certificate's principal or critical options are incompatible. There is also a latent PANIC risk: pcert.(*ssh.Certificate) is an unchecked type assertion that will panic (not error) if ParseAuthorizedKey returns a non-*Certificate type.
Common situations: User has multiple key pairs and mismatches a private key with a certificate signed under a different key. Certificate was regenerated by the CA but the user still references the old private key. Certificate and key from different environments (staging vs prod CA).
Related errors
- failed to parse certificate
- failed to parse private key
- failed to create signer from raw private key
- Failed to parse ssh private key
- cannot load client certificate
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/00e6842f5de6e4d7.
Report an issue: GitHub.
Appendix: source
Thrown at internal/communicator/ssh/provisioner.go:415
func signCertWithPrivateKey(pk string, certificate string) (ssh.AuthMethod, error) {
rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
if err != nil {
return nil, fmt.Errorf("failed to parse private key %q: %s", pk, err)
}
pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certificate))
if err != nil {
return nil, fmt.Errorf("failed to parse certificate %q: %s", certificate, err)
}
usigner, err := ssh.NewSignerFromKey(rawPk)
if err != nil {
return nil, fmt.Errorf("failed to create signer from raw private key %q: %s", rawPk, err)
}
ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)
if err != nil {
return nil, fmt.Errorf("failed to create cert signer %q: %s", usigner, err)
}
return ssh.PublicKeys(ucertSigner), nil
}
func readPrivateKey(pk string) (ssh.AuthMethod, error) {
// We parse the private key on our own first so that we can
// show a nicer error if the private key has a password.
block, _ := pem.Decode([]byte(pk))
if block == nil {
return nil, errors.New("Failed to read ssh private key: no key found")
}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
return nil, errors.New(
"Failed to read ssh private key: password protected keys are\n" +
"not supported. Please decrypt the key prior to use.")
}
View on GitHub (pinned to d32a084675)