mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-17 08:38:15 +02:00
Add support for UserPrincipalName SAN matching to the policy evaluator. Add unit tests. Unfortunately, adding a new test cert requires regenerating the existing ones as well, because the CA key isn't saved.
549 lines
21 KiB
Go
549 lines
21 KiB
Go
package evaluator
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pomerium/pomerium/config"
|
|
)
|
|
|
|
// These certificates can be regenerated by running:
|
|
//
|
|
// go run ./gen-test-certs.go
|
|
//
|
|
// (Copy and paste the output here.)
|
|
const (
|
|
testCA = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBZzCCAQ6gAwIBAgICEAAwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAaMRgw
|
|
FgYDVQQDEw9UcnVzdGVkIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
|
|
AATMLdnk1HSlNQCNimWztrBbBU0xghli5LWSk7P1/1CW3JPvujT0xC2tEpooAxWi
|
|
XECL22GH9E2pH/dQoDj2FP41o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
|
|
BAUwAwEB/zAdBgNVHQ4EFgQUoLhiwb9DEY80NUm/XfiLszTnetcwCgYIKoZIzj0E
|
|
AwIDRwAwRAIgWWAReqEjmvzpUKupOlcmEA4WBoPpsWWTDzFFApWE/+0CIG6FEkwQ
|
|
rJ4htyRqNMapRJmIHqgVxhyV/Pb7t4tsp6d7
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCert = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBYjCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAeMRww
|
|
GgYDVQQDExN0cnVzdGVkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
|
AQcDQgAEUk42ibrv64GOkeoZdw+AUiohrtmg2g89jq60E7+HPBrDHWI+jZ22EtBe
|
|
N9pphc6hktrlGM3oHn95NOemJ/mlAaM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
|
HwYDVR0jBBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwCgYIKoZIzj0EAwIDSAAw
|
|
RQIgGVB3Q4gFtZNgzjUUe1TuhJLy4NY1vRMrtVk1ywEC4hoCIQCg7XSIirpS4Mxc
|
|
kB9Ugn4FUX8SDD5+md2IReM27uKBjw==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testUntrustedCert = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBZTCCAQygAwIBAgICEAEwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAxMRVW50cnVz
|
|
dGVkIFJvb3QgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zNDA3MjEyMTA5MDFaMCAx
|
|
HjAcBgNVBAMTFXVudHJ1c3RlZCBjbGllbnQgY2VydDBZMBMGByqGSM49AgEGCCqG
|
|
SM49AwEHA0IABMD1+g0Dx5xcezQWJsWJX9AoNlPLsaqspsVS/rJoMoa01gE3fh8y
|
|
KaVH3N/SQH4E54TUywJA06wSIGN3SsNgpnajODA2MBMGA1UdJQQMMAoGCCsGAQUF
|
|
BwMCMB8GA1UdIwQYMBaAFHqcIcMoPGoBDLYSIol5EqKgXAA+MAoGCCqGSM49BAMC
|
|
A0cAMEQCIDCSM5mZkwApd+Ne0fYK3o0dP9CKeEd31lzF9PlMAzU2AiBL8EBVN5T6
|
|
M74llANbsQV6/9O3WSDYsvxvAGK/aN2pBg==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testRevokedCert = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBYjCCAQigAwIBAgICEAIwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAeMRww
|
|
GgYDVQQDExNyZXZva2VkIGNsaWVudCBjZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
|
|
AQcDQgAEfOs33d2sg3TQg19dh1bA5gv+wUljqfkykl/rj5vh3PwMoNgf38M09Lbs
|
|
fT6kz7wpKEA0G9/h/f2c4y59/d34ZqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
|
HwYDVR0jBBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwCgYIKoZIzj0EAwIDSAAw
|
|
RQIhAKNJwVxMvKIjkMfk9JhePG24CGawlIzCMJLRExQnIxzoAiAbMOnxje3tr/Mg
|
|
kl9linML1Km4f5aqOp2+TF9O/EHhKg==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testCRL = `
|
|
-----BEGIN X509 CRL-----
|
|
MIHdMIGFAgEBMAoGCCqGSM49BAMCMBoxGDAWBgNVBAMTD1RydXN0ZWQgUm9vdCBD
|
|
QRgPMDAwMTAxMDEwMDAwMDBaMBUwEwICEAIXDTI0MDcyMzIxMDkwMVqgMDAuMB8G
|
|
A1UdIwQYMBaAFKC4YsG/QxGPNDVJv134i7M053rXMAsGA1UdFAQEAgIgADAKBggq
|
|
hkjOPQQDAgNHADBEAiA6zjKyqK0IE8bO/LtnQT2CSRolOcdwHPMjtTkoO7ht8gIg
|
|
Qo8S83mFqwG14NiwaudxpYyYfGcCJZ6aaxqLc03ixiA=
|
|
-----END X509 CRL-----
|
|
`
|
|
testIntermediateCA = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBkTCCATegAwIBAgICEAMwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAiMSAw
|
|
HgYDVQQDExdUcnVzdGVkIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqG
|
|
SM49AwEHA0IABNPOBn1f6EJYlOBs9E4ZllV3rVvzGbh7Z3f3klIfKv0nmYZqTYsE
|
|
06GbMlc+Rdw//wQ40j9+Gnvk/yTVPUydtSSjYzBhMA4GA1UdDwEB/wQEAwIBBjAP
|
|
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQtnNz7iWzU1Fp427ksQsEf31WvgTAf
|
|
BgNVHSMEGDAWgBSguGLBv0MRjzQ1Sb9d+IuzNOd61zAKBggqhkjOPQQDAgNIADBF
|
|
AiEAzOI48p3sxhFnpp2s0LFxjzQXwOUqsKtlkWh16hEzG4MCIGspIDiBlxE594+M
|
|
iiciSIzesgppOJb1Hk/JS9r6qYhz
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidIntermediateCert = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBdTCCARqgAwIBAgICEAAwCgYIKoZIzj0EAwIwIjEgMB4GA1UEAxMXVHJ1c3Rl
|
|
ZCBJbnRlcm1lZGlhdGUgQ0EwIBgPMDAwMTAxMDEwMDAwMDBaFw0zNDA3MjEyMTA5
|
|
MDFaMCgxJjAkBgNVBAMTHWNsaWVudCBjZXJ0IGZyb20gaW50ZXJtZWRpYXRlMFkw
|
|
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi9mkxtKlhiiCHteqDHZP+YH4awImLtEx
|
|
i7s/Nt1AWp3VMQ/cxRsZcmRHceGIpPoinYpmGfVppxy9fddZ4IBjw6M4MDYwEwYD
|
|
VR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0jBBgwFoAULZzc+4ls1NRaeNu5LELBH99V
|
|
r4EwCgYIKoZIzj0EAwIDSQAwRgIhAJGUfGvzsTwMMqLzhSNdmKUWyajbN+wRF2gr
|
|
3CzI77OZAiEAh40Bew0mQQUs3WWilY0BauxtDtdcjX/IerxKTzEXKbg=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCertWithDNSSANs = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBlDCCATugAwIBAgICEAQwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAYMRYw
|
|
FAYDVQQDEw1jbGllbnQgY2VydCAzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
|
Dw9uoVvSDyVpqRN79Dtd3pf8pbTTKV8ZFDMa+pft3cWCSbzsxkHjx5yfiYmy94m0
|
|
IAUEkpsivA7w5zqhqZfONaNxMG8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
|
|
BBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwNwYDVR0RBDAwLoIVYS5jbGllbnQz
|
|
LmV4YW1wbGUuY29tghViLmNsaWVudDMuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwID
|
|
RwAwRAIgTgkgYXb/6sErId2S/1X8TkdQETmMeQ85e0zQLC2WGn0CIAPF5nrlf5EL
|
|
VLK0wso+qCFmqrPNnv4X8nYOEc3L7kPA
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCertWithEmailSAN = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBfTCCASKgAwIBAgICEAUwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAYMRYw
|
|
FAYDVQQDEw1jbGllbnQgY2VydCA0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
|
zMPGmaQ/Hhss/Pil1ku5QgFmVkaZdMHllql508HddNQ96t24vddILPS1YZwRv1SQ
|
|
D47sdwRcrgLcYvXAYhnfxKNYMFYwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
|
|
BBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwHgYDVR0RBBcwFYETY2xpZW50NEBl
|
|
eGFtcGxlLmNvbTAKBggqhkjOPQQDAgNJADBGAiEAiKp/7Cv8tUJBUr+Z+T6ztghs
|
|
hhSH/LZcLzRHJ8jtACoCIQD78Ut4V6/8eSOS3DHIDwqXwOZ/L06DwugzYP5/jlZA
|
|
cQ==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCertWithIPSAN = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBbTCCAROgAwIBAgICEAYwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAYMRYw
|
|
FAYDVQQDEw1jbGllbnQgY2VydCA1MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
|
cRD582BfNyLzyuvtxR/Z19mwWqRF6Kxx5ZSovtPKIeJ75Okiv1q20TOwGrEYLmHr
|
|
YCBrMd3YM56KKcEhSUi/nqNJMEcwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
|
|
BBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwDwYDVR0RBAgwBocEwKgKCjAKBggq
|
|
hkjOPQQDAgNIADBFAiAv+MGYGG9SaQFekl1e7zIG0rxMkbS8UPmkklR/RuXQ+gIh
|
|
ALvBj9aAXW9wN8nYLH9L2bPbMT//a42VuD6qvi6B0i6P
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCertWithURISAN = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBhTCCASugAwIBAgICEAcwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAYMRYw
|
|
FAYDVQQDEw1jbGllbnQgY2VydCA2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
|
B7Znrqifp50mCqJfdoCvEu0UP+W1qUZm1dckfMq02f9kgbn2JGgvb1nGE8gKA0n1
|
|
V6/QtuIUBnyosfX3IuF9qqNhMF8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
|
|
BBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwJwYDVR0RBCAwHoYcc3BpZmZlOi8v
|
|
ZXhhbXBsZS5jb20vZm9vL2JhcjAKBggqhkjOPQQDAgNIADBFAiAwp1maaT976oWE
|
|
f8RnxAPGX1Ecc77A6uhOM0ZCVrYDCQIhAJf5MW7BddAtbRYHyUEhwaVFA9Ci56m9
|
|
TEgriIGNirO9
|
|
-----END CERTIFICATE-----
|
|
`
|
|
testValidCertWithUPNSAN = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBhDCCASqgAwIBAgICEAcwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPVHJ1c3Rl
|
|
ZCBSb290IENBMCAYDzAwMDEwMTAxMDAwMDAwWhcNMzQwNzIxMjEwOTAxWjAYMRYw
|
|
FAYDVQQDEw1jbGllbnQgY2VydCA3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
|
Pbsk3ZsS7dPwVh+G+hG/nv9PcHfFpOrCiAP0/orxhTRiYD2QbxCB9KBC2YZKrMGF
|
|
pvhqM/fX6r1hB6o1Gz/J3KNgMF4wEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
|
|
BBgwFoAUoLhiwb9DEY80NUm/XfiLszTnetcwJgYDVR0RBB8wHaAbBgorBgEEAYI3
|
|
FAIDoA0MC3Rlc3RfZGV2aWNlMAoGCCqGSM49BAMCA0gAMEUCIQDG82BiX0pzkvW+
|
|
UgGq54X7ItNLu2uBFBTgR8NCJpBCMAIgaBzSkAyTSbbLU/gRrhD6HiyGoxDn38xh
|
|
HcsXWWvNgTQ=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
)
|
|
|
|
func Test_isValidClientCertificate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var noConstraints ClientCertConstraints
|
|
t.Run("no ca", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(
|
|
"", "", ClientCertificateInfo{Leaf: "WHATEVER!"}, noConstraints)
|
|
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{}, noConstraints)
|
|
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{
|
|
Presented: true,
|
|
Leaf: testValidCert,
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
})
|
|
t.Run("valid cert with intermediate", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidIntermediateCert,
|
|
Intermediates: testIntermediateCA,
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
})
|
|
t.Run("valid cert missing intermediate", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidIntermediateCert,
|
|
Intermediates: "",
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("intermediate CA as root", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testIntermediateCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidIntermediateCert,
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
})
|
|
t.Run("unsigned cert", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testUntrustedCert,
|
|
}, noConstraints)
|
|
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{
|
|
Presented: true,
|
|
Leaf: "WHATEVER!",
|
|
}, noConstraints)
|
|
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, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, testCRL, revokedCertInfo, noConstraints)
|
|
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,
|
|
}, noConstraints)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
})
|
|
t.Run("chain too deep", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidIntermediateCert,
|
|
Intermediates: testIntermediateCA,
|
|
}, ClientCertConstraints{MaxVerifyDepth: 1})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("any SAN", func(t *testing.T) {
|
|
matchAnySAN := ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile("^.*$"),
|
|
config.SANTypeEmail: regexp.MustCompile("^.*$"),
|
|
config.SANTypeIPAddress: regexp.MustCompile("^.*$"),
|
|
config.SANTypeURI: regexp.MustCompile("^.*$"),
|
|
}}
|
|
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCert, // no SANs
|
|
}, matchAnySAN)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithDNSSANs,
|
|
}, matchAnySAN)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithEmailSAN,
|
|
}, matchAnySAN)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithIPSAN,
|
|
}, matchAnySAN)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithURISAN,
|
|
}, matchAnySAN)
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
})
|
|
t.Run("DNS SAN", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithDNSSANs,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^a\..*\.example\.com$`),
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithDNSSANs,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeEmail: regexp.MustCompile(`^a\..*\.example\.com$`), // mismatched type
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("email SAN", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithEmailSAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeEmail: regexp.MustCompile(`^.*@example\.com$`),
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithEmailSAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeIPAddress: regexp.MustCompile(`^.*@example\.com$`), // mismatched type
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("IP address SAN", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithIPSAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeIPAddress: regexp.MustCompile(`^192\.168\.10\..*$`),
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithIPSAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeURI: regexp.MustCompile(`^192\.168\.10\..*$`), // mismatched type
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("URI SAN", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithURISAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeURI: regexp.MustCompile(`^spiffe://example\.com/.*$`),
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithURISAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^spiffe://example\.com/.*$`), // mismatched type
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
t.Run("UserPrincipalName SAN", func(t *testing.T) {
|
|
valid, err := isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithUPNSAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeUserPrincipalName: regexp.MustCompile(`^test_device$`),
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.True(t, valid, "should return true")
|
|
|
|
valid, err = isValidClientCertificate(testCA, "", ClientCertificateInfo{
|
|
Presented: true,
|
|
Leaf: testValidCertWithURISAN,
|
|
}, ClientCertConstraints{SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^test-device$`), // mismatched type
|
|
}})
|
|
assert.NoError(t, err, "should not return an error")
|
|
assert.False(t, valid, "should return false")
|
|
})
|
|
}
|
|
|
|
func TestClientCertConstraintsFromConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("default constraints", func(t *testing.T) {
|
|
var s config.DownstreamMTLSSettings
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{MaxVerifyDepth: 1}, c)
|
|
})
|
|
t.Run("no constraints", func(t *testing.T) {
|
|
s := config.DownstreamMTLSSettings{
|
|
MaxVerifyDepth: new(uint32),
|
|
}
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{}, c)
|
|
})
|
|
t.Run("larger max depth", func(t *testing.T) {
|
|
depth := uint32(5)
|
|
s := config.DownstreamMTLSSettings{
|
|
MaxVerifyDepth: &depth,
|
|
}
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{MaxVerifyDepth: 5}, c)
|
|
})
|
|
t.Run("one SAN match", func(t *testing.T) {
|
|
s := config.DownstreamMTLSSettings{
|
|
MatchSubjectAltNames: []config.SANMatcher{
|
|
{Type: config.SANTypeDNS, Pattern: `.*\.corp\.example\.com`},
|
|
},
|
|
}
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{
|
|
MaxVerifyDepth: 1,
|
|
SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^(.*\.corp\.example\.com)$`),
|
|
},
|
|
}, c)
|
|
})
|
|
t.Run("multiple SAN matches", func(t *testing.T) {
|
|
s := config.DownstreamMTLSSettings{
|
|
MatchSubjectAltNames: []config.SANMatcher{
|
|
{Type: config.SANTypeDNS, Pattern: `.*\.foo\.example\.com`},
|
|
{Type: config.SANTypeDNS, Pattern: `.*\.bar\.example\.com`},
|
|
},
|
|
}
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{
|
|
MaxVerifyDepth: 1,
|
|
SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^(.*\.foo\.example\.com)|(.*\.bar\.example\.com)$`),
|
|
},
|
|
}, c)
|
|
})
|
|
t.Run("multiple SAN matches", func(t *testing.T) {
|
|
s := config.DownstreamMTLSSettings{
|
|
MatchSubjectAltNames: []config.SANMatcher{
|
|
{Type: config.SANTypeDNS, Pattern: `.*\.foo\.example\.com`},
|
|
{Type: config.SANTypeEmail, Pattern: `.*@example\.com`},
|
|
},
|
|
}
|
|
c, err := ClientCertConstraintsFromConfig(&s)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &ClientCertConstraints{
|
|
MaxVerifyDepth: 1,
|
|
SANMatchers: SANMatchers{
|
|
config.SANTypeDNS: regexp.MustCompile(`^(.*\.foo\.example\.com)$`),
|
|
config.SANTypeEmail: regexp.MustCompile(`^(.*@example\.com)$`),
|
|
},
|
|
}, c)
|
|
})
|
|
t.Run("bad SAN match expression", func(t *testing.T) {
|
|
s := config.DownstreamMTLSSettings{
|
|
MatchSubjectAltNames: []config.SANMatcher{
|
|
{Type: config.SANTypeDNS, Pattern: "["},
|
|
},
|
|
}
|
|
_, err := ClientCertConstraintsFromConfig(&s)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestGetUserPrincipalNamesFromSAN(t *testing.T) {
|
|
b64 := func(raw string) []byte {
|
|
b, err := base64.StdEncoding.DecodeString(raw)
|
|
require.NoError(t, err)
|
|
return b
|
|
}
|
|
|
|
// One UserPrincipalName.
|
|
names, err := getUserPrincipalNamesFromSAN(b64(`
|
|
MBegFQYKKwYBBAGCNxQCA6AHDAVoZWxsbw==`))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []string{"hello"}, names)
|
|
|
|
// Multiple UserPrincipalNames.
|
|
names, err = getUserPrincipalNamesFromSAN(b64(`
|
|
MD+gEwYKKwYBBAGCNxQCA6AFDANmb2+gEwYKKwYBBAGCNxQCA6AFDANiYXKgEwYKKwYBBAGCNxQCA6AF
|
|
DANiYXo=`))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []string{"foo", "bar", "baz"}, names)
|
|
|
|
// Several OtherNames, only one UserPrincipalName.
|
|
names, err = getUserPrincipalNamesFromSAN(b64(`
|
|
MFigFgYDKQEBoA8bDUdlbmVyYWxTdHJpbmegIQYKKwYBBAGCNxQCA6ATDBFVc2VyUHJpbmNpcGFsTmFt
|
|
ZaAbBgMpAQKgFB4SAEIATQBQAFMAdAByAGkAbgBn`))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []string{"UserPrincipalName"}, names)
|
|
|
|
// Two DNS names, no OtherNames.
|
|
names, err = getUserPrincipalNamesFromSAN(b64(`
|
|
MC6CFWEuY2xpZW50My5leGFtcGxlLmNvbYIVYi5jbGllbnQzLmV4YW1wbGUuY29t`))
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, names)
|
|
|
|
// A UserPrincipalName with the wrong data type (GeneralString).
|
|
names, err = getUserPrincipalNamesFromSAN(b64(`
|
|
MB+gHQYKKwYBBAGCNxQCA6APGw1HZW5lcmFsU3RyaW5n`))
|
|
assert.ErrorContains(t, err, "expected UTF8String")
|
|
assert.Empty(t, names)
|
|
|
|
// Other malformed inputs.
|
|
names, err = getUserPrincipalNamesFromSAN(nil)
|
|
assert.ErrorContains(t, err, "error reading GeneralNames sequence")
|
|
assert.Empty(t, names)
|
|
|
|
names, err = getUserPrincipalNamesFromSAN(b64("MA+gDwYCRgGgBwwFaGVsbG8="))
|
|
assert.ErrorContains(t, err, "error reading GeneralName")
|
|
assert.Empty(t, names)
|
|
|
|
names, err = getUserPrincipalNamesFromSAN(b64("MA+gDQICRgGgBwwFaGVsbG8="))
|
|
assert.ErrorContains(t, err, "error reading OtherName type ID")
|
|
assert.Empty(t, names)
|
|
|
|
names, err = getUserPrincipalNamesFromSAN(b64("MBegFQYKKwYBBAGCNxQCA6AIDAVoZWxsbw=="))
|
|
assert.ErrorContains(t, err, "error reading UserPrincipalName value")
|
|
assert.Empty(t, names)
|
|
|
|
names, err = getUserPrincipalNamesFromSAN(b64("MBegFQYKKwYBBAGCNxQCA6EHDAVoZWxsbw=="))
|
|
assert.ErrorContains(t, err, "unexpected UserPrincipalName data tag")
|
|
assert.Empty(t, names)
|
|
}
|