mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 02:16:28 +02:00
authorize: implement client certificate CRL check (#4439)
Update isValidClientCertificate() to also consult the configured certificate revocation lists. Update existing test cases and add a new unit test to exercise the revocation support. Restore the skipped integration test case. Generate new test certificates and CRLs using a new `go run`-able source file.
This commit is contained in:
parent
e91600c158
commit
9d4d31cb4f
9 changed files with 352 additions and 92 deletions
|
@ -99,6 +99,11 @@ func newPolicyEvaluator(opts *config.Options, store *store.Store) (*evaluator.Ev
|
|||
return nil, fmt.Errorf("authorize: invalid client CA: %w", err)
|
||||
}
|
||||
|
||||
clientCRL, err := opts.GetClientCRL()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authorize: invalid client CRL: %w", err)
|
||||
}
|
||||
|
||||
authenticateURL, err := opts.GetInternalAuthenticateURL()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authorize: invalid authenticate url: %w", err)
|
||||
|
@ -112,6 +117,7 @@ func newPolicyEvaluator(opts *config.Options, store *store.Store) (*evaluator.Ev
|
|||
return evaluator.New(ctx, store,
|
||||
evaluator.WithPolicies(opts.GetAllPolicies()),
|
||||
evaluator.WithClientCA(clientCA),
|
||||
evaluator.WithClientCRL(clientCRL),
|
||||
evaluator.WithSigningKey(signingKey),
|
||||
evaluator.WithAuthenticateURL(authenticateURL.String()),
|
||||
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
type evaluatorConfig struct {
|
||||
policies []config.Policy
|
||||
clientCA []byte
|
||||
clientCRL []byte
|
||||
signingKey []byte
|
||||
authenticateURL string
|
||||
googleCloudServerlessAuthenticationServiceAccount string
|
||||
|
@ -38,6 +39,13 @@ func WithClientCA(clientCA []byte) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithClientCRL sets the client CRL in the config.
|
||||
func WithClientCRL(clientCRL []byte) Option {
|
||||
return func(cfg *evaluatorConfig) {
|
||||
cfg.clientCRL = clientCRL
|
||||
}
|
||||
}
|
||||
|
||||
// WithSigningKey sets the signing key and algorithm in the config.
|
||||
func WithSigningKey(signingKey []byte) Option {
|
||||
return func(cfg *evaluatorConfig) {
|
||||
|
|
|
@ -93,6 +93,7 @@ type Evaluator struct {
|
|||
policyEvaluators map[uint64]*PolicyEvaluator
|
||||
headersEvaluators *HeadersEvaluator
|
||||
clientCA []byte
|
||||
clientCRL []byte
|
||||
}
|
||||
|
||||
// New creates a new Evaluator.
|
||||
|
@ -112,6 +113,7 @@ func New(ctx context.Context, store *store.Store, options ...Option) (*Evaluator
|
|||
}
|
||||
|
||||
e.clientCA = cfg.clientCA
|
||||
e.clientCRL = cfg.clientCRL
|
||||
|
||||
e.policyEvaluators = make(map[uint64]*PolicyEvaluator)
|
||||
for i := range cfg.policies {
|
||||
|
@ -209,7 +211,8 @@ func (e *Evaluator) evaluatePolicy(ctx context.Context, req *Request) (*PolicyRe
|
|||
return nil, err
|
||||
}
|
||||
|
||||
isValidClientCertificate, err := isValidClientCertificate(clientCA, req.HTTP.ClientCertificate)
|
||||
isValidClientCertificate, err :=
|
||||
isValidClientCertificate(clientCA, string(e.clientCRL), req.HTTP.ClientCertificate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ func TestEvaluator(t *testing.T) {
|
|||
HTTP: RequestHTTP{
|
||||
ClientCertificate: ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: testUnsignedCert,
|
||||
Leaf: testUntrustedCert,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
|
@ -11,9 +12,9 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
var isValidClientCertificateCache, _ = lru.New2Q[[2]string, bool](100)
|
||||
var isValidClientCertificateCache, _ = lru.New2Q[[3]string, bool](100)
|
||||
|
||||
func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool, error) {
|
||||
func isValidClientCertificate(ca, crl string, certInfo ClientCertificateInfo) (bool, error) {
|
||||
// when ca is the empty string, client certificates are not required
|
||||
if ca == "" {
|
||||
return true, nil
|
||||
|
@ -25,25 +26,29 @@ func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool,
|
|||
return false, nil
|
||||
}
|
||||
|
||||
cacheKey := [2]string{ca, cert}
|
||||
cacheKey := [3]string{ca, crl, cert}
|
||||
|
||||
value, ok := isValidClientCertificateCache.Get(cacheKey)
|
||||
if ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
roots.AppendCertsFromPEM([]byte(ca))
|
||||
roots, err := parseCertificates([]byte(ca))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
xcert, err := parseCertificate(cert)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, verifyErr := xcert.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
})
|
||||
crls, err := parseCRLs([]byte(crl))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
verifyErr := verifyClientCertificate(xcert, roots, crls)
|
||||
valid := verifyErr == nil
|
||||
|
||||
if verifyErr != nil {
|
||||
|
@ -55,6 +60,55 @@ func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool,
|
|||
return valid, nil
|
||||
}
|
||||
|
||||
var errCertificateRevoked = errors.New("certificate revoked")
|
||||
|
||||
func verifyClientCertificate(
|
||||
cert *x509.Certificate,
|
||||
roots map[string]*x509.Certificate,
|
||||
crls map[string]*x509.RevocationList,
|
||||
) error {
|
||||
rootPool := x509.NewCertPool()
|
||||
for _, root := range roots {
|
||||
rootPool.AddCert(root)
|
||||
}
|
||||
|
||||
if _, err := cert.Verify(x509.VerifyOptions{
|
||||
Roots: rootPool,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Consult any CRL for the presented certificate's Issuer.
|
||||
issuer := string(cert.RawIssuer)
|
||||
crl := crls[issuer]
|
||||
if crl == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do we have a corresponding trusted CA certificate?
|
||||
root, ok := roots[issuer]
|
||||
if !ok {
|
||||
return fmt.Errorf("could not check CRL: no matching trusted CA for issuer %s",
|
||||
cert.Issuer)
|
||||
}
|
||||
|
||||
// Is the CRL signature itself valid?
|
||||
if err := crl.CheckSignatureFrom(root); err != nil {
|
||||
return fmt.Errorf("could not check CRL for issuer %s: signature verification "+
|
||||
"error: %w", cert.Issuer, err)
|
||||
}
|
||||
|
||||
// Is the client certificate listed as revoked in this CRL?
|
||||
for i := range crl.RevokedCertificates {
|
||||
if cert.SerialNumber.Cmp(crl.RevokedCertificates[i].SerialNumber) == 0 {
|
||||
return errCertificateRevoked
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCertificate(pemStr string) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode([]byte(pemStr))
|
||||
if block == nil {
|
||||
|
@ -65,3 +119,41 @@ func parseCertificate(pemStr string) (*x509.Certificate, error) {
|
|||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
func parseCertificates(certs []byte) (map[string]*x509.Certificate, error) {
|
||||
m := make(map[string]*x509.Certificate)
|
||||
for {
|
||||
var block *pem.Block
|
||||
block, certs = pem.Decode(certs)
|
||||
if block == nil {
|
||||
return m, nil
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(cert.RawSubject)] = cert
|
||||
}
|
||||
}
|
||||
|
||||
func parseCRLs(crl []byte) (map[string]*x509.RevocationList, error) {
|
||||
m := make(map[string]*x509.RevocationList)
|
||||
for {
|
||||
var block *pem.Block
|
||||
block, crl = pem.Decode(crl)
|
||||
if block == nil {
|
||||
return m, nil
|
||||
}
|
||||
if block.Type != "X509 CRL" {
|
||||
continue
|
||||
}
|
||||
l, err := x509.ParseRevocationList(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(l.RawIssuer)] = l
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,106 +6,84 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// These certificates can be regenerated by running:
|
||||
//
|
||||
// go run ./gen-test-certs.go
|
||||
//
|
||||
// (Copy and paste the output here.)
|
||||
const (
|
||||
testCA = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEtjCCAx6gAwIBAgIRAJFkXxMjoQzoojykk6CiiGkwDQYJKoZIhvcNAQELBQAw
|
||||
czEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSQwIgYDVQQLDBtjYWxl
|
||||
YkBwb3Atb3MgKENhbGViIERveHNleSkxKzApBgNVBAMMIm1rY2VydCBjYWxlYkBw
|
||||
b3Atb3MgKENhbGViIERveHNleSkwHhcNMjAwNDI0MTY1MzEwWhcNMzAwNDI0MTY1
|
||||
MzEwWjBzMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExJDAiBgNVBAsM
|
||||
G2NhbGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTErMCkGA1UEAwwibWtjZXJ0IGNh
|
||||
bGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTCCAaIwDQYJKoZIhvcNAQEBBQADggGP
|
||||
ADCCAYoCggGBAL2QSyQGjaGD97K7HSExJfMcuyEoh+ewAkPZ/HZR4n12zwAn1sLK
|
||||
RqusKSfMe8qG6KgsojXrJ9AXEkD7x3bmK5j/4M/lwlNGulg+k5MSu3leoLpOZwfX
|
||||
JQTu+HDzWubu5cjy7taHyeZc35VbOBWEaDJgVxmJvE9TJIOr8POZ7DD/rlkbgQas
|
||||
s6G/8cg2mRX0Rh3O20/1bvi9Uen/kraBgGMOyG5MfuiiTl3KsrGST848Q+jiSbu3
|
||||
5F5MAzdO4tlR6kqEZk/Igog6OPkTb82vMli/R+mR37JYncQcj0WNYS4PkfjofVpb
|
||||
FwrHtfdkVYJ9T2yNvQnJVu6MF9fhj9FqWQbsdbYKlUDow5KwI+BxmCAmGwgzmCOy
|
||||
ONkglj76fPKFkoF4s+DSFocbAwhdazaViAcCB+x6yohOUjgG7H9NJo0MasPHuqUO
|
||||
8d56Bf0BTXfNX6nOgYYisrOoEATCbs729vHMaQ/7pG2zf9dnEuw95gZTSr9Rv3dx
|
||||
2NjmM6+tNOMCzwIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgw
|
||||
BgEB/wIBADAdBgNVHQ4EFgQUShofXNkcXh2q4wnnWZ2bco24XEQwDQYJKoZIhvcN
|
||||
AQELBQADggGBAJQzfmr84xtvoUgnq8T4ND0Q166dlnbDnASRFhbmyZcxzvDJsPs4
|
||||
N45HbIg0xOsXOaBaE+jTSV4GmZ/vtyP8WbQrhabL2zpnjnLF1d9B0qx/RlIzoDEa
|
||||
e/0zc0R6RAd64/aE/jHNDhfTNXD/NmnI25RqgnsZXXXRVMTl+PzQ1A8XQghZVWHN
|
||||
vbyFFd3GE5Qs+vxMzwKCqp6f3MI8KyI2aM4hZZ+zULdEuSw0hWzMOkeZY6LC0flW
|
||||
/rpkT+GLA3uZ357iehSISLqnkIozw92ldov5oZDthoy3i1I6gIDkngk7BGKr42pD
|
||||
L2sWi1MEEIhymy4K1DnRkGre3mqzus2y/nE4ruuJlctq6QXcCSnko717vukVtoE8
|
||||
o5SkW4usivU8yZeBLt56sySRyCpe/T1XAFTQZ5Q4S5ssGmNpOLS9Aa5iOUz9/62S
|
||||
uvjFyvOEE3yqd/d3py8qm6olcjaMooVA8j5G+QF/UiH951azGIez6/Ui1lg1m0T6
|
||||
+YLkPqNIt0o9dQ==
|
||||
MIIBZzCCAQ6gAwIBAgICEAAwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
||||
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwNzMxMTUzMzE5WjAaMRgw
|
||||
FgYDVQQDEw9UcnVzdGVkIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
|
||||
AARGMVCBvgbkVB3OPltnBHAy9s9rtog2rlnzZ4BKzPBbLEM0uPYTOZa0LLxSMtCj
|
||||
N+Bu3wfGPgHU6/pJ2uEky7/Uo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQUXep6D8FTP6+5ZdR/HjP3pYfmxkwwCgYIKoZIzj0E
|
||||
AwIDRwAwRAIgSS5J6ii/n0gf2/UAMFb+UVG8n0nb1dcBCG55fSlWlVECIENVK+X3
|
||||
6SfUhfYSVBvOdS08AzMVvOM7aZbWaY9UirIf
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
testValidCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
/YxuOibV35vlaIzj21GRj4pYmVR1
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
testUnsignedCert = `
|
||||
testUntrustedCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIESTCCArGgAwIBAgIRAIE9860UHBIVofXB5cu/aWAwDQYJKoZIhvcNAQELBQAw
|
||||
czEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSQwIgYDVQQLDBtjYWxl
|
||||
YkBwb3Atb3MgKENhbGViIERveHNleSkxKzApBgNVBAMMIm1rY2VydCBjYWxlYkBw
|
||||
b3Atb3MgKENhbGViIERveHNleSkwHhcNMTkwNjAxMDAwMDAwWhcNMzAwNTIwMjIw
|
||||
NDAxWjBPMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUx
|
||||
JDAiBgNVBAsMG2NhbGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAKPgWHAJ58p7ZZ6MHA6QHA9rQQWKSvYbN9zz
|
||||
fCCURqHFbQHCCJs2D39XPioo9EMZcD6J7ldwEOJsdSNw3+dzBCvIl7wP6fqtbo/3
|
||||
SNgRaLAB+Mb4S8oek6P6zHkjuOXzodhCZjLO7oxY9pjGREy6hC/SjylJFgw9mKEG
|
||||
SYmsyCqeP5BfW9DghRgd5uJe0HtwlBZLPS91Mk5whn7YOxnWslS/REwZdd12s3DI
|
||||
WQdmvGhMakIAiMKmx+LX9qS3Ua2gUArHnSFXcOAg9iK+MM68T1KsQTCYnRZVK4v5
|
||||
Na4qEjiPhmkzzEExZa787ClL6UXfoXB+jXy2sXu0CDD4tv2D7R8CAwEAAaN8MHow
|
||||
DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAM
|
||||
BgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFH8wenPOF2tE2EIksItmlkWfgEMkMBoG
|
||||
A1UdEQQTMBGCD2ludmFsaWQtc3ViamVjdDANBgkqhkiG9w0BAQsFAAOCAYEAJCdl
|
||||
c6J/x/UY6vEDzplwR8iZ5s7dyKKF7bwNdjEvBREgkTY6GmwDC9HOmWWPs7vENqEX
|
||||
jUwHEK+v7A7AUIS4WeJrJgogzEDPI7ZlVtzQNviqMavzk/I1Us00WYtMQQFb1Sgz
|
||||
xIRskug5wH6vPcR4XbCftx6NP9UFG8pJLPTJ67ZUaTP23ccsToMM/Dd17LFrtleE
|
||||
9xAvdqA54vcBiJ99uts+xWlQznjIgdauNC6sOmL3JAflyj6aBy+Dcos9R35ERIXz
|
||||
3rRl25yXjtidPDo8YxmtHs+Ijw4R3iJ44NCcc/+LfACYUcua0cBF2Ixk2JrFYx8n
|
||||
wwRJukrHXI+RFBmSOlUripyyJH92H5vXvj8lO5wM8wVVVe8anr5TOvxFOAjNC5a3
|
||||
vJByvJQTUEkx8rT7zZi8eSQJHP3Eoqr9g4ajqIU22yrCxiiQXpZLJ4JFQQEgyD9A
|
||||
Y+E5W+FKfIBv9yvdNBYZsL6IZ0Yh1ctKwB5gnajO8+swx5BeaCIbBrCtOBSB
|
||||
MIIBZzCCAQygAwIBAgICEAEwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAxMRVW50cnVz
|
||||
dGVkIFJvb3QgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zMzA3MzExNTMzMTlaMCAx
|
||||
HjAcBgNVBAMTFXVudHJ1c3RlZCBjbGllbnQgY2VydDBZMBMGByqGSM49AgEGCCqG
|
||||
SM49AwEHA0IABBG2Qo/l0evcNKjwaJsi04BJJh7ec064lRiKaRMNRUK+UxkKmfbn
|
||||
0FobVtlioTmzeWCX8OJFPfO7y7/VLMiGVr+jODA2MBMGA1UdJQQMMAoGCCsGAQUF
|
||||
BwMCMB8GA1UdIwQYMBaAFCd2l26OflZF3LTFUEBB54ZQV3AUMAoGCCqGSM49BAMC
|
||||
A0kAMEYCIQCYEk3D4nHevIlKFg6f7O2/GdptzKU6F05pz4B3Aa8ahAIhAJcBGUNm
|
||||
cqQQJNOelJJmMeFOzmmTk7oNFxCGEC00wlGn
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
testRevokedCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBYzCCAQigAwIBAgICEAIwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
||||
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwNzMxMTUzMzE5WjAeMRww
|
||||
GgYDVQQDExNyZXZva2VkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
||||
AQcDQgAEoN/gKhZgyKhTmiC3qLHDQ54TIpgXBTvGKrdIRHO616XMkzj0lFZMHG5u
|
||||
LGK3qo8wJtyoalOFTkSck0kl3PD/9qM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
||||
HwYDVR0jBBgwFoAUXep6D8FTP6+5ZdR/HjP3pYfmxkwwCgYIKoZIzj0EAwIDSQAw
|
||||
RgIhAK6/oLtzvrK2Vrt1MRZJ6aGU2Cz28X0Y/4TOwFSvCK9AAiEAm4XPQXy6L0PE
|
||||
vfXoV8RW/RnndDhf8iDALvAaAuS82fU=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
testCRL = `
|
||||
-----BEGIN X509 CRL-----
|
||||
MIHfMIGFAgEBMAoGCCqGSM49BAMCMBoxGDAWBgNVBAMTD1RydXN0ZWQgUm9vdCBD
|
||||
QRgPMDAwMTAxMDEwMDAwMDBaMBUwEwICEAIXDTIzMDgwMzE1MzMxOVqgMDAuMB8G
|
||||
A1UdIwQYMBaAFF3qeg/BUz+vuWXUfx4z96WH5sZMMAsGA1UdFAQEAgIgADAKBggq
|
||||
hkjOPQQDAgNJADBGAiEApMG/hJxlMe9QNF8cCVjOFyTfVVBkfKtrFQDmElO46x4C
|
||||
IQCX9SYteNaaW+NVmGED6QfHXRWnDqHnXfe/mLxmnPVWzA==
|
||||
-----END X509 CRL-----
|
||||
`
|
||||
)
|
||||
|
||||
func Test_isValidClientCertificate(t *testing.T) {
|
||||
t.Run("no ca", func(t *testing.T) {
|
||||
valid, err := isValidClientCertificate("", ClientCertificateInfo{Leaf: "WHATEVER!"})
|
||||
valid, err := isValidClientCertificate("", "", ClientCertificateInfo{Leaf: "WHATEVER!"})
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.True(t, valid, "should return true")
|
||||
})
|
||||
t.Run("no cert", func(t *testing.T) {
|
||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{})
|
||||
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{})
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.False(t, valid, "should return false")
|
||||
})
|
||||
t.Run("valid cert", func(t *testing.T) {
|
||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: testValidCert,
|
||||
})
|
||||
|
@ -113,19 +91,42 @@ func Test_isValidClientCertificate(t *testing.T) {
|
|||
assert.True(t, valid, "should return true")
|
||||
})
|
||||
t.Run("unsigned cert", func(t *testing.T) {
|
||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: testUnsignedCert,
|
||||
Leaf: testUntrustedCert,
|
||||
})
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.False(t, valid, "should return false")
|
||||
})
|
||||
t.Run("not a cert", func(t *testing.T) {
|
||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
||||
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: "WHATEVER!",
|
||||
})
|
||||
assert.Error(t, err, "should return an error")
|
||||
assert.False(t, valid, "should return false")
|
||||
})
|
||||
t.Run("revoked cert", func(t *testing.T) {
|
||||
revokedCertInfo := ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: testRevokedCert,
|
||||
}
|
||||
|
||||
// The "revoked cert" should otherwise be valid (when no CRL is specified).
|
||||
valid, err := isValidClientCertificate(testCA, "", revokedCertInfo)
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.True(t, valid, "should return true")
|
||||
|
||||
valid, err = isValidClientCertificate(testCA, testCRL, revokedCertInfo)
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.False(t, valid, "should return false")
|
||||
|
||||
// Specifying a CRL containing the revoked cert should not affect other certs.
|
||||
valid, err = isValidClientCertificate(testCA, testCRL, ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Leaf: testValidCert,
|
||||
})
|
||||
assert.NoError(t, err, "should not return an error")
|
||||
assert.True(t, valid, "should return true")
|
||||
})
|
||||
}
|
||||
|
|
140
authorize/evaluator/gen-test-certs.go
Normal file
140
authorize/evaluator/gen-test-certs.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Returns a new self-signed certificate, as both PEM data and an
|
||||
// *x509.Certificate, along with the corresponding private key.
|
||||
func newSelfSignedCertificate(template *x509.Certificate) (
|
||||
string, *x509.Certificate, *ecdsa.PrivateKey,
|
||||
) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), cert, key
|
||||
}
|
||||
|
||||
// Returns a new certificate, as both PEM data and an *x509.Certificate, along
|
||||
// with the new certificate's corresponding private key.
|
||||
func newCertificate(template, issuer *x509.Certificate, issuerKey *ecdsa.PrivateKey) (
|
||||
string, *x509.Certificate, *ecdsa.PrivateKey,
|
||||
) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, issuer, key.Public(), issuerKey)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), cert, key
|
||||
}
|
||||
|
||||
// Returns a new CRL in PEM format.
|
||||
func newCRL(
|
||||
template *x509.RevocationList, issuer *x509.Certificate, issuerKey *ecdsa.PrivateKey,
|
||||
) string {
|
||||
der, err := x509.CreateRevocationList(rand.Reader, template, issuer, issuerKey)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return string(pem.EncodeToMemory(&pem.Block{Type: "X509 CRL", Bytes: der}))
|
||||
}
|
||||
|
||||
// Generates new test certificates and CRLs.
|
||||
func main() {
|
||||
notAfter := time.Now().Add(3650 * 24 * time.Hour)
|
||||
|
||||
rootPEM, rootCA, rootKey := newSelfSignedCertificate(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(0x1000),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Trusted Root CA",
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
})
|
||||
|
||||
trustedClientCertPEM, _, _ := newCertificate(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(0x1001),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "trusted client cert",
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}, rootCA, rootKey)
|
||||
|
||||
_, untrustedCA, untrustedCAKey := newSelfSignedCertificate(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(0x1000),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Untrusted Root CA",
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
})
|
||||
|
||||
untrustedClientCertPEM, _, _ := newCertificate(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(0x1001),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "untrusted client cert",
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}, untrustedCA, untrustedCAKey)
|
||||
|
||||
revokedClientCertPEM, revokedClientCert, _ := newCertificate(&x509.Certificate{
|
||||
SerialNumber: big.NewInt(0x1002),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "revoked client cert",
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}, rootCA, rootKey)
|
||||
|
||||
crlPEM := newCRL(&x509.RevocationList{
|
||||
Number: big.NewInt(0x2000),
|
||||
RevokedCertificates: []pkix.RevokedCertificate{
|
||||
{
|
||||
SerialNumber: revokedClientCert.SerialNumber,
|
||||
RevocationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
}, rootCA, rootKey)
|
||||
|
||||
fmt.Println(`
|
||||
const (
|
||||
testCA = ` + "`\n" + rootPEM + "`" + `
|
||||
testValidCert = ` + "`\n" + trustedClientCertPEM + "`" + `
|
||||
testUntrustedCert = ` + "`\n" + untrustedClientCertPEM + "`" + `
|
||||
testRevokedCert = ` + "`\n" + revokedClientCertPEM + "`" + `
|
||||
testCRL = ` + "`\n" + crlPEM + "`" + `
|
||||
)
|
||||
`)
|
||||
}
|
|
@ -966,6 +966,18 @@ func (o *Options) GetClientCA() ([]byte, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// GetClientCRL returns the client certificate revocation list bundle. If
|
||||
// neither client_crl nor client_crl_file is specified nil will be returned.
|
||||
func (o *Options) GetClientCRL() ([]byte, error) {
|
||||
if o.ClientCRL != "" {
|
||||
return base64.StdEncoding.DecodeString(o.ClientCRL)
|
||||
}
|
||||
if o.ClientCRLFile != "" {
|
||||
return os.ReadFile(o.ClientCRLFile)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetDataBrokerCertificate gets the optional databroker certificate. This method will return nil if no certificate is
|
||||
// specified.
|
||||
func (o *Options) GetDataBrokerCertificate() (*tls.Certificate, error) {
|
||||
|
|
|
@ -393,8 +393,6 @@ func TestDownstreamClientCA(t *testing.T) {
|
|||
assert.Equal(t, "/", result.Path)
|
||||
})
|
||||
t.Run("revoked client cert", func(t *testing.T) {
|
||||
t.Skip("CRL support must be reimplemented first")
|
||||
|
||||
// Configure an http.Client with a revoked client certificate.
|
||||
cert := loadCertificate(t, "downstream-1-client-revoked")
|
||||
client, transport := getClientWithTransport(t)
|
||||
|
|
Loading…
Add table
Reference in a new issue