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) }