google.golang.org/grpc@v1.74.2/internal/credentials/xds/handshake_info_test.go (about) 1 /* 2 * 3 * Copyright 2021 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package xds 20 21 import ( 22 "context" 23 "crypto/tls" 24 "crypto/x509" 25 "fmt" 26 "net" 27 "net/netip" 28 "net/url" 29 "os" 30 "regexp" 31 "strings" 32 "testing" 33 "time" 34 35 "google.golang.org/grpc/testdata" 36 37 "google.golang.org/grpc/credentials/tls/certprovider" 38 "google.golang.org/grpc/internal/credentials/spiffe" 39 "google.golang.org/grpc/internal/xds/matcher" 40 ) 41 42 type testCertProvider struct { 43 certprovider.Provider 44 } 45 46 type testCertProviderWithKeyMaterial struct { 47 certprovider.Provider 48 } 49 50 func TestDNSMatch(t *testing.T) { 51 tests := []struct { 52 desc string 53 host string 54 pattern string 55 wantMatch bool 56 }{ 57 { 58 desc: "invalid wildcard 1", 59 host: "aa.example.com", 60 pattern: "*a.example.com", 61 wantMatch: false, 62 }, 63 { 64 desc: "invalid wildcard 2", 65 host: "aa.example.com", 66 pattern: "a*.example.com", 67 wantMatch: false, 68 }, 69 { 70 desc: "invalid wildcard 3", 71 host: "abc.example.com", 72 pattern: "a*c.example.com", 73 wantMatch: false, 74 }, 75 { 76 desc: "wildcard in one of the middle components", 77 host: "abc.test.example.com", 78 pattern: "abc.*.example.com", 79 wantMatch: false, 80 }, 81 { 82 desc: "single component wildcard", 83 host: "a.example.com", 84 pattern: "*", 85 wantMatch: false, 86 }, 87 { 88 desc: "short host name", 89 host: "a.com", 90 pattern: "*.example.com", 91 wantMatch: false, 92 }, 93 { 94 desc: "suffix mismatch", 95 host: "a.notexample.com", 96 pattern: "*.example.com", 97 wantMatch: false, 98 }, 99 { 100 desc: "wildcard match across components", 101 host: "sub.test.example.com", 102 pattern: "*.example.com.", 103 wantMatch: false, 104 }, 105 { 106 desc: "host doesn't end in period", 107 host: "test.example.com", 108 pattern: "test.example.com.", 109 wantMatch: true, 110 }, 111 { 112 desc: "pattern doesn't end in period", 113 host: "test.example.com.", 114 pattern: "test.example.com", 115 wantMatch: true, 116 }, 117 { 118 desc: "case insensitive", 119 host: "TEST.EXAMPLE.COM.", 120 pattern: "test.example.com.", 121 wantMatch: true, 122 }, 123 { 124 desc: "simple match", 125 host: "test.example.com", 126 pattern: "test.example.com", 127 wantMatch: true, 128 }, 129 { 130 desc: "good wildcard", 131 host: "a.example.com", 132 pattern: "*.example.com", 133 wantMatch: true, 134 }, 135 } 136 137 for _, test := range tests { 138 t.Run(test.desc, func(t *testing.T) { 139 gotMatch := dnsMatch(test.host, test.pattern) 140 if gotMatch != test.wantMatch { 141 t.Fatalf("dnsMatch(%s, %s) = %v, want %v", test.host, test.pattern, gotMatch, test.wantMatch) 142 } 143 }) 144 } 145 } 146 147 func TestMatchingSANExists_FailureCases(t *testing.T) { 148 url1, err := url.Parse("http://golang.org") 149 if err != nil { 150 t.Fatalf("url.Parse() failed: %v", err) 151 } 152 url2, err := url.Parse("https://github.com/grpc/grpc-go") 153 if err != nil { 154 t.Fatalf("url.Parse() failed: %v", err) 155 } 156 inputCert := &x509.Certificate{ 157 DNSNames: []string{"foo.bar.example.com", "bar.baz.test.com", "*.example.com"}, 158 EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"}, 159 IPAddresses: []net.IP{ 160 netip.MustParseAddr("192.0.0.1").AsSlice(), 161 netip.MustParseAddr("2001:db8::68").AsSlice(), 162 }, 163 URIs: []*url.URL{url1, url2}, 164 } 165 166 tests := []struct { 167 desc string 168 sanMatchers []matcher.StringMatcher 169 }{ 170 { 171 desc: "exact match", 172 sanMatchers: []matcher.StringMatcher{ 173 matcher.StringMatcherForTesting(newStringP("abcd.test.com"), nil, nil, nil, nil, false), 174 matcher.StringMatcherForTesting(newStringP("http://golang"), nil, nil, nil, nil, false), 175 matcher.StringMatcherForTesting(newStringP("HTTP://GOLANG.ORG"), nil, nil, nil, nil, false), 176 }, 177 }, 178 { 179 desc: "prefix match", 180 sanMatchers: []matcher.StringMatcher{ 181 matcher.StringMatcherForTesting(nil, newStringP("i-aint-the-one"), nil, nil, nil, false), 182 matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false), 183 matcher.StringMatcherForTesting(nil, newStringP("FOO.BAR"), nil, nil, nil, false), 184 }, 185 }, 186 { 187 desc: "suffix match", 188 sanMatchers: []matcher.StringMatcher{ 189 matcher.StringMatcherForTesting(nil, nil, newStringP("i-aint-the-one"), nil, nil, false), 190 matcher.StringMatcherForTesting(nil, nil, newStringP("1::68"), nil, nil, false), 191 matcher.StringMatcherForTesting(nil, nil, newStringP(".COM"), nil, nil, false), 192 }, 193 }, 194 { 195 desc: "regex match", 196 sanMatchers: []matcher.StringMatcher{ 197 matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.examples\.com`), false), 198 matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false), 199 }, 200 }, 201 { 202 desc: "contains match", 203 sanMatchers: []matcher.StringMatcher{ 204 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("i-aint-the-one"), nil, false), 205 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:db8:1:1::68"), nil, false), 206 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, false), 207 }, 208 }, 209 } 210 211 for _, test := range tests { 212 t.Run(test.desc, func(t *testing.T) { 213 hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false) 214 215 if hi.MatchingSANExists(inputCert) { 216 t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v succeeded when expected to fail", inputCert, test.sanMatchers) 217 } 218 }) 219 } 220 } 221 222 func TestMatchingSANExists_Success(t *testing.T) { 223 url1, err := url.Parse("http://golang.org") 224 if err != nil { 225 t.Fatalf("url.Parse() failed: %v", err) 226 } 227 url2, err := url.Parse("https://github.com/grpc/grpc-go") 228 if err != nil { 229 t.Fatalf("url.Parse() failed: %v", err) 230 } 231 inputCert := &x509.Certificate{ 232 DNSNames: []string{"baz.test.com", "*.example.com"}, 233 EmailAddresses: []string{"foobar@example.com", "barbaz@test.com"}, 234 IPAddresses: []net.IP{ 235 netip.MustParseAddr("192.0.0.1").AsSlice(), 236 netip.MustParseAddr("2001:db8::68").AsSlice(), 237 }, 238 URIs: []*url.URL{url1, url2}, 239 } 240 241 tests := []struct { 242 desc string 243 sanMatchers []matcher.StringMatcher 244 }{ 245 { 246 desc: "no san matchers", 247 }, 248 { 249 desc: "exact match dns wildcard", 250 sanMatchers: []matcher.StringMatcher{ 251 matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false), 252 matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false), 253 matcher.StringMatcherForTesting(newStringP("abc.example.com"), nil, nil, nil, nil, false), 254 }, 255 }, 256 { 257 desc: "exact match ignore case", 258 sanMatchers: []matcher.StringMatcher{ 259 matcher.StringMatcherForTesting(newStringP("FOOBAR@EXAMPLE.COM"), nil, nil, nil, nil, true), 260 }, 261 }, 262 { 263 desc: "prefix match", 264 sanMatchers: []matcher.StringMatcher{ 265 matcher.StringMatcherForTesting(nil, nil, newStringP(".co.in"), nil, nil, false), 266 matcher.StringMatcherForTesting(nil, newStringP("192.168.1.1"), nil, nil, nil, false), 267 matcher.StringMatcherForTesting(nil, newStringP("baz.test"), nil, nil, nil, false), 268 }, 269 }, 270 { 271 desc: "prefix match ignore case", 272 sanMatchers: []matcher.StringMatcher{ 273 matcher.StringMatcherForTesting(nil, newStringP("BAZ.test"), nil, nil, nil, true), 274 }, 275 }, 276 { 277 desc: "suffix match", 278 sanMatchers: []matcher.StringMatcher{ 279 matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false), 280 matcher.StringMatcherForTesting(nil, nil, newStringP("192.168.1.1"), nil, nil, false), 281 matcher.StringMatcherForTesting(nil, nil, newStringP("@test.com"), nil, nil, false), 282 }, 283 }, 284 { 285 desc: "suffix match ignore case", 286 sanMatchers: []matcher.StringMatcher{ 287 matcher.StringMatcherForTesting(nil, nil, newStringP("@test.COM"), nil, nil, true), 288 }, 289 }, 290 { 291 desc: "regex match", 292 sanMatchers: []matcher.StringMatcher{ 293 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("https://github.com/grpc/grpc-java"), nil, false), 294 matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`192\.[0-9]{1,3}\.1\.1`), false), 295 matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(`.*\.test\.com`), false), 296 }, 297 }, 298 { 299 desc: "contains match", 300 sanMatchers: []matcher.StringMatcher{ 301 matcher.StringMatcherForTesting(newStringP("https://github.com/grpc/grpc-java"), nil, nil, nil, nil, false), 302 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("2001:68::db8"), nil, false), 303 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("192.0.0"), nil, false), 304 }, 305 }, 306 { 307 desc: "contains match ignore case", 308 sanMatchers: []matcher.StringMatcher{ 309 matcher.StringMatcherForTesting(nil, nil, nil, newStringP("GRPC"), nil, true), 310 }, 311 }, 312 } 313 314 for _, test := range tests { 315 t.Run(test.desc, func(t *testing.T) { 316 hi := NewHandshakeInfo(nil, nil, test.sanMatchers, false) 317 318 if !hi.MatchingSANExists(inputCert) { 319 t.Fatalf("hi.MatchingSANExists(%+v) with SAN matchers +%v failed when expected to succeed", inputCert, test.sanMatchers) 320 } 321 }) 322 } 323 } 324 325 func newStringP(s string) *string { 326 return &s 327 } 328 329 func TestEqual(t *testing.T) { 330 tests := []struct { 331 desc string 332 hi1 *HandshakeInfo 333 hi2 *HandshakeInfo 334 wantMatch bool 335 }{ 336 { 337 desc: "both HandshakeInfo are nil", 338 hi1: nil, 339 hi2: nil, 340 wantMatch: true, 341 }, 342 { 343 desc: "one HandshakeInfo is nil", 344 hi1: nil, 345 hi2: NewHandshakeInfo(&testCertProvider{}, nil, nil, false), 346 wantMatch: false, 347 }, 348 { 349 desc: "different root providers", 350 hi1: NewHandshakeInfo(&testCertProvider{}, nil, nil, false), 351 hi2: NewHandshakeInfo(&testCertProvider{}, nil, nil, false), 352 wantMatch: false, 353 }, 354 { 355 desc: "same providers, same SAN matchers", 356 hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{ 357 matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false), 358 }, false), 359 hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{ 360 matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false), 361 }, false), 362 wantMatch: true, 363 }, 364 { 365 desc: "same providers, different SAN matchers", 366 hi1: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{ 367 matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false), 368 }, false), 369 hi2: NewHandshakeInfo(testCertProvider{}, testCertProvider{}, []matcher.StringMatcher{ 370 matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false), 371 }, false), 372 wantMatch: false, 373 }, 374 { 375 desc: "same SAN matchers with different content", 376 hi1: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{ 377 matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false), 378 }, false), 379 hi2: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, []matcher.StringMatcher{ 380 matcher.StringMatcherForTesting(newStringP("foo.com"), nil, nil, nil, nil, false), 381 matcher.StringMatcherForTesting(newStringP("bar.com"), nil, nil, nil, nil, false), 382 }, false), 383 wantMatch: false, 384 }, 385 { 386 desc: "different requireClientCert flags", 387 hi1: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, true), 388 hi2: NewHandshakeInfo(&testCertProvider{}, &testCertProvider{}, nil, false), 389 wantMatch: false, 390 }, 391 { 392 desc: "same identity provider, different root provider", 393 hi1: NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false), 394 hi2: NewHandshakeInfo(&testCertProvider{}, testCertProvider{}, nil, false), 395 wantMatch: false, 396 }, 397 { 398 desc: "different identity provider, same root provider", 399 hi1: NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false), 400 hi2: NewHandshakeInfo(testCertProvider{}, &testCertProvider{}, nil, false), 401 wantMatch: false, 402 }, 403 } 404 405 for _, test := range tests { 406 t.Run(test.desc, func(t *testing.T) { 407 if gotMatch := test.hi1.Equal(test.hi2); gotMatch != test.wantMatch { 408 t.Errorf("hi1.Equal(hi2) = %v; wantMatch %v", gotMatch, test.wantMatch) 409 } 410 }) 411 } 412 } 413 414 func (p *testCertProviderWithKeyMaterial) KeyMaterial(_ context.Context) (*certprovider.KeyMaterial, error) { 415 km := &certprovider.KeyMaterial{} 416 spiffeBundleMapContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client_spiffebundle.json")) 417 if err != nil { 418 return nil, err 419 } 420 bundleMap, err := spiffe.BundleMapFromBytes(spiffeBundleMapContents) 421 if err != nil { 422 return nil, err 423 } 424 km.SPIFFEBundleMap = bundleMap 425 rootFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/ca.pem")) 426 if err != nil { 427 return nil, err 428 } 429 trustPool := x509.NewCertPool() 430 if !trustPool.AppendCertsFromPEM(rootFileContents) { 431 return nil, fmt.Errorf("Failed to parse root certificate") 432 } 433 km.Roots = trustPool 434 435 certFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client_spiffe.pem")) 436 if err != nil { 437 return nil, err 438 } 439 keyFileContents, err := os.ReadFile(testdata.Path("spiffe_end2end/client.key")) 440 if err != nil { 441 return nil, err 442 } 443 cert, err := tls.X509KeyPair(certFileContents, keyFileContents) 444 if err != nil { 445 return nil, err 446 } 447 km.Certs = []tls.Certificate{cert} 448 return km, nil 449 } 450 451 func TestBuildVerifyFuncFailures(t *testing.T) { 452 tests := []struct { 453 desc string 454 peerCertChain [][]byte 455 wantErr string 456 }{ 457 { 458 desc: "invalid x509", 459 peerCertChain: [][]byte{[]byte("NOT_A_CERT")}, 460 wantErr: "x509: malformed certificate", 461 }, 462 { 463 desc: "invalid SPIFFE ID in peer cert", 464 // server1.pem doesn't have a valid SPIFFE ID, so attempted to get a 465 // root from the SPIFFE Bundle Map will fail 466 peerCertChain: loadCert(t, testdata.Path("server1.pem"), testdata.Path("server1.key")), 467 wantErr: "spiffe: could not get spiffe ID from peer leaf cert but verification with spiffe trust map was configure", 468 }, 469 } 470 testProvider := testCertProviderWithKeyMaterial{} 471 hi := NewHandshakeInfo(&testProvider, &testProvider, nil, true) 472 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 473 defer cancel() 474 cfg, err := hi.ClientSideTLSConfig(ctx) 475 if err != nil { 476 t.Fatalf("hi.ClientSideTLSConfig() failed with err %v", err) 477 } 478 for _, tc := range tests { 479 t.Run(tc.desc, func(t *testing.T) { 480 err = cfg.VerifyPeerCertificate(tc.peerCertChain, nil) 481 if !strings.Contains(err.Error(), tc.wantErr) { 482 t.Errorf("VerifyPeerCertificate got err %v, want: %v", err, tc.wantErr) 483 } 484 }) 485 } 486 } 487 488 func loadCert(t *testing.T, certPath, keyPath string) [][]byte { 489 cert, err := tls.LoadX509KeyPair(certPath, keyPath) 490 if err != nil { 491 t.Fatalf("LoadX509KeyPair(%s, %s) failed: %v", certPath, keyPath, err) 492 } 493 return cert.Certificate 494 495 }