Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support p12 certificate format #415

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

wangtiga
Copy link

@wangtiga wangtiga commented Oct 2, 2023

This commit adds support p12 certificate format.

add three param -cacert-format -cert-format -pass

$ grpcurl -insecure -import-path ./protos -proto ./protos/apiService.proto -cacert ca.pem -cert client.pfx -cert-format P12 -pass pfxpassword -key client.key -d '{"msg":"hi"}' 127.0.0.1:50053 apiService.SayHello
{}

$ grpcurl -insecure -import-path ./protos -proto ./protos/apiService.proto -cacert ca.der -cacert-format DER -cert client.der -cert-format DER -key client.key -d '{"msg":"hi"}' 127.0.0.1:50053 apiService.SayHello
{}

$ grpcurl -insecure -import-path ./protos -proto ./protos/apiService.proto -cacert ca.der -cacert-format '' -cert client.der -cert-format '' -key client.key -d '{"msg":"hi"}' 127.0.0.1:50053 apiService.SayHello
{}

$ grpcurl -insecure -import-path ./protos -proto ./protos/apiService.proto -cacert ca.pem -cert client.pem -key client.key -d '{"msg":"hi"}' 127.0.0.1:50053 apiService.SayHello
{}

This should close #331

@ghost
Copy link

ghost commented Oct 2, 2023

👇 Click on the image for a new way to code review

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map legend

@dragonsinth
Copy link
Member

I have some questions....

  1. If we want to support a new type of cert (p12), what would you call the old type?
  2. Is password applicable to any other types of certs, or is this p12 specific?
  3. Could the cert type be auto detected without an explicit command line option?

@wangtiga
Copy link
Author

If we want to support a new type of cert (p12), what would you call the old type?

Is password applicable to any other types of certs, or is this p12 specific?

Passwords may also be applicable to PEM format certificates, as described in rfc7468 on the Textual Encoding of PKCS #8 Encrypted Private Key Info (https://datatracker.ietf.org/doc/html/rfc7468#section-11).

Could the cert type be auto detected without an explicit command line option?

  • In addition to the currently supported PEM and P12 certificate formats, there are actually other formats such as DER and ENG.
  • While it is possible to implement auto-detection for just PEM and P12 formats, considering the possibility of future compatibility with more certificate formats, it is better to have users explicitly specify the certificate format. This can be seen in the example of curl which allows users to specify the certificate type using the --cert-type <type> (TLS) Tells curl what type the provided client certificate is using. PEM, DER, ENG and P12 are recognized types. option https://curl.se/docs/manpage.html.

@dragonsinth
Copy link
Member

If we want to support a new type of cert (p12), what would you call the old type?

Gotcha. So if this PR merged, we'd have code written to support PEM and P12 but nothing else.

Is password applicable to any other types of certs, or is this p12 specific?

Passwords may also be applicable to PEM format certificates, as described in rfc7468 on the Textual Encoding of PKCS #8 Encrypted Private Key Info (https://datatracker.ietf.org/doc/html/rfc7468#section-11).

Seems like the password option should also apply to PEM then?

Could the cert type be auto detected without an explicit command line option?

  • In addition to the currently supported PEM and P12 certificate formats, there are actually other formats such as DER and ENG.
  • While it is possible to implement auto-detection for just PEM and P12 formats, considering the possibility of future compatibility with more certificate formats, it is better to have users explicitly specify the certificate format. This can be seen in the example of curl which allows users to specify the certificate type using the --cert-type <type> (TLS) Tells curl what type the provided client certificate is using. PEM, DER, ENG and P12 are recognized types. option https://curl.se/docs/manpage.html.

I get it, but I worry about option shock. If we added logic to auto-detect the cert type, is there any reason to think we couldn't extend such auto-detection to DER and ENG in the future?

Copy link
Contributor

@jhump jhump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some feedback. Note that even if the feedback were addressed, it's still up to @dragonsinth to decide whether to accept the change or not.

grpcurl.go Outdated
certificate, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
var certificate tls.Certificate
var err error
if strings.EqualFold(clientCertType, "p12") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For one, we never check that the alternative is "pem". So we accept a garbage cert type, as long as the cert file is actually pem-encoded, which is non-intuitive.

Also (1) why EqualFold? We can just make it case-sensitive. (2) Why string comparisons everywhere? Maybe introduce a new type CertType int to represent this, instead of it being stringly typed.

grpcurl.go Outdated
@@ -527,12 +529,18 @@ func ClientTransportCredentials(insecureSkipVerify bool, cacertFile, clientCertF
// given properties. If cacertFile is blank, only standard trusted certs are used to
// verify the server certs. If clientCertFile is blank, the client will not use a client
// certificate. If clientCertFile is not blank then clientKeyFile must not be blank.
func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) (*tls.Config, error) {
func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile, clientCertType, clientPass string) (*tls.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a backwards-compatible change. This is exported API, and any other callers of this would be broken by the signature change. This kind of capability would have to be exposed via a new function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestions. I am currently trying to implement auto-detection to DER and ENG formats, and I will work on addressing the issues you pointed out during this process.

Comment on lines 321 to 373
if (*key == "") != (*cert == "") && !strings.EqualFold(*certType, "p12") {
fail(nil, "The -cert and -key arguments must be used together and both be present.")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is insufficient. With "p12", it still doesn't make sense to provide a -key argument but not -cert, since the actual kay+cert is loaded from the -cert argument. Also, if they do specify both as different files, it should probably be an error with "p12", since the key argument is erroneous (and the current implementation would ignore it).

Also, nowhere is validating the the -cert-type flag be "p12" or "pem" as indicated in the help for the flag. So this allows users to set a garbage type, in which case the meaning is unclear. It also misleadingly lets them enter "der" or "eng", but then expects the file to actually be "pem".

grpcurl.go Outdated
for _, block := range pemBlocks {
pemBytes = append(pemBytes, pem.EncodeToMemory(block)...)
}
certificate, err := tls.X509KeyPair(pemBytes, pemBytes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pemBytes, pemBytes seems strange... shouldn't there be separate entries for the key and cert?

Copy link
Author

@wangtiga wangtiga Nov 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file format PKCS12 will contain both the CERTIFICATE and PRIVATE KEY data, while the tls.X509KeyPair function internally identifies the CERTIFICATE and PRIVATE KEY from the PEM data.

@@ -2,3 +2,4 @@ dist/
.idea/
VERSION
.tmp/
*.swp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"swp" files are temporary files generated by the vim.

@dragonsinth
Copy link
Member

@wangtiga I pushed a commit demonstrating PEM decoding in a test; could you do something similar for p12? Generate a bogus key and cert with a password and write a simple test calling loadClientCertP12 to demonstrate that it works?

@wangtiga
Copy link
Author

wangtiga commented Nov 9, 2023

@wangtiga I pushed a commit demonstrating PEM decoding in a test; could you do something similar for p12? Generate a bogus key and cert with a password and write a simple test calling loadClientCertP12 to demonstrate that it works?

Okay, I'm currently trying auto-detection to DER and ENG formats. Once it's done, I will add unit tests for the relevant formats.

@wangtiga wangtiga force-pushed the dev-ws branch 2 times, most recently from 1730e03 to 3dad42b Compare November 11, 2023 14:41
@wangtiga
Copy link
Author

  1. Currently, -cacert-format and -cert-format support three types of certificate formats: PEM, DER, and PKCS12, while key only supports the PEM format.

  2. The -pass parameter is only allowed when using the -cert-format flag with PKCS12. However, in reality, PEM-formatted private keys can also have password protection, but this feature is not currently supported.

  3. While we have added the -cacert-format '' and -cert-format '' options for auto-detection, the scope of automatic detection is limited. If we were to identify file contents, the current code would not be able to differentiate between PKCS12 and DER format certificate files. Specifically, a DER format file such as tls/client.der would be incorrectly identified as PKCS12, and a PEM format file such as tls/client.guess would not be recognized.

  4. I have also added some unit tests for different certificate formats. I am unsure if this approach is appropriate, so I would appreciate any guidance or feedback.

".jceks": CertKeyFormatJCEKS,
".jks": CertKeyFormatJCEKS, // Only partially supported
".der": CertKeyFormatDER,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file looks to have a lot of content copied from square/certigo/lib/certs.go -- why is so much code needing to be copied instead of finding a way to use the lib directly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main purpose is to add the functionality of auto-detecting file formats. In the square/certigo/lib package, the formatForFile function is private, so a new function lib.GuessFormatForFile has been added. Additionally, there are some minor adjustments, such as introducing the CertificateKeyFormat type to identify file formats and reducing direct string comparisons operations.


func (f CertificateKeyFormat) IsNone() bool {
return f == CertKeyFormatNONE
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems misleading.. these are called CertificateKeyFormat, but as far as I can tell the only supported key format is PEM, whereas the cert formats are what vary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both JCEKS and PKCS12 formats include both key and certificate files.

The key file can also be in DER format, but its usage frequency is low, so it is currently not supported.

I am not using the JCEKS format at the moment and haven't tested it, so the code for it is included but not made available for public use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for p12 certificate format
3 participants