Browse Source

pkg/transport: add "TLSInfo.CipherSuites" field

Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Gyuho Lee 7 years ago
parent
commit
3fb9cc84c6
2 changed files with 82 additions and 0 deletions
  1. 9 0
      pkg/transport/listener.go
  2. 73 0
      pkg/transport/transport_test.go

+ 9 - 0
pkg/transport/listener.go

@@ -74,6 +74,11 @@ type TLSInfo struct {
 	// connection will be closed immediately afterwards.
 	HandshakeFailure func(*tls.Conn, error)
 
+	// CipherSuites is a list of supported cipher suites.
+	// If empty, Go auto-populates it by default.
+	// Note that cipher suites are prioritized in the given order.
+	CipherSuites []uint16
+
 	selfCert bool
 
 	// parseFunc exists to simplify testing. Typically, parseFunc
@@ -243,6 +248,10 @@ func (info TLSInfo) baseConfig() (*tls.Config, error) {
 		ServerName: info.ServerName,
 	}
 
+	if len(info.CipherSuites) > 0 {
+		cfg.CipherSuites = info.CipherSuites
+	}
+
 	if info.AllowedCN != "" {
 		cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
 			for _, chains := range verifiedChains {

+ 73 - 0
pkg/transport/transport_test.go

@@ -0,0 +1,73 @@
+// Copyright 2018 The etcd 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 transport
+
+import (
+	"crypto/tls"
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+)
+
+// TestNewTransportTLSInvalidCipherSuites expects a client with invalid
+// cipher suites fail to handshake with the server.
+func TestNewTransportTLSInvalidCipherSuites(t *testing.T) {
+	tlsInfo, del, err := createSelfCert()
+	if err != nil {
+		t.Fatalf("unable to create cert: %v", err)
+	}
+	defer del()
+
+	cipherSuites := []uint16{
+		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+	}
+
+	// make server and client have unmatched cipher suites
+	srvTLS, cliTLS := *tlsInfo, *tlsInfo
+	srvTLS.CipherSuites, cliTLS.CipherSuites = cipherSuites[:2], cipherSuites[2:]
+
+	ln, err := NewListener("127.0.0.1:0", "https", &srvTLS)
+	if err != nil {
+		t.Fatalf("unexpected NewListener error: %v", err)
+	}
+	defer ln.Close()
+
+	donec := make(chan struct{})
+	go func() {
+		ln.Accept()
+		donec <- struct{}{}
+	}()
+	go func() {
+		tr, err := NewTransport(cliTLS, 3*time.Second)
+		if err != nil {
+			t.Fatalf("unexpected NewTransport error: %v", err)
+		}
+		cli := &http.Client{Transport: tr}
+		_, gerr := cli.Get("https://" + ln.Addr().String())
+		if gerr == nil || !strings.Contains(gerr.Error(), "tls: handshake failure") {
+			t.Fatal("expected client TLS handshake error")
+		}
+		ln.Close()
+		donec <- struct{}{}
+	}()
+	<-donec
+	<-donec
+}