Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for client certificate fingerprint whitelist #62

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 20 additions & 1 deletion docs/web-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <string> | default = "NoClientCert" ]

# CA certificate for client certificate authentication to the server.
[ client_ca_file: <filename> ]

# 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:
[ - <string> ] ]

# Minimum TLS version that is acceptable.
[ min_version: <string> | default = "TLS12" ]

Expand Down
6 changes: 6 additions & 0 deletions web/testdata/client2_selfsigned.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDC8CYtAwKp1uLWXLXFE
Ue2Bz6PijwHZcL7jAxtlk2dbW0GlRQ+rcalHCcnExIIKAAehZANiAATlPRxDnbJb
Zq9u+jh7DyEJumQZFqjIDFdFxfHtI6hwyMtlL6FIwpqn3z4uXs2wx6/NsD4XOChy
j/tXXKCHS/22+51TivjGA53c9bLgc4dK/uJJNSivp0kymbtA5vgKzJE=
-----END PRIVATE KEY-----
12 changes: 12 additions & 0 deletions web/testdata/client2_selfsigned.pem
Original file line number Diff line number Diff line change
@@ -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-----
6 changes: 6 additions & 0 deletions web/testdata/client_selfsigned.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDmoTxYcBfRrqYb/TJy
oHlBKo4/fNk2LBUZxpC3HeKasAQzS9AB1evw3k4M3Pe8c4+hZANiAASxUS40AV1Y
h1ABCLCoJcG9B8Twv/gg2tU0zqdW9FhK2Fu13MeZkTRJLFVgFzlmCj3o9dIX8iUi
RP9jYkQG6wHD44kb9NQ4A7fjs8DOANGWKgY/96liSh/ynPKCoWONW8w=
-----END PRIVATE KEY-----
12 changes: 12 additions & 0 deletions web/testdata/client_selfsigned.pem
Original file line number Diff line number Diff line change
@@ -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-----
2 changes: 1 addition & 1 deletion web/testdata/tls_config_noAuth.bad.yml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 4 additions & 0 deletions web/testdata/tls_config_noAuth.fingerprint.bad.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tls_server_config :
cert_file : "server.crt"
key_file : "server.key"
client_fingerprints: []
6 changes: 6 additions & 0 deletions web/testdata/tls_config_noAuth.fingerprint.good.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions web/testdata/tls_config_noAuth.fingerprint_empty.good.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tls_server_config:
cert_file: "server.crt"
key_file: "server.key"
client_auth_type: "RequireAnyClientCert"
client_fingerprints: []
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth.good.blocking.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_allCiphers.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_allCurves.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_inventedCiphers.bad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_inventedCurves.bad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_noHTTP2.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_someCiphers.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_someCurves.good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion web/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
65 changes: 56 additions & 9 deletions web/tls_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
//
Expand Down