cloudflare/cloudflared
Publicmirrored from https://github.com/cloudflare/cloudflaredAvailable
tlsconfig/tlsconfig_test.go
214lines · modecode
| 1 | // +build ignore |
| 2 | // TODO: Remove the above build tag and include this test when we start compiling with Golang 1.10.0+ |
| 3 | |
| 4 | package tlsconfig |
| 5 | |
| 6 | import ( |
| 7 | "crypto/x509" |
| 8 | "crypto/x509/pkix" |
| 9 | "encoding/asn1" |
| 10 | "os" |
| 11 | "testing" |
| 12 | |
| 13 | "github.com/stretchr/testify/assert" |
| 14 | ) |
| 15 | |
| 16 | // Generated using `openssl req -newkey rsa:512 -nodes -x509 -days 3650` |
| 17 | var samplePEM = []byte(` |
| 18 | -----BEGIN CERTIFICATE----- |
| 19 | MIIB4DCCAYoCCQCb/H0EUrdXEjANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV |
| 20 | UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv |
| 21 | dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE |
| 22 | AwwIVGVzdCBPbmUwHhcNMTgwNDI2MTYxMDUxWhcNMjgwNDIzMTYxMDUxWjB3MQsw |
| 23 | CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG |
| 24 | A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn |
| 25 | eTERMA8GA1UEAwwIVGVzdCBPbmUwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAwVQD |
| 26 | K0SJ25UFLznm2pU3zhzMEvpDEofHVNnCjk4mlDrtVop7PkKZ8pDEmuQANltUrxC8 |
| 27 | yHBE2wXMv+GlH+bDtwIDAQABMA0GCSqGSIb3DQEBCwUAA0EAjVYQzozIFPkt/HRY |
| 28 | uUoZ8zEHIDICb0syFf5VAjm9AgTwIPzUmD+c5vl6LWDnxq7L45nLCzhhQ6YmiwDz |
| 29 | X7Wcyg== |
| 30 | -----END CERTIFICATE----- |
| 31 | -----BEGIN CERTIFICATE----- |
| 32 | MIIB4DCCAYoCCQDZfCdAJ+mwzDANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV |
| 33 | UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv |
| 34 | dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE |
| 35 | AwwIVGVzdCBUd28wHhcNMTgwNDI2MTYxMTIwWhcNMjgwNDIzMTYxMTIwWjB3MQsw |
| 36 | CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG |
| 37 | A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn |
| 38 | eTERMA8GA1UEAwwIVGVzdCBUd28wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAoHKp |
| 39 | ROVK3zCSsH7ocYeyRAML4V7SFAbZcb4WIwDnE08oMBVRkQVcW5tqEkvG3RiClfzV |
| 40 | wZIJ3CfqKIeSNSDU9wIDAQABMA0GCSqGSIb3DQEBCwUAA0EAJw2gUbnPiq4C2p5b |
| 41 | iWzlA9Q7aKo+VQ4H7IZS7tTccr59nVjvH/TG3eWujpnocr4TOqW9M3CK1DF9mUGP |
| 42 | 3pQ3Jg== |
| 43 | -----END CERTIFICATE----- |
| 44 | `) |
| 45 | |
| 46 | var systemCertPoolSubjects []*pkix.Name |
| 47 | |
| 48 | type certificateFixture struct { |
| 49 | ou string |
| 50 | cn string |
| 51 | } |
| 52 | |
| 53 | func TestMain(m *testing.M) { |
| 54 | systemCertPool, err := x509.SystemCertPool() |
| 55 | if isUnrecoverableError(err) { |
| 56 | os.Exit(1) |
| 57 | } |
| 58 | |
| 59 | if systemCertPool == nil { |
| 60 | // On Windows, let's just assume the system cert pool was empty |
| 61 | systemCertPool = x509.NewCertPool() |
| 62 | } |
| 63 | |
| 64 | systemCertPoolSubjects, err = getCertPoolSubjects(systemCertPool) |
| 65 | if err != nil { |
| 66 | os.Exit(1) |
| 67 | } |
| 68 | |
| 69 | os.Exit(m.Run()) |
| 70 | } |
| 71 | |
| 72 | func TestLoadOriginCertPoolJustSystemPool(t *testing.T) { |
| 73 | certPoolSubjects := loadCertPoolSubjects(t, nil) |
| 74 | extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects) |
| 75 | |
| 76 | // Remove extra subjects from the cert pool |
| 77 | var filteredSystemCertPoolSubjects []*pkix.Name |
| 78 | |
| 79 | t.Log(extraSubjects) |
| 80 | |
| 81 | OUTER: |
| 82 | for _, subject := range certPoolSubjects { |
| 83 | for _, extraSubject := range extraSubjects { |
| 84 | if subject == extraSubject { |
| 85 | t.Log(extraSubject) |
| 86 | continue OUTER |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | filteredSystemCertPoolSubjects = append(filteredSystemCertPoolSubjects, subject) |
| 91 | } |
| 92 | |
| 93 | assert.Equal(t, len(filteredSystemCertPoolSubjects), len(systemCertPoolSubjects)) |
| 94 | |
| 95 | difference := subjectSubtract(systemCertPoolSubjects, filteredSystemCertPoolSubjects) |
| 96 | assert.Equal(t, 0, len(difference)) |
| 97 | } |
| 98 | |
| 99 | func TestLoadOriginCertPoolCFCertificates(t *testing.T) { |
| 100 | certPoolSubjects := loadCertPoolSubjects(t, nil) |
| 101 | |
| 102 | extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects) |
| 103 | |
| 104 | expected := []*certificateFixture{ |
| 105 | {ou: "CloudFlare Origin SSL ECC Certificate Authority"}, |
| 106 | {ou: "CloudFlare Origin SSL Certificate Authority"}, |
| 107 | {cn: "origin-pull.cloudflare.net"}, |
| 108 | {cn: "Argo Tunnel Sample Hello Server Certificate"}, |
| 109 | } |
| 110 | |
| 111 | assertFixturesMatchSubjects(t, expected, extraSubjects) |
| 112 | } |
| 113 | |
| 114 | func TestLoadOriginCertPoolWithExtraPEMs(t *testing.T) { |
| 115 | certPoolWithoutPEMSubjects := loadCertPoolSubjects(t, nil) |
| 116 | certPoolWithPEMSubjects := loadCertPoolSubjects(t, samplePEM) |
| 117 | |
| 118 | difference := subjectSubtract(certPoolWithoutPEMSubjects, certPoolWithPEMSubjects) |
| 119 | |
| 120 | assert.Equal(t, 2, len(difference)) |
| 121 | |
| 122 | expected := []*certificateFixture{ |
| 123 | {cn: "Test One"}, |
| 124 | {cn: "Test Two"}, |
| 125 | } |
| 126 | |
| 127 | assertFixturesMatchSubjects(t, expected, difference) |
| 128 | } |
| 129 | |
| 130 | func loadCertPoolSubjects(t *testing.T, originCAPoolPEM []byte) []*pkix.Name { |
| 131 | certPool, err := LoadOriginCertPool(originCAPoolPEM) |
| 132 | if isUnrecoverableError(err) { |
| 133 | t.Fatal(err) |
| 134 | } |
| 135 | assert.NotEmpty(t, certPool.Subjects()) |
| 136 | certPoolSubjects, err := getCertPoolSubjects(certPool) |
| 137 | if err != nil { |
| 138 | t.Fatal(err) |
| 139 | } |
| 140 | |
| 141 | return certPoolSubjects |
| 142 | } |
| 143 | |
| 144 | func assertFixturesMatchSubjects(t *testing.T, fixtures []*certificateFixture, subjects []*pkix.Name) { |
| 145 | assert.Equal(t, len(fixtures), len(subjects)) |
| 146 | |
| 147 | for _, fixture := range fixtures { |
| 148 | found := false |
| 149 | for _, subject := range subjects { |
| 150 | found = found || fixtureMatchesSubjectPredicate(fixture, subject) |
| 151 | } |
| 152 | |
| 153 | if !found { |
| 154 | t.Fail() |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | func fixtureMatchesSubjectPredicate(fixture *certificateFixture, subject *pkix.Name) bool { |
| 160 | cnMatch := true |
| 161 | if fixture.cn != "" { |
| 162 | cnMatch = fixture.cn == subject.CommonName |
| 163 | } |
| 164 | |
| 165 | ouMatch := true |
| 166 | if fixture.ou != "" { |
| 167 | ouMatch = len(subject.OrganizationalUnit) > 0 && fixture.ou == subject.OrganizationalUnit[0] |
| 168 | } |
| 169 | |
| 170 | return cnMatch && ouMatch |
| 171 | } |
| 172 | |
| 173 | func subjectSubtract(left []*pkix.Name, right []*pkix.Name) []*pkix.Name { |
| 174 | var difference []*pkix.Name |
| 175 | |
| 176 | var found bool |
| 177 | for _, r := range right { |
| 178 | found = false |
| 179 | for _, l := range left { |
| 180 | if (*l).String() == (*r).String() { |
| 181 | found = true |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if !found { |
| 186 | difference = append(difference, r) |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | return difference |
| 191 | } |
| 192 | |
| 193 | func getCertPoolSubjects(certPool *x509.CertPool) ([]*pkix.Name, error) { |
| 194 | var subjects []*pkix.Name |
| 195 | |
| 196 | for _, subject := range certPool.Subjects() { |
| 197 | var sequence pkix.RDNSequence |
| 198 | _, err := asn1.Unmarshal(subject, &sequence) |
| 199 | if err != nil { |
| 200 | return nil, err |
| 201 | } |
| 202 | |
| 203 | name := pkix.Name{} |
| 204 | name.FillFromRDNSequence(&sequence) |
| 205 | |
| 206 | subjects = append(subjects, &name) |
| 207 | } |
| 208 | |
| 209 | return subjects, nil |
| 210 | } |
| 211 | |
| 212 | func isUnrecoverableError(err error) bool { |
| 213 | return err != nil && err.Error() != "crypto/x509: system root pool is not available on Windows" |
| 214 | } |
| 215 | |