diff --git a/docs/web-configuration.md b/docs/web-configuration.md index 2f415265..8f1946dc 100644 --- a/docs/web-configuration.md +++ b/docs/web-configuration.md @@ -32,12 +32,31 @@ tls_server_config: # https://golang.org/pkg/crypto/tls/#ClientAuthType # # NOTE: If you want to enable client authentication, you need to use - # RequireAndVerifyClientCert. Other values are insecure. + # RequireAndVerifyClientCert, or RequireAnyClientCert with + # client_fingerprints option (see below). Other values are insecure. [ client_auth_type: | default = "NoClientCert" ] # CA certificate for client certificate authentication to the server. [ client_ca_file: ] + # List of accepted SHA256 client fingerprints. The format is hex string + # (with or without colons). Useful for simple setups that have no need + # for full-blown CA for clients. + # + # Fingerprint can be obtained using openssl x509 -fingerprint -sha256. + # + # NOTE: empty list is different from not setting this option at all. + # The former will reject all certificates outright, the latter will + # make client_auth_type decide on the certificate validity. + # + # NOTE: client_ca_file should be set to RequireAnyClientCert. + # RequestClientCert and VerifyClientCertIfGiven can be bypassed by not + # sending any certificate. RequireAndVerifyClientCert would require + # the client certificate to be signed by client_ca_file (in addition + # to be whitelisted), which is probably not what you want. + [ client_fingerprints: + [ - ] ] + # Minimum TLS version that is acceptable. [ min_version: | default = "TLS12" ] diff --git a/web/testdata/client2_selfsigned.key b/web/testdata/client2_selfsigned.key new file mode 100644 index 00000000..d4dad255 --- /dev/null +++ b/web/testdata/client2_selfsigned.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDC8CYtAwKp1uLWXLXFE +Ue2Bz6PijwHZcL7jAxtlk2dbW0GlRQ+rcalHCcnExIIKAAehZANiAATlPRxDnbJb +Zq9u+jh7DyEJumQZFqjIDFdFxfHtI6hwyMtlL6FIwpqn3z4uXs2wx6/NsD4XOChy +j/tXXKCHS/22+51TivjGA53c9bLgc4dK/uJJNSivp0kymbtA5vgKzJE= +-----END PRIVATE KEY----- diff --git a/web/testdata/client2_selfsigned.pem b/web/testdata/client2_selfsigned.pem new file mode 100644 index 00000000..2bd61a7a --- /dev/null +++ b/web/testdata/client2_selfsigned.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIByjCCAU+gAwIBAgIUYcG9p4RzCRdvUGa9BWvc6rB/wMYwCgYIKoZIzj0EAwIw +EDEOMAwGA1UEAwwFdGVzdDIwIBcNMjEwODIwMTUzMjE4WhgPMjEyMTA3MjcxNTMy +MThaMBAxDjAMBgNVBAMMBXRlc3QyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE5T0c +Q52yW2avbvo4ew8hCbpkGRaoyAxXRcXx7SOocMjLZS+hSMKap98+Ll7NsMevzbA+ +Fzgoco/7V1ygh0v9tvudU4r4xgOd3PWy4HOHSv7iSTUor6dJMpm7QOb4CsyRo2gw +ZjAdBgNVHQ4EFgQUWpsZ2aWo6WEI2LiNQXoWKYr0rlkwHwYDVR0jBBgwFoAUWpsZ +2aWo6WEI2LiNQXoWKYr0rlkwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUEDDAKBggr +BgEFBQcDAjAKBggqhkjOPQQDAgNpADBmAjEA/Mv4OjCqVw8PzxQW4FJmZNyJB4ps +xkAUBRpDy75n64ICsWKX/Mille0bo+C8d63JAjEA3IH/y1O4oyCaawNpibfcwSZK +7ND9Z+WTJi50EumXUWKirmb/V59ToH5nc10x7NDX +-----END CERTIFICATE----- diff --git a/web/testdata/client_selfsigned.key b/web/testdata/client_selfsigned.key new file mode 100644 index 00000000..68d922d1 --- /dev/null +++ b/web/testdata/client_selfsigned.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDmoTxYcBfRrqYb/TJy +oHlBKo4/fNk2LBUZxpC3HeKasAQzS9AB1evw3k4M3Pe8c4+hZANiAASxUS40AV1Y +h1ABCLCoJcG9B8Twv/gg2tU0zqdW9FhK2Fu13MeZkTRJLFVgFzlmCj3o9dIX8iUi +RP9jYkQG6wHD44kb9NQ4A7fjs8DOANGWKgY/96liSh/ynPKCoWONW8w= +-----END PRIVATE KEY----- diff --git a/web/testdata/client_selfsigned.pem b/web/testdata/client_selfsigned.pem new file mode 100644 index 00000000..d25ddca8 --- /dev/null +++ b/web/testdata/client_selfsigned.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxzCCAU2gAwIBAgIUGCNnsX0qd0HD7UaQsx67ze0UaNowCgYIKoZIzj0EAwIw +DzENMAsGA1UEAwwEdGVzdDAgFw0yMTA4MjAxNDQ5MTRaGA8yMTIxMDcyNzE0NDkx +NFowDzENMAsGA1UEAwwEdGVzdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABLFRLjQB +XViHUAEIsKglwb0HxPC/+CDa1TTOp1b0WErYW7Xcx5mRNEksVWAXOWYKPej10hfy +JSJE/2NiRAbrAcPjiRv01DgDt+OzwM4A0ZYqBj/3qWJKH/Kc8oKhY41bzKNoMGYw +HQYDVR0OBBYEFPRbKtRBgw+AZ0b6T8oWw/+QoyjaMB8GA1UdIwQYMBaAFPRbKtRB +gw+AZ0b6T8oWw/+QoyjaMA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYB +BQUHAwIwCgYIKoZIzj0EAwIDaAAwZQIwZqwXMJiTycZdmLN+Pwk/8Sb7wQazbocb +16Zw5mZXqFJ4K+74OQMZ33i82hYohtE/AjEAn0a8q8QupgiXpr0I/PvGTRKqLQRM +0mptBvpn/DcB2p3Hi80GJhtchz9Z0OqbMX4S +-----END CERTIFICATE----- diff --git a/web/testdata/tls_config_noAuth.bad.yml b/web/testdata/tls_config_noAuth.bad.yml index a5101bfd..ac506ed3 100644 --- a/web/testdata/tls_config_noAuth.bad.yml +++ b/web/testdata/tls_config_noAuth.bad.yml @@ -1,4 +1,4 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" - client_ca_file : "tls-ca-chain.pem" + client_ca_file : "/dev/null" diff --git a/web/testdata/tls_config_noAuth.fingerprint.bad.yml b/web/testdata/tls_config_noAuth.fingerprint.bad.yml new file mode 100644 index 00000000..5abea069 --- /dev/null +++ b/web/testdata/tls_config_noAuth.fingerprint.bad.yml @@ -0,0 +1,4 @@ +tls_server_config : + cert_file : "server.crt" + key_file : "server.key" + client_fingerprints: [] diff --git a/web/testdata/tls_config_noAuth.fingerprint.good.yml b/web/testdata/tls_config_noAuth.fingerprint.good.yml new file mode 100644 index 00000000..23eeadbe --- /dev/null +++ b/web/testdata/tls_config_noAuth.fingerprint.good.yml @@ -0,0 +1,6 @@ +tls_server_config: + cert_file: "server.crt" + key_file: "server.key" + client_auth_type: "RequireAnyClientCert" + client_fingerprints: + - 92:CA:04:83:30:41:AE:6D:70:07:C3:3F:77:D7:1D:17:3E:00:7A:61:71:7D:39:A6:6F:02:67:10:50:CC:BA:37 diff --git a/web/testdata/tls_config_noAuth.fingerprint_empty.good.yml b/web/testdata/tls_config_noAuth.fingerprint_empty.good.yml new file mode 100644 index 00000000..22e2603d --- /dev/null +++ b/web/testdata/tls_config_noAuth.fingerprint_empty.good.yml @@ -0,0 +1,5 @@ +tls_server_config: + cert_file: "server.crt" + key_file: "server.key" + client_auth_type: "RequireAnyClientCert" + client_fingerprints: [] diff --git a/web/testdata/tls_config_noAuth.good.blocking.yml b/web/testdata/tls_config_noAuth.good.blocking.yml index a3e0756f..5457f686 100644 --- a/web/testdata/tls_config_noAuth.good.blocking.yml +++ b/web/testdata/tls_config_noAuth.good.blocking.yml @@ -2,4 +2,3 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "RequireAndVerifyClientCert" - client_ca_file: "tls-ca-chain.pem" \ No newline at end of file diff --git a/web/testdata/tls_config_noAuth.good.yml b/web/testdata/tls_config_noAuth.good.yml index 82cf8333..703c8153 100644 --- a/web/testdata/tls_config_noAuth.good.yml +++ b/web/testdata/tls_config_noAuth.good.yml @@ -2,4 +2,3 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" diff --git a/web/testdata/tls_config_noAuth.requireandverifyclientcert.good.yml b/web/testdata/tls_config_noAuth.requireandverifyclientcert.good.yml new file mode 100644 index 00000000..29dfe917 --- /dev/null +++ b/web/testdata/tls_config_noAuth.requireandverifyclientcert.good.yml @@ -0,0 +1,5 @@ +tls_server_config: + cert_file: "server.crt" + key_file: "server.key" + client_auth_type: "RequireAndVerifyClientCert" + client_ca_file: "client_selfsigned.pem" diff --git a/web/testdata/tls_config_noAuth.requireanyclientcert.good.yml b/web/testdata/tls_config_noAuth.requireanyclientcert.good.yml index 78ad2625..548a5927 100644 --- a/web/testdata/tls_config_noAuth.requireanyclientcert.good.yml +++ b/web/testdata/tls_config_noAuth.requireanyclientcert.good.yml @@ -2,4 +2,3 @@ tls_server_config: cert_file: "server.crt" key_file: "server.key" client_auth_type: "RequireAnyClientCert" - client_ca_file: "tls-ca-chain.pem" diff --git a/web/testdata/tls_config_noAuth_allCiphers.good.yml b/web/testdata/tls_config_noAuth_allCiphers.good.yml index b956c65b..57feeb47 100644 --- a/web/testdata/tls_config_noAuth_allCiphers.good.yml +++ b/web/testdata/tls_config_noAuth_allCiphers.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 diff --git a/web/testdata/tls_config_noAuth_allCurves.good.yml b/web/testdata/tls_config_noAuth_allCurves.good.yml index 38213aec..8872f545 100644 --- a/web/testdata/tls_config_noAuth_allCurves.good.yml +++ b/web/testdata/tls_config_noAuth_allCurves.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" curve_preferences: - CurveP256 - CurveP384 diff --git a/web/testdata/tls_config_noAuth_inventedCiphers.bad.yml b/web/testdata/tls_config_noAuth_inventedCiphers.bad.yml index 476d565f..b9a502a6 100644 --- a/web/testdata/tls_config_noAuth_inventedCiphers.bad.yml +++ b/web/testdata/tls_config_noAuth_inventedCiphers.bad.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA2048 diff --git a/web/testdata/tls_config_noAuth_inventedCurves.bad.yml b/web/testdata/tls_config_noAuth_inventedCurves.bad.yml index be0a33c1..f0fc9013 100644 --- a/web/testdata/tls_config_noAuth_inventedCurves.bad.yml +++ b/web/testdata/tls_config_noAuth_inventedCurves.bad.yml @@ -2,6 +2,5 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" curve_preferences: - CurveP257 diff --git a/web/testdata/tls_config_noAuth_noHTTP2.good.yml b/web/testdata/tls_config_noAuth_noHTTP2.good.yml index d934dbc6..b81793fa 100644 --- a/web/testdata/tls_config_noAuth_noHTTP2.good.yml +++ b/web/testdata/tls_config_noAuth_noHTTP2.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_RSA_WITH_AES_128_CBC_SHA max_version: TLS12 diff --git a/web/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml b/web/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml index 25891418..2ee06f5b 100644 --- a/web/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml +++ b/web/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_RSA_WITH_AES_128_CBC_SHA max_version: TLS12 diff --git a/web/testdata/tls_config_noAuth_someCiphers.good.yml b/web/testdata/tls_config_noAuth_someCiphers.good.yml index 2a399f1b..15b6c2b1 100644 --- a/web/testdata/tls_config_noAuth_someCiphers.good.yml +++ b/web/testdata/tls_config_noAuth_someCiphers.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 diff --git a/web/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml b/web/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml index ca5faf9d..3d7edfba 100644 --- a/web/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml +++ b/web/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" cipher_suites: - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 diff --git a/web/testdata/tls_config_noAuth_someCurves.good.yml b/web/testdata/tls_config_noAuth_someCurves.good.yml index 96f2ed7a..d875e9aa 100644 --- a/web/testdata/tls_config_noAuth_someCurves.good.yml +++ b/web/testdata/tls_config_noAuth_someCurves.good.yml @@ -2,7 +2,6 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" min_version: TLS13 curve_preferences: - CurveP521 diff --git a/web/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml b/web/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml index 6e873d5f..cd6afd4e 100644 --- a/web/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml +++ b/web/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml @@ -2,5 +2,4 @@ tls_server_config : cert_file : "server.crt" key_file : "server.key" client_auth_type : "VerifyClientCertIfGiven" - client_ca_file : "tls-ca-chain.pem" min_version: TLS111 diff --git a/web/tls_config.go b/web/tls_config.go index 42eb5d43..7460659b 100644 --- a/web/tls_config.go +++ b/web/tls_config.go @@ -14,13 +14,16 @@ package web import ( + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/hex" "fmt" "io/ioutil" "net" "net/http" "path/filepath" + "strings" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -40,15 +43,16 @@ type Config struct { } type TLSStruct struct { - TLSCertPath string `yaml:"cert_file"` - TLSKeyPath string `yaml:"key_file"` - ClientAuth string `yaml:"client_auth_type"` - ClientCAs string `yaml:"client_ca_file"` - CipherSuites []cipher `yaml:"cipher_suites"` - CurvePreferences []curve `yaml:"curve_preferences"` - MinVersion tlsVersion `yaml:"min_version"` - MaxVersion tlsVersion `yaml:"max_version"` - PreferServerCipherSuites bool `yaml:"prefer_server_cipher_suites"` + TLSCertPath string `yaml:"cert_file"` + TLSKeyPath string `yaml:"key_file"` + ClientAuth string `yaml:"client_auth_type"` + ClientCAs string `yaml:"client_ca_file"` + ClientFingerprints []sha256fingerprint `yaml:"client_fingerprints"` + CipherSuites []cipher `yaml:"cipher_suites"` + CurvePreferences []curve `yaml:"curve_preferences"` + MinVersion tlsVersion `yaml:"min_version"` + MaxVersion tlsVersion `yaml:"max_version"` + PreferServerCipherSuites bool `yaml:"prefer_server_cipher_suites"` } // SetDirectory joins any relative file paths with dir. @@ -169,6 +173,22 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { if c.ClientCAs != "" && cfg.ClientAuth == tls.NoClientCert { return nil, errors.New("Client CA's have been configured without a Client Auth Policy") } + if c.ClientFingerprints != nil && cfg.ClientAuth == tls.NoClientCert { + return nil, errors.New("client fingerprint whitelist has been configured without a Client Auth Policy") + } + if c.ClientFingerprints != nil { + cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + if len(rawCerts) > 0 { + fingerprint := sha256.Sum256(rawCerts[0]) + for _, allowedFingerprint := range c.ClientFingerprints { + if fingerprint == allowedFingerprint { + return nil + } + } + } + return errors.New("invalid client certificate presented") + } + } return cfg, nil } @@ -343,6 +363,33 @@ func (tv *tlsVersion) MarshalYAML() (interface{}, error) { return fmt.Sprintf("%v", tv), nil } +type sha256fingerprint [sha256.Size]byte + +func (fp *sha256fingerprint) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + + // openssl x509 -fingerprint outputs data in format with colons + s = strings.ReplaceAll(s, ":", "") + + n, err := hex.Decode(fp[:], []byte(s)) + if err != nil { + return err + } + + if n != len(*fp) { + return fmt.Errorf("invalid sha256 fingerprint length: got=%d expected=%d", n, len(*fp)) + } + return nil +} + +func (fp *sha256fingerprint) MarshalYAML() (interface{}, error) { + res := hex.EncodeToString(fp[:]) + return res, nil +} + // Listen starts the server on the given address. Based on the file // tlsConfigPath, TLS or basic auth could be enabled. // diff --git a/web/tls_config_test.go b/web/tls_config_test.go index 32ba409c..e1ba01f8 100644 --- a/web/tls_config_test.go +++ b/web/tls_config_test.go @@ -44,6 +44,7 @@ var ( "Invalid CertPath": regexp.MustCompile(`missing cert_file`), "Invalid KeyPath": regexp.MustCompile(`missing key_file`), "ClientCA set without policy": regexp.MustCompile(`Client CA's have been configured without a Client Auth Policy`), + "whitelist set without policy": regexp.MustCompile(`client fingerprint whitelist has been configured without a Client Auth Policy`), "Bad password": regexp.MustCompile(`hashedSecret too short to be a bcrypted password`), "Unauthorized": regexp.MustCompile(`Unauthorized`), "Forbidden": regexp.MustCompile(`Forbidden`), @@ -85,6 +86,7 @@ type TestInputs struct { CurvePreferences []tls.CurveID Username string Password string + ClientCertificate string } func TestYAMLFiles(t *testing.T) { @@ -144,6 +146,11 @@ func TestYAMLFiles(t *testing.T) { YAMLConfigPath: "testdata/tls_config_noAuth.bad.yml", ExpectedError: ErrorMap["ClientCA set without policy"], }, + { + Name: `invalid config yml (invalid ClientAuth with fingerprint whitelist)`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint.bad.yml", + ExpectedError: ErrorMap["whitelist set without policy"], + }, { Name: `invalid config yml (invalid ClientCAs filepath)`, YAMLConfigPath: "testdata/tls_config_auth_clientCAs_invalid.bad.yml", @@ -287,6 +294,74 @@ func TestServerBehaviour(t *testing.T) { UseTLSClient: true, ExpectedError: ErrorMap["Bad certificate"], }, + { + Name: `valid tls config yml and tls client with RequireAnyClientCert (present certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.requireanyclientcert.good.yml", + UseTLSClient: true, + ClientCertificate: "client_selfsigned", + ExpectedError: nil, + }, + { + Name: `valid tls config yml and tls client with RequireAndVerifyClientCert`, + YAMLConfigPath: "testdata/tls_config_noAuth.requireandverifyclientcert.good.yml", + UseTLSClient: true, + ExpectedError: ErrorMap["Bad certificate"], + }, + { + Name: `valid tls config yml and tls client with RequireAndVerifyClientCert (present certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.requireandverifyclientcert.good.yml", + UseTLSClient: true, + ClientCertificate: "client_selfsigned", + ExpectedError: nil, + }, + { + Name: `valid tls config yml and tls client with RequireAndVerifyClientCert (present wrong certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.requireandverifyclientcert.good.yml", + UseTLSClient: true, + ClientCertificate: "client2_selfsigned", + ExpectedError: ErrorMap["Bad certificate"], + }, + // fingerprint whitelist test follow + { + Name: `valid tls config yml and tls client with fingerprint whitelist`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint.good.yml", + UseTLSClient: true, + ExpectedError: ErrorMap["Bad certificate"], + }, + { + Name: `valid tls config yml and tls client with fingerprint (present certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint.good.yml", + UseTLSClient: true, + ClientCertificate: "client_selfsigned", + ExpectedError: nil, + }, + { + Name: `valid tls config yml and tls client with fingerprint (present wrong certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint.good.yml", + UseTLSClient: true, + ClientCertificate: "client2_selfsigned", + ExpectedError: ErrorMap["Bad certificate"], + }, + { + Name: `valid tls config yml and tls client with empty fingerprint whitelist`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint_empty.good.yml", + UseTLSClient: true, + ExpectedError: ErrorMap["Bad certificate"], + }, + { + Name: `valid tls config yml and tls client with empty fingerprint whitelist (present certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint_empty.good.yml", + UseTLSClient: true, + ClientCertificate: "client_selfsigned", + ExpectedError: ErrorMap["Bad certificate"], + }, + { + Name: `valid tls config yml and tls client with empty fingerprint whitelist (present certificate)`, + YAMLConfigPath: "testdata/tls_config_noAuth.fingerprint_empty.good.yml", + UseTLSClient: true, + ClientCertificate: "client2_selfsigned", + ExpectedError: ErrorMap["Bad certificate"], + }, } for _, testInputs := range testTables { t.Run(testInputs.Name, testInputs.Test) @@ -330,7 +405,7 @@ func TestConfigReloading(t *testing.T) { recordConnectionError(err) }() - client := getTLSClient() + client := getTLSClient("") TestClientConnection := func() error { time.Sleep(250 * time.Millisecond) @@ -404,7 +479,7 @@ func (test *TestInputs) Test(t *testing.T) { var client *http.Client var proto string if test.UseTLSClient { - client = getTLSClient() + client = getTLSClient(test.ClientCertificate) t := client.Transport.(*http.Transport) t.TLSClientConfig.MaxVersion = test.ClientMaxTLSVersion if len(test.CipherSuites) > 0 { @@ -496,11 +571,23 @@ func (test *TestInputs) isCorrectError(returnedError error) bool { return true } -func getTLSClient() *http.Client { +func getTLSClient(clientCertName string) *http.Client { cert, err := ioutil.ReadFile("testdata/tls-ca-chain.pem") if err != nil { panic("Unable to start TLS client. Check cert path") } + + var clientCertficate tls.Certificate + if clientCertName != "" { + clientCertficate, err = tls.LoadX509KeyPair( + "testdata/"+clientCertName+".pem", + "testdata/"+clientCertName+".key", + ) + if err != nil { + panic(fmt.Sprintf("failed to load client certificate: %v", err)) + } + } + client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ @@ -509,6 +596,9 @@ func getTLSClient() *http.Client { caCertPool.AppendCertsFromPEM(cert) return caCertPool }(), + GetClientCertificate: func(req *tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &clientCertficate, nil + }, }, }, }