{"record":{"id":"12d36c1ecc18343f","repo":"grafana/k6","slug":"failed-to-decode-pem-key","errorCode":null,"errorMessage":"failed to decode PEM key","messagePattern":"failed to decode PEM key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":131,"sourceCode":"\tdefer func() { _ = fdsetFile.Close() }()\n\tfdsetBytes, err := io.ReadAll(fdsetFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't read protoset: %w\", err)\n\t}\n\n\tfdset := &descriptorpb.FileDescriptorSet{}\n\tif err = proto.Unmarshal(fdsetBytes, fdset); err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't unmarshal protoset file %s: %w\", protosetPath, err)\n\t}\n\n\treturn c.convertToMethodInfo(fdset)\n}\n\n// Note: this function was lifted from `lib/options.go`\nfunc decryptPrivateKey(key, password []byte) ([]byte, error) {\n\tblock, _ := pem.Decode(key)\n\tif block == nil {\n\t\treturn nil, errors.New(\"failed to decode PEM key\")\n\t}\n\n\tblockType := block.Type\n\tif blockType == \"ENCRYPTED PRIVATE KEY\" {\n\t\treturn nil, errors.New(\"encrypted pkcs8 formatted key is not supported\")\n\t}\n\t/*\n\t   Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still\n\t   being used here because it is deprecated due to it not supporting *good* cryptography\n\t   ultimately though we want to support something so we will be using it for now.\n\t*/\n\tdecryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkey = pem.EncodeToMemory(&pem.Block{\n\t\tType:  blockType,\n\t\tBytes: decryptedKey,","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/grafana/k6/blob/8d06114777191d21ef885d05c5f8aa9e1ed838ae/internal/js/modules/k6/grpc/client.go#L113-L149","documentation":"When client.connect() receives TLS params containing a non-empty password (params.tls.password), k6 runs decryptPrivateKey on the key PEM before building the TLS config. pem.Decode returning nil means the key bytes are not a PEM block at all - no '-----BEGIN...-----' envelope - so decryption cannot even start. (Lifted from lib/options.go, same behavior as the --tls K6_OPTIONS handling of encrypted keys.)","triggerScenarios":"client.connect(addr, { tls: { cert, key, password } }) where key is a DER/binary key, a corrupted or truncated PEM, a file path instead of contents, or empty/whitespace bytes. The password being non-empty is what routes the key through decryptPrivateKey; an unencrypted key with no password never hits this path.","commonSituations":"Passing a file path in tls.key instead of the PEM contents (forgetting open()); double-encoding or line-wrap corruption when pasting the PEM into JSON; mixing up the cert and key fields so binary/mismatched data lands in key; trailing BOM/whitespace from copy-paste breaking pem.Decode.","solutions":["Ensure tls.key contains the full PEM text including the BEGIN/END PRIVATE KEY lines - usually via open('./client.key') which k6 reads as a string.","Verify the key decodes: 'openssl pkey -in client.key -noout && echo ok'.","If the key is not encrypted, simply omit tls.password - k6 then uses the PEM as-is via tls.X509KeyPair.","Check you did not swap cert and key values."],"exampleFix":"// before: path string instead of PEM contents\nclient.connect('grpc.acme.com:443', {\n  tls: { cert: './cert.pem', key: './key.pem', password: 'secret' }, // throws\n});\n\n// after: read the PEM contents\nclient.connect('grpc.acme.com:443', {\n  tls: { cert: open('./cert.pem'), key: open('./key.pem'), password: 'secret' },\n});","handlingStrategy":"validation","validationCode":"function assertPemKey(pem, label = 'key') {\n  if (!/-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(pem)) {\n    throw new Error(`${label} is not a PEM private key (missing BEGIN/END envelope)`);\n  }\n  return pem;\n}\n// usage\nclient.connect(addr, { tls: { cert: open('c.pem'), key: assertPemKey(open('k.pem')), password: pw } });","typeGuard":"const looksLikePem = (s) => typeof s === 'string' && s.includes('-----BEGIN') && s.includes('-----END');","tryCatchPattern":"try {\n  client.connect(addr, params);\n} catch (e) {\n  if (/failed to decode PEM key/.test(e.message)) {\n    throw new Error('tls.key must contain PEM contents (use open()), not a path or DER bytes');\n  }\n  throw e;\n}","preventionTips":["Always load key material with open(), never inline paths.","Verify PEMs in CI: openssl pkey -in key.pem -noout.","Skip tls.password entirely for unencrypted keys."],"tags":["k6","grpc","tls","pem","private-key","connect"],"backgroundTag":"invalid-pem-data","analyzedSha":"8d06114777191d21ef885d05c5f8aa9e1ed838ae","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}