Netflix/chaosmonkey · critical

pkcs.ToPEM failed

Error message

pkcs.ToPEM failed

What it means

spinnaker.getClient builds an mTLS http client from PKCS#12 (.p12) data. golang.org/x/crypto/pkcs12.ToPEM failed to decode/decrypt the pfx blob into PEM blocks — typically the data is not valid PKCS#12, is corrupted, or the supplied password is wrong. The library wraps the underlying pkcs12 error with this message.

Source

Thrown at spinnaker/spinnaker.go:70

// as represented by Spinnaker API
type spinnakerServerGroup struct {
	Name      string
	Region    string
	Disabled  bool
	Instances []spinnakerInstance
}

// spinnakerInstance represents an instance as represented by Spinnaker API
type spinnakerInstance struct {
	Name string
}

// getClient takes PKCS#12 data (encrypted cert data in .p12 format) and the
// password for the encrypted cert, and returns an http client that does TLS client auth
func getClient(pfxData []byte, password string) (*http.Client, error) {
	blocks, err := pkcs12.ToPEM(pfxData, password)
	if err != nil {
		return nil, errors.Wrap(err, "pkcs.ToPEM failed")
	}

	// The first block is the cert and the last block is the private key
	certPEMBlock := pem.EncodeToMemory(blocks[0])
	keyPEMBlock := pem.EncodeToMemory(blocks[len(blocks)-1])

	cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
	if err != nil {
		return nil, errors.Wrap(err, "tls.X509KeyPair failed")
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
	}
	transport := &http.Transport{TLSClientConfig: tlsConfig}
	return &http.Client{Transport: transport}, nil
}

View on GitHub (pinned to eaa28fb761)

Solutions

  1. Verify the password: decrypt the spinnaker encrypted password correctly (check KMS/decryptor config) and confirm it opens the .p12 (e.g. `openssl pkcs12 -info -in cert.p12`)
  2. Confirm certPath points to an actual PKCS#12 file, not a PEM/certificate chain; re-export with `openssl pkcs12 -export`
  3. Re-download/copy the file and check it is not truncated or empty
  4. If using a modern OpenSSL-generated p12 that fails to parse, re-export with `-legacy` or legacy algorithms compatible with golang.org/x/crypto/pkcs12

Example fix

// before
openssl pkcs12 -export -out spinnaker.p12 -inkey key.pem -in cert.pem   # wrong password stored in config
// after
openssl pkcs12 -export -passout pass:correctpass -out spinnaker.p12 -inkey key.pem -in cert.pem
# and ensure cfg.SpinnakerEncryptedPassword() decrypts to "correctpass"
Defensive patterns

Strategy: validation

Validate before calling

pfx, err := ioutil.ReadFile(certPath)
if err != nil {
	return fmt.Errorf("cannot read cert file: %w", err)
}
// Probe decode/decrypt before calling spinnaker.New
if _, err := pkcs12.ToPEM(pfx, password); err != nil {
	return fmt.Errorf("p12 decode failed (bad file or wrong password): %w", err)
}

Type guard

func looksLikePKCS12(data []byte) bool {
	// PKCS#12 is a DER ASN.1 structure; pfx starts with SEQUENCE 0x30
	return len(data) > 4 && data[0] == 0x30
}

Try / catch

sp, err := spinnaker.New(endpoint, certPath, password, "", "", user)
if err != nil && strings.Contains(err.Error(), "pkcs.ToPEM failed") {
	return fmt.Errorf("check that %s is a valid .p12 and the password is correct: %w", certPath, err)
}

Prevention

When it happens

Trigger: Calling spinnaker.New with a non-empty certPath whose file bytes are not valid PKCS#12 (e.g. a PEM file, truncated download, or HTML error page saved as .p12), or a password that does not decrypt the pfx (via NewFromConfig with a wrong/undecryptable spinnaker encrypted password).

Common situations: Config points spinnaker.certificate at the wrong file; cert re-exported in a format pkcs12 (as implemented) can't parse (e.g. newer OpenSSL 3 defaults like AES-based p12 that older x/crypto can't decrypt, or MAC mismatch); KMS/credstash decryption returns the wrong password string; file mounted empty in a container.

Related errors


AI-assisted analysis of Netflix/chaosmonkey@eaa28fb761 (2026-09-03). Data as JSON: /api/errors/cfe8f63cb909b5f8. Report an issue: GitHub.