mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-26 14:38:09 +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)
|
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()
|
authenticateURL, err := opts.GetInternalAuthenticateURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("authorize: invalid authenticate url: %w", err)
|
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,
|
return evaluator.New(ctx, store,
|
||||||
evaluator.WithPolicies(opts.GetAllPolicies()),
|
evaluator.WithPolicies(opts.GetAllPolicies()),
|
||||||
evaluator.WithClientCA(clientCA),
|
evaluator.WithClientCA(clientCA),
|
||||||
|
evaluator.WithClientCRL(clientCRL),
|
||||||
evaluator.WithSigningKey(signingKey),
|
evaluator.WithSigningKey(signingKey),
|
||||||
evaluator.WithAuthenticateURL(authenticateURL.String()),
|
evaluator.WithAuthenticateURL(authenticateURL.String()),
|
||||||
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
|
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
type evaluatorConfig struct {
|
type evaluatorConfig struct {
|
||||||
policies []config.Policy
|
policies []config.Policy
|
||||||
clientCA []byte
|
clientCA []byte
|
||||||
|
clientCRL []byte
|
||||||
signingKey []byte
|
signingKey []byte
|
||||||
authenticateURL string
|
authenticateURL string
|
||||||
googleCloudServerlessAuthenticationServiceAccount 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.
|
// WithSigningKey sets the signing key and algorithm in the config.
|
||||||
func WithSigningKey(signingKey []byte) Option {
|
func WithSigningKey(signingKey []byte) Option {
|
||||||
return func(cfg *evaluatorConfig) {
|
return func(cfg *evaluatorConfig) {
|
||||||
|
|
|
@ -93,6 +93,7 @@ type Evaluator struct {
|
||||||
policyEvaluators map[uint64]*PolicyEvaluator
|
policyEvaluators map[uint64]*PolicyEvaluator
|
||||||
headersEvaluators *HeadersEvaluator
|
headersEvaluators *HeadersEvaluator
|
||||||
clientCA []byte
|
clientCA []byte
|
||||||
|
clientCRL []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Evaluator.
|
// 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.clientCA = cfg.clientCA
|
||||||
|
e.clientCRL = cfg.clientCRL
|
||||||
|
|
||||||
e.policyEvaluators = make(map[uint64]*PolicyEvaluator)
|
e.policyEvaluators = make(map[uint64]*PolicyEvaluator)
|
||||||
for i := range cfg.policies {
|
for i := range cfg.policies {
|
||||||
|
@ -209,7 +211,8 @@ func (e *Evaluator) evaluatePolicy(ctx context.Context, req *Request) (*PolicyRe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidClientCertificate, err := isValidClientCertificate(clientCA, req.HTTP.ClientCertificate)
|
isValidClientCertificate, err :=
|
||||||
|
isValidClientCertificate(clientCA, string(e.clientCRL), req.HTTP.ClientCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
|
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,7 +172,7 @@ func TestEvaluator(t *testing.T) {
|
||||||
HTTP: RequestHTTP{
|
HTTP: RequestHTTP{
|
||||||
ClientCertificate: ClientCertificateInfo{
|
ClientCertificate: ClientCertificateInfo{
|
||||||
Presented: true,
|
Presented: true,
|
||||||
Leaf: testUnsignedCert,
|
Leaf: testUntrustedCert,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru/v2"
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
|
@ -11,9 +12,9 @@ import (
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"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
|
// when ca is the empty string, client certificates are not required
|
||||||
if ca == "" {
|
if ca == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -25,25 +26,29 @@ func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool,
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheKey := [2]string{ca, cert}
|
cacheKey := [3]string{ca, crl, cert}
|
||||||
|
|
||||||
value, ok := isValidClientCertificateCache.Get(cacheKey)
|
value, ok := isValidClientCertificateCache.Get(cacheKey)
|
||||||
if ok {
|
if ok {
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
roots := x509.NewCertPool()
|
roots, err := parseCertificates([]byte(ca))
|
||||||
roots.AppendCertsFromPEM([]byte(ca))
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
xcert, err := parseCertificate(cert)
|
xcert, err := parseCertificate(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, verifyErr := xcert.Verify(x509.VerifyOptions{
|
crls, err := parseCRLs([]byte(crl))
|
||||||
Roots: roots,
|
if err != nil {
|
||||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
return false, err
|
||||||
})
|
}
|
||||||
|
|
||||||
|
verifyErr := verifyClientCertificate(xcert, roots, crls)
|
||||||
valid := verifyErr == nil
|
valid := verifyErr == nil
|
||||||
|
|
||||||
if verifyErr != nil {
|
if verifyErr != nil {
|
||||||
|
@ -55,6 +60,55 @@ func isValidClientCertificate(ca string, certInfo ClientCertificateInfo) (bool,
|
||||||
return valid, nil
|
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) {
|
func parseCertificate(pemStr string) (*x509.Certificate, error) {
|
||||||
block, _ := pem.Decode([]byte(pemStr))
|
block, _ := pem.Decode([]byte(pemStr))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
@ -65,3 +119,41 @@ func parseCertificate(pemStr string) (*x509.Certificate, error) {
|
||||||
}
|
}
|
||||||
return x509.ParseCertificate(block.Bytes)
|
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"
|
"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 (
|
const (
|
||||||
testCA = `
|
testCA = `
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIEtjCCAx6gAwIBAgIRAJFkXxMjoQzoojykk6CiiGkwDQYJKoZIhvcNAQELBQAw
|
MIIBZzCCAQ6gAwIBAgICEAAwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
||||||
czEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSQwIgYDVQQLDBtjYWxl
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwNzMxMTUzMzE5WjAaMRgw
|
||||||
YkBwb3Atb3MgKENhbGViIERveHNleSkxKzApBgNVBAMMIm1rY2VydCBjYWxlYkBw
|
FgYDVQQDEw9UcnVzdGVkIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
|
||||||
b3Atb3MgKENhbGViIERveHNleSkwHhcNMjAwNDI0MTY1MzEwWhcNMzAwNDI0MTY1
|
AARGMVCBvgbkVB3OPltnBHAy9s9rtog2rlnzZ4BKzPBbLEM0uPYTOZa0LLxSMtCj
|
||||||
MzEwWjBzMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExJDAiBgNVBAsM
|
N+Bu3wfGPgHU6/pJ2uEky7/Uo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
|
||||||
G2NhbGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTErMCkGA1UEAwwibWtjZXJ0IGNh
|
BAUwAwEB/zAdBgNVHQ4EFgQUXep6D8FTP6+5ZdR/HjP3pYfmxkwwCgYIKoZIzj0E
|
||||||
bGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTCCAaIwDQYJKoZIhvcNAQEBBQADggGP
|
AwIDRwAwRAIgSS5J6ii/n0gf2/UAMFb+UVG8n0nb1dcBCG55fSlWlVECIENVK+X3
|
||||||
ADCCAYoCggGBAL2QSyQGjaGD97K7HSExJfMcuyEoh+ewAkPZ/HZR4n12zwAn1sLK
|
6SfUhfYSVBvOdS08AzMVvOM7aZbWaY9UirIf
|
||||||
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==
|
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
`
|
`
|
||||||
testValidCert = `
|
testValidCert = `
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIESDCCArCgAwIBAgIQG/h9GflpINqLLv4Tde9+STANBgkqhkiG9w0BAQsFADBz
|
MIIBYTCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
||||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExJDAiBgNVBAsMG2NhbGVi
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzMwNzMxMTUzMzE5WjAeMRww
|
||||||
QHBvcC1vcyAoQ2FsZWIgRG94c2V5KTErMCkGA1UEAwwibWtjZXJ0IGNhbGViQHBv
|
GgYDVQQDExN0cnVzdGVkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
||||||
cC1vcyAoQ2FsZWIgRG94c2V5KTAeFw0xOTA2MDEwMDAwMDBaFw0zMDA1MjAyMDM4
|
AQcDQgAEfAYP3ZwiKJgk9zXpR/CMHYlAxjweJaMJihIS2FTA5gb0xBcTEe5AGpNF
|
||||||
NDRaME8xJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEk
|
CHWPk4YCB25VeHg9GmY9Q1+qDD1hdqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
||||||
MCIGA1UECwwbY2FsZWJAcG9wLW9zIChDYWxlYiBEb3hzZXkpMIIBIjANBgkqhkiG
|
HwYDVR0jBBgwFoAUXep6D8FTP6+5ZdR/HjP3pYfmxkwwCgYIKoZIzj0EAwIDRwAw
|
||||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ouz2dlXHALdxiLcLwAvxg02CN/Jdcrmyyzm
|
RAIgProROtxpvKS/qjrjonSvacnhdU0JwoXj2DgYvF/qjrUCIAXlHkdEzyXmTLuu
|
||||||
bzKHqIpknotZSlbPgE/mp5wMwIoyMqFIEm3IzXFEf3cjFYYG4b6wp4zlFrx7jCOa
|
/YxuOibV35vlaIzj21GRj4pYmVR1
|
||||||
vhEHpH3yM71xt1I/BME6VrmX7sRKO90dwpTxCOadx9aGEn1AlHuPfhMMm/WTLynD
|
|
||||||
d5hbsHKp7eZMYHvQnferTelq5NnBySBP/HaAtF76qTSQzHev5K/cgioDZAaM0dnP
|
|
||||||
bicl0Zay+f5INrDr9XtQo/FHwGI/YLMW5TWXYmHjYmdD8s4Tg/KUoRMgJp4mlkkF
|
|
||||||
9t1pwArbNFU/4wQWPbpWBLh1gcnQxojSZ3a6aI+V+REDzV/PVQIDAQABo3wwejAO
|
|
||||||
BgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwG
|
|
||||||
A1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUShofXNkcXh2q4wnnWZ2bco24XEQwGgYD
|
|
||||||
VR0RBBMwEYIPZXhhbXBsZS1zdWJqZWN0MA0GCSqGSIb3DQEBCwUAA4IBgQC78S2n
|
|
||||||
6jcKfWbm24g/U5tkWiBVnBk1jphH7Ct69Lw2JNstGLtNs4AiE9lKmXDQ82MiAFYg
|
|
||||||
gaeiHRhTebkOTF9Kx3Jwq7bwhhzONqPp5a0SkY4EWjZ7c5k/fZc8DkrRE71hOgMf
|
|
||||||
rFbRBZCywBVtGbXIA1uMsCijTe4sQF1ZA918NmfjhpIhRHljQJM16RJ753s+0CZ8
|
|
||||||
WomOW4JrtjJefRuV97PRADvRNQbtZYelnoTfbp1afGhbQpKjyylCDGlpJS4mGrSA
|
|
||||||
lPaRVhEB+wI8gA3lzpa6adXsc1yueZ19++dxQNYxAawCMQNjjxy3aLWzy8aPWxxq
|
|
||||||
Qo/Q9rqjre3SpJfARLOV9ezQNbqsXvJW+5DcoG5dx8s6jAhMusNjUHpf6oVgnv65
|
|
||||||
3Bvl124bZyf9q4lW9g8pvZkrgQ3Fx2IahqhXhyF5zrqf2r9+1l0fXocIUP2GQ+Fr
|
|
||||||
b9j9bWWhov5aidEjPwpFeTmzcGqCWQBEA4H+yo/4YaIN0sOfE2yaAmc3gcU=
|
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
`
|
`
|
||||||
testUnsignedCert = `
|
testUntrustedCert = `
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIESTCCArGgAwIBAgIRAIE9860UHBIVofXB5cu/aWAwDQYJKoZIhvcNAQELBQAw
|
MIIBZzCCAQygAwIBAgICEAEwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAxMRVW50cnVz
|
||||||
czEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSQwIgYDVQQLDBtjYWxl
|
dGVkIFJvb3QgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zMzA3MzExNTMzMTlaMCAx
|
||||||
YkBwb3Atb3MgKENhbGViIERveHNleSkxKzApBgNVBAMMIm1rY2VydCBjYWxlYkBw
|
HjAcBgNVBAMTFXVudHJ1c3RlZCBjbGllbnQgY2VydDBZMBMGByqGSM49AgEGCCqG
|
||||||
b3Atb3MgKENhbGViIERveHNleSkwHhcNMTkwNjAxMDAwMDAwWhcNMzAwNTIwMjIw
|
SM49AwEHA0IABBG2Qo/l0evcNKjwaJsi04BJJh7ec064lRiKaRMNRUK+UxkKmfbn
|
||||||
NDAxWjBPMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUx
|
0FobVtlioTmzeWCX8OJFPfO7y7/VLMiGVr+jODA2MBMGA1UdJQQMMAoGCCsGAQUF
|
||||||
JDAiBgNVBAsMG2NhbGViQHBvcC1vcyAoQ2FsZWIgRG94c2V5KTCCASIwDQYJKoZI
|
BwMCMB8GA1UdIwQYMBaAFCd2l26OflZF3LTFUEBB54ZQV3AUMAoGCCqGSM49BAMC
|
||||||
hvcNAQEBBQADggEPADCCAQoCggEBAKPgWHAJ58p7ZZ6MHA6QHA9rQQWKSvYbN9zz
|
A0kAMEYCIQCYEk3D4nHevIlKFg6f7O2/GdptzKU6F05pz4B3Aa8ahAIhAJcBGUNm
|
||||||
fCCURqHFbQHCCJs2D39XPioo9EMZcD6J7ldwEOJsdSNw3+dzBCvIl7wP6fqtbo/3
|
cqQQJNOelJJmMeFOzmmTk7oNFxCGEC00wlGn
|
||||||
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
|
|
||||||
-----END CERTIFICATE-----
|
-----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) {
|
func Test_isValidClientCertificate(t *testing.T) {
|
||||||
t.Run("no ca", func(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.NoError(t, err, "should not return an error")
|
||||||
assert.True(t, valid, "should return true")
|
assert.True(t, valid, "should return true")
|
||||||
})
|
})
|
||||||
t.Run("no cert", func(t *testing.T) {
|
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.NoError(t, err, "should not return an error")
|
||||||
assert.False(t, valid, "should return false")
|
assert.False(t, valid, "should return false")
|
||||||
})
|
})
|
||||||
t.Run("valid cert", func(t *testing.T) {
|
t.Run("valid cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||||
Presented: true,
|
Presented: true,
|
||||||
Leaf: testValidCert,
|
Leaf: testValidCert,
|
||||||
})
|
})
|
||||||
|
@ -113,19 +91,42 @@ func Test_isValidClientCertificate(t *testing.T) {
|
||||||
assert.True(t, valid, "should return true")
|
assert.True(t, valid, "should return true")
|
||||||
})
|
})
|
||||||
t.Run("unsigned cert", func(t *testing.T) {
|
t.Run("unsigned cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||||
Presented: true,
|
Presented: true,
|
||||||
Leaf: testUnsignedCert,
|
Leaf: testUntrustedCert,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err, "should not return an error")
|
assert.NoError(t, err, "should not return an error")
|
||||||
assert.False(t, valid, "should return false")
|
assert.False(t, valid, "should return false")
|
||||||
})
|
})
|
||||||
t.Run("not a cert", func(t *testing.T) {
|
t.Run("not a cert", func(t *testing.T) {
|
||||||
valid, err := isValidClientCertificate(testCA, ClientCertificateInfo{
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
||||||
Presented: true,
|
Presented: true,
|
||||||
Leaf: "WHATEVER!",
|
Leaf: "WHATEVER!",
|
||||||
})
|
})
|
||||||
assert.Error(t, err, "should return an error")
|
assert.Error(t, err, "should return an error")
|
||||||
assert.False(t, valid, "should return false")
|
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
|
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
|
// GetDataBrokerCertificate gets the optional databroker certificate. This method will return nil if no certificate is
|
||||||
// specified.
|
// specified.
|
||||||
func (o *Options) GetDataBrokerCertificate() (*tls.Certificate, error) {
|
func (o *Options) GetDataBrokerCertificate() (*tls.Certificate, error) {
|
||||||
|
|
|
@ -393,8 +393,6 @@ func TestDownstreamClientCA(t *testing.T) {
|
||||||
assert.Equal(t, "/", result.Path)
|
assert.Equal(t, "/", result.Path)
|
||||||
})
|
})
|
||||||
t.Run("revoked client cert", func(t *testing.T) {
|
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.
|
// Configure an http.Client with a revoked client certificate.
|
||||||
cert := loadCertificate(t, "downstream-1-client-revoked")
|
cert := loadCertificate(t, "downstream-1-client-revoked")
|
||||||
client, transport := getClientWithTransport(t)
|
client, transport := getClientWithTransport(t)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue