{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":128,"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":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/client.go#L110-L146","documentation":"When client.connect() is given TLS certificates that include a password, k6 decrypts the private key via decryptPrivateKey (lifted from lib/options.go). pem.Decode must find a PEM block; a key that is not PEM-encoded (binary DER, wrong file, truncated copy-paste, empty content) yields a nil block and this error.","triggerScenarios":"client.connect(addr, { tls: { certs: [[certPem, keyPem, password]] } }) where keyPem is DER/binary, is actually the certificate or CA file, or has lost its '-----BEGIN ... PRIVATE KEY-----' header/footer.","commonSituations":"Pointing the key slot at a .der or .crt file; PEM fences broken by templating or YAML/JSON escaping; key variable left empty so another value is passed by mistake.","solutions":["Verify the key content starts with -----BEGIN RSA PRIVATE KEY----- (or EC/PRIVATE KEY) and ends with -----END ...-----","Convert DER to PEM: openssl rsa -inform DER -in key.der -out key.pem","Check the argument order in the certs triple [cert, key, password] and that the key file was read completely"],"exampleFix":"// before: keyFile is DER/binary\nclient.connect(addr, { tls: { certs: [[certPem, keyDer, 'pass']] } });\n\n// after: convert once, then\n// openssl rsa -inform DER -in key.der -out key.pem\nclient.connect(addr, { tls: { certs: [[certPem, keyPem, 'pass']] } });","handlingStrategy":"validation","validationCode":"function isPemKey(s) {\n  return typeof s === 'string' && /^-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(s.trim());\n}\n\nif (!isPemKey(keyPem)) throw new Error('tls key is not PEM-encoded (missing BEGIN PRIVATE KEY fence)');\nclient.connect(addr, { tls: { certs: [[certPem, keyPem, password]] } });","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check for the BEGIN/END PRIVATE KEY fences before connecting","Never point the key slot at DER files -- convert with openssl first","Read cert material from dedicated secret files, not templated strings, to avoid fence corruption"],"tags":["grpc","tls","pem","certificates","connect"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}