github.com/hashicorp/vault/sdk@v0.11.0/helper/ocsp/ocsp_test.go (about) 1 // Copyright (c) 2017-2022 Snowflake Computing Inc. All rights reserved. 2 3 package ocsp 4 5 import ( 6 "bytes" 7 "context" 8 "crypto" 9 "crypto/ecdsa" 10 "crypto/elliptic" 11 "crypto/rand" 12 "crypto/tls" 13 "crypto/x509" 14 "crypto/x509/pkix" 15 "errors" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "math/big" 20 "net" 21 "net/http" 22 "net/http/httptest" 23 "net/url" 24 "testing" 25 "time" 26 27 "github.com/hashicorp/go-hclog" 28 "github.com/hashicorp/go-retryablehttp" 29 lru "github.com/hashicorp/golang-lru" 30 "github.com/stretchr/testify/require" 31 "golang.org/x/crypto/ocsp" 32 ) 33 34 func TestOCSP(t *testing.T) { 35 targetURL := []string{ 36 "https://sfcdev1.blob.core.windows.net/", 37 "https://sfctest0.snowflakecomputing.com/", 38 "https://s3-us-west-2.amazonaws.com/sfc-snowsql-updates/?prefix=1.1/windows_x86_64", 39 } 40 41 conf := VerifyConfig{ 42 OcspFailureMode: FailOpenFalse, 43 } 44 c := New(testLogFactory, 10) 45 transports := []*http.Transport{ 46 newInsecureOcspTransport(nil), 47 c.NewTransport(&conf), 48 } 49 50 for _, tgt := range targetURL { 51 c.ocspResponseCache, _ = lru.New2Q(10) 52 for _, tr := range transports { 53 c := &http.Client{ 54 Transport: tr, 55 Timeout: 30 * time.Second, 56 } 57 req, err := http.NewRequest("GET", tgt, bytes.NewReader(nil)) 58 if err != nil { 59 t.Fatalf("fail to create a request. err: %v", err) 60 } 61 res, err := c.Do(req) 62 if err != nil { 63 t.Fatalf("failed to GET contents. err: %v", err) 64 } 65 defer res.Body.Close() 66 _, err = ioutil.ReadAll(res.Body) 67 if err != nil { 68 t.Fatalf("failed to read content body for %v", tgt) 69 } 70 71 } 72 } 73 } 74 75 /** 76 // Used for development, requires an active Vault with PKI setup 77 func TestMultiOCSP(t *testing.T) { 78 79 targetURL := []string{ 80 "https://localhost:8200/v1/pki/ocsp", 81 "https://localhost:8200/v1/pki/ocsp", 82 "https://localhost:8200/v1/pki/ocsp", 83 } 84 85 b, _ := pem.Decode([]byte(vaultCert)) 86 caCert, _ := x509.ParseCertificate(b.Bytes) 87 conf := VerifyConfig{ 88 OcspFailureMode: FailOpenFalse, 89 QueryAllServers: true, 90 OcspServersOverride: targetURL, 91 ExtraCas: []*x509.Certificate{caCert}, 92 } 93 c := New(testLogFactory, 10) 94 transports := []*http.Transport{ 95 newInsecureOcspTransport(conf.ExtraCas), 96 c.NewTransport(&conf), 97 } 98 99 tgt := "https://localhost:8200/v1/pki/ca/pem" 100 c.ocspResponseCache, _ = lru.New2Q(10) 101 for _, tr := range transports { 102 c := &http.Client{ 103 Transport: tr, 104 Timeout: 30 * time.Second, 105 } 106 req, err := http.NewRequest("GET", tgt, bytes.NewReader(nil)) 107 if err != nil { 108 t.Fatalf("fail to create a request. err: %v", err) 109 } 110 res, err := c.Do(req) 111 if err != nil { 112 t.Fatalf("failed to GET contents. err: %v", err) 113 } 114 defer res.Body.Close() 115 _, err = ioutil.ReadAll(res.Body) 116 if err != nil { 117 t.Fatalf("failed to read content body for %v", tgt) 118 } 119 } 120 } 121 */ 122 123 func TestUnitEncodeCertIDGood(t *testing.T) { 124 targetURLs := []string{ 125 "faketestaccount.snowflakecomputing.com:443", 126 "s3-us-west-2.amazonaws.com:443", 127 "sfcdev1.blob.core.windows.net:443", 128 } 129 for _, tt := range targetURLs { 130 chainedCerts := getCert(tt) 131 for i := 0; i < len(chainedCerts)-1; i++ { 132 subject := chainedCerts[i] 133 issuer := chainedCerts[i+1] 134 ocspServers := subject.OCSPServer 135 if len(ocspServers) == 0 { 136 t.Fatalf("no OCSP server is found. cert: %v", subject.Subject) 137 } 138 ocspReq, err := ocsp.CreateRequest(subject, issuer, &ocsp.RequestOptions{}) 139 if err != nil { 140 t.Fatalf("failed to create OCSP request. err: %v", err) 141 } 142 var ost *ocspStatus 143 _, ost = extractCertIDKeyFromRequest(ocspReq) 144 if ost.err != nil { 145 t.Fatalf("failed to extract cert ID from the OCSP request. err: %v", ost.err) 146 } 147 // better hash. Not sure if the actual OCSP server accepts this, though. 148 ocspReq, err = ocsp.CreateRequest(subject, issuer, &ocsp.RequestOptions{Hash: crypto.SHA512}) 149 if err != nil { 150 t.Fatalf("failed to create OCSP request. err: %v", err) 151 } 152 _, ost = extractCertIDKeyFromRequest(ocspReq) 153 if ost.err != nil { 154 t.Fatalf("failed to extract cert ID from the OCSP request. err: %v", ost.err) 155 } 156 // tweaked request binary 157 ocspReq, err = ocsp.CreateRequest(subject, issuer, &ocsp.RequestOptions{Hash: crypto.SHA512}) 158 if err != nil { 159 t.Fatalf("failed to create OCSP request. err: %v", err) 160 } 161 ocspReq[10] = 0 // random change 162 _, ost = extractCertIDKeyFromRequest(ocspReq) 163 if ost.err == nil { 164 t.Fatal("should have failed") 165 } 166 } 167 } 168 } 169 170 func TestUnitCheckOCSPResponseCache(t *testing.T) { 171 c := New(testLogFactory, 10) 172 dummyKey0 := certIDKey{ 173 NameHash: "dummy0", 174 IssuerKeyHash: "dummy0", 175 SerialNumber: "dummy0", 176 } 177 dummyKey := certIDKey{ 178 NameHash: "dummy1", 179 IssuerKeyHash: "dummy1", 180 SerialNumber: "dummy1", 181 } 182 currentTime := float64(time.Now().UTC().Unix()) 183 c.ocspResponseCache.Add(dummyKey0, &ocspCachedResponse{time: currentTime}) 184 subject := &x509.Certificate{} 185 issuer := &x509.Certificate{} 186 ost, err := c.checkOCSPResponseCache(&dummyKey, subject, issuer) 187 if err != nil { 188 t.Fatal(err) 189 } 190 if ost.code != ocspMissedCache { 191 t.Fatalf("should have failed. expected: %v, got: %v", ocspMissedCache, ost.code) 192 } 193 // old timestamp 194 c.ocspResponseCache.Add(dummyKey, &ocspCachedResponse{time: float64(1395054952)}) 195 ost, err = c.checkOCSPResponseCache(&dummyKey, subject, issuer) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if ost.code != ocspCacheExpired { 200 t.Fatalf("should have failed. expected: %v, got: %v", ocspCacheExpired, ost.code) 201 } 202 203 // invalid validity 204 c.ocspResponseCache.Add(dummyKey, &ocspCachedResponse{time: float64(currentTime - 1000)}) 205 ost, err = c.checkOCSPResponseCache(&dummyKey, subject, nil) 206 if err == nil && isValidOCSPStatus(ost.code) { 207 t.Fatalf("should have failed.") 208 } 209 } 210 211 func TestUnitExpiredOCSPResponse(t *testing.T) { 212 rootCaKey, rootCa, leafCert := createCaLeafCerts(t) 213 214 expiredOcspResponse := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 215 now := time.Now() 216 ocspRes := ocsp.Response{ 217 SerialNumber: big.NewInt(2), 218 ThisUpdate: now.Add(-1 * time.Hour), 219 NextUpdate: now.Add(-30 * time.Minute), 220 Status: ocsp.Good, 221 } 222 response, err := ocsp.CreateResponse(rootCa, rootCa, ocspRes, rootCaKey) 223 if err != nil { 224 _, _ = w.Write(ocsp.InternalErrorErrorResponse) 225 t.Fatalf("failed generating OCSP response: %v", err) 226 } 227 _, _ = w.Write(response) 228 }) 229 ts := httptest.NewServer(expiredOcspResponse) 230 defer ts.Close() 231 232 logFactory := func() hclog.Logger { 233 return hclog.NewNullLogger() 234 } 235 client := New(logFactory, 100) 236 237 ctx := context.Background() 238 239 config := &VerifyConfig{ 240 OcspEnabled: true, 241 OcspServersOverride: []string{ts.URL}, 242 OcspFailureMode: FailOpenFalse, 243 QueryAllServers: false, 244 } 245 246 status, err := client.GetRevocationStatus(ctx, leafCert, rootCa, config) 247 require.ErrorContains(t, err, "invalid validity", 248 "Expected error got response: %v, %v", status, err) 249 } 250 251 func createCaLeafCerts(t *testing.T) (*ecdsa.PrivateKey, *x509.Certificate, *x509.Certificate) { 252 rootCaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 253 require.NoError(t, err, "failed generated root key for CA") 254 255 // Validate we reject CSRs that contain CN that aren't in the original order 256 cr := &x509.Certificate{ 257 Subject: pkix.Name{CommonName: "Root Cert"}, 258 SerialNumber: big.NewInt(1), 259 IsCA: true, 260 BasicConstraintsValid: true, 261 SignatureAlgorithm: x509.ECDSAWithSHA256, 262 NotBefore: time.Now().Add(-1 * time.Second), 263 NotAfter: time.Now().AddDate(1, 0, 0), 264 KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, 265 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}, 266 } 267 rootCaBytes, err := x509.CreateCertificate(rand.Reader, cr, cr, &rootCaKey.PublicKey, rootCaKey) 268 require.NoError(t, err, "failed generating root ca") 269 270 rootCa, err := x509.ParseCertificate(rootCaBytes) 271 require.NoError(t, err, "failed parsing root ca") 272 273 leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 274 require.NoError(t, err, "failed generated leaf key") 275 276 cr = &x509.Certificate{ 277 Subject: pkix.Name{CommonName: "Leaf Cert"}, 278 SerialNumber: big.NewInt(2), 279 SignatureAlgorithm: x509.ECDSAWithSHA256, 280 NotBefore: time.Now().Add(-1 * time.Second), 281 NotAfter: time.Now().AddDate(1, 0, 0), 282 KeyUsage: x509.KeyUsageDigitalSignature, 283 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 284 } 285 leafCertBytes, err := x509.CreateCertificate(rand.Reader, cr, rootCa, &leafKey.PublicKey, rootCaKey) 286 require.NoError(t, err, "failed generating root ca") 287 288 leafCert, err := x509.ParseCertificate(leafCertBytes) 289 require.NoError(t, err, "failed parsing root ca") 290 return rootCaKey, rootCa, leafCert 291 } 292 293 func TestUnitValidateOCSP(t *testing.T) { 294 ocspRes := &ocsp.Response{} 295 ost, err := validateOCSP(ocspRes) 296 if err == nil && isValidOCSPStatus(ost.code) { 297 t.Fatalf("should have failed.") 298 } 299 300 currentTime := time.Now() 301 ocspRes.ThisUpdate = currentTime.Add(-2 * time.Hour) 302 ocspRes.NextUpdate = currentTime.Add(2 * time.Hour) 303 ocspRes.Status = ocsp.Revoked 304 ost, err = validateOCSP(ocspRes) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 if ost.code != ocspStatusRevoked { 310 t.Fatalf("should have failed. expected: %v, got: %v", ocspStatusRevoked, ost.code) 311 } 312 ocspRes.Status = ocsp.Good 313 ost, err = validateOCSP(ocspRes) 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 if ost.code != ocspStatusGood { 319 t.Fatalf("should have success. expected: %v, got: %v", ocspStatusGood, ost.code) 320 } 321 ocspRes.Status = ocsp.Unknown 322 ost, err = validateOCSP(ocspRes) 323 if err != nil { 324 t.Fatal(err) 325 } 326 if ost.code != ocspStatusUnknown { 327 t.Fatalf("should have failed. expected: %v, got: %v", ocspStatusUnknown, ost.code) 328 } 329 ocspRes.Status = ocsp.ServerFailed 330 ost, err = validateOCSP(ocspRes) 331 if err != nil { 332 t.Fatal(err) 333 } 334 if ost.code != ocspStatusOthers { 335 t.Fatalf("should have failed. expected: %v, got: %v", ocspStatusOthers, ost.code) 336 } 337 } 338 339 func TestUnitEncodeCertID(t *testing.T) { 340 var st *ocspStatus 341 _, st = extractCertIDKeyFromRequest([]byte{0x1, 0x2}) 342 if st.code != ocspFailedDecomposeRequest { 343 t.Fatalf("failed to get OCSP status. expected: %v, got: %v", ocspFailedDecomposeRequest, st.code) 344 } 345 } 346 347 func getCert(addr string) []*x509.Certificate { 348 tcpConn, err := net.DialTimeout("tcp", addr, 40*time.Second) 349 if err != nil { 350 panic(err) 351 } 352 defer tcpConn.Close() 353 354 err = tcpConn.SetDeadline(time.Now().Add(10 * time.Second)) 355 if err != nil { 356 panic(err) 357 } 358 config := tls.Config{InsecureSkipVerify: true, ServerName: addr} 359 360 conn := tls.Client(tcpConn, &config) 361 defer conn.Close() 362 363 err = conn.Handshake() 364 if err != nil { 365 panic(err) 366 } 367 368 state := conn.ConnectionState() 369 370 return state.PeerCertificates 371 } 372 373 func TestOCSPRetry(t *testing.T) { 374 c := New(testLogFactory, 10) 375 certs := getCert("s3-us-west-2.amazonaws.com:443") 376 dummyOCSPHost := &url.URL{ 377 Scheme: "https", 378 Host: "dummyOCSPHost", 379 } 380 client := &fakeHTTPClient{ 381 cnt: 3, 382 success: true, 383 body: []byte{1, 2, 3}, 384 logger: hclog.New(hclog.DefaultOptions), 385 t: t, 386 } 387 res, b, st, err := c.retryOCSP( 388 context.TODO(), 389 client, fakeRequestFunc, 390 dummyOCSPHost, 391 make(map[string]string), []byte{0}, certs[len(certs)-1]) 392 if err == nil { 393 fmt.Printf("should fail: %v, %v, %v\n", res, b, st) 394 } 395 client = &fakeHTTPClient{ 396 cnt: 30, 397 success: true, 398 body: []byte{1, 2, 3}, 399 logger: hclog.New(hclog.DefaultOptions), 400 t: t, 401 } 402 res, b, st, err = c.retryOCSP( 403 context.TODO(), 404 client, fakeRequestFunc, 405 dummyOCSPHost, 406 make(map[string]string), []byte{0}, certs[len(certs)-1]) 407 if err == nil { 408 fmt.Printf("should fail: %v, %v, %v\n", res, b, st) 409 } 410 } 411 412 type tcCanEarlyExit struct { 413 results []*ocspStatus 414 resultLen int 415 retFailOpen *ocspStatus 416 retFailClosed *ocspStatus 417 } 418 419 func TestCanEarlyExitForOCSP(t *testing.T) { 420 testcases := []tcCanEarlyExit{ 421 { // 0 422 results: []*ocspStatus{ 423 { 424 code: ocspStatusGood, 425 }, 426 { 427 code: ocspStatusGood, 428 }, 429 { 430 code: ocspStatusGood, 431 }, 432 }, 433 retFailOpen: nil, 434 retFailClosed: nil, 435 }, 436 { // 1 437 results: []*ocspStatus{ 438 { 439 code: ocspStatusRevoked, 440 err: errors.New("revoked"), 441 }, 442 { 443 code: ocspStatusGood, 444 }, 445 { 446 code: ocspStatusGood, 447 }, 448 }, 449 retFailOpen: &ocspStatus{ocspStatusRevoked, errors.New("revoked")}, 450 retFailClosed: &ocspStatus{ocspStatusRevoked, errors.New("revoked")}, 451 }, 452 { // 2 453 results: []*ocspStatus{ 454 { 455 code: ocspStatusUnknown, 456 err: errors.New("unknown"), 457 }, 458 { 459 code: ocspStatusGood, 460 }, 461 { 462 code: ocspStatusGood, 463 }, 464 }, 465 retFailOpen: nil, 466 retFailClosed: &ocspStatus{ocspStatusUnknown, errors.New("unknown")}, 467 }, 468 { // 3: not taken as revoked if any invalid OCSP response (ocspInvalidValidity) is included. 469 results: []*ocspStatus{ 470 { 471 code: ocspStatusRevoked, 472 err: errors.New("revoked"), 473 }, 474 { 475 code: ocspInvalidValidity, 476 }, 477 { 478 code: ocspStatusGood, 479 }, 480 }, 481 retFailOpen: nil, 482 retFailClosed: &ocspStatus{ocspStatusRevoked, errors.New("revoked")}, 483 }, 484 { // 4: not taken as revoked if the number of results don't match the expected results. 485 results: []*ocspStatus{ 486 { 487 code: ocspStatusRevoked, 488 err: errors.New("revoked"), 489 }, 490 { 491 code: ocspStatusGood, 492 }, 493 }, 494 resultLen: 3, 495 retFailOpen: nil, 496 retFailClosed: &ocspStatus{ocspStatusRevoked, errors.New("revoked")}, 497 }, 498 } 499 c := New(testLogFactory, 10) 500 for idx, tt := range testcases { 501 expectedLen := len(tt.results) 502 if tt.resultLen > 0 { 503 expectedLen = tt.resultLen 504 } 505 r := c.canEarlyExitForOCSP(tt.results, expectedLen, &VerifyConfig{OcspFailureMode: FailOpenTrue}) 506 if !(tt.retFailOpen == nil && r == nil) && !(tt.retFailOpen != nil && r != nil && tt.retFailOpen.code == r.code) { 507 t.Fatalf("%d: failed to match return. expected: %v, got: %v", idx, tt.retFailOpen, r) 508 } 509 r = c.canEarlyExitForOCSP(tt.results, expectedLen, &VerifyConfig{OcspFailureMode: FailOpenFalse}) 510 if !(tt.retFailClosed == nil && r == nil) && !(tt.retFailClosed != nil && r != nil && tt.retFailClosed.code == r.code) { 511 t.Fatalf("%d: failed to match return. expected: %v, got: %v", idx, tt.retFailClosed, r) 512 } 513 } 514 } 515 516 var testLogger = hclog.New(hclog.DefaultOptions) 517 518 func testLogFactory() hclog.Logger { 519 return testLogger 520 } 521 522 type fakeHTTPClient struct { 523 cnt int // number of retry 524 success bool // return success after retry in cnt times 525 timeout bool // timeout 526 body []byte // return body 527 t *testing.T 528 logger hclog.Logger 529 redirected bool 530 } 531 532 func (c *fakeHTTPClient) Do(_ *retryablehttp.Request) (*http.Response, error) { 533 c.cnt-- 534 if c.cnt < 0 { 535 c.cnt = 0 536 } 537 c.t.Log("fakeHTTPClient.cnt", c.cnt) 538 539 var retcode int 540 if !c.redirected { 541 c.redirected = true 542 c.cnt++ 543 retcode = 405 544 } else if c.success && c.cnt == 1 { 545 retcode = 200 546 } else { 547 if c.timeout { 548 // simulate timeout 549 time.Sleep(time.Second * 1) 550 return nil, &fakeHTTPError{ 551 err: "Whatever reason (Client.Timeout exceeded while awaiting headers)", 552 timeout: true, 553 } 554 } 555 retcode = 0 556 } 557 558 ret := &http.Response{ 559 StatusCode: retcode, 560 Body: &fakeResponseBody{body: c.body}, 561 } 562 return ret, nil 563 } 564 565 type fakeHTTPError struct { 566 err string 567 timeout bool 568 } 569 570 func (e *fakeHTTPError) Error() string { return e.err } 571 func (e *fakeHTTPError) Timeout() bool { return e.timeout } 572 func (e *fakeHTTPError) Temporary() bool { return true } 573 574 type fakeResponseBody struct { 575 body []byte 576 cnt int 577 } 578 579 func (b *fakeResponseBody) Read(p []byte) (n int, err error) { 580 if b.cnt == 0 { 581 copy(p, b.body) 582 b.cnt = 1 583 return len(b.body), nil 584 } 585 b.cnt = 0 586 return 0, io.EOF 587 } 588 589 func (b *fakeResponseBody) Close() error { 590 return nil 591 } 592 593 func fakeRequestFunc(_, _ string, _ interface{}) (*retryablehttp.Request, error) { 594 return nil, nil 595 } 596 597 const vaultCert = `-----BEGIN CERTIFICATE----- 598 MIIDuTCCAqGgAwIBAgIUA6VeVD1IB5rXcCZRAqPO4zr/GAMwDQYJKoZIhvcNAQEL 599 BQAwcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMREwDwYDVQQHDAhTb21lQ2l0 600 eTESMBAGA1UECgwJTXlDb21wYW55MRMwEQYDVQQLDApNeURpdmlzaW9uMRowGAYD 601 VQQDDBF3d3cuY29uaHVnZWNvLmNvbTAeFw0yMjA5MDcxOTA1MzdaFw0yNDA5MDYx 602 OTA1MzdaMHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTERMA8GA1UEBwwIU29t 603 ZUNpdHkxEjAQBgNVBAoMCU15Q29tcGFueTETMBEGA1UECwwKTXlEaXZpc2lvbjEa 604 MBgGA1UEAwwRd3d3LmNvbmh1Z2Vjby5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB 605 DwAwggEKAoIBAQDL9qzEXi4PIafSAqfcwcmjujFvbG1QZbI8swxnD+w8i4ufAQU5 606 LDmvMrGo3ZbhJ0mCihYmFxpjhRdP2raJQ9TysHlPXHtDRpr9ckWTKBz2oIfqVtJ2 607 qzteQkWCkDAO7kPqzgCFsMeoMZeONRkeGib0lEzQAbW/Rqnphg8zVVkyQ71DZ7Pc 608 d5WkC2E28kKcSramhWfVFpxG3hSIrLOX2esEXteLRzKxFPf+gi413JZFKYIWrebP 609 u5t0++MLNpuX322geoki4BWMjQsd47XILmxZ4aj33ScZvdrZESCnwP76hKIxg9mO 610 lMxrqSWKVV5jHZrElSEj9LYJgDO1Y6eItn7hAgMBAAGjRzBFMAsGA1UdDwQEAwIE 611 MDATBgNVHSUEDDAKBggrBgEFBQcDATAhBgNVHREEGjAYggtleGFtcGxlLmNvbYIJ 612 bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQA5dPdf5SdtMwe2uSspO/EuWqbM 613 497vMQBW1Ey8KRKasJjhvOVYMbe7De5YsnW4bn8u5pl0zQGF4hEtpmifAtVvziH/ 614 K+ritQj9VVNbLLCbFcg+b0kfjt4yrDZ64vWvIeCgPjG1Kme8gdUUWgu9dOud5gdx 615 qg/tIFv4TRS/eIIymMlfd9owOD3Ig6S5fy4NaAJFAwXf8+3Rzuc+e7JSAPgAufjh 616 tOTWinxvoiOLuYwo9CyGgq4qKBFsrY0aE0gdA7oTQkpbEbo2EbqiWUl/PTCl1Y4Z 617 nSZ0n+4q9QC9RLrWwYTwh838d5RVLUst2mBKSA+vn7YkqmBJbdBC6nkd7n7H 618 -----END CERTIFICATE----- 619 `