From 95fbe59fd21065701089be727947e1a6e1832bce Mon Sep 17 00:00:00 2001 From: wangtiga Date: Sat, 11 Nov 2023 22:40:57 +0800 Subject: [PATCH] support p12 certificate format, add param -cert-type and -pass --- .gitignore | 1 + cmd/grpcurl/grpcurl.go | 39 +++++++++++++++++++++++++++++---- go.mod | 3 ++- go.sum | 7 ++---- grpcurl.go | 49 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 3a2f7da..1386dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist/ .idea/ VERSION .tmp/ +*.swp diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index 6ef517b..6b52117 100644 --- a/cmd/grpcurl/grpcurl.go +++ b/cmd/grpcurl/grpcurl.go @@ -66,7 +66,13 @@ var ( Ignored if -insecure is specified.`)) cert = flags.String("cert", "", prettify(` File containing client certificate (public key), to present to the - server. Not valid with -plaintext option. Must also provide -key option.`)) + server. Not valid with -plaintext option. Must also provide -key option + when use PEM certificate file.`)) + certTypeString = flags.String("cert-type", "", prettify(` + Client certificate file type. (PEM/P12)`)) + certType = grpcurl.CertTypePEM + pass = flags.String("pass", "", prettify(` + Pass phrase for the key`)) key = flags.String("key", "", prettify(` File containing client private key, to present to the server. Not valid with -plaintext option. Must also provide -cert option.`)) @@ -289,6 +295,17 @@ func main() { // default behavior is to use tls usetls := !*plaintext && !*usealts + // converto to CertificateFileType + if len(*certTypeString) == 0 { + certType = grpcurl.CertTypePEM // default PEM + } else if strings.EqualFold(*certTypeString, "PEM") { + certType = grpcurl.CertTypePEM + } else if strings.EqualFold(*certTypeString, "P12") { + certType = grpcurl.CertTypeP12 + } else { + fail(nil, "The -cert-type argument must be PEM or P12.") + } + // Do extra validation on arguments and figure out what user asked us to do. if *connectTimeout < 0 { fail(nil, "The -connect-timeout argument must not be negative.") @@ -314,9 +331,23 @@ func main() { if *key != "" && !usetls { fail(nil, "The -key argument can only be used with TLS.") } - if (*key == "") != (*cert == "") { - fail(nil, "The -cert and -key arguments must be used together and both be present.") + + switch certType { + case grpcurl.CertTypePEM: + if (*key == "") != (*cert == "") { + fail(nil, "The -cert and -key arguments must be used together and both be present when -cert-type is PEM.") + } + case grpcurl.CertTypeP12: + if *key != "" { + fail(nil, "The -key arguments must not be used when -cert-type is P12.") + } + if *cert == "" { + fail(nil, "The -cert arguments must be used when -cert-type is P12.") + } + default: + fail(nil, "Not support cert type %v.", certType) } + if *altsHandshakerServiceAddress != "" && !*usealts { fail(nil, "The -alts-handshaker-service argument must be used with the -alts argument.") } @@ -451,7 +482,7 @@ func main() { } creds = alts.NewClientCreds(clientOptions) } else if usetls { - tlsConf, err := grpcurl.ClientTLSConfig(*insecure, *cacert, *cert, *key) + tlsConf, err := grpcurl.ClientTLSConfigV2(*insecure, *cacert, *cert, *key, certType, *pass) if err != nil { fail(err, "Failed to create TLS config") } diff --git a/go.mod b/go.mod index 2b6dac4..2348612 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,8 @@ go 1.18 require ( github.com/golang/protobuf v1.5.3 github.com/jhump/protoreflect v1.15.3 - google.golang.org/grpc v1.57.1 + golang.org/x/crypto v0.14.0 + google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index f1bc045..9bd231e 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -37,20 +36,18 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/grpcurl.go b/grpcurl.go index 6010b3a..1ef06d2 100644 --- a/grpcurl.go +++ b/grpcurl.go @@ -12,6 +12,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" + "encoding/pem" "errors" "fmt" "io/ioutil" @@ -25,6 +26,7 @@ import ( "github.com/jhump/protoreflect/desc" "github.com/jhump/protoreflect/desc/protoprint" "github.com/jhump/protoreflect/dynamic" + "golang.org/x/crypto/pkcs12" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -523,16 +525,40 @@ func ClientTransportCredentials(insecureSkipVerify bool, cacertFile, clientCertF return credentials.NewTLS(tlsConf), nil } +type CertificateType int + +const ( + // The certificate file contains PEM encoded data + CertTypePEM CertificateType = 1 + // The certificate file contains PFX data describing PKCS#12. + CertTypeP12 CertificateType = 2 +) + // ClientTLSConfig builds transport-layer config for a gRPC client using the // 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) { + return ClientTLSConfigV2(insecureSkipVerify, cacertFile, clientCertFile, clientKeyFile, CertTypePEM, "") +} + +// ClientTLSConfigV2 builds transport-layer config for a gRPC client using the +// given properties. Support certificate file both PEM and P12. +func ClientTLSConfigV2(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string, clientCertType CertificateType, clientPass string) (*tls.Config, error) { var tlsConf tls.Config if clientCertFile != "" { // Load the client certificates from disk - certificate, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) + var certificate tls.Certificate + var err error + switch clientCertType { + case CertTypeP12: + certificate, err = loadClientCertP12(clientCertFile, clientPass) + case CertTypePEM: + certificate, err = tls.LoadX509KeyPair(clientCertFile, clientKeyFile) + default: + return nil, fmt.Errorf("not support client certificate type: %v", clientCertType) + } if err != nil { return nil, fmt.Errorf("could not load client key pair: %v", err) } @@ -560,6 +586,27 @@ func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, client return &tlsConf, nil } +func loadClientCertP12(pfxFile, pfxPassword string) (tls.Certificate, error) { + b, err := os.ReadFile(pfxFile) + if err != nil { + return tls.Certificate{}, fmt.Errorf("os.ReadFile err: %w", err) + } + pemBlocks, err := pkcs12.ToPEM(b, pfxPassword) + if err != nil { + return tls.Certificate{}, fmt.Errorf("pkcs12.ToPEM err: %w", err) + } + + var pemBytes []byte + for _, block := range pemBlocks { + pemBytes = append(pemBytes, pem.EncodeToMemory(block)...) + } + certificate, err := tls.X509KeyPair(pemBytes, pemBytes) + if err != nil { + return tls.Certificate{}, err + } + return certificate, nil +} + // ServerTransportCredentials builds transport credentials for a gRPC server using the // given properties. If cacertFile is blank, the server will not request client certs // unless requireClientCerts is true. When requireClientCerts is false and cacertFile is