temporalio/temporal · error · ErrTLSConfig

unable to load decoded CA Cert as PEM

Error message

unable to load decoded CA Cert as PEM

What it means

This is the check in parseCAs that reports any error from parseCertsFromPEM after the certificate list was non-empty. In practice it is nearly unreachable: the zero-certificate case is already handled by the 'unable to parse certs as PEM' branch, so this only fires if parsing both returned certs and errored simultaneously. It is wrapped with ErrTLSConfig like its siblings.

Source

Thrown at common/auth/tls_config_helper.go:164

			return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to read client ca file", err)
		}
	} else if temporalTls.CaData != "" {
		caBytes, err = base64.StdEncoding.DecodeString(temporalTls.CaData)
		if err != nil {
			return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to decode client ca data", err)
		}
	}
	if len(caBytes) > 0 {
		caCertPool := x509.NewCertPool()
		caCerts, err := parseCertsFromPEM(caBytes)
		if len(caCerts) == 0 {
			return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to parse certs as PEM", err)
		}
		for _, cert := range caCerts {
			caCertPool.AddCert(cert)
		}
		if err != nil {
			return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to load decoded CA Cert as PEM", err)
		}
		return caCertPool, nil
	}
	return nil, nil
}

func parseCertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
	for len(pemCerts) > 0 {
		var block *pem.Block
		block, pemCerts = pem.Decode(pemCerts)
		if block == nil {
			break
		}
		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
			continue
		}

		certBytes := block.Bytes

View on GitHub (pinned to bde624efd1)

Solutions

  1. Inspect the chained error and validate the CA bundle with openssl x509 / openssl verify to find malformed blocks.
  2. Split a large CA bundle into individual PEM certs and add them back one at a time to isolate the bad block.
  3. Regenerate or re-download the CA bundle from the source authority.

Example fix

// Split and validate the bundle externally
// openssl x509 -in ca-bundle.pem -noout -subject
// before: one possibly malformed bundle file
// after: only verified PEM certs concatenated:
//   cat ca1.pem ca2.pem > /etc/temporal/certs/ca.pem
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate the CA bundle parses cleanly and completely
func checkBundle(caBytes []byte) error {
	pool := x509.NewCertPool()
	if !pool.AppendCertsFromPEM(caBytes) {
		return fmt.Errorf("bundle contains no parseable certs")
	}
	return nil
}

Try / catch

pool, err := parseCAs(cfg) // via NewTLSConfig
if err != nil {
	if errors.Is(err, auth.ErrTLSConfig) {
		logger.Error("CA bundle rejected", tag.Key, err)
	}
	return err
}

Prevention

When it happens

Trigger: NewTLSConfig -> parseCAs where parseCertsFromPEM returns a non-empty cert list AND a non-nil error — an edge case during PEM parsing of partially malformed data.

Common situations: Rarely seen by developers; may surface with unusual CA bundles containing both valid certs and malformed blocks, depending on parseCertsFromPEM behavior.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/af5fa20bdeb7dd88. Report an issue: GitHub.