Skip to content

Commit dd39cdb

Browse files
authored
credentials: if not set, restrict to TLS v1.2+ and CipherSuites per RFC7540 (#6797)
1 parent 8645f95 commit dd39cdb

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed

credentials/tls.go

+29
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,39 @@ func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
153153
return nil
154154
}
155155

156+
// The following cipher suites are forbidden for use with HTTP/2 by
157+
// https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
158+
var tls12ForbiddenCipherSuites = map[uint16]struct{}{
159+
tls.TLS_RSA_WITH_AES_128_CBC_SHA: {},
160+
tls.TLS_RSA_WITH_AES_256_CBC_SHA: {},
161+
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: {},
162+
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: {},
163+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {},
164+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {},
165+
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {},
166+
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {},
167+
}
168+
156169
// NewTLS uses c to construct a TransportCredentials based on TLS.
157170
func NewTLS(c *tls.Config) TransportCredentials {
158171
tc := &tlsCreds{credinternal.CloneTLSConfig(c)}
159172
tc.config.NextProtos = credinternal.AppendH2ToNextProtos(tc.config.NextProtos)
173+
// If the user did not configure a MinVersion and did not configure a
174+
// MaxVersion < 1.2, use MinVersion=1.2, which is required by
175+
// https://datatracker.ietf.org/doc/html/rfc7540#section-9.2
176+
if tc.config.MinVersion == 0 && (tc.config.MaxVersion == 0 || tc.config.MaxVersion >= tls.VersionTLS12) {
177+
tc.config.MinVersion = tls.VersionTLS12
178+
}
179+
// If the user did not configure CipherSuites, use all "secure" cipher
180+
// suites reported by the TLS package, but remove some explicitly forbidden
181+
// by https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
182+
if tc.config.CipherSuites == nil {
183+
for _, cs := range tls.CipherSuites() {
184+
if _, ok := tls12ForbiddenCipherSuites[cs.ID]; !ok {
185+
tc.config.CipherSuites = append(tc.config.CipherSuites, cs.ID)
186+
}
187+
}
188+
}
160189
return tc
161190
}
162191

credentials/tls_ext_test.go

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
*
3+
* Copyright 2023 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package credentials_test
20+
21+
import (
22+
"context"
23+
"crypto/tls"
24+
"crypto/x509"
25+
"fmt"
26+
"os"
27+
"strings"
28+
"testing"
29+
"time"
30+
31+
"google.golang.org/grpc"
32+
"google.golang.org/grpc/codes"
33+
"google.golang.org/grpc/credentials"
34+
"google.golang.org/grpc/internal/grpctest"
35+
"google.golang.org/grpc/internal/stubserver"
36+
"google.golang.org/grpc/status"
37+
"google.golang.org/grpc/testdata"
38+
39+
testgrpc "google.golang.org/grpc/interop/grpc_testing"
40+
testpb "google.golang.org/grpc/interop/grpc_testing"
41+
)
42+
43+
const defaultTestTimeout = 10 * time.Second
44+
45+
type s struct {
46+
grpctest.Tester
47+
}
48+
49+
func Test(t *testing.T) {
50+
grpctest.RunSubTests(t, s{})
51+
}
52+
53+
var serverCert tls.Certificate
54+
var certPool *x509.CertPool
55+
var serverName = "x.test.example.com"
56+
57+
func init() {
58+
var err error
59+
serverCert, err = tls.LoadX509KeyPair(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem"))
60+
if err != nil {
61+
panic(fmt.Sprintf("tls.LoadX509KeyPair(server1.pem, server1.key) failed: %v", err))
62+
}
63+
64+
b, err := os.ReadFile(testdata.Path("x509/server_ca_cert.pem"))
65+
if err != nil {
66+
panic(fmt.Sprintf("Error reading CA cert file: %v", err))
67+
}
68+
certPool = x509.NewCertPool()
69+
if !certPool.AppendCertsFromPEM(b) {
70+
panic("Error appending cert from PEM")
71+
}
72+
}
73+
74+
// Tests that the MinVersion of tls.Config is set to 1.2 if it is not already
75+
// set by the user.
76+
func (s) TestTLS_MinVersion12(t *testing.T) {
77+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
78+
defer cancel()
79+
80+
// Create server creds without a minimum version.
81+
serverCreds := credentials.NewTLS(&tls.Config{
82+
// MinVersion should be set to 1.2 by gRPC by default.
83+
Certificates: []tls.Certificate{serverCert},
84+
})
85+
ss := stubserver.StubServer{
86+
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
87+
return &testpb.Empty{}, nil
88+
},
89+
}
90+
91+
// Create client creds that supports V1.0-V1.1.
92+
clientCreds := credentials.NewTLS(&tls.Config{
93+
ServerName: serverName,
94+
RootCAs: certPool,
95+
MinVersion: tls.VersionTLS10,
96+
MaxVersion: tls.VersionTLS11,
97+
})
98+
99+
// Start server and client separately, because Start() blocks on a
100+
// successful connection, which we will not get.
101+
if err := ss.StartServer(grpc.Creds(serverCreds)); err != nil {
102+
t.Fatalf("Error starting server: %v", err)
103+
}
104+
defer ss.Stop()
105+
106+
cc, err := grpc.Dial(ss.Address, grpc.WithTransportCredentials(clientCreds))
107+
if err != nil {
108+
t.Fatalf("grpc.Dial error: %v", err)
109+
}
110+
defer cc.Close()
111+
112+
client := testgrpc.NewTestServiceClient(cc)
113+
114+
const wantStr = "authentication handshake failed"
115+
if _, err = client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable || !strings.Contains(status.Convert(err).Message(), wantStr) {
116+
t.Fatalf("EmptyCall err = %v; want code=%v, message contains %q", err, codes.Unavailable, wantStr)
117+
}
118+
}
119+
120+
// Tests that the MinVersion of tls.Config is not changed if it is set by the
121+
// user.
122+
func (s) TestTLS_MinVersionOverridable(t *testing.T) {
123+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
124+
defer cancel()
125+
126+
var allCipherSuites []uint16
127+
for _, cs := range tls.CipherSuites() {
128+
allCipherSuites = append(allCipherSuites, cs.ID)
129+
}
130+
131+
// Create server creds that allow v1.0.
132+
serverCreds := credentials.NewTLS(&tls.Config{
133+
MinVersion: tls.VersionTLS10,
134+
Certificates: []tls.Certificate{serverCert},
135+
CipherSuites: allCipherSuites,
136+
})
137+
ss := stubserver.StubServer{
138+
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
139+
return &testpb.Empty{}, nil
140+
},
141+
}
142+
143+
// Create client creds that supports V1.0-V1.1.
144+
clientCreds := credentials.NewTLS(&tls.Config{
145+
ServerName: serverName,
146+
RootCAs: certPool,
147+
CipherSuites: allCipherSuites,
148+
MinVersion: tls.VersionTLS10,
149+
MaxVersion: tls.VersionTLS11,
150+
})
151+
152+
if err := ss.Start([]grpc.ServerOption{grpc.Creds(serverCreds)}, grpc.WithTransportCredentials(clientCreds)); err != nil {
153+
t.Fatalf("Error starting stub server: %v", err)
154+
}
155+
defer ss.Stop()
156+
157+
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
158+
t.Fatalf("EmptyCall err = %v; want <nil>", err)
159+
}
160+
}
161+
162+
// Tests that CipherSuites is set to exclude HTTP/2 forbidden suites by default.
163+
func (s) TestTLS_CipherSuites(t *testing.T) {
164+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
165+
defer cancel()
166+
167+
// Create server creds without cipher suites.
168+
serverCreds := credentials.NewTLS(&tls.Config{
169+
Certificates: []tls.Certificate{serverCert},
170+
})
171+
ss := stubserver.StubServer{
172+
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
173+
return &testpb.Empty{}, nil
174+
},
175+
}
176+
177+
// Create client creds that use a forbidden suite only.
178+
clientCreds := credentials.NewTLS(&tls.Config{
179+
ServerName: serverName,
180+
RootCAs: certPool,
181+
CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA},
182+
MaxVersion: tls.VersionTLS12, // TLS1.3 cipher suites are not configurable, so limit to 1.2.
183+
})
184+
185+
// Start server and client separately, because Start() blocks on a
186+
// successful connection, which we will not get.
187+
if err := ss.StartServer(grpc.Creds(serverCreds)); err != nil {
188+
t.Fatalf("Error starting server: %v", err)
189+
}
190+
defer ss.Stop()
191+
192+
cc, err := grpc.Dial("dns:"+ss.Address, grpc.WithTransportCredentials(clientCreds))
193+
if err != nil {
194+
t.Fatalf("grpc.Dial error: %v", err)
195+
}
196+
defer cc.Close()
197+
198+
client := testgrpc.NewTestServiceClient(cc)
199+
200+
const wantStr = "authentication handshake failed"
201+
if _, err = client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable || !strings.Contains(status.Convert(err).Message(), wantStr) {
202+
t.Fatalf("EmptyCall err = %v; want code=%v, message contains %q", err, codes.Unavailable, wantStr)
203+
}
204+
}
205+
206+
// Tests that CipherSuites is not overridden when it is set.
207+
func (s) TestTLS_CipherSuitesOverridable(t *testing.T) {
208+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
209+
defer cancel()
210+
211+
// Create server that allows only a forbidden cipher suite.
212+
serverCreds := credentials.NewTLS(&tls.Config{
213+
Certificates: []tls.Certificate{serverCert},
214+
CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA},
215+
})
216+
ss := stubserver.StubServer{
217+
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
218+
return &testpb.Empty{}, nil
219+
},
220+
}
221+
222+
// Create server that allows only a forbidden cipher suite.
223+
clientCreds := credentials.NewTLS(&tls.Config{
224+
ServerName: serverName,
225+
RootCAs: certPool,
226+
CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA},
227+
MaxVersion: tls.VersionTLS12, // TLS1.3 cipher suites are not configurable, so limit to 1.2.
228+
})
229+
230+
if err := ss.Start([]grpc.ServerOption{grpc.Creds(serverCreds)}, grpc.WithTransportCredentials(clientCreds)); err != nil {
231+
t.Fatalf("Error starting stub server: %v", err)
232+
}
233+
defer ss.Stop()
234+
235+
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
236+
t.Fatalf("EmptyCall err = %v; want <nil>", err)
237+
}
238+
}

0 commit comments

Comments
 (0)