Added flag for user configurable cipher suites
Configuration of list of cipher suites allows a user to disable use of weak ciphers or continue to support them for legacy usage if they so choose. List of available cipher suites at: https://golang.org/pkg/crypto/tls/#pkg-constants Default cipher suites have been updated to: - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_AES_128_GCM_SHA256 - TLS_CHACHA20_POLY1305_SHA256 - TLS_AES_256_GCM_SHA384 MinimumTLS has also been updated to include TLS 1.3 as an option and now defaults to TLS 1.2 since 1.0 and 1.1 have been deprecated. Signed-off-by: David Luu <david@davidluu.info>
This commit is contained in:
parent
22c074842e
commit
1e625d0076
6 changed files with 412 additions and 41 deletions
2
Makefile
2
Makefile
|
@ -50,7 +50,7 @@ version/version.go:
|
|||
|
||||
check: ## run all linters (TODO: enable "unused", "varcheck", "ineffassign", "unconvert", "staticheck", "goimports", "structcheck")
|
||||
@echo "$(WHALE) $@"
|
||||
@GO111MODULE=off golangci-lint run
|
||||
@golangci-lint run
|
||||
|
||||
test: ## run tests, except integration test with test.short
|
||||
@echo "$(WHALE) $@"
|
||||
|
|
|
@ -111,6 +111,9 @@ type Configuration struct {
|
|||
// Specifies the lowest TLS version allowed
|
||||
MinimumTLS string `yaml:"minimumtls,omitempty"`
|
||||
|
||||
// Specifies a list of cipher suites allowed
|
||||
CipherSuites []string `yaml:"ciphersuites,omitempty"`
|
||||
|
||||
// LetsEncrypt is used to configuration setting up TLS through
|
||||
// Let's Encrypt instead of manually specifying certificate and
|
||||
// key. If a TLS certificate is specified, the Let's Encrypt
|
||||
|
|
|
@ -80,11 +80,12 @@ var configStruct = Configuration{
|
|||
RelativeURLs bool `yaml:"relativeurls,omitempty"`
|
||||
DrainTimeout time.Duration `yaml:"draintimeout,omitempty"`
|
||||
TLS struct {
|
||||
Certificate string `yaml:"certificate,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
ClientCAs []string `yaml:"clientcas,omitempty"`
|
||||
MinimumTLS string `yaml:"minimumtls,omitempty"`
|
||||
LetsEncrypt struct {
|
||||
Certificate string `yaml:"certificate,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
ClientCAs []string `yaml:"clientcas,omitempty"`
|
||||
MinimumTLS string `yaml:"minimumtls,omitempty"`
|
||||
CipherSuites []string `yaml:"ciphersuites,omitempty"`
|
||||
LetsEncrypt struct {
|
||||
CacheFile string `yaml:"cachefile,omitempty"`
|
||||
Email string `yaml:"email,omitempty"`
|
||||
Hosts []string `yaml:"hosts,omitempty"`
|
||||
|
@ -103,11 +104,12 @@ var configStruct = Configuration{
|
|||
} `yaml:"http2,omitempty"`
|
||||
}{
|
||||
TLS: struct {
|
||||
Certificate string `yaml:"certificate,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
ClientCAs []string `yaml:"clientcas,omitempty"`
|
||||
MinimumTLS string `yaml:"minimumtls,omitempty"`
|
||||
LetsEncrypt struct {
|
||||
Certificate string `yaml:"certificate,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
ClientCAs []string `yaml:"clientcas,omitempty"`
|
||||
MinimumTLS string `yaml:"minimumtls,omitempty"`
|
||||
CipherSuites []string `yaml:"ciphersuites,omitempty"`
|
||||
LetsEncrypt struct {
|
||||
CacheFile string `yaml:"cachefile,omitempty"`
|
||||
Email string `yaml:"email,omitempty"`
|
||||
Hosts []string `yaml:"hosts,omitempty"`
|
||||
|
|
|
@ -795,7 +795,10 @@ http:
|
|||
clientcas:
|
||||
- /path/to/ca.pem
|
||||
- /path/to/another/ca.pem
|
||||
minimumtls: tls1.0
|
||||
minimumtls: tls1.2
|
||||
ciphersuites:
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
letsencrypt:
|
||||
cachefile: /path/to/cache-file
|
||||
email: emailused@letsencrypt.com
|
||||
|
@ -831,10 +834,49 @@ and proxy connections to the registry server.
|
|||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------------------------------------------------|
|
||||
| `certificate` | yes | Absolute path to the x509 certificate file. |
|
||||
| `key` | yes | Absolute path to the x509 private key file. |
|
||||
| `clientcas` | no | An array of absolute paths to x509 CA files. |
|
||||
| `minimumtls` | no | Minimum TLS version allowed (tls1.0, tls1.1, tls1.2). Defaults to tls1.0 |
|
||||
| `certificate` | yes | Absolute path to the x509 certificate file. |
|
||||
| `key` | yes | Absolute path to the x509 private key file. |
|
||||
| `clientcas` | no | An array of absolute paths to x509 CA files. |
|
||||
| `minimumtls` | no | Minimum TLS version allowed (tls1.0, tls1.1, tls1.2, tls1.3). Defaults to tls1.2 |
|
||||
| `ciphersuites` | no | Cipher suites allowed. Please see below for allowed values and default. |
|
||||
|
||||
Available cipher suites:
|
||||
- TLS_RSA_WITH_RC4_128_SHA
|
||||
- TLS_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
- TLS_RSA_WITH_AES_128_CBC_SHA
|
||||
- TLS_RSA_WITH_AES_256_CBC_SHA
|
||||
- TLS_RSA_WITH_AES_128_CBC_SHA256
|
||||
- TLS_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
|
||||
- TLS_ECDHE_RSA_WITH_RC4_128_SHA
|
||||
- TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
- TLS_AES_128_GCM_SHA256
|
||||
- TLS_AES_256_GCM_SHA384
|
||||
- TLS_CHACHA20_POLY1305_SHA256
|
||||
|
||||
Default cipher suites:
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_AES_128_GCM_SHA256
|
||||
- TLS_CHACHA20_POLY1305_SHA256
|
||||
- TLS_AES_256_GCM_SHA384
|
||||
|
||||
### `letsencrypt`
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -33,6 +34,60 @@ import (
|
|||
"github.com/distribution/distribution/v3/version"
|
||||
)
|
||||
|
||||
// a map of TLS cipher suite names to constants in https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
var cipherSuites = map[string]uint16{
|
||||
// TLS 1.0 - 1.2 cipher suites
|
||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
// TLS 1.3 cipher suites
|
||||
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
|
||||
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
|
||||
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
// a list of default ciphersuites to utilize
|
||||
var defaultCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_AES_128_GCM_SHA256,
|
||||
tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
// maps tls version strings to constants
|
||||
var defaultTLSVersionStr = "tls1.2"
|
||||
var tlsVersions = map[string]uint16{
|
||||
// user specified values
|
||||
"tls1.0": tls.VersionTLS10,
|
||||
"tls1.1": tls.VersionTLS11,
|
||||
"tls1.2": tls.VersionTLS12,
|
||||
"tls1.3": tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// this channel gets notified when process receives signal. It is global to ease unit testing
|
||||
var quit = make(chan os.Signal, 1)
|
||||
|
||||
|
@ -127,6 +182,35 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg
|
|||
}, nil
|
||||
}
|
||||
|
||||
// takes a list of cipher suites and converts it to a list of respective tls constants
|
||||
// if an empty list is provided, then the defaults will be used
|
||||
func getCipherSuites(names []string) ([]uint16, error) {
|
||||
if len(names) == 0 {
|
||||
return defaultCipherSuites, nil
|
||||
}
|
||||
cipherSuiteConsts := make([]uint16, len(names))
|
||||
for i, name := range names {
|
||||
cipherSuiteConst, ok := cipherSuites[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown TLS cipher suite '%s' specified for http.tls.cipherSuites", name)
|
||||
}
|
||||
cipherSuiteConsts[i] = cipherSuiteConst
|
||||
}
|
||||
return cipherSuiteConsts, nil
|
||||
}
|
||||
|
||||
// takes a list of cipher suite ids and converts it to a list of respective names
|
||||
func getCipherSuiteNames(ids []uint16) []string {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
names := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
names[i] = tls.CipherSuiteName(id)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// ListenAndServe runs the registry's HTTP server.
|
||||
func (registry *Registry) ListenAndServe() error {
|
||||
config := registry.config
|
||||
|
@ -137,35 +221,27 @@ func (registry *Registry) ListenAndServe() error {
|
|||
}
|
||||
|
||||
if config.HTTP.TLS.Certificate != "" || config.HTTP.TLS.LetsEncrypt.CacheFile != "" {
|
||||
var tlsMinVersion uint16
|
||||
if config.HTTP.TLS.MinimumTLS == "" {
|
||||
tlsMinVersion = tls.VersionTLS10
|
||||
} else {
|
||||
switch config.HTTP.TLS.MinimumTLS {
|
||||
case "tls1.0":
|
||||
tlsMinVersion = tls.VersionTLS10
|
||||
case "tls1.1":
|
||||
tlsMinVersion = tls.VersionTLS11
|
||||
case "tls1.2":
|
||||
tlsMinVersion = tls.VersionTLS12
|
||||
default:
|
||||
return fmt.Errorf("unknown minimum TLS level '%s' specified for http.tls.minimumtls", config.HTTP.TLS.MinimumTLS)
|
||||
}
|
||||
dcontext.GetLogger(registry.app).Infof("restricting TLS to %s or higher", config.HTTP.TLS.MinimumTLS)
|
||||
config.HTTP.TLS.MinimumTLS = defaultTLSVersionStr
|
||||
}
|
||||
tlsMinVersion, ok := tlsVersions[config.HTTP.TLS.MinimumTLS]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown minimum TLS level '%s' specified for http.tls.minimumtls", config.HTTP.TLS.MinimumTLS)
|
||||
}
|
||||
dcontext.GetLogger(registry.app).Infof("restricting TLS version to %s or higher", config.HTTP.TLS.MinimumTLS)
|
||||
|
||||
tlsCipherSuites, err := getCipherSuites(config.HTTP.TLS.CipherSuites)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dcontext.GetLogger(registry.app).Infof("restricting TLS cipher suites to: %s", strings.Join(getCipherSuiteNames(tlsCipherSuites), ","))
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
ClientAuth: tls.NoClientCert,
|
||||
NextProtos: nextProtos(config),
|
||||
MinVersion: tlsMinVersion,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
CipherSuites: tlsCipherSuites,
|
||||
}
|
||||
|
||||
if config.HTTP.TLS.LetsEncrypt.CacheFile != "" {
|
||||
|
|
|
@ -3,12 +3,24 @@ package registry
|
|||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -38,18 +50,30 @@ func TestNextProtos(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func setupRegistry() (*Registry, error) {
|
||||
type registryTLSConfig struct {
|
||||
cipherSuites []string
|
||||
certificatePath string
|
||||
privateKeyPath string
|
||||
certificate *tls.Certificate
|
||||
}
|
||||
|
||||
func setupRegistry(tlsCfg *registryTLSConfig, addr string) (*Registry, error) {
|
||||
config := &configuration.Configuration{}
|
||||
// TODO: this needs to change to something ephemeral as the test will fail if there is any server
|
||||
// already listening on port 5000
|
||||
config.HTTP.Addr = ":5000"
|
||||
config.HTTP.Addr = addr
|
||||
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
|
||||
if tlsCfg != nil {
|
||||
config.HTTP.TLS.CipherSuites = tlsCfg.cipherSuites
|
||||
config.HTTP.TLS.Certificate = tlsCfg.certificatePath
|
||||
config.HTTP.TLS.Key = tlsCfg.privateKeyPath
|
||||
}
|
||||
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
|
||||
return NewRegistry(context.Background(), config)
|
||||
}
|
||||
|
||||
func TestGracefulShutdown(t *testing.T) {
|
||||
registry, err := setupRegistry()
|
||||
registry, err := setupRegistry(nil, ":5000")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -98,3 +122,227 @@ func TestGracefulShutdown(t *testing.T) {
|
|||
t.Error("Body is not {}; ", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCipherSuite(t *testing.T) {
|
||||
resp, err := getCipherSuites([]string{"TLS_RSA_WITH_AES_128_CBC_SHA"})
|
||||
if err != nil || len(resp) != 1 || resp[0] != tls.TLS_RSA_WITH_AES_128_CBC_SHA {
|
||||
t.Errorf("expected cipher suite %q, got %q",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
strings.Join(getCipherSuiteNames(resp), ","),
|
||||
)
|
||||
}
|
||||
|
||||
resp, err = getCipherSuites([]string{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_AES_128_GCM_SHA256"})
|
||||
if err != nil || len(resp) != 2 ||
|
||||
resp[0] != tls.TLS_RSA_WITH_AES_128_CBC_SHA || resp[1] != tls.TLS_AES_128_GCM_SHA256 {
|
||||
t.Errorf("expected cipher suites %q, got %q",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA,TLS_AES_128_GCM_SHA256",
|
||||
strings.Join(getCipherSuiteNames(resp), ","),
|
||||
)
|
||||
}
|
||||
|
||||
_, err = getCipherSuites([]string{"TLS_RSA_WITH_AES_128_CBC_SHA", "bad_input"})
|
||||
if err == nil {
|
||||
t.Error("did not return expected error about unknown cipher suite")
|
||||
}
|
||||
}
|
||||
|
||||
func buildRegistryTLSConfig(name, keyType string, cipherSuites []string) (*registryTLSConfig, error) {
|
||||
var priv interface{}
|
||||
var pub crypto.PublicKey
|
||||
var err error
|
||||
switch keyType {
|
||||
case "rsa":
|
||||
priv, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create rsa private key: %v", err)
|
||||
}
|
||||
rsaKey := priv.(*rsa.PrivateKey)
|
||||
pub = rsaKey.Public()
|
||||
case "ecdsa":
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ecdsa private key: %v", err)
|
||||
}
|
||||
ecdsaKey := priv.(*ecdsa.PrivateKey)
|
||||
pub = ecdsaKey.Public()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type: %v", keyType)
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Minute)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create serial number: %v", err)
|
||||
}
|
||||
cert := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"registry_test"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
DNSNames: []string{"localhost"},
|
||||
IsCA: true,
|
||||
}
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, pub, priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(os.TempDir()); os.IsNotExist(err) {
|
||||
os.Mkdir(os.TempDir(), 1777)
|
||||
}
|
||||
|
||||
certPath := path.Join(os.TempDir(), name+".pem")
|
||||
certOut, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create pem: %v", err)
|
||||
}
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
return nil, fmt.Errorf("failed to write data to %s: %v", certPath, err)
|
||||
}
|
||||
if err := certOut.Close(); err != nil {
|
||||
return nil, fmt.Errorf("error closing %s: %v", certPath, err)
|
||||
}
|
||||
|
||||
keyPath := path.Join(os.TempDir(), name+".key")
|
||||
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open %s for writing: %v", keyPath, err)
|
||||
}
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal private key: %v", err)
|
||||
}
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
||||
return nil, fmt.Errorf("failed to write data to key.pem: %v", err)
|
||||
}
|
||||
if err := keyOut.Close(); err != nil {
|
||||
return nil, fmt.Errorf("error closing %s: %v", keyPath, err)
|
||||
}
|
||||
|
||||
tlsCert := tls.Certificate{
|
||||
Certificate: [][]byte{derBytes},
|
||||
PrivateKey: priv,
|
||||
}
|
||||
|
||||
tlsTestCfg := registryTLSConfig{
|
||||
cipherSuites: cipherSuites,
|
||||
certificatePath: certPath,
|
||||
privateKeyPath: keyPath,
|
||||
certificate: &tlsCert,
|
||||
}
|
||||
|
||||
return &tlsTestCfg, nil
|
||||
}
|
||||
|
||||
func TestRegistrySupportedCipherSuite(t *testing.T) {
|
||||
name := "registry_test_server_supported_cipher"
|
||||
cipherSuites := []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}
|
||||
serverTLS, err := buildRegistryTLSConfig(name, "rsa", cipherSuites)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
registry, err := setupRegistry(serverTLS, ":5001")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// run registry server
|
||||
var errchan chan error
|
||||
go func() {
|
||||
errchan <- registry.ListenAndServe()
|
||||
}()
|
||||
select {
|
||||
case err = <-errchan:
|
||||
t.Fatalf("Error listening: %v", err)
|
||||
default:
|
||||
}
|
||||
|
||||
// Wait for some unknown random time for server to start listening
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// send tls request with server supported cipher suite
|
||||
clientCipherSuites, err := getCipherSuites(cipherSuites)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clientTLS := tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
CipherSuites: clientCipherSuites,
|
||||
}
|
||||
dialer := net.Dialer{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
conn, err := tls.DialWithDialer(&dialer, "tcp", "127.0.0.1:5001", &clientTLS)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(conn, "GET /v2/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Status != "200 OK" {
|
||||
t.Error("response status is not 200 OK: ", resp.Status)
|
||||
}
|
||||
if body, err := ioutil.ReadAll(resp.Body); err != nil || string(body) != "{}" {
|
||||
t.Error("Body is not {}; ", string(body))
|
||||
}
|
||||
|
||||
// send stop signal
|
||||
quit <- os.Interrupt
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestRegistryUnsupportedCipherSuite(t *testing.T) {
|
||||
name := "registry_test_server_unsupported_cipher"
|
||||
serverTLS, err := buildRegistryTLSConfig(name, "rsa", []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA358"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
registry, err := setupRegistry(serverTLS, ":5002")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// run registry server
|
||||
var errchan chan error
|
||||
go func() {
|
||||
errchan <- registry.ListenAndServe()
|
||||
}()
|
||||
select {
|
||||
case err = <-errchan:
|
||||
t.Fatalf("Error listening: %v", err)
|
||||
default:
|
||||
}
|
||||
|
||||
// Wait for some unknown random time for server to start listening
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// send tls request with server unsupported cipher suite
|
||||
clientTLS := tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
|
||||
}
|
||||
dialer := net.Dialer{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
_, err = tls.DialWithDialer(&dialer, "tcp", "127.0.0.1:5002", &clientTLS)
|
||||
if err == nil {
|
||||
t.Error("expected TLS connection to timeout")
|
||||
}
|
||||
|
||||
// send stop signal
|
||||
quit <- os.Interrupt
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue