diff --git a/security/advancedtls/advancedtls.go b/security/advancedtls/advancedtls.go index 3c703232a2e9..242fc912f7dd 100644 --- a/security/advancedtls/advancedtls.go +++ b/security/advancedtls/advancedtls.go @@ -401,31 +401,15 @@ func (o *Options) serverConfig() (*tls.Config, error) { } } // Propagate identity-certificate-related fields in tls.Config. - switch { - case o.IdentityOptions.Certificates != nil: - config.Certificates = o.IdentityOptions.Certificates - case o.IdentityOptions.GetIdentityCertificatesForServer != nil: - config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - return buildGetCertificates(clientHello, o) - } - case o.IdentityOptions.IdentityProvider != nil: - o.IdentityOptions.GetIdentityCertificatesForServer = func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) { - km, err := o.IdentityOptions.IdentityProvider.KeyMaterial(context.Background()) - if err != nil { - return nil, err - } - var certChains []*tls.Certificate - for i := 0; i < len(km.Certs); i++ { - certChains = append(certChains, &km.Certs[i]) - } - return certChains, nil - } - config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - return buildGetCertificates(clientHello, o) - } - default: + if o.IdentityOptions.nonNilFieldCount() == 0 { return nil, fmt.Errorf("needs to specify at least one field in IdentityCertificateOptions") } + if o.IdentityOptions.Certificates != nil { + config.Certificates = o.IdentityOptions.Certificates + } + config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return buildGetCertificates(clientHello, o) + } return config, nil } diff --git a/security/advancedtls/internal/testutils/testutils.go b/security/advancedtls/internal/testutils/testutils.go index dd263662ec53..35ef38785f96 100644 --- a/security/advancedtls/internal/testutils/testutils.go +++ b/security/advancedtls/internal/testutils/testutils.go @@ -53,6 +53,20 @@ type CertStore struct { // identity. It has "localhost" as its common name, and is trusted by // ClientTrust1. ServerPeerLocalhost1 tls.Certificate + // ServerPeerECDSALocalhost1 is the ECDSA certificate sent by server to + // prove its identity. It has "localhost" as its SAN, and is trusted by + // ClientTrust1. + ServerPeerECDSALocalhost1 tls.Certificate + // ClientPeerECDSALocalhost1 is the ECDSA certificate sent by client to + // prove its identity. It has "localhost" as its SAN, and is trusted by + // ServerTrust1. + ClientPeerECDSALocalhost1 tls.Certificate + // ServerECDSACert1 is the ECDSA certificate sent by server to prove its + // identity. It is trusted by ClientTrust1. + ServerECDSACert1 tls.Certificate + // ClientECDSACert1 is the ECDSA certificate sent by client to prove its + // identity. It is trusted by ServerTrust1. + ClientECDSACert1 tls.Certificate // ClientTrust1 is the root certificate used on the client side. ClientTrust1 *x509.CertPool // ClientTrust2 is the root certificate used on the client side. @@ -107,6 +121,18 @@ func (cs *CertStore) LoadCerts() error { if cs.ServerPeerLocalhost1, err = tls.LoadX509KeyPair(testdata.Path("server_cert_localhost_1.pem"), testdata.Path("server_key_localhost_1.pem")); err != nil { return err } + if cs.ServerPeerECDSALocalhost1, err = tls.LoadX509KeyPair(testdata.Path("server_ecdsa_cert_localhost_1.pem"), testdata.Path("server_ecdsa_key_localhost_1.pem")); err != nil { + return err + } + if cs.ClientPeerECDSALocalhost1, err = tls.LoadX509KeyPair(testdata.Path("client_ecdsa_cert_localhost_1.pem"), testdata.Path("client_ecdsa_key_localhost_1.pem")); err != nil { + return err + } + if cs.ServerECDSACert1, err = tls.LoadX509KeyPair(testdata.Path("server_ecdsa_cert_1.pem"), testdata.Path("server_ecdsa_key_1.pem")); err != nil { + return err + } + if cs.ClientECDSACert1, err = tls.LoadX509KeyPair(testdata.Path("client_ecdsa_cert_1.pem"), testdata.Path("client_ecdsa_key_1.pem")); err != nil { + return err + } if cs.ClientTrust1, err = readTrustCert(testdata.Path("client_trust_cert_1.pem")); err != nil { return err } diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go new file mode 100644 index 000000000000..9b35db896da4 --- /dev/null +++ b/security/advancedtls/multi_cert_integration_test.go @@ -0,0 +1,1169 @@ +/* + * + * Copyright 2026 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package advancedtls + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/tls/certprovider" + pb "google.golang.org/grpc/examples/helloworld/helloworld" + "google.golang.org/grpc/security/advancedtls/internal/testutils" +) + +// staticIdentityProvider implements certprovider.Provider for in-memory testing. +type staticIdentityProvider struct { + certs []tls.Certificate +} + +func (p *staticIdentityProvider) KeyMaterial(context.Context) (*certprovider.KeyMaterial, error) { + return &certprovider.KeyMaterial{Certs: p.certs}, nil +} + +func (p *staticIdentityProvider) Close() {} + +func dialAndCall(ctx context.Context, addr string, authority string, creds credentials.TransportCredentials, shouldFail bool) (*grpc.ClientConn, error) { + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds), grpc.WithAuthority(authority), grpc.WithDisableServiceConfig()) + if err != nil { + return nil, err + } + client := pb.NewGreeterClient(conn) + _, err = client.SayHello(ctx, &pb.HelloRequest{Name: "test"}) + if want, got := shouldFail, err != nil; got != want { + conn.Close() + return nil, fmt.Errorf("want and got mismatch, want shouldFail=%v, got fail=%v, rpc error: %v", want, got, err) + } + return conn, nil +} + +// TestBuildGetCertificates_SignatureAlgorithmsExtension_TLS13 verifies that +// buildGetCertificates and ClientHelloInfo.SupportsCertificate evaluate the +// signature_algorithms TLS extension in TLS 1.3 to select the supported certificate +// (RSA vs ECDSA) without relying on TLS 1.2 CipherSuites. +func (s) TestBuildGetCertificates_SignatureAlgorithmsExtension_TLS13(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + opts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + } + + // 1. ClientHello in TLS 1.3 offering only RSA signature algorithms (RSASSA-PSS). + chiRSA := &tls.ClientHelloInfo{ + ServerName: "localhost", + SignatureSchemes: []tls.SignatureScheme{ + tls.PSSWithSHA256, + tls.PSSWithSHA384, + tls.PSSWithSHA512, + }, + SupportedCurves: []tls.CurveID{tls.X25519, tls.CurveP256}, + SupportedVersions: []uint16{tls.VersionTLS13}, + } + // Verify SupportsCertificate behavior directly for TLS 1.3 + if err := chiRSA.SupportsCertificate(&cs.ServerPeerLocalhost1); err != nil { + t.Fatalf("chiRSA.SupportsCertificate(RSA) failed unexpectedly in TLS 1.3: %v", err) + } + if err := chiRSA.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err == nil { + t.Fatalf("chiRSA.SupportsCertificate(ECDSA) succeeded unexpectedly for RSA-only signature schemes") + } + + selectedRSA, err := buildGetCertificates(chiRSA, opts) + if err != nil { + t.Fatalf("buildGetCertificates(chiRSA) returned error: %v", err) + } + if !bytes.Equal(selectedRSA.Certificate[0], cs.ServerPeerLocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiRSA) selected unexpected cert, want RSA cert") + } + + // 2. ClientHello in TLS 1.3 offering only ECDSA signature algorithms. + chiECDSA := &tls.ClientHelloInfo{ + ServerName: "localhost", + SignatureSchemes: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + tls.ECDSAWithP384AndSHA384, + }, + SupportedCurves: []tls.CurveID{tls.X25519, tls.CurveP256}, + SupportedVersions: []uint16{tls.VersionTLS13}, + } + // Verify SupportsCertificate behavior directly for TLS 1.3 + if err := chiECDSA.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err != nil { + t.Fatalf("chiECDSA.SupportsCertificate(ECDSA) failed unexpectedly in TLS 1.3: %v", err) + } + if err := chiECDSA.SupportsCertificate(&cs.ServerPeerLocalhost1); err == nil { + t.Fatalf("chiECDSA.SupportsCertificate(RSA) succeeded unexpectedly for ECDSA-only signature schemes") + } + + selectedECDSA, err := buildGetCertificates(chiECDSA, opts) + if err != nil { + t.Fatalf("buildGetCertificates(chiECDSA) returned error: %v", err) + } + if !bytes.Equal(selectedECDSA.Certificate[0], cs.ServerPeerECDSALocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiECDSA) selected unexpected cert, want ECDSA cert") + } + + // 3. ClientHello in TLS 1.3 offering only incompatible signature algorithms (e.g. Ed25519). + chiIncompatible := &tls.ClientHelloInfo{ + ServerName: "localhost", + SignatureSchemes: []tls.SignatureScheme{ + tls.Ed25519, + }, + SupportedCurves: []tls.CurveID{tls.X25519}, + SupportedVersions: []uint16{tls.VersionTLS13}, + } + if err := chiIncompatible.SupportsCertificate(&cs.ServerPeerLocalhost1); err == nil { + t.Fatalf("chiIncompatible.SupportsCertificate(RSA) succeeded unexpectedly for Ed25519-only signature schemes") + } + if err := chiIncompatible.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err == nil { + t.Fatalf("chiIncompatible.SupportsCertificate(ECDSA) succeeded unexpectedly for Ed25519-only signature schemes") + } + + // 4. Reversed order: verify buildGetCertificates returns RSA cert when RSA is requested even if ECDSA is first in list. + optsReversed := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + } + selectedRSAFromReversed, err := buildGetCertificates(chiRSA, optsReversed) + if err != nil { + t.Fatalf("buildGetCertificates(chiRSA) from reversed returned error: %v", err) + } + if !bytes.Equal(selectedRSAFromReversed.Certificate[0], cs.ServerPeerLocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiRSA) selected unexpected cert from reversed list, want RSA cert") + } + + // 5. Dual support with client preference: ClientHello offers [ECDSA, RSA] (prefers ECDSA over RSA). + // When server is configured with [RSA, ECDSA]: server's certificate slice order controls preference -> selects RSA. + // When server is configured with [ECDSA, RSA]: server's certificate slice order controls preference -> selects ECDSA. + chiDualECDSAPreferred := &tls.ClientHelloInfo{ + ServerName: "localhost", + SignatureSchemes: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + tls.PSSWithSHA256, + }, + SupportedCurves: []tls.CurveID{tls.X25519, tls.CurveP256}, + SupportedVersions: []uint16{tls.VersionTLS13}, + } + if err := chiDualECDSAPreferred.SupportsCertificate(&cs.ServerPeerLocalhost1); err != nil { + t.Fatalf("chiDualECDSAPreferred.SupportsCertificate(RSA) failed unexpectedly: %v", err) + } + if err := chiDualECDSAPreferred.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err != nil { + t.Fatalf("chiDualECDSAPreferred.SupportsCertificate(ECDSA) failed unexpectedly: %v", err) + } + + // Server configured with [RSA, ECDSA] -> selects RSA (server certificate order controls) + selectedFromRSAFirst, err := buildGetCertificates(chiDualECDSAPreferred, opts) + if err != nil { + t.Fatalf("buildGetCertificates(chiDualECDSAPreferred, [RSA, ECDSA]) failed: %v", err) + } + if !bytes.Equal(selectedFromRSAFirst.Certificate[0], cs.ServerPeerLocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiDualECDSAPreferred, [RSA, ECDSA]) selected unexpected cert, want RSA cert") + } + + // Server configured with [ECDSA, RSA] -> selects ECDSA (server certificate order controls) + selectedFromECDSAFirst, err := buildGetCertificates(chiDualECDSAPreferred, optsReversed) + if err != nil { + t.Fatalf("buildGetCertificates(chiDualECDSAPreferred, [ECDSA, RSA]) failed: %v", err) + } + if !bytes.Equal(selectedFromECDSAFirst.Certificate[0], cs.ServerPeerECDSALocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiDualECDSAPreferred, [ECDSA, RSA]) selected unexpected cert, want ECDSA cert") + } +} + +// TestBuildGetCertificates_SignatureAlgorithmsAndCipherSuites_TLS12 verifies that +// buildGetCertificates and ClientHelloInfo.SupportsCertificate correctly evaluate +// CipherSuites and SignatureSchemes in TLS 1.2 to select the matching certificate. +func (s) TestBuildGetCertificates_SignatureAlgorithmsAndCipherSuites_TLS12(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + opts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + } + + // 1. ClientHello in TLS 1.2 with RSA cipher suite and RSA signature schemes. + chiRSA := &tls.ClientHelloInfo{ + ServerName: "localhost", + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + SignatureSchemes: []tls.SignatureScheme{ + tls.PKCS1WithSHA256, + tls.PSSWithSHA256, + }, + SupportedCurves: []tls.CurveID{tls.X25519, tls.CurveP256}, + SupportedVersions: []uint16{tls.VersionTLS12}, + } + if err := chiRSA.SupportsCertificate(&cs.ServerPeerLocalhost1); err != nil { + t.Fatalf("chiRSA.SupportsCertificate(RSA) failed in TLS 1.2: %v", err) + } + if err := chiRSA.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err == nil { + t.Fatalf("chiRSA.SupportsCertificate(ECDSA) succeeded unexpectedly for RSA cipher suite in TLS 1.2") + } + + selectedRSA, err := buildGetCertificates(chiRSA, opts) + if err != nil { + t.Fatalf("buildGetCertificates(chiRSA) returned error: %v", err) + } + if !bytes.Equal(selectedRSA.Certificate[0], cs.ServerPeerLocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiRSA) selected unexpected cert, want RSA cert") + } + + // 2. ClientHello in TLS 1.2 with ECDSA cipher suite and ECDSA signature schemes. + chiECDSA := &tls.ClientHelloInfo{ + ServerName: "localhost", + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, + SignatureSchemes: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + }, + SupportedCurves: []tls.CurveID{tls.X25519, tls.CurveP256}, + SupportedVersions: []uint16{tls.VersionTLS12}, + } + if err := chiECDSA.SupportsCertificate(&cs.ServerPeerECDSALocalhost1); err != nil { + t.Fatalf("chiECDSA.SupportsCertificate(ECDSA) failed in TLS 1.2: %v", err) + } + if err := chiECDSA.SupportsCertificate(&cs.ServerPeerLocalhost1); err == nil { + t.Fatalf("chiECDSA.SupportsCertificate(RSA) succeeded unexpectedly for ECDSA cipher suite in TLS 1.2") + } + + selectedECDSA, err := buildGetCertificates(chiECDSA, opts) + if err != nil { + t.Fatalf("buildGetCertificates(chiECDSA) returned error: %v", err) + } + if !bytes.Equal(selectedECDSA.Certificate[0], cs.ServerPeerECDSALocalhost1.Certificate[0]) { + t.Errorf("buildGetCertificates(chiECDSA) selected unexpected cert, want ECDSA cert") + } +} + +// TestServerMultipleCerts_TLS13_Negotiation tests end-to-end gRPC communication +// where both server and client strictly enforce TLS 1.3 (MinTLSVersion: TLS 1.3, +// MaxTLSVersion: TLS 1.3) with no CipherSuites configured, and the server negotiates +// multiple certificates (RSA and ECDSA with identical SNI "localhost") using +// SupportsCertificate. +func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + serverOptions func() *Options + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with direct Certificates slice [RSA, ECDSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with reversed Certificates slice [ECDSA, RSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer returning [ECDSA, RSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) { + return []*tls.Certificate{&cs.ServerPeerECDSALocalhost1, &cs.ServerPeerLocalhost1}, nil + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer returning [RSA, ECDSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) { + return []*tls.Certificate{&cs.ServerPeerLocalhost1, &cs.ServerPeerECDSALocalhost1}, nil + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with IdentityProvider supplying [RSA, ECDSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + IdentityProvider: &staticIdentityProvider{ + certs: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with IdentityProvider supplying [ECDSA, RSA] in TLS 1.3", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + IdentityProvider: &staticIdentityProvider{ + certs: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + wantNegotiatedAlgo: x509.ECDSA, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverOpts := tc.serverOptions() + serverTLSCreds, err := NewServerCreds(serverOpts) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + // Client connects using strictly TLS 1.3 with no CipherSuites configured. + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + negotiatedAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + negotiatedAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, false) + if err != nil { + t.Fatalf("TLS 1.3 client call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_TLS12_Negotiation tests end-to-end gRPC communication +// in TLS 1.2 where the server is configured with both RSA and ECDSA certificates +// sharing the same SNI ("localhost"), and RSA vs ECDSA clients negotiate between +// them using CipherSuites and SignatureSchemes via SupportsCertificate. +func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + serverOptions func() *Options + clientCipherSuites []uint16 + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with direct Certificates slice [RSA, ECDSA], RSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with direct Certificates slice [RSA, ECDSA], ECDSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with reversed Certificates slice [ECDSA, RSA], RSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with reversed Certificates slice [ECDSA, RSA], ECDSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with [RSA, ECDSA], client supporting both [RSA, ECDSA] in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with [ECDSA, RSA], client supporting both [ECDSA, RSA] in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer returning [RSA, ECDSA], RSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) { + return []*tls.Certificate{&cs.ServerPeerLocalhost1, &cs.ServerPeerECDSALocalhost1}, nil + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer returning [RSA, ECDSA], ECDSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) { + return []*tls.Certificate{&cs.ServerPeerLocalhost1, &cs.ServerPeerECDSALocalhost1}, nil + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with IdentityProvider supplying [RSA, ECDSA], RSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + IdentityProvider: &staticIdentityProvider{ + certs: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with IdentityProvider supplying [RSA, ECDSA], ECDSA client in TLS 1.2", + serverOptions: func() *Options { + return &Options{ + IdentityOptions: IdentityCertificateOptions{ + IdentityProvider: &staticIdentityProvider{ + certs: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverOpts := tc.serverOptions() + serverTLSCreds, err := NewServerCreds(serverOpts) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + VerificationType: CertAndHostVerification, + CipherSuites: tc.clientCipherSuites, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + negotiatedAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + negotiatedAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, false) + if err != nil { + t.Fatalf("client call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_TLS13_MutualTLS tests mTLS end-to-end strictly in TLS 1.3 +// where the server has dual RSA and ECDSA certificates and requires client certs. +func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + serverCerts []tls.Certificate + clientCert tls.Certificate + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + clientCert: cs.ClientCert1, + wantNegotiatedAlgo: x509.RSA, + }, + { + // TODO(gtcooke94): Client default shows support for RSA and ECDSA - when + // knobs are added to advancedtls.go to control signature algorithms, + // modify this test + desc: "Server configured with [RSA, ECDSA], ECDSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + clientCert: cs.ClientPeerECDSALocalhost1, + wantNegotiatedAlgo: x509.RSA, + }, + { + // TODO(gtcooke94): Client default shows support for RSA and ECDSA - when + // knobs are added to advancedtls.go to control signature algorithms, + // modify this test + desc: "Server configured with reversed [ECDSA, RSA], RSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + clientCert: cs.ClientCert1, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with reversed [ECDSA, RSA], ECDSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + clientCert: cs.ClientPeerECDSALocalhost1, + wantNegotiatedAlgo: x509.ECDSA, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: tc.serverCerts, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: true, + VerificationType: CertVerification, + } + serverTLSCreds, err := NewServerCreds(serverOptions) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{tc.clientCert}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + negotiatedAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + negotiatedAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, false) + if err != nil { + t.Fatalf("mTLS TLS 1.3 call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated server certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_TLS12_MutualTLS tests mTLS end-to-end in TLS 1.2 +// where the server has both RSA and ECDSA certificate chains and client certificates of both types connect. +func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + serverCerts []tls.Certificate + clientCert tls.Certificate + clientCipherSuites []uint16 + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + clientCert: cs.ClientCert1, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with [RSA, ECDSA], ECDSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + clientCert: cs.ClientPeerECDSALocalhost1, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with reversed [ECDSA, RSA], RSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + clientCert: cs.ClientCert1, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with reversed [ECDSA, RSA], ECDSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + clientCert: cs.ClientPeerECDSALocalhost1, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: tc.serverCerts, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: true, + VerificationType: CertVerification, + } + serverTLSCreds, err := NewServerCreds(serverOptions) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{tc.clientCert}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + VerificationType: CertAndHostVerification, + CipherSuites: tc.clientCipherSuites, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + negotiatedAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + negotiatedAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, false) + if err != nil { + t.Fatalf("client call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated server certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_TLS13_DynamicSelection verifies that GetIdentityCertificatesForServer +// dynamically receives ClientHelloInfo with signature_algorithms in TLS 1.3 and uses +// SupportsCertificate to return the matching certificate chain. +func (s) TestServerMultipleCerts_TLS13_DynamicSelection(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + candidates []*tls.Certificate + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Dynamic selection preferring ECDSA [ECDSA, RSA] in TLS 1.3", + candidates: []*tls.Certificate{&cs.ServerPeerECDSALocalhost1, &cs.ServerPeerLocalhost1}, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Dynamic selection preferring RSA [RSA, ECDSA] in TLS 1.3", + candidates: []*tls.Certificate{&cs.ServerPeerLocalhost1, &cs.ServerPeerECDSALocalhost1}, + wantNegotiatedAlgo: x509.RSA, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + var capturedSigSchemes []tls.SignatureScheme + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(chi *tls.ClientHelloInfo) ([]*tls.Certificate, error) { + capturedSigSchemes = chi.SignatureSchemes + var supported []*tls.Certificate + for _, c := range tc.candidates { + if err := chi.SupportsCertificate(c); err == nil { + supported = append(supported, c) + } + } + if len(supported) == 0 { + return nil, fmt.Errorf("no supported certificate for client signature schemes: %v", chi.SignatureSchemes) + } + return supported, nil + }, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + RequireClientCert: false, + VerificationType: CertVerification, + } + serverTLSCreds, err := NewServerCreds(serverOptions) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + negotiatedAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + negotiatedAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, false) + if err != nil { + t.Fatalf("TLS 1.3 call failed: %v", err) + } + conn.Close() + + if len(capturedSigSchemes) == 0 { + t.Errorf("expected non-empty ClientHelloInfo.SignatureSchemes in TLS 1.3") + } + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_IncompatibleAlgorithm tests that when the server only has +// an RSA cert, an ECDSA-only client fails in TLS 1.2, and vice versa. +func (s) TestServerMultipleCerts_IncompatibleAlgorithm(t *testing.T) { + cs := &testutils.CertStore{} + if err := cs.LoadCerts(); err != nil { + t.Fatalf("cs.LoadCerts() failed, err: %v", err) + } + + testCases := []struct { + desc string + serverCerts []tls.Certificate + clientCipherSuites []uint16 + }{ + { + desc: "Server RSA-only, client ECDSA-only in TLS 1.2 fails", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1}, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + }, + { + desc: "Server ECDSA-only, client RSA-only in TLS 1.2 fails", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1}, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: tc.serverCerts, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ServerTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + RequireClientCert: false, + VerificationType: CertVerification, + } + serverTLSCreds, err := NewServerCreds(serverOptions) + if err != nil { + t.Fatalf("NewServerCreds failed: %v", err) + } + s := grpc.NewServer(grpc.Creds(serverTLSCreds)) + defer s.Stop() + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + addr := lis.Addr().String() + pb.RegisterGreeterServer(s, greeterServer{}) + go s.Serve(lis) + + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + VerificationType: CertAndHostVerification, + CipherSuites: tc.clientCipherSuites, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + } + clientCreds, err := NewClientCreds(clientOpts) + if err != nil { + t.Fatalf("NewClientCreds failed: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + conn, err := dialAndCall(ctx, addr, "localhost", clientCreds, true) + if err != nil { + t.Fatalf("dialAndCall error: %v", err) + } + if conn != nil { + conn.Close() + } + }) + } +} diff --git a/security/advancedtls/sni.go b/security/advancedtls/sni.go index 916e2723401c..ca56cc439e16 100644 --- a/security/advancedtls/sni.go +++ b/security/advancedtls/sni.go @@ -19,20 +19,38 @@ package advancedtls import ( + "context" "crypto/tls" "fmt" ) // buildGetCertificates returns the certificate that matches the SNI field -// for the given ClientHelloInfo, defaulting to the first element of o.GetCertificates. +// and signature schemes for the given ClientHelloInfo, defaulting to the first +// element of the configured certificates. func buildGetCertificates(clientHello *tls.ClientHelloInfo, o *Options) (*tls.Certificate, error) { - if o.IdentityOptions.GetIdentityCertificatesForServer == nil { + var certificates []*tls.Certificate + switch { + case o.IdentityOptions.GetIdentityCertificatesForServer != nil: + var err error + certificates, err = o.IdentityOptions.GetIdentityCertificatesForServer(clientHello) + if err != nil { + return nil, err + } + case len(o.IdentityOptions.Certificates) > 0: + for i := range o.IdentityOptions.Certificates { + certificates = append(certificates, &o.IdentityOptions.Certificates[i]) + } + case o.IdentityOptions.IdentityProvider != nil: + km, err := o.IdentityOptions.IdentityProvider.KeyMaterial(context.Background()) + if err != nil { + return nil, err + } + for i := range km.Certs { + certificates = append(certificates, &km.Certs[i]) + } + default: return nil, fmt.Errorf("function GetCertificates must be specified") } - certificates, err := o.IdentityOptions.GetIdentityCertificatesForServer(clientHello) - if err != nil { - return nil, err - } if len(certificates) == 0 { return nil, fmt.Errorf("no certificates configured") } @@ -40,7 +58,7 @@ func buildGetCertificates(clientHello *tls.ClientHelloInfo, o *Options) (*tls.Ce if len(certificates) == 1 { return certificates[0], nil } - // Choose the SNI certificate using SupportsCertificate. + // Choose the certificate using SupportsCertificate. for _, cert := range certificates { if err := clientHello.SupportsCertificate(cert); err == nil { return cert, nil diff --git a/security/advancedtls/testdata/README.md b/security/advancedtls/testdata/README.md index 1001459ee4b8..e23481627c24 100644 --- a/security/advancedtls/testdata/README.md +++ b/security/advancedtls/testdata/README.md @@ -1,48 +1,96 @@ -About This Directory -------------- -This testdata directory contains the certificates used in the tests of package advancedtls. +# AdvancedTLS Test Credentials -How to Generate Test Certificates Using OpenSSL -------------- +This testdata directory contains X.509 certificates and private keys used in the tests for the `advancedtls` package. -Supposing we are going to create a `subject_cert.pem` that is trusted by `ca_cert.pem`, here are the -commands we run: +## Credentials Overview -1. Generate the private key, `ca_key.pem`, and the cert `ca_cert.pem`, for the CA: +### Trust Roots / Certificate Authorities (CAs) +* **`client_trust_cert_1.pem` / `client_trust_key_1.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed Root CA) + * **Subject**: `C=US, ST=CA, L=SVL, O=Internet Widgits Pty Ltd` + * **Purpose**: Used on client side to verify server identity. Signs `server_cert_1.pem`, `server_cert_localhost_1.pem`, `server_ecdsa_cert_1.pem`, and `server_ecdsa_cert_localhost_1.pem`. +* **`client_trust_cert_2.pem` / `client_trust_key_2.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed Root CA) + * **Subject**: `C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=foo.bar.client2.trust.com` + * **Purpose**: Used on client side to verify server identity. Signs `server_cert_2.pem`. +* **`server_trust_cert_1.pem` / `server_trust_key_1.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed Root CA) + * **Subject**: `C=US, ST=VA, O=Internet Widgits Pty Ltd, CN=foo.bar.hoo.ca.com` + * **Purpose**: Used on server side to verify client identity in mTLS. Signs `client_cert_1.pem`, `client_ecdsa_cert_1.pem`, `client_ecdsa_cert_localhost_1.pem`, and `another_client_cert_1.pem`. +* **`server_trust_cert_2.pem` / `server_trust_key_2.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed Root CA) + * **Subject**: `C=US, ST=CA, O=Internet Widgits Pty Ltd, CN=foo.bar.server2.trust.com` + * **Purpose**: Used on server side to verify client identity in mTLS. Signs `client_cert_2.pem`. - ``` - $ openssl req -x509 -newkey rsa:4096 -keyout ca_key.pem -out ca_cert.pem -nodes -days $DURATION_DAYS - ``` +### Server Identity Certificates +* **`server_cert_1.pem` / `server_key_1.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=foo.bar.com` + * **Issuer**: `client_trust_cert_1.pem` +* **`server_cert_2.pem` / `server_key_2.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=foo.bar.server2.com` + * **Issuer**: `client_trust_cert_2.pem` +* **`server_cert_3.pem` / `server_key_3.pem`**: + * **Algorithm**: RSA 2048-bit (Self-signed) + * **Subject**: `CN=foo.bar.server3.com` + * **SANs**: `DNS:google.com`, `DNS:apple.com`, `DNS:amazon.com` +* **`server_cert_localhost_1.pem` / `server_key_localhost_1.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=localhost` + * **SAN**: `DNS:localhost` + * **Issuer**: `client_trust_cert_1.pem` +* **`server_ecdsa_cert_1.pem` / `server_ecdsa_key_1.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=foo.bar.com` + * **Issuer**: `client_trust_cert_1.pem` + * **Purpose**: Used for TLS 1.3 / TLS 1.2 multi-certificate algorithm negotiation and fallback tests. +* **`server_ecdsa_cert_localhost_1.pem` / `server_ecdsa_key_localhost_1.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=localhost` + * **SAN**: `DNS:localhost` + * **Issuer**: `client_trust_cert_1.pem` + * **Purpose**: Used for dual-certificate TLS 1.3 / TLS 1.2 SNI and negotiation integration tests connecting to localhost. -2. Generate a private key `subject_key.pem` for the subject: +### Client Identity Certificates +* **`client_cert_1.pem` / `client_key_1.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=foo.bar.hoo.com` + * **Issuer**: `server_trust_cert_1.pem` +* **`client_cert_2.pem` / `client_key_2.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=foo.bar.client2.com` + * **Issuer**: `server_trust_cert_2.pem` +* **`another_client_cert_1.pem` / `another_client_key_1.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=foo.bar.another.client.com` + * **Issuer**: `server_trust_cert_1.pem` +* **`client_ecdsa_cert_1.pem` / `client_ecdsa_key_1.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=foo.bar.hoo.com` + * **Issuer**: `server_trust_cert_1.pem` + * **Purpose**: Used for mutual TLS (mTLS) with ECDSA client authentication. +* **`client_ecdsa_cert_localhost_1.pem` / `client_ecdsa_key_localhost_1.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=localhost` + * **SAN**: `DNS:localhost` + * **Issuer**: `server_trust_cert_1.pem` + * **Purpose**: Used for mutual TLS (mTLS) with ECDSA client authentication connecting to localhost. - ``` - $ openssl genrsa -out subject_key.pem 4096 - ``` +### Certificate Revocation List (CRL) Testdata +* **`crl/`**: + * Contains CA certificates, client/server certificates, and CRL files (both empty and revoked) used for certificate revocation testing in `advancedtls`. -3. Generate a CSR `csr.pem` using `subject_key.pem`: +## Certificate Generation - ``` - $ openssl req -new -key subject_key.pem -out csr.pem - ``` - For some cases, we might want to add some extra SAN fields in `subject_cert.pem`. - In those cases, we can create a configuration file(for example, localhost-openssl.cnf), and do the following: - ``` - $ openssl req -new -key subject_key.pem -out csr.pem -config $CONFIG_FILE_NAME - ``` +All certificates and keys in this directory can be generated using the `create.sh` script: -4. Use `ca_key.pem` and `ca_cert.pem` to sign `csr.pem`, and get a certificate, `subject_cert.pem`, for the subject: +```bash +./create.sh +``` - This step requires some additional configuration steps and please check out [this answer from StackOverflow](https://stackoverflow.com/a/21340898) for more. +To generate CRL test certificates, run `provider_create.sh` in the `crl/` subdirectory: - ``` - $ openssl ca -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out subject_cert.pem -in csr.pem -keyfile ca_key.pem -cert ca_cert.pem - ``` - Please see an example configuration template at `openssl-ca.cnf`. -5. Verify the `subject_cert.pem` is trusted by `ca_cert.pem`: - - - ``` - $ openssl verify -verbose -CAfile ca_cert.pem subject_cert.pem - - ``` +```bash +cd crl && ./provider_create.sh +``` diff --git a/security/advancedtls/testdata/client_ecdsa_cert_1.pem b/security/advancedtls/testdata/client_ecdsa_cert_1.pem new file mode 100644 index 000000000000..81ab354d0b23 --- /dev/null +++ b/security/advancedtls/testdata/client_ecdsa_cert_1.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDozCCAYugAwIBAgIBBDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCVkExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEb +MBkGA1UEAwwSZm9vLmJhci5ob28uY2EuY29tMB4XDTI2MDgxMzE2NTc0NVoXDTM2 +MDgxMDE2NTc0NVowVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMSEwHwYDVQQK +DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMMD2Zvby5iYXIuaG9v +LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHBTgsMe7up9arIlzdcB6zPC +DuycVJ3kFIOXz7+2g88kY+D74RDiO1ys3neJOxOdQ/03bqhDlvHGvJcShDtWWO6j +QjBAMB0GA1UdDgQWBBQXTw2YLitRwuRZKGtPnK4pDFuvkDAfBgNVHSMEGDAWgBS0 +GQgc/BAjxTCGIrzLsV+t6npd8TANBgkqhkiG9w0BAQsFAAOCAgEABUoOJfo4tsRS +89kNvNiGWe1QE8ux65Zt5lTOIXtTWMB5lmIR4Eg+3Q1Q33y3lyTTGeXvHToDECcm +0LoUdeZSVrCH8RNa6IOwyL3GW88A9VJ17hm6Re/4KyxRmU9XEyZoRiooNXchQzR4 +jiMXuWsPqssW9jETPxJQBeiW/gVLzPd+jpL+jQF9HLlBKA7kwvTSbq+qU3O7HCdd +6OOEVSSnFVKuAcBncY664c7BEj3RwBcPKUvP++Gfc3teL+PqOO1pEg2+j5nmqci9 +UDKoEysvz7VsUi7fhOKLTUNjGhA0MZb9OKwT+HzF8GEmMI9uTZUtSwA3LkZd4q6I +Hff9R2DWjAOBxvsH9kQ2oirCTOkp4eR7pFX+tKXiFMH8ccqAPUNM1biG2vdJt0we +kJNTqepS2/Lt59ZjToKJ1gi9+7nl+p5tOkv9BYVUyp/00DpInuT2phAjl7pZEw1R +pQ+Cd97beq8wQlHarJnnAtj8ZcCBc41UP2b5OHb9PQhVRJ4k7GhVweWejIQ3PuYl +8B7aIjtxLa6ZpJ/I03sC2SicI1Ckwu699UEPrU1cyDZet8KGlWS8AgJdyEQoDh4n +Wz/CB0v4AwXn4zP9kpUDPDi3DiRheYvZgUGsS2Ob2OYsbTNoHSFjaA9WeVk5xvGL +QfuWLc0q+czW/p8ABdXBm/LdAbtcwUo= +-----END CERTIFICATE----- diff --git a/security/advancedtls/testdata/client_ecdsa_cert_localhost_1.pem b/security/advancedtls/testdata/client_ecdsa_cert_localhost_1.pem new file mode 100644 index 000000000000..0f7202a43b8d --- /dev/null +++ b/security/advancedtls/testdata/client_ecdsa_cert_localhost_1.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1zCCAb+gAwIBAgIBBjANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCVkExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEb +MBkGA1UEAwwSZm9vLmJhci5ob28uY2EuY29tMB4XDTI2MDgxMzE2NTkwM1oXDTM2 +MDgxMDE2NTkwM1owXTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAw +DgYDVQQHDAdDaGljYWdvMRUwEwYDVQQKDAxFeGFtcGxlLCBDby4xEjAQBgNVBAMM +CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMrbqEgIbo+x1Q00 +Xbu15A4uW/x7t6eOMarvXVHhXREzk2eJvZHIZ1SYrlBZfk7MP/vqP+EML5x53DMc +o1GaDGajcDBuMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBQGA1UdEQQNMAuCCWxv +Y2FsaG9zdDAdBgNVHQ4EFgQUJKsjqfvi+5eS7/B37So68uqjQ5kwHwYDVR0jBBgw +FoAUtBkIHPwQI8UwhiK8y7Ffrep6XfEwDQYJKoZIhvcNAQELBQADggIBACMVQwb5 +KXTuvQBeZDxiJlLM+L/AYqnRALn8osdrNyiMtPdrQJb67jyYt3naR5ATGQsScE3K +Lyj4bkRbyxeSNTNxB1ucdsNpIPB7NlVCh4gPf1h2Gj8M7Gi+H4+RLy5W4fyq+8IO +FZ3aIZF/9wDAt/tgcT17CKljsSEVuRWof3+2iX9nODzWT4yy6MJg7eah+SOSfhiz +/noy+33BxyR202ueSQbvaJ/DBTbDHW2n47cgYTlZNkpFNaWUDtYiEeWioUv7WQDi +ztAVhawyudKJXju+TY/rmbFQ536P21OAccQvO6BD23gHfKZU0CIsZTO8PxJ0NlIW +igJu73MaNphX4/DuiltFgQqZA8ZgQSPiiVdXoS1Pv2zq9utU9xESVy9YsKgAEX3v +nwXEuHD3EQfxiHP0v6LR0AY9FCEHmhye9kUZCTG0/H+s46hloPoHQB2rHWPfPOGX +Lp9Q79XpIdWnVgU/VWSKAtXhOPQrv101r2dgULvbZmqBJzs6zxT2KMDu7Oxwmcta +bEQVi/Tck1QE8HdI+gbV71/YUcbZnN7MrRoimjvIYlL3kbuDWm84r85OMoRzyEm2 +o8KB86mxsD09eL6aXP4inxpssqQxlvPTAmi7MeTT82ci2wiREDrwSmluo96nd6ev +jkNH+ole0DtSoe37S61wTeUwTg9r8Tk1no1M +-----END CERTIFICATE----- diff --git a/security/advancedtls/testdata/client_ecdsa_key_1.pem b/security/advancedtls/testdata/client_ecdsa_key_1.pem new file mode 100644 index 000000000000..c6ad071087d6 --- /dev/null +++ b/security/advancedtls/testdata/client_ecdsa_key_1.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKK7UVvgvvN1edk8bfKvdWO5bqMzRJQqC53ZSwMXVq59oAoGCCqGSM49 +AwEHoUQDQgAEcFOCwx7u6n1qsiXN1wHrM8IO7JxUneQUg5fPv7aDzyRj4PvhEOI7 +XKzed4k7E51D/TduqEOW8ca8lxKEO1ZY7g== +-----END EC PRIVATE KEY----- diff --git a/security/advancedtls/testdata/client_ecdsa_key_localhost_1.pem b/security/advancedtls/testdata/client_ecdsa_key_localhost_1.pem new file mode 100644 index 000000000000..b6b328eebc7d --- /dev/null +++ b/security/advancedtls/testdata/client_ecdsa_key_localhost_1.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIUYyaUoOlbyV6nvrxkt6JCov0PoPUzNBAT6sBW/3s3LoAoGCCqGSM49 +AwEHoUQDQgAEytuoSAhuj7HVDTRdu7XkDi5b/Hu3p44xqu9dUeFdETOTZ4m9kchn +VJiuUFl+Tsw/++o/4QwvnHncMxyjUZoMZg== +-----END EC PRIVATE KEY----- diff --git a/security/advancedtls/testdata/create.sh b/security/advancedtls/testdata/create.sh new file mode 100755 index 000000000000..2eb36d818e33 --- /dev/null +++ b/security/advancedtls/testdata/create.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +# Create the client root CA certs (used to verify server identity). +openssl req -x509 -newkey rsa:4096 \ + -keyout client_trust_key_1.pem \ + -out client_trust_cert_1.pem \ + -days 3650 \ + -nodes \ + -subj "/C=US/ST=CA/L=SVL/O=Internet Widgits Pty Ltd" \ + -sha256 + +openssl req -x509 -newkey rsa:4096 \ + -keyout client_trust_key_2.pem \ + -out client_trust_cert_2.pem \ + -days 3650 \ + -nodes \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.client2.trust.com" \ + -sha256 + +# Create the server root CA certs (used to verify client identity). +openssl req -x509 -newkey rsa:4096 \ + -keyout server_trust_key_1.pem \ + -out server_trust_cert_1.pem \ + -days 3650 \ + -nodes \ + -subj "/C=US/ST=VA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.ca.com" \ + -sha256 + +openssl req -x509 -newkey rsa:4096 \ + -keyout server_trust_key_2.pem \ + -out server_trust_cert_2.pem \ + -days 3650 \ + -nodes \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.server2.trust.com" \ + -sha256 + +# Generate RSA server certificates. +openssl genrsa -out server_key_1.pem 4096 +openssl req -new -key server_key_1.pem \ + -subj "/C=US/ST=CA/L=DUMMYCITY/O=Internet Widgits Pty Ltd/CN=foo.bar.com" \ + -out server_csr_1.pem +openssl x509 -req -in server_csr_1.pem \ + -CA client_trust_cert_1.pem -CAkey client_trust_key_1.pem \ + -days 3650 -set_serial 1000 -out server_cert_1.pem -sha256 + +openssl genrsa -out server_key_2.pem 4096 +openssl req -new -key server_key_2.pem \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.server2.com" \ + -out server_csr_2.pem +openssl x509 -req -in server_csr_2.pem \ + -CA client_trust_cert_2.pem -CAkey client_trust_key_2.pem \ + -days 3650 -set_serial 1000 -out server_cert_2.pem -sha256 + +openssl req -x509 -newkey rsa:2048 \ + -keyout server_key_3.pem \ + -out server_cert_3.pem \ + -days 3650 -nodes \ + -subj "/C=US/ST=CA/L=San Jose/O=End Point/OU=Infra/CN=foo.bar.server3.com/emailAddress=cindyxue@google.com" \ + -addext "subjectAltName = DNS:google.com, DNS:apple.com, DNS:amazon.com" \ + -sha256 + +openssl genrsa -out server_key_localhost_1.pem 4096 +openssl req -new -key server_key_localhost_1.pem \ + -subj "/C=US/ST=Illinois/L=Chicago/O=Example, Co./CN=localhost" \ + -config localhost-openssl.cnf -out server_csr_localhost_1.pem +openssl x509 -req -in server_csr_localhost_1.pem \ + -CA client_trust_cert_1.pem -CAkey client_trust_key_1.pem \ + -days 3650 -set_serial 1001 -out server_cert_localhost_1.pem \ + -extfile localhost-openssl.cnf -extensions v3_req -sha256 + +# Generate ECDSA server certificates. +openssl ecparam -genkey -name prime256v1 -out server_ecdsa_key_1.pem +openssl req -new -key server_ecdsa_key_1.pem \ + -subj "/C=US/ST=CA/L=DUMMYCITY/O=Internet Widgits Pty Ltd/CN=foo.bar.com" \ + -out server_ecdsa_csr_1.pem +openssl x509 -req -in server_ecdsa_csr_1.pem \ + -CA client_trust_cert_1.pem -CAkey client_trust_key_1.pem \ + -days 3650 -set_serial 1002 -out server_ecdsa_cert_1.pem -sha256 + +openssl ecparam -genkey -name prime256v1 -out server_ecdsa_key_localhost_1.pem +openssl req -new -key server_ecdsa_key_localhost_1.pem \ + -subj "/C=US/ST=Illinois/L=Chicago/O=Example, Co./CN=localhost" \ + -config localhost-openssl.cnf -out server_ecdsa_csr_localhost_1.pem +openssl x509 -req -in server_ecdsa_csr_localhost_1.pem \ + -CA client_trust_cert_1.pem -CAkey client_trust_key_1.pem \ + -days 3650 -set_serial 1003 -out server_ecdsa_cert_localhost_1.pem \ + -extfile localhost-openssl.cnf -extensions v3_req -sha256 + +# Generate RSA client certificates. +openssl genrsa -out client_key_1.pem 4096 +openssl req -new -key client_key_1.pem \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.com" \ + -out client_csr_1.pem +openssl x509 -req -in client_csr_1.pem \ + -CA server_trust_cert_1.pem -CAkey server_trust_key_1.pem \ + -days 3650 -set_serial 1000 -out client_cert_1.pem -sha256 + +openssl genrsa -out client_key_2.pem 4096 +openssl req -new -key client_key_2.pem \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.client2.com" \ + -out client_csr_2.pem +openssl x509 -req -in client_csr_2.pem \ + -CA server_trust_cert_2.pem -CAkey server_trust_key_2.pem \ + -days 3650 -set_serial 1000 -out client_cert_2.pem -sha256 + +openssl genrsa -out another_client_key_1.pem 4096 +openssl req -new -key another_client_key_1.pem \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.another.client.com" \ + -out another_client_csr_1.pem +openssl x509 -req -in another_client_csr_1.pem \ + -CA server_trust_cert_1.pem -CAkey server_trust_key_1.pem \ + -days 3650 -set_serial 1001 -out another_client_cert_1.pem -sha256 + +# Generate ECDSA client certificates. +openssl ecparam -genkey -name prime256v1 -out client_ecdsa_key_1.pem +openssl req -new -key client_ecdsa_key_1.pem \ + -subj "/C=US/ST=CA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.com" \ + -out client_ecdsa_csr_1.pem +openssl x509 -req -in client_ecdsa_csr_1.pem \ + -CA server_trust_cert_1.pem -CAkey server_trust_key_1.pem \ + -days 3650 -set_serial 1002 -out client_ecdsa_cert_1.pem -sha256 + +openssl ecparam -genkey -name prime256v1 -out client_ecdsa_key_localhost_1.pem +openssl req -new -key client_ecdsa_key_localhost_1.pem \ + -subj "/C=US/ST=Illinois/L=Chicago/O=Example, Co./CN=localhost" \ + -config localhost-openssl.cnf -out client_ecdsa_csr_localhost_1.pem +openssl x509 -req -in client_ecdsa_csr_localhost_1.pem \ + -CA server_trust_cert_1.pem -CAkey server_trust_key_1.pem \ + -days 3650 -set_serial 1003 -out client_ecdsa_cert_localhost_1.pem \ + -extfile localhost-openssl.cnf -extensions v3_req -sha256 + +# Verification +openssl verify -verbose -CAfile client_trust_cert_1.pem server_cert_1.pem +openssl verify -verbose -CAfile client_trust_cert_2.pem server_cert_2.pem +openssl verify -verbose -CAfile client_trust_cert_1.pem server_cert_localhost_1.pem +openssl verify -verbose -CAfile client_trust_cert_1.pem server_ecdsa_cert_1.pem +openssl verify -verbose -CAfile client_trust_cert_1.pem server_ecdsa_cert_localhost_1.pem +openssl verify -verbose -CAfile server_trust_cert_1.pem client_cert_1.pem +openssl verify -verbose -CAfile server_trust_cert_2.pem client_cert_2.pem +openssl verify -verbose -CAfile server_trust_cert_1.pem another_client_cert_1.pem +openssl verify -verbose -CAfile server_trust_cert_1.pem client_ecdsa_cert_1.pem +openssl verify -verbose -CAfile server_trust_cert_1.pem client_ecdsa_cert_localhost_1.pem + +# Cleanup CSR files. +rm -f *.csr *_csr*.pem diff --git a/security/advancedtls/testdata/server_ecdsa_cert_1.pem b/security/advancedtls/testdata/server_ecdsa_cert_1.pem new file mode 100644 index 000000000000..1ee3f16ed024 --- /dev/null +++ b/security/advancedtls/testdata/server_ecdsa_cert_1.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDpDCCAYygAwIBAgIBBDANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExDDAKBgNVBAcMA1NWTDEhMB8GA1UECgwYSW50ZXJuZXQgV2lk +Z2l0cyBQdHkgTHRkMB4XDTI2MDgxMzE2NTc0NVoXDTM2MDgxMDE2NTc0NVowZzEL +MAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlEVU1NWUNJVFkxITAf +BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLZm9vLmJh +ci5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASIookbNWS++L4PWeEH6dcW +YZEWdMqQRiW6dMCWt7M+03O1+SpjMCGZ7zhAQRqou8YtsBLS0h0MSm8ijnjetTdZ +o0IwQDAdBgNVHQ4EFgQUUV5ZP01mNoHAC1tpyQ8jDkZnMFkwHwYDVR0jBBgwFoAU +WqXasZnU5Q7mHpTq//xi4u0J8QYwDQYJKoZIhvcNAQELBQADggIBAFb9PA7kK3RQ +jIDXR4Azn0LTzuYjpanYNTiYGILAZyKXdqrsutpZLteV1RMGrGvviLjY3aT7+LNf +BOEokho41UgVCQ4dkYEG5RVmuoaufJswF0YRBG6A6617l5GSVWt2nuj/PIqqgUpi +Y7KM0WPVaNE8F94uY9hEz6KDWjfAKUJvd8c/QxGbu7YPmka4zho5VsTK7LRkYze0 +d4G3S8L8WhsXueD2f9n8aMazkxw7yh04isBU7w9Y/QHXq2DuFmM0A8IcChQAJbF4 +RMmi6gljbmh8pqZ8f8RXtSEYPQJU3JlwcH5D7D0S/vXLI3FB9+DaJ5L03mKH6xAx +/TYa7YWaybhZe7T/3Uor1WRVoxKFxYAhxI48w0/nU+t+YrTy6Al8qCTvGLiNc8XL +P9/73paDDQ83fd+rT0N+Is693jI9XRcVPJbtZ8uMx9u3IdJHzWGJl2dRDSnP4sl3 +gqlGvfbYU/1hiV0qELHb6Pp+xP5jizfphV15hX05lA1xqge9FH/i3/1hm1rWsWj9 +gvpOKTRkaaH1tHk/8QYE0JvZ+c1Y6RWY4o6k31yCyBT58eCI0CbE4h4C0PmJZaLj +1XBZGEdaUY8pIeLz+fI+aR+SI+nVlPATQfcvB9HKbcZSNu8CZA9ATHumkZ5cTRnw +C33ZFaBJTqsCSFbcCQJhOGzDVg/DhnVP +-----END CERTIFICATE----- diff --git a/security/advancedtls/testdata/server_ecdsa_cert_localhost_1.pem b/security/advancedtls/testdata/server_ecdsa_cert_localhost_1.pem new file mode 100644 index 000000000000..b0526fc842fa --- /dev/null +++ b/security/advancedtls/testdata/server_ecdsa_cert_localhost_1.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyDCCAbCgAwIBAgIBBjANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExDDAKBgNVBAcMA1NWTDEhMB8GA1UECgwYSW50ZXJuZXQgV2lk +Z2l0cyBQdHkgTHRkMB4XDTI2MDgxMzE2NTkwMVoXDTM2MDgxMDE2NTkwMVowXTEL +MAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdv +MRUwEwYDVQQKDAxFeGFtcGxlLCBDby4xEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABH71z7pIfsXtpA9xTNwffGxVkfOZ6VxZsR1c +8pxaOH/XKWV0nvVUQTFlKr1RAAAi3x6ZxWgj9yuBNpB6b5qIKpejcDBuMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAdBgNVHQ4E +FgQU43pC5dOXzkHhfxcEI00oG1IceWowHwYDVR0jBBgwFoAUWqXasZnU5Q7mHpTq +//xi4u0J8QYwDQYJKoZIhvcNAQELBQADggIBAC1CTHXo5W4G9lekkRLPaO8AbZ4u +IR/gmlE4keAWEVeZlNtqwUtlIGngRqH2aluRIjNEPgSdJo+6+mI2bnialaFVNlL0 +HRf8Jggk91+50QzbIKmNaO64Hjz5Nnr5zsNhhOr9KQJtZXy6FHpQG51dxRX+mbR+ +xmdrZW5DFY8mVxkgNIemzsRNF/g0zdYVGeh0xTskQTWCJIJNgLXauYFXT1u7IpHO +SQV44tILnB6anIN0yytVNkRCwRRRg4DZ8sJ3CW7p3hZZrT8uthELEul+u/wZ7R8U +eeBKHOdIIwKg5fILMkiA9NHmL4e8IhYP3JMPslu3LCPyj/TF14QbyJPENxczsNUv +SE2xXQJbijyr+6XlfTry5xLKG5i2K2sCNpsmb+1ETTMcwBTyaixblcJvHQKYRPZ6 +MnfwtwbsCVeYs71W3Vfv9QBPkl/qe4xcEOhnfsIJ+2LWnobrH9PjpZHecmzktD1k +HGQT50lP3vgszi4GGUotvcSVbz3wkODHOtXFhgrsSUr5mfK09+NANm8w7X6aXIkL +rZAqPYbrQ5wLl+mvwQbdBLqiqWmH9xIFAasma/nJMcWYH+M9y9yYnlquCheGqirk +U0zy8NRKpC1vCPTc8zK4NF099+YkUXjo/ClQymCNp7Q97xhK9P17Je1n0yUe9j2k +yskh5z3LJmOOhTnn +-----END CERTIFICATE----- diff --git a/security/advancedtls/testdata/server_ecdsa_key_1.pem b/security/advancedtls/testdata/server_ecdsa_key_1.pem new file mode 100644 index 000000000000..33ef875c254b --- /dev/null +++ b/security/advancedtls/testdata/server_ecdsa_key_1.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHumukPuEFqdVmzUQA6NIGawtz52iKlabW8qGeaH9+UpoAoGCCqGSM49 +AwEHoUQDQgAEiKKJGzVkvvi+D1nhB+nXFmGRFnTKkEYlunTAlrezPtNztfkqYzAh +me84QEEaqLvGLbAS0tIdDEpvIo543rU3WQ== +-----END EC PRIVATE KEY----- diff --git a/security/advancedtls/testdata/server_ecdsa_key_localhost_1.pem b/security/advancedtls/testdata/server_ecdsa_key_localhost_1.pem new file mode 100644 index 000000000000..9c61c7b5f3a8 --- /dev/null +++ b/security/advancedtls/testdata/server_ecdsa_key_localhost_1.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIClZ7x7YHcrne4XLOCu3bae0JqfMrfRnSrIEFBLxjc6moAoGCCqGSM49 +AwEHoUQDQgAEfvXPukh+xe2kD3FM3B98bFWR85npXFmxHVzynFo4f9cpZXSe9VRB +MWUqvVEAACLfHpnFaCP3K4E2kHpvmogqlw== +-----END EC PRIVATE KEY-----