temporalio/temporal · error · ErrTLSConfig

unable to decode client ca data

Error message

unable to decode client ca data

What it means

parseCAs returns this error when CaData is set but base64.StdEncoding.DecodeString fails, meaning the value is not valid base64 (invalid characters, wrong padding, or a raw PEM pasted without encoding). The decode error is chained and wrapped with ErrTLSConfig.

Source

Thrown at common/auth/tls_config_helper.go:151

	if temporalTls.CaData != "" && temporalTls.CaFile != "" {
		return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of caData or caFile properties should be specified")
	}
	return nil
}

func parseCAs(temporalTls *TLS) (*x509.CertPool, error) {
	var caBytes []byte
	var err error
	if temporalTls.CaFile != "" {
		caBytes, err = os.ReadFile(temporalTls.CaFile)
		if err != nil {
			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
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Re-encode the CA PEM with: base64 -w0 ca.pem (single-line, no newlines) and use that exact string for CaData.
  2. Confirm you are not double-encoding: the value must decode once to PEM text beginning with -----BEGIN CERTIFICATE-----.
  3. Strip surrounding whitespace/quotes or YAML multiline issues that corrupt the base64 string.
  4. Alternatively switch to CaFile pointing at the PEM on disk to avoid encoding entirely.

Example fix

// before
tls:
  caData: "-----BEGIN CERTIFICATE-----\nMIID..."   # raw PEM, not base64
// after
tls:
  caData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ..." # base64 -w0 ca.pem
Defensive patterns

Strategy: validation

Validate before calling

func checkCaDataIsBase64(caData string) error {
	decoded, err := base64.StdEncoding.DecodeString(caData)
	if err != nil {
		return fmt.Errorf("caData is not valid base64: %w", err)
	}
	if !strings.Contains(string(decoded), "BEGIN CERTIFICATE") {
		return fmt.Errorf("caData does not decode to PEM certificate")
	}
	return nil
}

Prevention

When it happens

Trigger: NewTLSConfig -> parseCAs with CaData containing non-base64 text — e.g. a PEM block pasted verbatim, a base64 string with newlines/whitespace handled incorrectly, or truncated data.

Common situations: Pasting certificate content directly into YAML instead of base64-encoding it; editors/tools adding line breaks that break decoding depending on how config is parsed; double-encoding (base64 of base64) leaving stray characters; quoting/escaping issues in templated config.

Understand the failure class

Related errors


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