From ca96b4ff159ee8ea0626f20870956a66f6fda437 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Thu, 13 Aug 2026 19:48:00 +0000 Subject: [PATCH 01/12] initial work on dual cert negotiation --- security/advancedtls/advancedtls.go | 3 + .../internal/testutils/testutils.go | 26 + .../multi_cert_integration_test.go | 507 ++++++++++++++++++ security/advancedtls/sni.go | 22 +- .../testdata/client_ecdsa_cert_1.pem | 22 + .../client_ecdsa_cert_localhost_1.pem | 23 + .../testdata/client_ecdsa_key_1.pem | 8 + .../testdata/client_ecdsa_key_localhost_1.pem | 8 + .../testdata/server_ecdsa_cert_1.pem | 22 + .../server_ecdsa_cert_localhost_1.pem | 23 + .../testdata/server_ecdsa_key_1.pem | 8 + .../testdata/server_ecdsa_key_localhost_1.pem | 8 + test/multi_cert_tls_test.go | 274 ++++++++++ testdata/x509/client_ecdsa_cert.pem | 23 + testdata/x509/client_ecdsa_key.pem | 8 + testdata/x509/create.sh | 42 ++ testdata/x509/server_ecdsa_cert.pem | 23 + testdata/x509/server_ecdsa_key.pem | 8 + 18 files changed, 1051 insertions(+), 7 deletions(-) create mode 100644 security/advancedtls/multi_cert_integration_test.go create mode 100644 security/advancedtls/testdata/client_ecdsa_cert_1.pem create mode 100644 security/advancedtls/testdata/client_ecdsa_cert_localhost_1.pem create mode 100644 security/advancedtls/testdata/client_ecdsa_key_1.pem create mode 100644 security/advancedtls/testdata/client_ecdsa_key_localhost_1.pem create mode 100644 security/advancedtls/testdata/server_ecdsa_cert_1.pem create mode 100644 security/advancedtls/testdata/server_ecdsa_cert_localhost_1.pem create mode 100644 security/advancedtls/testdata/server_ecdsa_key_1.pem create mode 100644 security/advancedtls/testdata/server_ecdsa_key_localhost_1.pem create mode 100644 test/multi_cert_tls_test.go create mode 100644 testdata/x509/client_ecdsa_cert.pem create mode 100644 testdata/x509/client_ecdsa_key.pem create mode 100644 testdata/x509/server_ecdsa_cert.pem create mode 100644 testdata/x509/server_ecdsa_key.pem diff --git a/security/advancedtls/advancedtls.go b/security/advancedtls/advancedtls.go index 3c703232a2e9..e6fcc00f470e 100644 --- a/security/advancedtls/advancedtls.go +++ b/security/advancedtls/advancedtls.go @@ -404,6 +404,9 @@ func (o *Options) serverConfig() (*tls.Config, error) { switch { case o.IdentityOptions.Certificates != nil: config.Certificates = o.IdentityOptions.Certificates + config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + return buildGetCertificates(clientHello, o) + } case o.IdentityOptions.GetIdentityCertificatesForServer != nil: config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { return buildGetCertificates(clientHello, o) 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..e913e819e728 --- /dev/null +++ b/security/advancedtls/multi_cert_integration_test.go @@ -0,0 +1,507 @@ +/* + * + * 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") + } +} + +// 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 + }{ + { + 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, + } + }, + }, + { + 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, + } + }, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer callback 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, + } + }, + }, + { + desc: "Server configured with IdentityProvider 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, + } + }, + }, + } + + 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 != x509.RSA && negotiatedAlgo != x509.ECDSA { + t.Errorf("negotiated certificate algorithm = %v, want RSA or ECDSA", negotiatedAlgo) + } + }) + } +} + +// TestServerMultipleCerts_TLS13_MutualTLS tests mTLS end-to-end strictly in TLS 1.3 +// where the server has both RSA and ECDSA certificate chains 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) + } + + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + 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) + + // 1. RSA client with RSA client certificate in TLS 1.3 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientCert1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = 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("RSA mTLS TLS 1.3 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != x509.RSA && serverCertAlgo != x509.ECDSA { + t.Errorf("serverCertAlgo = %v, want RSA or ECDSA", serverCertAlgo) + } + } + + // 2. ECDSA client with ECDSA client certificate in TLS 1.3 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = 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("ECDSA mTLS TLS 1.3 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != x509.RSA && serverCertAlgo != x509.ECDSA { + t.Errorf("serverCertAlgo = %v, want RSA or ECDSA", serverCertAlgo) + } + } +} + +// 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) + } + + var capturedSigSchemes []tls.SignatureScheme + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + GetIdentityCertificatesForServer: func(chi *tls.ClientHelloInfo) ([]*tls.Certificate, error) { + capturedSigSchemes = chi.SignatureSchemes + // Filter certificates using SupportsCertificate against the client's offered signature algorithms + candidates := []*tls.Certificate{&cs.ServerPeerECDSALocalhost1, &cs.ServerPeerLocalhost1} + var supported []*tls.Certificate + for _, c := range 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) + + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + } + 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") + } +} diff --git a/security/advancedtls/sni.go b/security/advancedtls/sni.go index 916e2723401c..6e93df97ea8e 100644 --- a/security/advancedtls/sni.go +++ b/security/advancedtls/sni.go @@ -24,15 +24,23 @@ import ( ) // 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 + if o.IdentityOptions.GetIdentityCertificatesForServer != nil { + var err error + certificates, err = o.IdentityOptions.GetIdentityCertificatesForServer(clientHello) + if err != nil { + return nil, err + } + } else if len(o.IdentityOptions.Certificates) > 0 { + for i := range o.IdentityOptions.Certificates { + certificates = append(certificates, &o.IdentityOptions.Certificates[i]) + } + } else { 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 +48,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/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/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----- diff --git a/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go new file mode 100644 index 000000000000..30599dffd715 --- /dev/null +++ b/test/multi_cert_tls_test.go @@ -0,0 +1,274 @@ +/* + * + * 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 test + +import ( + "context" + "crypto/tls" + "crypto/x509" + "net" + "os" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + testgrpc "google.golang.org/grpc/interop/grpc_testing" + testpb "google.golang.org/grpc/interop/grpc_testing" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/testdata" +) + +// loadTestCert loads a tls.Certificate from the specified testdata paths. +func loadTestCert(t *testing.T, certFile, keyFile string) tls.Certificate { + t.Helper() + cert, err := tls.LoadX509KeyPair(testdata.Path(certFile), testdata.Path(keyFile)) + if err != nil { + t.Fatalf("tls.LoadX509KeyPair(%q, %q) failed: %v", certFile, keyFile, err) + } + return cert +} + +// loadCertPool loads a certificate pool from the specified testdata path. +func loadCertPool(t *testing.T, caFile string) *x509.CertPool { + t.Helper() + data, err := os.ReadFile(testdata.Path(caFile)) + if err != nil { + t.Fatalf("os.ReadFile(%q) failed: %v", caFile, err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(data) { + t.Fatalf("AppendCertsFromPEM failed for %q", caFile) + } + return pool +} + +// TestServerMultipleCerts_TLS13_Negotiation tests end-to-end gRPC communication +// where both server and client strictly enforce TLS 1.3, the server possesses +// both RSA and ECDSA certificate chains with the same SNI (*.test.example.com), +// and certificate selection operates via SupportsCertificate and the signature_algorithms +// TLS extension without depending on TLS 1.2 CipherSuites. +func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { + rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") + caPool := loadCertPool(t, "x509/server_ca_cert.pem") + + testCases := []struct { + desc string + serverConfig *tls.Config + }{ + { + desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.3", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{rsaCert, ecdsaCert}, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }, + }, + { + desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.3", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{ecdsaCert, rsaCert}, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }, + }, + { + desc: "Server configured with GetCertificate callback evaluating SupportsCertificate in TLS 1.3", + serverConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + for _, c := range []*tls.Certificate{&ecdsaCert, &rsaCert} { + if err := chi.SupportsCertificate(c); err == nil { + return c, nil + } + } + return &rsaCert, nil + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverCreds := credentials.NewTLS(tc.serverConfig) + s := grpc.NewServer(grpc.Creds(serverCreds)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("Client EmptyCall failed in TLS 1.3: %v", err) + } + + tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(tlsInfo.State.PeerCertificates) == 0 { + t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) + } + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + if negotiatedAlgo != x509.RSA && negotiatedAlgo != x509.ECDSA { + t.Errorf("negotiated certificate algorithm = %v, want RSA or ECDSA", negotiatedAlgo) + } + }) + } +} + +// 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 certificates. +func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { + rsaServerCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + ecdsaServerCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") + serverCAPool := loadCertPool(t, "x509/server_ca_cert.pem") + clientCAPool := loadCertPool(t, "x509/client_ca_cert.pem") + + rsaClientCert := loadTestCert(t, "x509/client1_cert.pem", "x509/client1_key.pem") + ecdsaClientCert := loadTestCert(t, "x509/client_ecdsa_cert.pem", "x509/client_ecdsa_key.pem") + + var lastClientCertAlgo x509.PublicKeyAlgorithm + var lastTLSVersion uint16 + unaryInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if p, ok := peer.FromContext(ctx); ok { + if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { + lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + lastTLSVersion = tlsInfo.State.Version + } + } + return handler(ctx, req) + } + + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + ClientCAs: clientCAPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + // 1. RSA client in TLS 1.3 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("RSA client EmptyCall failed in TLS 1.3: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + if lastTLSVersion != tls.VersionTLS13 { + t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) + } + if lastClientCertAlgo != x509.RSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) + } + } + + // 2. ECDSA client in TLS 1.3 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{ecdsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("ECDSA client EmptyCall failed in TLS 1.3: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + if lastTLSVersion != tls.VersionTLS13 { + t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) + } + if lastClientCertAlgo != x509.ECDSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) + } + } +} diff --git a/testdata/x509/client_ecdsa_cert.pem b/testdata/x509/client_ecdsa_cert.pem new file mode 100644 index 000000000000..25e9ce9587a7 --- /dev/null +++ b/testdata/x509/client_ecdsa_cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzDCCAbSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFzAVBgNV +BAMMDnRlc3QtY2xpZW50X2NhMB4XDTI2MDgxMzE2NTcyNVoXDTM2MDgxMDE2NTcy +NVowUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTAL +BgNVBAoMBGdSUEMxGjAYBgNVBAMMEXRlc3QtY2xpZW50LWVjZHNhMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAE1XMBcBqmOZofcQlJ3dfX6AbzQYJBJ1VaAvk74BnK +2kL9biaIPQ6KWGvh+xu7PlJ3Rcm86bKGO/jlQ3Vla7aVPaN4MHYwDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUmjegSDXkos6mI605ku1FgzYkk7swDgYDVR0PAQH/BAQD +AgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFOr3a0MblN9W +9Opu7VsDn3crpoDCMA0GCSqGSIb3DQEBCwUAA4ICAQAmr2FUdY01sjFCMsCKkWmt +Bq7MAQbkdPNkKzp+fygRmMAdJYdL12ujQfS2jb16jVLTEywAm2FKyP/xdOFZI5Vu +yzRMILe25js5Xssy0aK/x197iawMdxZWSQBjadmwnzYN+6KWekP7RZ/S/jdlUANF +BFrkAAJcU9bb2XBLRUHivRoeZh82dG1gNMuosW8LWXzdMJ9xpXzJXS0YJ//OVbD5 +8raQu15jrSdEHLQyH5L2DBBhf5GX1SR+6D7TwiXa5v4yNt7VVEmupDtQ0Gm+AXdu +fsHBhA5HyCM1CIRn+OV8n2X97HP1w4PazI8cFGj6KQSJBRvitbzz8dFcDhRY/igO +BKW+OtuA8wcy2R8gVoAkuX7IKBgfVrtoHtCX3mS4FYgZf/ARyeloNoPq0V7vG8yK +Xwgg5CWDVKZ9Z/FFl+DRR2LQ9wR2e9fNbvwGNXx6nRyyy+PIU8RXTzKDmVAlf5/M +ymJ83NrsfZszhrlTYzUoUWBOfl8Gr652aITPw3/Iqozx77u+mBGQon5yxiIKf5vW +TX+2AWlwubuU1ZSb54DrggKr7wbISDZ0A8iP1CoCOmXfgAcLT7qxqSodGHBv4PgU +Y8yAW31pB4CLtnlj33B3a/Yy3JFKFHLWQodiomIqnxgAJI1RLbYILOWHTGuusrKf +IKg48tUgZNO6w30nc6s+Qg== +-----END CERTIFICATE----- diff --git a/testdata/x509/client_ecdsa_key.pem b/testdata/x509/client_ecdsa_key.pem new file mode 100644 index 000000000000..34ea4bbe2a1e --- /dev/null +++ b/testdata/x509/client_ecdsa_key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJSE6PQ5QiExKQwJNQzvt2tpYdi5VB1cfdQpoJ+mMovIoAoGCCqGSM49 +AwEHoUQDQgAE1XMBcBqmOZofcQlJ3dfX6AbzQYJBJ1VaAvk74BnK2kL9biaIPQ6K +WGvh+xu7PlJ3Rcm86bKGO/jlQ3Vla7aVPQ== +-----END EC PRIVATE KEY----- diff --git a/testdata/x509/create.sh b/testdata/x509/create.sh index 378bd10cf24f..f8bf8cbd6de3 100755 --- a/testdata/x509/create.sh +++ b/testdata/x509/create.sh @@ -147,5 +147,47 @@ openssl x509 -req \ -sha256 openssl verify -verbose -CAfile client_with_spiffe_cert.pem +# Generate ECDSA server cert. +openssl ecparam -genkey -name prime256v1 -out server_ecdsa_key.pem +openssl req -new \ + -key server_ecdsa_key.pem \ + -days 3650 \ + -out server_ecdsa_csr.pem \ + -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=test-server-ecdsa/ \ + -config ./openssl.cnf \ + -reqexts test_server +openssl x509 -req \ + -in server_ecdsa_csr.pem \ + -CAkey server_ca_key.pem \ + -CA server_ca_cert.pem \ + -days 3650 \ + -set_serial 1002 \ + -out server_ecdsa_cert.pem \ + -extfile ./openssl.cnf \ + -extensions test_server \ + -sha256 +openssl verify -verbose -CAfile server_ca_cert.pem server_ecdsa_cert.pem + +# Generate ECDSA client cert. +openssl ecparam -genkey -name prime256v1 -out client_ecdsa_key.pem +openssl req -new \ + -key client_ecdsa_key.pem \ + -days 3650 \ + -out client_ecdsa_csr.pem \ + -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=test-client-ecdsa/ \ + -config ./openssl.cnf \ + -reqexts test_client +openssl x509 -req \ + -in client_ecdsa_csr.pem \ + -CAkey client_ca_key.pem \ + -CA client_ca_cert.pem \ + -days 3650 \ + -set_serial 1002 \ + -out client_ecdsa_cert.pem \ + -extfile ./openssl.cnf \ + -extensions test_client \ + -sha256 +openssl verify -verbose -CAfile client_ca_cert.pem client_ecdsa_cert.pem + # Cleanup the CSRs. rm *_csr.pem diff --git a/testdata/x509/server_ecdsa_cert.pem b/testdata/x509/server_ecdsa_cert.pem new file mode 100644 index 000000000000..4002a12f8988 --- /dev/null +++ b/testdata/x509/server_ecdsa_cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0zCCAbugAwIBAgICA+owDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFzAVBgNV +BAMMDnRlc3Qtc2VydmVyX2NhMB4XDTI2MDgxMzE2NTcyM1oXDTM2MDgxMDE2NTcy +M1owUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTAL +BgNVBAoMBGdSUEMxGjAYBgNVBAMMEXRlc3Qtc2VydmVyLWVjZHNhMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEm/JuvNODUPllRhUGl9VnRjF9S+Suhr14fWdkPfw0 +YhoPVtnxR+6FA2PENmgjaXYDb0lkWnfIc6UzppUMVKwS+KN/MH0wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUh4bM24iV3qkQVWNEC0ZASRmTChswDgYDVR0PAQH/BAQD +AgOoMB0GA1UdEQQWMBSCEioudGVzdC5leGFtcGxlLmNvbTAfBgNVHSMEGDAWgBQl +88evytJrL7t5BGRbJUeMOW4jaTANBgkqhkiG9w0BAQsFAAOCAgEAu0FnaAY/lxYU +DfFWmKCd92UwOrKqyEP5LFj0TAUgZ0E3NH+0DZffs50ze1X27wjzRwgyX1PLQFb7 +VnctxP1LOU3KZX3lGAA7HXsYXnYyu96BV7r66NUWx4uOfgyFSZ3l2YqQkSpbR8xV +n7kz6z9nRageENrNrXkjt/4YCsBDa9jcZBzQZlzji0rOl/34VKIA6knq7VYvsM7T +7lXkBnihkaI8bXbtmGQtzI8+MaLmRSuKd/QABb2U0Sbp7RtOP5cIi/NhsIwI1ikA +s1fN3oFG8VZ11H7yY2J8XOaJZWYXZRx0r5D+jhWBRiq2VG4aeQyrUHVD7otsTxf/ +lJsQPauuQBbQsvcVXD5nl5KhBQ9cqB18vfAJL6B2RnFASLyoVqBTYMAzXg2bFB5b +SYATQBXbuVj4BpUGNtwSbf27cSu8XEVU64pUYBxBdCMQAKdv0yUwxdLG2NpeCuMa +lX/Bjh8Jjrf7yf7S2oGx4p4ooyxd3CpW7XgB9uUtItnNkkiGUXE1N2VPaZeZqUtj +pUDKDen28IwK6YLyGl6hxLXaDU3q/UpPgb0h83ar/1K0yYMitF7RJWS+OC9hs1ld +vZSeDG4iUjyiVTdpYU7TNoWvHzSmB2KeMBlyLfcA1lZm60DGkgtJ53FLPYDuwmTr +adoDgJgrEC7e+zOa3BfP0oGEQAqlrEA= +-----END CERTIFICATE----- diff --git a/testdata/x509/server_ecdsa_key.pem b/testdata/x509/server_ecdsa_key.pem new file mode 100644 index 000000000000..3d903a9e0499 --- /dev/null +++ b/testdata/x509/server_ecdsa_key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBMH+sEh/wd90sM3632flgeue6DJIQG1vK1WSIMHWJHuoAoGCCqGSM49 +AwEHoUQDQgAEm/JuvNODUPllRhUGl9VnRjF9S+Suhr14fWdkPfw0YhoPVtnxR+6F +A2PENmgjaXYDb0lkWnfIc6UzppUMVKwS+A== +-----END EC PRIVATE KEY----- From b710dbf371656564f73e703110ad3b6a18c684bf Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Fri, 14 Aug 2026 19:02:26 +0000 Subject: [PATCH 02/12] also tls1.2 --- .../multi_cert_integration_test.go | 429 ++++++++++++++++++ test/multi_cert_tls_test.go | 273 +++++++++++ 2 files changed, 702 insertions(+) diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go index e913e819e728..fd551fa5df33 100644 --- a/security/advancedtls/multi_cert_integration_test.go +++ b/security/advancedtls/multi_cert_integration_test.go @@ -159,6 +159,77 @@ func (s) TestBuildGetCertificates_SignatureAlgorithmsExtension_TLS13(t *testing. } } +// 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 @@ -308,6 +379,179 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } } +// 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 + }{ + { + desc: "Server configured with direct Certificates slice [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, + } + }, + }, + { + desc: "Server configured with reversed Certificates slice [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, + } + }, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer callback 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, + } + }, + }, + } + + 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) + + // 1. Connect with an RSA-only client in TLS 1.2. + { + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + VerificationType: CertAndHostVerification, + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + 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("RSA client call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != x509.RSA { + t.Errorf("RSA client negotiated certificate algorithm = %v, want %v (x509.RSA)", negotiatedAlgo, x509.RSA) + } + } + + // 2. Connect with an ECDSA-only client in TLS 1.2. + { + var negotiatedAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + VerificationType: CertAndHostVerification, + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + 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("ECDSA client call failed: %v", err) + } + conn.Close() + + if negotiatedAlgo != x509.ECDSA { + t.Errorf("ECDSA client negotiated certificate algorithm = %v, want %v (x509.ECDSA)", negotiatedAlgo, x509.ECDSA) + } + } + }) + } +} + // TestServerMultipleCerts_TLS13_MutualTLS tests mTLS end-to-end strictly in TLS 1.3 // where the server has both RSA and ECDSA certificate chains and requires client certs. func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { @@ -429,6 +673,129 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { } } +// 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) + } + + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + }, + 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) + + // 1. RSA client with RSA client certificate in TLS 1.2 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientCert1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + VerificationType: CertAndHostVerification, + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = 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("RSA mTLS TLS 1.2 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != x509.RSA { + t.Errorf("serverCertAlgo = %v, want %v (x509.RSA)", serverCertAlgo, x509.RSA) + } + } + + // 2. ECDSA client with ECDSA client certificate in TLS 1.2 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS12, + MaxTLSVersion: tls.VersionTLS12, + VerificationType: CertAndHostVerification, + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = 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("ECDSA mTLS TLS 1.2 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != x509.ECDSA { + t.Errorf("serverCertAlgo = %v, want %v (x509.ECDSA)", serverCertAlgo, x509.ECDSA) + } + } +} + // 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. @@ -505,3 +872,65 @@ func (s) TestServerMultipleCerts_TLS13_DynamicSelection(t *testing.T) { t.Errorf("expected non-empty ClientHelloInfo.SignatureSchemes in TLS 1.3") } } + +// 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) + } + + // Server with ONLY RSA certificate + serverOptions := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ServerPeerLocalhost1}, + }, + 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) + + // ECDSA-only client connecting to RSA-only server must fail + clientOpts := &Options{ + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + VerificationType: CertAndHostVerification, + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + 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/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go index 30599dffd715..d05c13c8ddd1 100644 --- a/test/multi_cert_tls_test.go +++ b/test/multi_cert_tls_test.go @@ -157,6 +157,123 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } } +// TestServerMultipleCerts_TLS12_Negotiation tests end-to-end gRPC communication +// in TLS 1.2 where the server has both RSA and ECDSA certificate chains, and clients +// negotiate between them using CipherSuites and SignatureSchemes via SupportsCertificate. +func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { + rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") + caPool := loadCertPool(t, "x509/server_ca_cert.pem") + + testCases := []struct { + desc string + serverConfig *tls.Config + }{ + { + desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.2", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{rsaCert, ecdsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + }, + { + desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.2", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{ecdsaCert, rsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverCreds := credentials.NewTLS(tc.serverConfig) + s := grpc.NewServer(grpc.Creds(serverCreds)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + // 1. RSA-only client in TLS 1.2 + { + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("RSA client EmptyCall failed: %v", err) + } + + tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(tlsInfo.State.PeerCertificates) == 0 { + t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) + } + negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + if negotiatedAlgo != x509.RSA { + t.Errorf("RSA client negotiated certificate algorithm = %v, want %v (x509.RSA)", negotiatedAlgo, x509.RSA) + } + } + + // 2. ECDSA-only client in TLS 1.2 + { + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("ECDSA client EmptyCall failed: %v", err) + } + + tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(tlsInfo.State.PeerCertificates) == 0 { + t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) + } + negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + if negotiatedAlgo != x509.ECDSA { + t.Errorf("ECDSA client negotiated certificate algorithm = %v, want %v (x509.ECDSA)", negotiatedAlgo, x509.ECDSA) + } + } + }) + } +} + // 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 certificates. func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { @@ -272,3 +389,159 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { } } } + +// TestServerMultipleCerts_TLS12_MutualTLS tests mTLS end-to-end in TLS 1.2 +// where the server has dual RSA and ECDSA certificates and requires client certs. +func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { + rsaServerCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + ecdsaServerCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") + serverCAPool := loadCertPool(t, "x509/server_ca_cert.pem") + clientCAPool := loadCertPool(t, "x509/client_ca_cert.pem") + + rsaClientCert := loadTestCert(t, "x509/client1_cert.pem", "x509/client1_key.pem") + ecdsaClientCert := loadTestCert(t, "x509/client_ecdsa_cert.pem", "x509/client_ecdsa_key.pem") + + var lastClientCertAlgo x509.PublicKeyAlgorithm + unaryInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if p, ok := peer.FromContext(ctx); ok { + if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { + lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + } + } + return handler(ctx, req) + } + + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + ClientCAs: clientCAPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + // 1. RSA client in TLS 1.2 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("RSA client EmptyCall failed in TLS 1.2: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != x509.RSA { + t.Errorf("server certificate algorithm = %v, want %v (x509.RSA)", serverAlgo, x509.RSA) + } + if lastClientCertAlgo != x509.RSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) + } + } + + // 2. ECDSA client in TLS 1.2 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{ecdsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("ECDSA client EmptyCall failed in TLS 1.2: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != x509.ECDSA { + t.Errorf("server certificate algorithm = %v, want %v (x509.ECDSA)", serverAlgo, x509.ECDSA) + } + if lastClientCertAlgo != x509.ECDSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) + } + } +} + +// TestServerMultipleCerts_IncompatibleAlgorithm tests that when the server possesses +// only RSA certificates, a client offering only ECDSA cipher suites fails to handshake in TLS 1.2. +func (s) TestServerMultipleCerts_IncompatibleAlgorithm(t *testing.T) { + rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + caPool := loadCertPool(t, "x509/server_ca_cert.pem") + + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + s := grpc.NewServer(grpc.Creds(serverCreds)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil { + t.Fatalf("EmptyCall succeeded unexpectedly when client offered only incompatible cipher suites") + } +} From 006906445be7c7181139f705a9d60d384be33d6a Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Mon, 17 Aug 2026 20:47:21 +0000 Subject: [PATCH 03/12] cleanup --- security/advancedtls/advancedtls.go | 33 ++++++----------------------- security/advancedtls/sni.go | 16 +++++++++++--- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/security/advancedtls/advancedtls.go b/security/advancedtls/advancedtls.go index e6fcc00f470e..242fc912f7dd 100644 --- a/security/advancedtls/advancedtls.go +++ b/security/advancedtls/advancedtls.go @@ -401,34 +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 - config.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - return buildGetCertificates(clientHello, o) - } - 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/sni.go b/security/advancedtls/sni.go index 6e93df97ea8e..ca56cc439e16 100644 --- a/security/advancedtls/sni.go +++ b/security/advancedtls/sni.go @@ -19,6 +19,7 @@ package advancedtls import ( + "context" "crypto/tls" "fmt" ) @@ -28,17 +29,26 @@ import ( // element of the configured certificates. func buildGetCertificates(clientHello *tls.ClientHelloInfo, o *Options) (*tls.Certificate, error) { var certificates []*tls.Certificate - if o.IdentityOptions.GetIdentityCertificatesForServer != nil { + switch { + case o.IdentityOptions.GetIdentityCertificatesForServer != nil: var err error certificates, err = o.IdentityOptions.GetIdentityCertificatesForServer(clientHello) if err != nil { return nil, err } - } else if len(o.IdentityOptions.Certificates) > 0 { + case len(o.IdentityOptions.Certificates) > 0: for i := range o.IdentityOptions.Certificates { certificates = append(certificates, &o.IdentityOptions.Certificates[i]) } - } else { + 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") } if len(certificates) == 0 { From 5e278887e5df1d4d54ef413390b1994b215285fc Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Mon, 17 Aug 2026 20:51:37 +0000 Subject: [PATCH 04/12] fix revive unused-parameter in tests and remove duplicate lines --- test/multi_cert_tls_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go index d05c13c8ddd1..f24a4d1600ff 100644 --- a/test/multi_cert_tls_test.go +++ b/test/multi_cert_tls_test.go @@ -287,7 +287,7 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { var lastClientCertAlgo x509.PublicKeyAlgorithm var lastTLSVersion uint16 - unaryInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + unaryInterceptor := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { if p, ok := peer.FromContext(ctx); ok { if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm @@ -402,7 +402,7 @@ func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { ecdsaClientCert := loadTestCert(t, "x509/client_ecdsa_cert.pem", "x509/client_ecdsa_key.pem") var lastClientCertAlgo x509.PublicKeyAlgorithm - unaryInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + unaryInterceptor := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { if p, ok := peer.FromContext(ctx); ok { if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm From d4dcf47296435e22599796aa501104c682f1ba01 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Tue, 18 Aug 2026 13:55:22 +0000 Subject: [PATCH 05/12] enforce exact wantNegotiatedAlgorithm in multi-cert table tests --- .../multi_cert_integration_test.go | 284 +++++++++++------- test/multi_cert_tls_test.go | 217 +++++++------ 2 files changed, 307 insertions(+), 194 deletions(-) diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go index fd551fa5df33..7ac73606f3ad 100644 --- a/security/advancedtls/multi_cert_integration_test.go +++ b/security/advancedtls/multi_cert_integration_test.go @@ -242,8 +242,9 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } testCases := []struct { - desc string - serverOptions func() *Options + desc string + serverOptions func() *Options + wantNegotiatedAlgorithm x509.PublicKeyAlgorithm }{ { desc: "Server configured with direct Certificates slice [RSA, ECDSA] in TLS 1.3", @@ -261,6 +262,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, + wantNegotiatedAlgorithm: x509.RSA, }, { desc: "Server configured with reversed Certificates slice [ECDSA, RSA] in TLS 1.3", @@ -278,9 +280,30 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, + wantNegotiatedAlgorithm: x509.ECDSA, }, { - desc: "Server configured with GetIdentityCertificatesForServer callback in TLS 1.3", + 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, + } + }, + wantNegotiatedAlgorithm: x509.ECDSA, + }, + { + desc: "Server configured with GetIdentityCertificatesForServer returning [RSA, ECDSA] in TLS 1.3", serverOptions: func() *Options { return &Options{ IdentityOptions: IdentityCertificateOptions{ @@ -297,9 +320,10 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, + wantNegotiatedAlgorithm: x509.RSA, }, { - desc: "Server configured with IdentityProvider in TLS 1.3", + desc: "Server configured with IdentityProvider supplying [RSA, ECDSA] in TLS 1.3", serverOptions: func() *Options { return &Options{ IdentityOptions: IdentityCertificateOptions{ @@ -316,6 +340,27 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, + wantNegotiatedAlgorithm: 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, + } + }, + wantNegotiatedAlgorithm: x509.ECDSA, }, } @@ -372,8 +417,8 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } conn.Close() - if negotiatedAlgo != x509.RSA && negotiatedAlgo != x509.ECDSA { - t.Errorf("negotiated certificate algorithm = %v, want RSA or ECDSA", negotiatedAlgo) + if negotiatedAlgo != tc.wantNegotiatedAlgorithm { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgorithm) } }) } @@ -553,123 +598,144 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { } // TestServerMultipleCerts_TLS13_MutualTLS tests mTLS end-to-end strictly in TLS 1.3 -// where the server has both RSA and ECDSA certificate chains and requires client certs. +// 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) } - serverOptions := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + testCases := []struct { + desc string + serverCerts []tls.Certificate + wantServerCertAlgorithm x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with [RSA, ECDSA] in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + wantServerCertAlgorithm: x509.RSA, }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ServerTrust1, + { + desc: "Server configured with reversed [ECDSA, RSA] in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, + wantServerCertAlgorithm: x509.ECDSA, }, - 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) + 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() - // 1. RSA client with RSA client certificate in TLS 1.3 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientCert1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS13, - MaxTLSVersion: tls.VersionTLS13, - VerificationType: CertAndHostVerification, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err - } - serverCertAlgo = cert.PublicKeyAlgorithm + 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) + + // 1. RSA client with RSA client certificate in TLS 1.3 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientCert1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, } - 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("RSA mTLS TLS 1.3 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != x509.RSA && serverCertAlgo != x509.ECDSA { - t.Errorf("serverCertAlgo = %v, want RSA or ECDSA", serverCertAlgo) - } - } + 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("RSA mTLS TLS 1.3 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != tc.wantServerCertAlgorithm { + t.Errorf("serverCertAlgo = %v, want %v", serverCertAlgo, tc.wantServerCertAlgorithm) + } + } - // 2. ECDSA client with ECDSA client certificate in TLS 1.3 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS13, - MaxTLSVersion: tls.VersionTLS13, - VerificationType: CertAndHostVerification, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err - } - serverCertAlgo = cert.PublicKeyAlgorithm + // 2. ECDSA client with ECDSA client certificate in TLS 1.3 + { + var serverCertAlgo x509.PublicKeyAlgorithm + clientOpts := &Options{ + IdentityOptions: IdentityCertificateOptions{ + Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, + }, + RootOptions: RootCertificateOptions{ + RootCertificates: cs.ClientTrust1, + }, + MinTLSVersion: tls.VersionTLS13, + MaxTLSVersion: tls.VersionTLS13, + VerificationType: CertAndHostVerification, + AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { + if params.Leaf != nil { + serverCertAlgo = params.Leaf.PublicKeyAlgorithm + } else if len(params.RawCerts) > 0 { + cert, err := x509.ParseCertificate(params.RawCerts[0]) + if err != nil { + return nil, err + } + serverCertAlgo = cert.PublicKeyAlgorithm + } + return &PostHandshakeVerificationResults{}, nil + }, } - 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("ECDSA mTLS TLS 1.3 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != x509.RSA && serverCertAlgo != x509.ECDSA { - t.Errorf("serverCertAlgo = %v, want RSA or ECDSA", serverCertAlgo) - } + 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("ECDSA mTLS TLS 1.3 call failed: %v", err) + } + conn.Close() + if serverCertAlgo != tc.wantServerCertAlgorithm { + t.Errorf("serverCertAlgo = %v, want %v", serverCertAlgo, tc.wantServerCertAlgorithm) + } + } + }) } } diff --git a/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go index f24a4d1600ff..3ef2291332c3 100644 --- a/test/multi_cert_tls_test.go +++ b/test/multi_cert_tls_test.go @@ -69,8 +69,9 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { caPool := loadCertPool(t, "x509/server_ca_cert.pem") testCases := []struct { - desc string - serverConfig *tls.Config + desc string + serverConfig *tls.Config + wantNegotiatedAlgorithm x509.PublicKeyAlgorithm }{ { desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.3", @@ -79,6 +80,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, }, + wantNegotiatedAlgorithm: x509.RSA, }, { desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.3", @@ -87,9 +89,10 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, }, + wantNegotiatedAlgorithm: x509.ECDSA, }, { - desc: "Server configured with GetCertificate callback evaluating SupportsCertificate in TLS 1.3", + desc: "Server configured with GetCertificate callback preferring ECDSA in TLS 1.3", serverConfig: &tls.Config{ MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, @@ -102,6 +105,23 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { return &rsaCert, nil }, }, + wantNegotiatedAlgorithm: x509.ECDSA, + }, + { + desc: "Server configured with GetCertificate callback preferring RSA in TLS 1.3", + serverConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + for _, c := range []*tls.Certificate{&rsaCert, &ecdsaCert} { + if err := chi.SupportsCertificate(c); err == nil { + return c, nil + } + } + return &ecdsaCert, nil + }, + }, + wantNegotiatedAlgorithm: x509.RSA, }, } @@ -150,8 +170,8 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) } negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - if negotiatedAlgo != x509.RSA && negotiatedAlgo != x509.ECDSA { - t.Errorf("negotiated certificate algorithm = %v, want RSA or ECDSA", negotiatedAlgo) + if negotiatedAlgo != tc.wantNegotiatedAlgorithm { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgorithm) } }) } @@ -297,96 +317,123 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { return handler(ctx, req) } - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - ClientCAs: clientCAPool, - ClientAuth: tls.RequireAndVerifyClientCert, - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) + testCases := []struct { + desc string + serverCerts []tls.Certificate + wantServerCertAlgorithm x509.PublicKeyAlgorithm + }{ + { + desc: "Server configured with [RSA, ECDSA] in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + wantServerCertAlgorithm: x509.RSA, + }, + { + desc: "Server configured with reversed [ECDSA, RSA] in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, + wantServerCertAlgorithm: x509.ECDSA, + }, } - defer lis.Close() - go s.Serve(lis) - addr := lis.Addr().String() + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: tc.serverCerts, + ClientCAs: clientCAPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) + defer s.Stop() - // 1. RSA client in TLS 1.3 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() + addr := lis.Addr().String() - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("RSA client EmptyCall failed in TLS 1.3: %v", err) - } + // 1. RSA client in TLS 1.3 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{rsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - if lastTLSVersion != tls.VersionTLS13 { - t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) - } - if lastClientCertAlgo != x509.RSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) - } - } + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() - // 2. ECDSA client in TLS 1.3 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{ecdsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("RSA client EmptyCall failed in TLS 1.3: %v", err) + } - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + if lastTLSVersion != tls.VersionTLS13 { + t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) + } + if lastClientCertAlgo != x509.RSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) + } + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantServerCertAlgorithm { + t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantServerCertAlgorithm) + } + } - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("ECDSA client EmptyCall failed in TLS 1.3: %v", err) - } + // 2. ECDSA client in TLS 1.3 + { + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{ecdsaClientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - if lastTLSVersion != tls.VersionTLS13 { - t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) - } - if lastClientCertAlgo != x509.ECDSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) - } + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("ECDSA client EmptyCall failed in TLS 1.3: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + if lastTLSVersion != tls.VersionTLS13 { + t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) + } + if lastClientCertAlgo != x509.ECDSA { + t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) + } + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantServerCertAlgorithm { + t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantServerCertAlgorithm) + } + } + }) } } From c414972d413202421b27d914f5e1b0d3461b5b9c Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Tue, 18 Aug 2026 14:32:19 +0000 Subject: [PATCH 06/12] add unit test case 5 for dual client preference vs server certificate order --- .../multi_cert_integration_test.go | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go index 7ac73606f3ad..ef232f378334 100644 --- a/security/advancedtls/multi_cert_integration_test.go +++ b/security/advancedtls/multi_cert_integration_test.go @@ -157,6 +157,43 @@ func (s) TestBuildGetCertificates_SignatureAlgorithmsExtension_TLS13(t *testing. 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 From 1f1af77386cc98f79b0b8cbcaddd91bf8f699ae3 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 16:57:42 +0000 Subject: [PATCH 07/12] add more README for certs --- security/advancedtls/testdata/README.md | 122 ++++++++++++++------ security/advancedtls/testdata/create.sh | 145 ++++++++++++++++++++++++ testdata/x509/README.md | 78 ++++++++++++- testdata/x509/create.sh | 2 +- 4 files changed, 304 insertions(+), 43 deletions(-) create mode 100755 security/advancedtls/testdata/create.sh 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/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/testdata/x509/README.md b/testdata/x509/README.md index 661caf4ac858..91f7af075626 100644 --- a/testdata/x509/README.md +++ b/testdata/x509/README.md @@ -1,6 +1,74 @@ -This directory contains x509 certificates and associated private keys used in -gRPC-Go tests. +# X.509 Test Credentials -How were these test certs/keys generated ? ------------------------------------------- -Run `./create.sh` +This directory contains X.509 certificates and private keys used in gRPC-Go TLS tests (such as `test/multi_cert_tls_test.go` and other end-to-end integration tests). + +## Credentials Overview + +### Certificate Authorities (Root CAs) +* **`server_ca_cert.pem` / `server_ca_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-server_ca, O=gRPC, L=SVL, ST=CA, C=US` + * **Purpose**: Signs server identity certificates (`server1_cert.pem`, `server2_cert.pem`, `server_ecdsa_cert.pem`). +* **`client_ca_cert.pem` / `client_ca_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-client_ca, O=gRPC, L=SVL, ST=CA, C=US` + * **Purpose**: Signs client identity certificates (`client1_cert.pem`, `client2_cert.pem`, `client_ecdsa_cert.pem`, `client_with_spiffe_cert.pem`). + +### Server Certificates +* **`server1_cert.pem` / `server1_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-server1` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `server_ca_cert.pem` +* **`server2_cert.pem` / `server2_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-server2` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `server_ca_cert.pem` +* **`server_ecdsa_cert.pem` / `server_ecdsa_key.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=test-server-ecdsa` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `server_ca_cert.pem` + * **Purpose**: Used for multiple-certificate negotiation tests in TLS 1.3 and TLS 1.2 alongside RSA server certificates. + +### Client Certificates +* **`client1_cert.pem` / `client1_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-client1` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `client_ca_cert.pem` +* **`client2_cert.pem` / `client2_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-client2` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `client_ca_cert.pem` +* **`client_ecdsa_cert.pem` / `client_ecdsa_key.pem`**: + * **Algorithm**: ECDSA P-256 (`prime256v1`) + * **Subject**: `CN=test-client-ecdsa` + * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` + * **Issuer**: `client_ca_cert.pem` + * **Purpose**: Used for mutual TLS (mTLS) tests with ECDSA client authentication. + +### SPIFFE & Custom SAN Certificates +* **`spiffe_cert.pem` / `spiffe_key.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed) + * **Subject**: `CN=test-client1` + * **SAN**: `URI:spiffe://foo.bar.com/client/workload/1` +* **`multiple_uri_cert.pem` / `multiple_uri_key.pem`**: + * **Algorithm**: RSA 4096-bit (Self-signed) + * **Subject**: `CN=test-client1` + * **SANs**: `URI:spiffe://foo.bar.com/client/workload/1`, `URI:https://bar.baz.com/client` +* **`client_with_spiffe_cert.pem` / `client_with_spiffe_key.pem`**: + * **Algorithm**: RSA 4096-bit + * **Subject**: `CN=test-client1` + * **SANs**: `URI:spiffe://foo.bar.com/client/workload/1`, `DNS:*.test.example.com` + * **Issuer**: `client_ca_cert.pem` + +## Certificate Generation + +All certificates and keys in this directory are generated using the `create.sh` script: + +```bash +./create.sh +``` diff --git a/testdata/x509/create.sh b/testdata/x509/create.sh index f8bf8cbd6de3..1a6a4f10baa6 100755 --- a/testdata/x509/create.sh +++ b/testdata/x509/create.sh @@ -190,4 +190,4 @@ openssl x509 -req \ openssl verify -verbose -CAfile client_ca_cert.pem client_ecdsa_cert.pem # Cleanup the CSRs. -rm *_csr.pem +rm -f *_csr.pem From f18a319f09a39df09744ababdb1878a0c07c8bbd Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 17:26:18 +0000 Subject: [PATCH 08/12] cleanup tests --- .../multi_cert_integration_test.go | 913 ++++++++++-------- test/multi_cert_tls_test.go | 562 +++++------ 2 files changed, 812 insertions(+), 663 deletions(-) diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go index ef232f378334..062a2324b0f2 100644 --- a/security/advancedtls/multi_cert_integration_test.go +++ b/security/advancedtls/multi_cert_integration_test.go @@ -279,9 +279,9 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } testCases := []struct { - desc string - serverOptions func() *Options - wantNegotiatedAlgorithm x509.PublicKeyAlgorithm + desc string + serverOptions func() *Options + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { desc: "Server configured with direct Certificates slice [RSA, ECDSA] in TLS 1.3", @@ -299,7 +299,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.RSA, + wantNegotiatedAlgo: x509.RSA, }, { desc: "Server configured with reversed Certificates slice [ECDSA, RSA] in TLS 1.3", @@ -317,7 +317,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, { desc: "Server configured with GetIdentityCertificatesForServer returning [ECDSA, RSA] in TLS 1.3", @@ -337,7 +337,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, { desc: "Server configured with GetIdentityCertificatesForServer returning [RSA, ECDSA] in TLS 1.3", @@ -357,7 +357,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.RSA, + wantNegotiatedAlgo: x509.RSA, }, { desc: "Server configured with IdentityProvider supplying [RSA, ECDSA] in TLS 1.3", @@ -377,7 +377,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.RSA, + wantNegotiatedAlgo: x509.RSA, }, { desc: "Server configured with IdentityProvider supplying [ECDSA, RSA] in TLS 1.3", @@ -397,7 +397,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, - wantNegotiatedAlgorithm: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, } @@ -454,8 +454,8 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { } conn.Close() - if negotiatedAlgo != tc.wantNegotiatedAlgorithm { - t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgorithm) + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) } }) } @@ -472,11 +472,13 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { } testCases := []struct { - desc string - serverOptions func() *Options + desc string + serverOptions func() *Options + clientCipherSuites []uint16 + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { - desc: "Server configured with direct Certificates slice [RSA, ECDSA] in TLS 1.2", + desc: "Server configured with direct Certificates slice [RSA, ECDSA], RSA client in TLS 1.2", serverOptions: func() *Options { return &Options{ IdentityOptions: IdentityCertificateOptions{ @@ -491,9 +493,30 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { 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] in TLS 1.2", + 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{ @@ -508,9 +531,89 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { VerificationType: CertVerification, } }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, }, { - desc: "Server configured with GetIdentityCertificatesForServer callback in TLS 1.2", + 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{ @@ -527,6 +630,50 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { 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, }, } @@ -549,86 +696,42 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { pb.RegisterGreeterServer(s, greeterServer{}) go s.Serve(lis) - // 1. Connect with an RSA-only client in TLS 1.2. - { - var negotiatedAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - VerificationType: CertAndHostVerification, - CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - 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 + 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 } - 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("RSA client call failed: %v", err) - } - conn.Close() - - if negotiatedAlgo != x509.RSA { - t.Errorf("RSA client negotiated certificate algorithm = %v, want %v (x509.RSA)", negotiatedAlgo, x509.RSA) - } + 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() - // 2. Connect with an ECDSA-only client in TLS 1.2. - { - var negotiatedAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - VerificationType: CertAndHostVerification, - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - 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("ECDSA client call failed: %v", err) - } - conn.Close() - - if negotiatedAlgo != x509.ECDSA { - t.Errorf("ECDSA client negotiated certificate algorithm = %v, want %v (x509.ECDSA)", negotiatedAlgo, x509.ECDSA) - } + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) } }) } @@ -643,19 +746,41 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { } testCases := []struct { - desc string - serverCerts []tls.Certificate - wantServerCertAlgorithm x509.PublicKeyAlgorithm + desc string + serverCerts []tls.Certificate + clientCert tls.Certificate + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { - desc: "Server configured with [RSA, ECDSA] in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, - wantServerCertAlgorithm: x509.RSA, + 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] in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{cs.ServerPeerECDSALocalhost1, cs.ServerPeerLocalhost1}, - wantServerCertAlgorithm: 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, }, } @@ -689,88 +814,44 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { pb.RegisterGreeterServer(s, greeterServer{}) go s.Serve(lis) - // 1. RSA client with RSA client certificate in TLS 1.3 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientCert1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS13, - MaxTLSVersion: tls.VersionTLS13, - VerificationType: CertAndHostVerification, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err - } - serverCertAlgo = cert.PublicKeyAlgorithm + 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 } - 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("RSA mTLS TLS 1.3 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != tc.wantServerCertAlgorithm { - t.Errorf("serverCertAlgo = %v, want %v", serverCertAlgo, tc.wantServerCertAlgorithm) - } + 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() - // 2. ECDSA client with ECDSA client certificate in TLS 1.3 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS13, - MaxTLSVersion: tls.VersionTLS13, - VerificationType: CertAndHostVerification, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err - } - serverCertAlgo = 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("ECDSA mTLS TLS 1.3 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != tc.wantServerCertAlgorithm { - t.Errorf("serverCertAlgo = %v, want %v", serverCertAlgo, tc.wantServerCertAlgorithm) - } + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated server certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) } }) } @@ -784,118 +865,114 @@ func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { t.Fatalf("cs.LoadCerts() failed, err: %v", err) } - serverOptions := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ServerPeerLocalhost1, cs.ServerPeerECDSALocalhost1}, + 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, }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ServerTrust1, + { + 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, }, - 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) + 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() - // 1. RSA client with RSA client certificate in TLS 1.2 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientCert1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS12, - MaxTLSVersion: tls.VersionTLS12, - VerificationType: CertAndHostVerification, - CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err - } - serverCertAlgo = 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("RSA mTLS TLS 1.2 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != x509.RSA { - t.Errorf("serverCertAlgo = %v, want %v (x509.RSA)", serverCertAlgo, x509.RSA) - } - } + 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) - // 2. ECDSA client with ECDSA client certificate in TLS 1.2 - { - var serverCertAlgo x509.PublicKeyAlgorithm - clientOpts := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ClientPeerECDSALocalhost1}, - }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS12, - MaxTLSVersion: tls.VersionTLS12, - VerificationType: CertAndHostVerification, - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - AdditionalPeerVerification: func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error) { - if params.Leaf != nil { - serverCertAlgo = params.Leaf.PublicKeyAlgorithm - } else if len(params.RawCerts) > 0 { - cert, err := x509.ParseCertificate(params.RawCerts[0]) - if err != nil { - return nil, err + 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 } - serverCertAlgo = 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("ECDSA mTLS TLS 1.2 call failed: %v", err) - } - conn.Close() - if serverCertAlgo != x509.ECDSA { - t.Errorf("serverCertAlgo = %v, want %v (x509.ECDSA)", serverCertAlgo, x509.ECDSA) - } + 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) + } + }) } } @@ -908,71 +985,106 @@ func (s) TestServerMultipleCerts_TLS13_DynamicSelection(t *testing.T) { t.Fatalf("cs.LoadCerts() failed, err: %v", err) } - var capturedSigSchemes []tls.SignatureScheme - serverOptions := &Options{ - IdentityOptions: IdentityCertificateOptions{ - GetIdentityCertificatesForServer: func(chi *tls.ClientHelloInfo) ([]*tls.Certificate, error) { - capturedSigSchemes = chi.SignatureSchemes - // Filter certificates using SupportsCertificate against the client's offered signature algorithms - candidates := []*tls.Certificate{&cs.ServerPeerECDSALocalhost1, &cs.ServerPeerLocalhost1} - var supported []*tls.Certificate - for _, c := range 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 - }, + 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, }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ServerTrust1, + { + desc: "Dynamic selection preferring RSA [RSA, ECDSA] in TLS 1.3", + candidates: []*tls.Certificate{&cs.ServerPeerLocalhost1, &cs.ServerPeerECDSALocalhost1}, + wantNegotiatedAlgo: x509.RSA, }, - 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) + 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() - clientOpts := &Options{ - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - MinTLSVersion: tls.VersionTLS13, - MaxTLSVersion: tls.VersionTLS13, - VerificationType: CertAndHostVerification, - } - 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() + 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) - if len(capturedSigSchemes) == 0 { - t.Errorf("expected non-empty ClientHelloInfo.SignatureSchemes in TLS 1.3") + 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) + } + }) } } @@ -984,56 +1096,75 @@ func (s) TestServerMultipleCerts_IncompatibleAlgorithm(t *testing.T) { t.Fatalf("cs.LoadCerts() failed, err: %v", err) } - // Server with ONLY RSA certificate - serverOptions := &Options{ - IdentityOptions: IdentityCertificateOptions{ - Certificates: []tls.Certificate{cs.ServerPeerLocalhost1}, + 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}, }, - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ServerTrust1, + { + 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}, }, - 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) + 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() - // ECDSA-only client connecting to RSA-only server must fail - clientOpts := &Options{ - RootOptions: RootCertificateOptions{ - RootCertificates: cs.ClientTrust1, - }, - VerificationType: CertAndHostVerification, - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - 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() + 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/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go index 3ef2291332c3..e08c30048c25 100644 --- a/test/multi_cert_tls_test.go +++ b/test/multi_cert_tls_test.go @@ -69,9 +69,9 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { caPool := loadCertPool(t, "x509/server_ca_cert.pem") testCases := []struct { - desc string - serverConfig *tls.Config - wantNegotiatedAlgorithm x509.PublicKeyAlgorithm + desc string + serverConfig *tls.Config + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.3", @@ -80,7 +80,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, }, - wantNegotiatedAlgorithm: x509.RSA, + wantNegotiatedAlgo: x509.RSA, }, { desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.3", @@ -89,7 +89,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, }, - wantNegotiatedAlgorithm: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, { desc: "Server configured with GetCertificate callback preferring ECDSA in TLS 1.3", @@ -105,7 +105,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { return &rsaCert, nil }, }, - wantNegotiatedAlgorithm: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, { desc: "Server configured with GetCertificate callback preferring RSA in TLS 1.3", @@ -121,7 +121,7 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { return &ecdsaCert, nil }, }, - wantNegotiatedAlgorithm: x509.RSA, + wantNegotiatedAlgo: x509.RSA, }, } @@ -170,8 +170,8 @@ func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) } negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - if negotiatedAlgo != tc.wantNegotiatedAlgorithm { - t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgorithm) + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) } }) } @@ -186,24 +186,70 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { caPool := loadCertPool(t, "x509/server_ca_cert.pem") testCases := []struct { - desc string - serverConfig *tls.Config + desc string + serverConfig *tls.Config + clientCipherSuites []uint16 + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { - desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.2", + desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.2", serverConfig: &tls.Config{ Certificates: []tls.Certificate{rsaCert, ecdsaCert}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.RSA, }, { - desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.2", + desc: "Server configured with [RSA, ECDSA], ECDSA client in TLS 1.2", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{rsaCert, ecdsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + 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", serverConfig: &tls.Config{ Certificates: []tls.Certificate{ecdsaCert, rsaCert}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, }, + 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", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{ecdsaCert, rsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + 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", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{rsaCert, ecdsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + 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 reversed [ECDSA, RSA], client supporting both [ECDSA, RSA] in TLS 1.2", + serverConfig: &tls.Config{ + Certificates: []tls.Certificate{ecdsaCert, rsaCert}, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantNegotiatedAlgo: x509.ECDSA, }, } @@ -223,72 +269,35 @@ func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { addr := lis.Addr().String() - // 1. RSA-only client in TLS 1.2 - { - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("RSA client EmptyCall failed: %v", err) - } - - tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok || len(tlsInfo.State.PeerCertificates) == 0 { - t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) - } - negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - if negotiatedAlgo != x509.RSA { - t.Errorf("RSA client negotiated certificate algorithm = %v, want %v (x509.RSA)", negotiatedAlgo, x509.RSA) - } + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + CipherSuites: tc.clientCipherSuites, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("client EmptyCall failed: %v", err) } - // 2. ECDSA-only client in TLS 1.2 - { - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("ECDSA client EmptyCall failed: %v", err) - } - - tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok || len(tlsInfo.State.PeerCertificates) == 0 { - t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) - } - negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - if negotiatedAlgo != x509.ECDSA { - t.Errorf("ECDSA client negotiated certificate algorithm = %v, want %v (x509.ECDSA)", negotiatedAlgo, x509.ECDSA) - } + tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(tlsInfo.State.PeerCertificates) == 0 { + t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) + } + negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm + if negotiatedAlgo != tc.wantNegotiatedAlgo { + t.Errorf("negotiated certificate algorithm = %v, want %v", negotiatedAlgo, tc.wantNegotiatedAlgo) } }) } @@ -318,19 +327,39 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { } testCases := []struct { - desc string - serverCerts []tls.Certificate - wantServerCertAlgorithm x509.PublicKeyAlgorithm + desc string + serverCerts []tls.Certificate + clientCert tls.Certificate + wantClientAlgo x509.PublicKeyAlgorithm + wantNegotiatedAlgo x509.PublicKeyAlgorithm }{ { - desc: "Server configured with [RSA, ECDSA] in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - wantServerCertAlgorithm: x509.RSA, + desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + clientCert: rsaClientCert, + wantClientAlgo: x509.RSA, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server configured with [RSA, ECDSA], ECDSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + clientCert: ecdsaClientCert, + wantClientAlgo: x509.ECDSA, + wantNegotiatedAlgo: x509.RSA, }, { - desc: "Server configured with reversed [ECDSA, RSA] in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, - wantServerCertAlgorithm: x509.ECDSA, + desc: "Server configured with reversed [ECDSA, RSA], RSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, + clientCert: rsaClientCert, + wantClientAlgo: x509.RSA, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server configured with reversed [ECDSA, RSA], ECDSA client in TLS 1.3 mTLS", + serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, + clientCert: ecdsaClientCert, + wantClientAlgo: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, }, } @@ -356,82 +385,40 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { addr := lis.Addr().String() - // 1. RSA client in TLS 1.3 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("RSA client EmptyCall failed in TLS 1.3: %v", err) - } - - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - if lastTLSVersion != tls.VersionTLS13 { - t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) - } - if lastClientCertAlgo != x509.RSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) - } - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantServerCertAlgorithm { - t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantServerCertAlgorithm) - } + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{tc.clientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + MinVersion: tls.VersionTLS13, + MaxVersion: tls.VersionTLS13, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) } + defer conn.Close() - // 2. ECDSA client in TLS 1.3 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{ecdsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("ECDSA client EmptyCall failed in TLS 1.3: %v", err) - } - - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - if lastTLSVersion != tls.VersionTLS13 { - t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) - } - if lastClientCertAlgo != x509.ECDSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) - } - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantServerCertAlgorithm { - t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantServerCertAlgorithm) - } + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("client EmptyCall failed in TLS 1.3 mTLS: %v", err) + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if tlsInfo.State.Version != tls.VersionTLS13 { + t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) + } + if lastTLSVersion != tls.VersionTLS13 { + t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) + } + if lastClientCertAlgo != tc.wantClientAlgo { + t.Errorf("client certificate algorithm verified by server = %v, want %v", lastClientCertAlgo, tc.wantClientAlgo) + } + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantNegotiatedAlgo { + t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantNegotiatedAlgo) } }) } @@ -458,92 +445,101 @@ func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { return handler(ctx, req) } - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - ClientCAs: clientCAPool, - ClientAuth: tls.RequireAndVerifyClientCert, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) + testCases := []struct { + desc string + serverCerts []tls.Certificate + clientCert tls.Certificate + clientCipherSuites []uint16 + wantClientAlgo x509.PublicKeyAlgorithm + wantNegotiatedAlgo x509.PublicKeyAlgorithm + }{ + { + desc: "Server [RSA, ECDSA], RSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + clientCert: rsaClientCert, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantClientAlgo: x509.RSA, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server [RSA, ECDSA], ECDSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, + clientCert: ecdsaClientCert, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantClientAlgo: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, + }, + { + desc: "Server reversed [ECDSA, RSA], RSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, + clientCert: rsaClientCert, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + wantClientAlgo: x509.RSA, + wantNegotiatedAlgo: x509.RSA, + }, + { + desc: "Server reversed [ECDSA, RSA], ECDSA client in TLS 1.2 mTLS", + serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, + clientCert: ecdsaClientCert, + clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + wantClientAlgo: x509.ECDSA, + wantNegotiatedAlgo: x509.ECDSA, + }, } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - // 1. RSA client in TLS 1.2 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: tc.serverCerts, + ClientCAs: clientCAPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) + defer s.Stop() - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("RSA client EmptyCall failed in TLS 1.2: %v", err) - } + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != x509.RSA { - t.Errorf("server certificate algorithm = %v, want %v (x509.RSA)", serverAlgo, x509.RSA) - } - if lastClientCertAlgo != x509.RSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.RSA)", lastClientCertAlgo, x509.RSA) - } - } + addr := lis.Addr().String() - // 2. ECDSA client in TLS 1.2 - { - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{ecdsaClientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() + clientCreds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{tc.clientCert}, + RootCAs: serverCAPool, + ServerName: "x.test.example.com", + CipherSuites: tc.clientCipherSuites, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("ECDSA client EmptyCall failed in TLS 1.2: %v", err) - } + var p peer.Peer + if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { + t.Fatalf("client EmptyCall failed in TLS 1.2 mTLS: %v", err) + } - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != x509.ECDSA { - t.Errorf("server certificate algorithm = %v, want %v (x509.ECDSA)", serverAlgo, x509.ECDSA) - } - if lastClientCertAlgo != x509.ECDSA { - t.Errorf("client certificate algorithm verified by server = %v, want %v (x509.ECDSA)", lastClientCertAlgo, x509.ECDSA) - } + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantNegotiatedAlgo { + t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantNegotiatedAlgo) + } + if lastClientCertAlgo != tc.wantClientAlgo { + t.Errorf("client certificate algorithm verified by server = %v, want %v", lastClientCertAlgo, tc.wantClientAlgo) + } + }) } } @@ -551,44 +547,66 @@ func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { // only RSA certificates, a client offering only ECDSA cipher suites fails to handshake in TLS 1.2. func (s) TestServerMultipleCerts_IncompatibleAlgorithm(t *testing.T) { rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") + ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") caPool := loadCertPool(t, "x509/server_ca_cert.pem") - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{rsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - s := grpc.NewServer(grpc.Creds(serverCreds)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %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{rsaCert}, + 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{ecdsaCert}, + clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + }, } - defer conn.Close() - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + serverCreds := credentials.NewTLS(&tls.Config{ + Certificates: tc.serverCerts, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + s := grpc.NewServer(grpc.Creds(serverCreds)) + defer s.Stop() + + testgrpc.RegisterTestServiceServer(s, &testServer{}) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer lis.Close() + go s.Serve(lis) + + addr := lis.Addr().String() + + clientCreds := credentials.NewTLS(&tls.Config{ + RootCAs: caPool, + ServerName: "x.test.example.com", + CipherSuites: tc.clientCipherSuites, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + }) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) + if err != nil { + t.Fatalf("grpc.NewClient failed: %v", err) + } + defer conn.Close() + + client := testgrpc.NewTestServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() - if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil { - t.Fatalf("EmptyCall succeeded unexpectedly when client offered only incompatible cipher suites") + if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil { + t.Fatalf("EmptyCall succeeded unexpectedly when client offered only incompatible cipher suites") + } + }) } } From 8ef7fb1594e14cf0e80c7ce733da2c329fc57df4 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 17:27:43 +0000 Subject: [PATCH 09/12] remove multi_cert_tls_test.go - it doesn't actually test the changes made in advancedtls --- test/multi_cert_tls_test.go | 612 ------------------------------------ 1 file changed, 612 deletions(-) delete mode 100644 test/multi_cert_tls_test.go diff --git a/test/multi_cert_tls_test.go b/test/multi_cert_tls_test.go deleted file mode 100644 index e08c30048c25..000000000000 --- a/test/multi_cert_tls_test.go +++ /dev/null @@ -1,612 +0,0 @@ -/* - * - * 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 test - -import ( - "context" - "crypto/tls" - "crypto/x509" - "net" - "os" - "testing" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - testgrpc "google.golang.org/grpc/interop/grpc_testing" - testpb "google.golang.org/grpc/interop/grpc_testing" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/testdata" -) - -// loadTestCert loads a tls.Certificate from the specified testdata paths. -func loadTestCert(t *testing.T, certFile, keyFile string) tls.Certificate { - t.Helper() - cert, err := tls.LoadX509KeyPair(testdata.Path(certFile), testdata.Path(keyFile)) - if err != nil { - t.Fatalf("tls.LoadX509KeyPair(%q, %q) failed: %v", certFile, keyFile, err) - } - return cert -} - -// loadCertPool loads a certificate pool from the specified testdata path. -func loadCertPool(t *testing.T, caFile string) *x509.CertPool { - t.Helper() - data, err := os.ReadFile(testdata.Path(caFile)) - if err != nil { - t.Fatalf("os.ReadFile(%q) failed: %v", caFile, err) - } - pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(data) { - t.Fatalf("AppendCertsFromPEM failed for %q", caFile) - } - return pool -} - -// TestServerMultipleCerts_TLS13_Negotiation tests end-to-end gRPC communication -// where both server and client strictly enforce TLS 1.3, the server possesses -// both RSA and ECDSA certificate chains with the same SNI (*.test.example.com), -// and certificate selection operates via SupportsCertificate and the signature_algorithms -// TLS extension without depending on TLS 1.2 CipherSuites. -func (s) TestServerMultipleCerts_TLS13_Negotiation(t *testing.T) { - rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") - ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") - caPool := loadCertPool(t, "x509/server_ca_cert.pem") - - testCases := []struct { - desc string - serverConfig *tls.Config - wantNegotiatedAlgo x509.PublicKeyAlgorithm - }{ - { - desc: "Server configured with [RSA, ECDSA] certificates in TLS 1.3", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{rsaCert, ecdsaCert}, - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }, - wantNegotiatedAlgo: x509.RSA, - }, - { - desc: "Server configured with reversed [ECDSA, RSA] certificates in TLS 1.3", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{ecdsaCert, rsaCert}, - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }, - wantNegotiatedAlgo: x509.ECDSA, - }, - { - desc: "Server configured with GetCertificate callback preferring ECDSA in TLS 1.3", - serverConfig: &tls.Config{ - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { - for _, c := range []*tls.Certificate{&ecdsaCert, &rsaCert} { - if err := chi.SupportsCertificate(c); err == nil { - return c, nil - } - } - return &rsaCert, nil - }, - }, - wantNegotiatedAlgo: x509.ECDSA, - }, - { - desc: "Server configured with GetCertificate callback preferring RSA in TLS 1.3", - serverConfig: &tls.Config{ - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { - for _, c := range []*tls.Certificate{&rsaCert, &ecdsaCert} { - if err := chi.SupportsCertificate(c); err == nil { - return c, nil - } - } - return &ecdsaCert, nil - }, - }, - wantNegotiatedAlgo: x509.RSA, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - serverCreds := credentials.NewTLS(tc.serverConfig) - s := grpc.NewServer(grpc.Creds(serverCreds)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("Client EmptyCall failed in TLS 1.3: %v", err) - } - - tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok || len(tlsInfo.State.PeerCertificates) == 0 { - t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) - } - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - 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 has both RSA and ECDSA certificate chains, and clients -// negotiate between them using CipherSuites and SignatureSchemes via SupportsCertificate. -func (s) TestServerMultipleCerts_TLS12_Negotiation(t *testing.T) { - rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") - ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") - caPool := loadCertPool(t, "x509/server_ca_cert.pem") - - testCases := []struct { - desc string - serverConfig *tls.Config - clientCipherSuites []uint16 - wantNegotiatedAlgo x509.PublicKeyAlgorithm - }{ - { - desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.2", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{rsaCert, ecdsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - 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", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{rsaCert, ecdsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - 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", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{ecdsaCert, rsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - 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", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{ecdsaCert, rsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - 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", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{rsaCert, ecdsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - 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 reversed [ECDSA, RSA], client supporting both [ECDSA, RSA] in TLS 1.2", - serverConfig: &tls.Config{ - Certificates: []tls.Certificate{ecdsaCert, rsaCert}, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }, - clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - wantNegotiatedAlgo: x509.ECDSA, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - serverCreds := credentials.NewTLS(tc.serverConfig) - s := grpc.NewServer(grpc.Creds(serverCreds)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - CipherSuites: tc.clientCipherSuites, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("client EmptyCall failed: %v", err) - } - - tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok || len(tlsInfo.State.PeerCertificates) == 0 { - t.Fatalf("Failed to retrieve TLSInfo or peer certificates: %v", p.AuthInfo) - } - negotiatedAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - 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 certificates. -func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { - rsaServerCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") - ecdsaServerCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") - serverCAPool := loadCertPool(t, "x509/server_ca_cert.pem") - clientCAPool := loadCertPool(t, "x509/client_ca_cert.pem") - - rsaClientCert := loadTestCert(t, "x509/client1_cert.pem", "x509/client1_key.pem") - ecdsaClientCert := loadTestCert(t, "x509/client_ecdsa_cert.pem", "x509/client_ecdsa_key.pem") - - var lastClientCertAlgo x509.PublicKeyAlgorithm - var lastTLSVersion uint16 - unaryInterceptor := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { - if p, ok := peer.FromContext(ctx); ok { - if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { - lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - lastTLSVersion = tlsInfo.State.Version - } - } - return handler(ctx, req) - } - - testCases := []struct { - desc string - serverCerts []tls.Certificate - clientCert tls.Certificate - wantClientAlgo x509.PublicKeyAlgorithm - wantNegotiatedAlgo x509.PublicKeyAlgorithm - }{ - { - desc: "Server configured with [RSA, ECDSA], RSA client in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - clientCert: rsaClientCert, - wantClientAlgo: x509.RSA, - wantNegotiatedAlgo: x509.RSA, - }, - { - desc: "Server configured with [RSA, ECDSA], ECDSA client in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - clientCert: ecdsaClientCert, - wantClientAlgo: x509.ECDSA, - wantNegotiatedAlgo: x509.RSA, - }, - { - desc: "Server configured with reversed [ECDSA, RSA], RSA client in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, - clientCert: rsaClientCert, - wantClientAlgo: x509.RSA, - wantNegotiatedAlgo: x509.ECDSA, - }, - { - desc: "Server configured with reversed [ECDSA, RSA], ECDSA client in TLS 1.3 mTLS", - serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, - clientCert: ecdsaClientCert, - wantClientAlgo: x509.ECDSA, - wantNegotiatedAlgo: x509.ECDSA, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: tc.serverCerts, - ClientCAs: clientCAPool, - ClientAuth: tls.RequireAndVerifyClientCert, - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{tc.clientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - MinVersion: tls.VersionTLS13, - MaxVersion: tls.VersionTLS13, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("client EmptyCall failed in TLS 1.3 mTLS: %v", err) - } - - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if tlsInfo.State.Version != tls.VersionTLS13 { - t.Errorf("negotiated TLS version = %x, want %x (TLS 1.3)", tlsInfo.State.Version, tls.VersionTLS13) - } - if lastTLSVersion != tls.VersionTLS13 { - t.Errorf("server observed TLS version = %x, want %x (TLS 1.3)", lastTLSVersion, tls.VersionTLS13) - } - if lastClientCertAlgo != tc.wantClientAlgo { - t.Errorf("client certificate algorithm verified by server = %v, want %v", lastClientCertAlgo, tc.wantClientAlgo) - } - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantNegotiatedAlgo { - t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantNegotiatedAlgo) - } - }) - } -} - -// TestServerMultipleCerts_TLS12_MutualTLS tests mTLS end-to-end in TLS 1.2 -// where the server has dual RSA and ECDSA certificates and requires client certs. -func (s) TestServerMultipleCerts_TLS12_MutualTLS(t *testing.T) { - rsaServerCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") - ecdsaServerCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") - serverCAPool := loadCertPool(t, "x509/server_ca_cert.pem") - clientCAPool := loadCertPool(t, "x509/client_ca_cert.pem") - - rsaClientCert := loadTestCert(t, "x509/client1_cert.pem", "x509/client1_key.pem") - ecdsaClientCert := loadTestCert(t, "x509/client_ecdsa_cert.pem", "x509/client_ecdsa_key.pem") - - var lastClientCertAlgo x509.PublicKeyAlgorithm - unaryInterceptor := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { - if p, ok := peer.FromContext(ctx); ok { - if tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo); ok && len(tlsInfo.State.PeerCertificates) > 0 { - lastClientCertAlgo = tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm - } - } - return handler(ctx, req) - } - - testCases := []struct { - desc string - serverCerts []tls.Certificate - clientCert tls.Certificate - clientCipherSuites []uint16 - wantClientAlgo x509.PublicKeyAlgorithm - wantNegotiatedAlgo x509.PublicKeyAlgorithm - }{ - { - desc: "Server [RSA, ECDSA], RSA client in TLS 1.2 mTLS", - serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - clientCert: rsaClientCert, - clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - wantClientAlgo: x509.RSA, - wantNegotiatedAlgo: x509.RSA, - }, - { - desc: "Server [RSA, ECDSA], ECDSA client in TLS 1.2 mTLS", - serverCerts: []tls.Certificate{rsaServerCert, ecdsaServerCert}, - clientCert: ecdsaClientCert, - clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - wantClientAlgo: x509.ECDSA, - wantNegotiatedAlgo: x509.ECDSA, - }, - { - desc: "Server reversed [ECDSA, RSA], RSA client in TLS 1.2 mTLS", - serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, - clientCert: rsaClientCert, - clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - wantClientAlgo: x509.RSA, - wantNegotiatedAlgo: x509.RSA, - }, - { - desc: "Server reversed [ECDSA, RSA], ECDSA client in TLS 1.2 mTLS", - serverCerts: []tls.Certificate{ecdsaServerCert, rsaServerCert}, - clientCert: ecdsaClientCert, - clientCipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - wantClientAlgo: x509.ECDSA, - wantNegotiatedAlgo: x509.ECDSA, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: tc.serverCerts, - ClientCAs: clientCAPool, - ClientAuth: tls.RequireAndVerifyClientCert, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - s := grpc.NewServer(grpc.Creds(serverCreds), grpc.UnaryInterceptor(unaryInterceptor)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{tc.clientCert}, - RootCAs: serverCAPool, - ServerName: "x.test.example.com", - CipherSuites: tc.clientCipherSuites, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - var p peer.Peer - if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p)); err != nil { - t.Fatalf("client EmptyCall failed in TLS 1.2 mTLS: %v", err) - } - - tlsInfo := p.AuthInfo.(credentials.TLSInfo) - if serverAlgo := tlsInfo.State.PeerCertificates[0].PublicKeyAlgorithm; serverAlgo != tc.wantNegotiatedAlgo { - t.Errorf("server certificate algorithm = %v, want %v", serverAlgo, tc.wantNegotiatedAlgo) - } - if lastClientCertAlgo != tc.wantClientAlgo { - t.Errorf("client certificate algorithm verified by server = %v, want %v", lastClientCertAlgo, tc.wantClientAlgo) - } - }) - } -} - -// TestServerMultipleCerts_IncompatibleAlgorithm tests that when the server possesses -// only RSA certificates, a client offering only ECDSA cipher suites fails to handshake in TLS 1.2. -func (s) TestServerMultipleCerts_IncompatibleAlgorithm(t *testing.T) { - rsaCert := loadTestCert(t, "x509/server1_cert.pem", "x509/server1_key.pem") - ecdsaCert := loadTestCert(t, "x509/server_ecdsa_cert.pem", "x509/server_ecdsa_key.pem") - caPool := loadCertPool(t, "x509/server_ca_cert.pem") - - testCases := []struct { - desc string - serverCerts []tls.Certificate - clientCipherSuites []uint16 - }{ - { - desc: "Server RSA-only, client ECDSA-only in TLS 1.2 fails", - serverCerts: []tls.Certificate{rsaCert}, - 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{ecdsaCert}, - clientCipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - serverCreds := credentials.NewTLS(&tls.Config{ - Certificates: tc.serverCerts, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - s := grpc.NewServer(grpc.Creds(serverCreds)) - defer s.Stop() - - testgrpc.RegisterTestServiceServer(s, &testServer{}) - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("net.Listen failed: %v", err) - } - defer lis.Close() - go s.Serve(lis) - - addr := lis.Addr().String() - - clientCreds := credentials.NewTLS(&tls.Config{ - RootCAs: caPool, - ServerName: "x.test.example.com", - CipherSuites: tc.clientCipherSuites, - MinVersion: tls.VersionTLS12, - MaxVersion: tls.VersionTLS12, - }) - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(clientCreds), grpc.WithAuthority("x.test.example.com"), grpc.WithDisableServiceConfig()) - if err != nil { - t.Fatalf("grpc.NewClient failed: %v", err) - } - defer conn.Close() - - client := testgrpc.NewTestServiceClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) - defer cancel() - - if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil { - t.Fatalf("EmptyCall succeeded unexpectedly when client offered only incompatible cipher suites") - } - }) - } -} From 3d31583b41cde2131d8ad4bb2e4a5d9604e82609 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 17:41:17 +0000 Subject: [PATCH 10/12] formatting --- security/advancedtls/multi_cert_integration_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/advancedtls/multi_cert_integration_test.go b/security/advancedtls/multi_cert_integration_test.go index 062a2324b0f2..9b35db896da4 100644 --- a/security/advancedtls/multi_cert_integration_test.go +++ b/security/advancedtls/multi_cert_integration_test.go @@ -761,8 +761,7 @@ func (s) TestServerMultipleCerts_TLS13_MutualTLS(t *testing.T) { // 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", + 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, From a9efad83d3b79ccb20922c7f5424baebbc6157b0 Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 17:42:33 +0000 Subject: [PATCH 11/12] revert testdata/x509 dir changes --- testdata/x509/README.md | 78 ++--------------------------- testdata/x509/client_ecdsa_cert.pem | 23 --------- testdata/x509/create.sh | 44 +--------------- testdata/x509/server_ecdsa_cert.pem | 23 --------- 4 files changed, 6 insertions(+), 162 deletions(-) delete mode 100644 testdata/x509/client_ecdsa_cert.pem delete mode 100644 testdata/x509/server_ecdsa_cert.pem diff --git a/testdata/x509/README.md b/testdata/x509/README.md index 91f7af075626..661caf4ac858 100644 --- a/testdata/x509/README.md +++ b/testdata/x509/README.md @@ -1,74 +1,6 @@ -# X.509 Test Credentials +This directory contains x509 certificates and associated private keys used in +gRPC-Go tests. -This directory contains X.509 certificates and private keys used in gRPC-Go TLS tests (such as `test/multi_cert_tls_test.go` and other end-to-end integration tests). - -## Credentials Overview - -### Certificate Authorities (Root CAs) -* **`server_ca_cert.pem` / `server_ca_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-server_ca, O=gRPC, L=SVL, ST=CA, C=US` - * **Purpose**: Signs server identity certificates (`server1_cert.pem`, `server2_cert.pem`, `server_ecdsa_cert.pem`). -* **`client_ca_cert.pem` / `client_ca_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-client_ca, O=gRPC, L=SVL, ST=CA, C=US` - * **Purpose**: Signs client identity certificates (`client1_cert.pem`, `client2_cert.pem`, `client_ecdsa_cert.pem`, `client_with_spiffe_cert.pem`). - -### Server Certificates -* **`server1_cert.pem` / `server1_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-server1` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `server_ca_cert.pem` -* **`server2_cert.pem` / `server2_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-server2` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `server_ca_cert.pem` -* **`server_ecdsa_cert.pem` / `server_ecdsa_key.pem`**: - * **Algorithm**: ECDSA P-256 (`prime256v1`) - * **Subject**: `CN=test-server-ecdsa` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `server_ca_cert.pem` - * **Purpose**: Used for multiple-certificate negotiation tests in TLS 1.3 and TLS 1.2 alongside RSA server certificates. - -### Client Certificates -* **`client1_cert.pem` / `client1_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-client1` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `client_ca_cert.pem` -* **`client2_cert.pem` / `client2_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-client2` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `client_ca_cert.pem` -* **`client_ecdsa_cert.pem` / `client_ecdsa_key.pem`**: - * **Algorithm**: ECDSA P-256 (`prime256v1`) - * **Subject**: `CN=test-client-ecdsa` - * **SANs**: `DNS:*.test.example.com`, `DNS:*.test.example.com.cn`, `DNS:waterzooi.test.google.be` - * **Issuer**: `client_ca_cert.pem` - * **Purpose**: Used for mutual TLS (mTLS) tests with ECDSA client authentication. - -### SPIFFE & Custom SAN Certificates -* **`spiffe_cert.pem` / `spiffe_key.pem`**: - * **Algorithm**: RSA 4096-bit (Self-signed) - * **Subject**: `CN=test-client1` - * **SAN**: `URI:spiffe://foo.bar.com/client/workload/1` -* **`multiple_uri_cert.pem` / `multiple_uri_key.pem`**: - * **Algorithm**: RSA 4096-bit (Self-signed) - * **Subject**: `CN=test-client1` - * **SANs**: `URI:spiffe://foo.bar.com/client/workload/1`, `URI:https://bar.baz.com/client` -* **`client_with_spiffe_cert.pem` / `client_with_spiffe_key.pem`**: - * **Algorithm**: RSA 4096-bit - * **Subject**: `CN=test-client1` - * **SANs**: `URI:spiffe://foo.bar.com/client/workload/1`, `DNS:*.test.example.com` - * **Issuer**: `client_ca_cert.pem` - -## Certificate Generation - -All certificates and keys in this directory are generated using the `create.sh` script: - -```bash -./create.sh -``` +How were these test certs/keys generated ? +------------------------------------------ +Run `./create.sh` diff --git a/testdata/x509/client_ecdsa_cert.pem b/testdata/x509/client_ecdsa_cert.pem deleted file mode 100644 index 25e9ce9587a7..000000000000 --- a/testdata/x509/client_ecdsa_cert.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDzDCCAbSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCVVMx -CzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFzAVBgNV -BAMMDnRlc3QtY2xpZW50X2NhMB4XDTI2MDgxMzE2NTcyNVoXDTM2MDgxMDE2NTcy -NVowUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTAL -BgNVBAoMBGdSUEMxGjAYBgNVBAMMEXRlc3QtY2xpZW50LWVjZHNhMFkwEwYHKoZI -zj0CAQYIKoZIzj0DAQcDQgAE1XMBcBqmOZofcQlJ3dfX6AbzQYJBJ1VaAvk74BnK -2kL9biaIPQ6KWGvh+xu7PlJ3Rcm86bKGO/jlQ3Vla7aVPaN4MHYwDAYDVR0TAQH/ -BAIwADAdBgNVHQ4EFgQUmjegSDXkos6mI605ku1FgzYkk7swDgYDVR0PAQH/BAQD -AgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFOr3a0MblN9W -9Opu7VsDn3crpoDCMA0GCSqGSIb3DQEBCwUAA4ICAQAmr2FUdY01sjFCMsCKkWmt -Bq7MAQbkdPNkKzp+fygRmMAdJYdL12ujQfS2jb16jVLTEywAm2FKyP/xdOFZI5Vu -yzRMILe25js5Xssy0aK/x197iawMdxZWSQBjadmwnzYN+6KWekP7RZ/S/jdlUANF -BFrkAAJcU9bb2XBLRUHivRoeZh82dG1gNMuosW8LWXzdMJ9xpXzJXS0YJ//OVbD5 -8raQu15jrSdEHLQyH5L2DBBhf5GX1SR+6D7TwiXa5v4yNt7VVEmupDtQ0Gm+AXdu -fsHBhA5HyCM1CIRn+OV8n2X97HP1w4PazI8cFGj6KQSJBRvitbzz8dFcDhRY/igO -BKW+OtuA8wcy2R8gVoAkuX7IKBgfVrtoHtCX3mS4FYgZf/ARyeloNoPq0V7vG8yK -Xwgg5CWDVKZ9Z/FFl+DRR2LQ9wR2e9fNbvwGNXx6nRyyy+PIU8RXTzKDmVAlf5/M -ymJ83NrsfZszhrlTYzUoUWBOfl8Gr652aITPw3/Iqozx77u+mBGQon5yxiIKf5vW -TX+2AWlwubuU1ZSb54DrggKr7wbISDZ0A8iP1CoCOmXfgAcLT7qxqSodGHBv4PgU -Y8yAW31pB4CLtnlj33B3a/Yy3JFKFHLWQodiomIqnxgAJI1RLbYILOWHTGuusrKf -IKg48tUgZNO6w30nc6s+Qg== ------END CERTIFICATE----- diff --git a/testdata/x509/create.sh b/testdata/x509/create.sh index 1a6a4f10baa6..378bd10cf24f 100755 --- a/testdata/x509/create.sh +++ b/testdata/x509/create.sh @@ -147,47 +147,5 @@ openssl x509 -req \ -sha256 openssl verify -verbose -CAfile client_with_spiffe_cert.pem -# Generate ECDSA server cert. -openssl ecparam -genkey -name prime256v1 -out server_ecdsa_key.pem -openssl req -new \ - -key server_ecdsa_key.pem \ - -days 3650 \ - -out server_ecdsa_csr.pem \ - -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=test-server-ecdsa/ \ - -config ./openssl.cnf \ - -reqexts test_server -openssl x509 -req \ - -in server_ecdsa_csr.pem \ - -CAkey server_ca_key.pem \ - -CA server_ca_cert.pem \ - -days 3650 \ - -set_serial 1002 \ - -out server_ecdsa_cert.pem \ - -extfile ./openssl.cnf \ - -extensions test_server \ - -sha256 -openssl verify -verbose -CAfile server_ca_cert.pem server_ecdsa_cert.pem - -# Generate ECDSA client cert. -openssl ecparam -genkey -name prime256v1 -out client_ecdsa_key.pem -openssl req -new \ - -key client_ecdsa_key.pem \ - -days 3650 \ - -out client_ecdsa_csr.pem \ - -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=test-client-ecdsa/ \ - -config ./openssl.cnf \ - -reqexts test_client -openssl x509 -req \ - -in client_ecdsa_csr.pem \ - -CAkey client_ca_key.pem \ - -CA client_ca_cert.pem \ - -days 3650 \ - -set_serial 1002 \ - -out client_ecdsa_cert.pem \ - -extfile ./openssl.cnf \ - -extensions test_client \ - -sha256 -openssl verify -verbose -CAfile client_ca_cert.pem client_ecdsa_cert.pem - # Cleanup the CSRs. -rm -f *_csr.pem +rm *_csr.pem diff --git a/testdata/x509/server_ecdsa_cert.pem b/testdata/x509/server_ecdsa_cert.pem deleted file mode 100644 index 4002a12f8988..000000000000 --- a/testdata/x509/server_ecdsa_cert.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID0zCCAbugAwIBAgICA+owDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCVVMx -CzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFzAVBgNV -BAMMDnRlc3Qtc2VydmVyX2NhMB4XDTI2MDgxMzE2NTcyM1oXDTM2MDgxMDE2NTcy -M1owUzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTVkwxDTAL -BgNVBAoMBGdSUEMxGjAYBgNVBAMMEXRlc3Qtc2VydmVyLWVjZHNhMFkwEwYHKoZI -zj0CAQYIKoZIzj0DAQcDQgAEm/JuvNODUPllRhUGl9VnRjF9S+Suhr14fWdkPfw0 -YhoPVtnxR+6FA2PENmgjaXYDb0lkWnfIc6UzppUMVKwS+KN/MH0wDAYDVR0TAQH/ -BAIwADAdBgNVHQ4EFgQUh4bM24iV3qkQVWNEC0ZASRmTChswDgYDVR0PAQH/BAQD -AgOoMB0GA1UdEQQWMBSCEioudGVzdC5leGFtcGxlLmNvbTAfBgNVHSMEGDAWgBQl -88evytJrL7t5BGRbJUeMOW4jaTANBgkqhkiG9w0BAQsFAAOCAgEAu0FnaAY/lxYU -DfFWmKCd92UwOrKqyEP5LFj0TAUgZ0E3NH+0DZffs50ze1X27wjzRwgyX1PLQFb7 -VnctxP1LOU3KZX3lGAA7HXsYXnYyu96BV7r66NUWx4uOfgyFSZ3l2YqQkSpbR8xV -n7kz6z9nRageENrNrXkjt/4YCsBDa9jcZBzQZlzji0rOl/34VKIA6knq7VYvsM7T -7lXkBnihkaI8bXbtmGQtzI8+MaLmRSuKd/QABb2U0Sbp7RtOP5cIi/NhsIwI1ikA -s1fN3oFG8VZ11H7yY2J8XOaJZWYXZRx0r5D+jhWBRiq2VG4aeQyrUHVD7otsTxf/ -lJsQPauuQBbQsvcVXD5nl5KhBQ9cqB18vfAJL6B2RnFASLyoVqBTYMAzXg2bFB5b -SYATQBXbuVj4BpUGNtwSbf27cSu8XEVU64pUYBxBdCMQAKdv0yUwxdLG2NpeCuMa -lX/Bjh8Jjrf7yf7S2oGx4p4ooyxd3CpW7XgB9uUtItnNkkiGUXE1N2VPaZeZqUtj -pUDKDen28IwK6YLyGl6hxLXaDU3q/UpPgb0h83ar/1K0yYMitF7RJWS+OC9hs1ld -vZSeDG4iUjyiVTdpYU7TNoWvHzSmB2KeMBlyLfcA1lZm60DGkgtJ53FLPYDuwmTr -adoDgJgrEC7e+zOa3BfP0oGEQAqlrEA= ------END CERTIFICATE----- From c9e3d9bc05d57881ca6aefb674a7a58d9920851d Mon Sep 17 00:00:00 2001 From: Gregory Cooke Date: Wed, 19 Aug 2026 17:43:20 +0000 Subject: [PATCH 12/12] remove key files --- testdata/x509/client_ecdsa_key.pem | 8 -------- testdata/x509/server_ecdsa_key.pem | 8 -------- 2 files changed, 16 deletions(-) delete mode 100644 testdata/x509/client_ecdsa_key.pem delete mode 100644 testdata/x509/server_ecdsa_key.pem diff --git a/testdata/x509/client_ecdsa_key.pem b/testdata/x509/client_ecdsa_key.pem deleted file mode 100644 index 34ea4bbe2a1e..000000000000 --- a/testdata/x509/client_ecdsa_key.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIJSE6PQ5QiExKQwJNQzvt2tpYdi5VB1cfdQpoJ+mMovIoAoGCCqGSM49 -AwEHoUQDQgAE1XMBcBqmOZofcQlJ3dfX6AbzQYJBJ1VaAvk74BnK2kL9biaIPQ6K -WGvh+xu7PlJ3Rcm86bKGO/jlQ3Vla7aVPQ== ------END EC PRIVATE KEY----- diff --git a/testdata/x509/server_ecdsa_key.pem b/testdata/x509/server_ecdsa_key.pem deleted file mode 100644 index 3d903a9e0499..000000000000 --- a/testdata/x509/server_ecdsa_key.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIBMH+sEh/wd90sM3632flgeue6DJIQG1vK1WSIMHWJHuoAoGCCqGSM49 -AwEHoUQDQgAEm/JuvNODUPllRhUGl9VnRjF9S+Suhr14fWdkPfw0YhoPVtnxR+6F -A2PENmgjaXYDb0lkWnfIc6UzppUMVKwS+A== ------END EC PRIVATE KEY-----