{"record":{"id":"21af7dc18c624c78","repo":"grpc/grpc-go","slug":"pemfile-private-key-file-and-identity-cert-file-s","errorCode":null,"errorMessage":"pemfile: private key file and identity cert file should be both specified or not specified","messagePattern":"pemfile: private key file and identity cert file should be both specified or not specified","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/tls/certprovider/pemfile/watcher.go","lineNumber":85,"sourceCode":"\t// SPIFFEBundleMapFile, the SPIFFEBundleMapFile will be preferred.\n\t// Optional.\n\tSPIFFEBundleMapFile string\n\t// RefreshDuration is the amount of time the plugin waits before checking\n\t// for updates in the specified files.\n\t// Optional. If not set, a default value (1 hour) will be used.\n\tRefreshDuration time.Duration\n}\n\nfunc (o Options) canonical() []byte {\n\treturn []byte(fmt.Sprintf(\"%s:%s:%s:%s:%s\", o.CertFile, o.KeyFile, o.RootFile, o.SPIFFEBundleMapFile, o.RefreshDuration))\n}\n\nfunc (o Options) validate() error {\n\tif o.CertFile == \"\" && o.KeyFile == \"\" && o.RootFile == \"\" && o.SPIFFEBundleMapFile == \"\" {\n\t\treturn fmt.Errorf(\"pemfile: at least one credential file needs to be specified\")\n\t}\n\tif keySpecified, certSpecified := o.KeyFile != \"\", o.CertFile != \"\"; keySpecified != certSpecified {\n\t\treturn fmt.Errorf(\"pemfile: private key file and identity cert file should be both specified or not specified\")\n\t}\n\t// C-core has a limitation that they cannot verify that a certificate file\n\t// matches a key file. So, the only way to get around this is to make sure\n\t// that both files are in the same directory and that they do an atomic\n\t// read. Even though Java/Go do not have this limitation, we want the\n\t// overall plugin behavior to be consistent across languages.\n\tif certDir, keyDir := filepath.Dir(o.CertFile), filepath.Dir(o.KeyFile); certDir != keyDir {\n\t\treturn errors.New(\"pemfile: certificate and key file must be in the same directory\")\n\t}\n\treturn nil\n}\n\n// NewProvider returns a new certificate provider plugin that is configured to\n// watch the PEM files specified in the passed in options.\nfunc NewProvider(o Options) (certprovider.Provider, error) {\n\tif err := o.validate(); err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/tls/certprovider/pemfile/watcher.go#L67-L103","documentation":"Options.validate() requires CertFile and KeyFile to be specified together or both omitted. Specifying one without the other is invalid because an identity requires both the certificate chain and its private key. validate() checks this with the XOR `keySpecified != certSpecified`.","triggerScenarios":"Configuring only certificate_file without private_key_file, or only private_key_file without certificate_file, in either the Options struct passed to NewProvider or the file_watcher JSON.","commonSituations":"Operator updates the cert path but forgets the key path; templating only substitutes one of the two variables; mTLS config where the user thought the key file was optional.","solutions":["Set both certificate_file and private_key_file to valid, matching PEM paths.","If you do not need a client/server identity (only root CA validation), remove both fields entirely so neither is set.","Verify cert and key pair with `openssl x509 -noout -modulus | openssl md5` and the matching key modulus."],"exampleFix":"// before\npemfile.Options{CertFile: \"/tls/server.crt\"} // missing key\n\n// after\npemfile.Options{CertFile: \"/tls/server.crt\", KeyFile: \"/tls/server.key\"}","handlingStrategy":"validation","validationCode":"func validateCertKeyPair(o pemfile.Options) error {\n    if (o.CertFile == \"\") != (o.KeyFile == \"\") {\n        return errors.New(\"pemfile: cert and key must be set together\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := opts.validate(); err != nil { return err }","preventionTips":["Treat certificate_file and private_key_file as a single required-together tuple.","Cross-check that both files exist and form a matching pair (openssl modulus compare).","In config templates, gate both variables on the same flag."],"tags":["grpc","config","pemfile","validation","mtls","certprovider"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}