{"record":{"id":"af5fa20bdeb7dd88","repo":"temporalio/temporal","slug":"unable-to-load-decoded-ca-cert-as-pem","errorCode":null,"errorMessage":"unable to load decoded CA Cert as PEM","messagePattern":"unable to load decoded CA Cert as PEM","errorType":"validation","errorClass":"ErrTLSConfig","httpStatus":null,"severity":"error","filePath":"common/auth/tls_config_helper.go","lineNumber":164,"sourceCode":"\t\t\treturn nil, fmt.Errorf(\"%w: %s (%w)\", ErrTLSConfig, \"unable to read client ca file\", err)\n\t\t}\n\t} else if temporalTls.CaData != \"\" {\n\t\tcaBytes, err = base64.StdEncoding.DecodeString(temporalTls.CaData)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: %s (%w)\", ErrTLSConfig, \"unable to decode client ca data\", err)\n\t\t}\n\t}\n\tif len(caBytes) > 0 {\n\t\tcaCertPool := x509.NewCertPool()\n\t\tcaCerts, err := parseCertsFromPEM(caBytes)\n\t\tif len(caCerts) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"%w: %s (%w)\", ErrTLSConfig, \"unable to parse certs as PEM\", err)\n\t\t}\n\t\tfor _, cert := range caCerts {\n\t\t\tcaCertPool.AddCert(cert)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%w: %s (%w)\", ErrTLSConfig, \"unable to load decoded CA Cert as PEM\", err)\n\t\t}\n\t\treturn caCertPool, nil\n\t}\n\treturn nil, nil\n}\n\nfunc parseCertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {\n\tfor len(pemCerts) > 0 {\n\t\tvar block *pem.Block\n\t\tblock, pemCerts = pem.Decode(pemCerts)\n\t\tif block == nil {\n\t\t\tbreak\n\t\t}\n\t\tif block.Type != \"CERTIFICATE\" || len(block.Headers) != 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tcertBytes := block.Bytes","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/auth/tls_config_helper.go#L146-L182","documentation":"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.","triggerScenarios":"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.","commonSituations":"Rarely seen by developers; may surface with unusual CA bundles containing both valid certs and malformed blocks, depending on parseCertsFromPEM behavior.","solutions":["Inspect the chained error and validate the CA bundle with openssl x509 / openssl verify to find malformed blocks.","Split a large CA bundle into individual PEM certs and add them back one at a time to isolate the bad block.","Regenerate or re-download the CA bundle from the source authority."],"exampleFix":"// Split and validate the bundle externally\n// openssl x509 -in ca-bundle.pem -noout -subject\n// before: one possibly malformed bundle file\n// after: only verified PEM certs concatenated:\n//   cat ca1.pem ca2.pem > /etc/temporal/certs/ca.pem","handlingStrategy":"try-catch","validationCode":"// Pre-validate the CA bundle parses cleanly and completely\nfunc checkBundle(caBytes []byte) error {\n\tpool := x509.NewCertPool()\n\tif !pool.AppendCertsFromPEM(caBytes) {\n\t\treturn fmt.Errorf(\"bundle contains no parseable certs\")\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"pool, err := parseCAs(cfg) // via NewTLSConfig\nif err != nil {\n\tif errors.Is(err, auth.ErrTLSConfig) {\n\t\tlogger.Error(\"CA bundle rejected\", tag.Key, err)\n\t}\n\treturn err\n}","preventionTips":["Split large CA bundles into individually verified PEM files before concatenating","Regenerate bundles from the issuing authority rather than hand-editing","Test each cert in a bundle with openssl x509 before combining"],"tags":["tls","pem","certificates","edge-case"],"backgroundTag":"invalid-pem-certificate","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}