1package cert 2 3import ( 4 "bytes" 5 "crypto/rand" 6 "crypto/rsa" 7 "crypto/tls" 8 "crypto/x509" 9 "crypto/x509/pkix" 10 "encoding/pem" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "log" 15 "math/big" 16 "net/http" 17 "net/http/httptest" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "reflect" 22 "strings" 23 "testing" 24 "time" 25 26 "golang.org/x/net/http2" 27 28 "github.com/fabiolb/fabio/config" 29 consulapi "github.com/hashicorp/consul/api" 30 vaultapi "github.com/hashicorp/vault/api" 31 "github.com/pascaldekloe/goe/verify" 32) 33 34func TestTLSConfig(t *testing.T) { 35 certPEM, keyPEM := makePEM("localhost", time.Minute) 36 cert, err := tls.X509KeyPair(certPEM, keyPEM) 37 if err != nil { 38 t.Fatalf("X509KeyPair: got %s want nil", err) 39 } 40 pool := makeCertPool(certPEM) 41 src := &StaticSource{cert, pool} 42 tlsmin := uint16(0x1000) 43 tlsmax := uint16(0x2000) 44 tlsciphers := []uint16{0x1234, 0x5678} 45 nextprotos := []string{"h2", "http/1.1"} 46 47 cfg, err := TLSConfig(src, false, tlsmin, tlsmax, tlsciphers) 48 if err != nil { 49 t.Fatalf("got error %v want nil", err) 50 } 51 if got, want := cfg.MinVersion, tlsmin; got != want { 52 t.Fatalf("got tls min version %04x want %04x", got, want) 53 } 54 if got, want := cfg.MaxVersion, tlsmax; got != want { 55 t.Fatalf("got tls max version %04x want %04x", got, want) 56 } 57 if got, want := cfg.CipherSuites, tlsciphers; !reflect.DeepEqual(got, want) { 58 t.Fatalf("got tls ciphers %v want %v", got, want) 59 } 60 if got, want := cfg.NextProtos, nextprotos; !reflect.DeepEqual(got, want) { 61 t.Fatalf("got next protos %v want %v", got, want) 62 } 63 if got, want := cfg.ClientCAs, pool; got != want { 64 t.Fatalf("got client CAs %v want %v", got, want) 65 } 66 if got, want := cfg.ClientAuth, tls.RequireAndVerifyClientCert; got != want { 67 t.Fatalf("got client auth type %v want %v", got, want) 68 } 69 if cfg.GetCertificate == nil { 70 t.Fatalf("got GetCertificate() nil want not nil") 71 } 72} 73 74func TestNewSource(t *testing.T) { 75 certsource := func(typ string) config.CertSource { 76 return config.CertSource{ 77 Type: typ, 78 Name: "name", 79 CertPath: "cert", 80 KeyPath: "key", 81 ClientCAPath: "clientca", 82 CAUpgradeCN: "upgcn", 83 Refresh: 3 * time.Second, 84 Header: http.Header{"A": []string{"b"}}, 85 } 86 } 87 tests := []struct { 88 desc string 89 cfg config.CertSource 90 src Source 91 err string 92 }{ 93 { 94 desc: "invalid", 95 cfg: config.CertSource{ 96 Type: "invalid", 97 }, 98 src: nil, 99 err: `invalid certificate source "invalid"`, 100 }, 101 { 102 desc: "file", 103 cfg: certsource("file"), 104 src: FileSource{ 105 CertFile: "cert", 106 KeyFile: "key", 107 ClientAuthFile: "clientca", 108 CAUpgradeCN: "upgcn", 109 }, 110 }, 111 { 112 desc: "path", 113 cfg: certsource("path"), 114 src: PathSource{ 115 CertPath: "cert", 116 ClientCAPath: "clientca", 117 CAUpgradeCN: "upgcn", 118 Refresh: 3 * time.Second, 119 }, 120 }, 121 { 122 desc: "http", 123 cfg: certsource("http"), 124 src: HTTPSource{ 125 CertURL: "cert", 126 ClientCAURL: "clientca", 127 CAUpgradeCN: "upgcn", 128 Refresh: 3 * time.Second, 129 }, 130 }, 131 { 132 desc: "consul", 133 cfg: certsource("consul"), 134 src: ConsulSource{ 135 CertURL: "cert", 136 ClientCAURL: "clientca", 137 CAUpgradeCN: "upgcn", 138 }, 139 }, 140 { 141 desc: "vault", 142 cfg: certsource("vault"), 143 src: &VaultSource{ 144 Client: DefaultVaultClient, 145 CertPath: "cert", 146 ClientCAPath: "clientca", 147 CAUpgradeCN: "upgcn", 148 Refresh: 3 * time.Second, 149 }, 150 }, 151 } 152 153 for i, tt := range tests { 154 tt := tt // capture loop var 155 t.Run(tt.desc, func(t *testing.T) { 156 var errmsg string 157 src, err := NewSource(tt.cfg) 158 if err != nil { 159 errmsg = err.Error() 160 } 161 if got, want := errmsg, tt.err; got != want { 162 t.Fatalf("%d: got %q want %q", i, got, want) 163 } 164 got, want := src, tt.src 165 verify.Values(t, "src", got, want) 166 }) 167 } 168} 169 170type StaticSource struct { 171 cert tls.Certificate 172 pool *x509.CertPool 173} 174 175func (s StaticSource) Certificates() chan []tls.Certificate { 176 ch := make(chan []tls.Certificate, 1) 177 ch <- []tls.Certificate{s.cert} 178 close(ch) 179 return ch 180} 181 182func (s StaticSource) LoadClientCAs() (*x509.CertPool, error) { 183 return s.pool, nil 184} 185 186func TestStaticSource(t *testing.T) { 187 certPEM, keyPEM := makePEM("localhost", time.Minute) 188 cert, err := tls.X509KeyPair(certPEM, keyPEM) 189 if err != nil { 190 t.Fatalf("X509KeyPair: got %s want nil", err) 191 } 192 testSource(t, StaticSource{cert, nil}, makeCertPool(certPEM), 0) 193} 194 195func TestFileSource(t *testing.T) { 196 dir := tempDir() 197 defer os.RemoveAll(dir) 198 certPEM, keyPEM := makePEM("localhost", time.Minute) 199 certFile, keyFile := saveCert(dir, "localhost", certPEM, keyPEM) 200 testSource(t, FileSource{CertFile: certFile, KeyFile: keyFile}, makeCertPool(certPEM), 0) 201} 202 203func TestPathSource(t *testing.T) { 204 dir := tempDir() 205 defer os.RemoveAll(dir) 206 certPEM, keyPEM := makePEM("localhost", time.Minute) 207 saveCert(dir, "localhost", certPEM, keyPEM) 208 testSource(t, PathSource{CertPath: dir}, makeCertPool(certPEM), 10*time.Millisecond) 209} 210 211func TestHTTPSource(t *testing.T) { 212 dir := tempDir() 213 defer os.RemoveAll(dir) 214 certPEM, keyPEM := makePEM("localhost", time.Minute) 215 certFile, keyFile := saveCert(dir, "localhost", certPEM, keyPEM) 216 listFile := filepath.Base(certFile) + "\n" + filepath.Base(keyFile) + "\n" 217 writeFile(filepath.Join(dir, "list"), []byte(listFile)) 218 219 srv := httptest.NewServer(http.FileServer(http.Dir(dir))) 220 defer srv.Close() 221 222 testSource(t, HTTPSource{CertURL: srv.URL + "/list"}, makeCertPool(certPEM), 500*time.Millisecond) 223} 224 225func TestConsulSource(t *testing.T) { 226 const certURL = "http://127.0.0.1:8500/v1/kv/fabio/test/consul-server" 227 228 // run a consul server if it isn't already running 229 _, err := http.Get("http://127.0.0.1:8500/v1/status/leader") 230 if err != nil { 231 consul := os.Getenv("CONSUL_EXE") 232 if consul == "" { 233 consul = "consul" 234 } 235 236 version, err := exec.Command(consul, "--version").Output() 237 if err != nil { 238 t.Fatalf("Failed to run %s --version", consul) 239 } 240 cr := bytes.IndexRune(version, '\n') 241 t.Logf("Starting %s: %s", consul, string(version[:cr])) 242 243 start := time.Now() 244 cmd := exec.Command(consul, "agent", "-bind", "127.0.0.1", "-server", "-dev") 245 if err := cmd.Start(); err != nil { 246 t.Fatalf("Failed to start consul server. %s", err) 247 } 248 defer cmd.Process.Kill() 249 250 isUp := func() bool { 251 resp, err := http.Get("http://127.0.0.1:8500/v1/status/leader") 252 // /v1/status/leader returns '\n""' while consul is in leader election mode 253 // and '"127.0.0.1:8300"' when not. So we punt by checking the 254 // body length instead of the actual body content :) 255 if err != nil { 256 return false 257 } 258 defer resp.Body.Close() 259 260 if resp.StatusCode != 200 { 261 return false 262 } 263 264 n, err := io.Copy(ioutil.Discard, resp.Body) 265 return err == nil && n > 10 266 } 267 268 // We need give consul ~8-10 seconds to become ready until I've 269 // figured out whether we can speed this up. Make sure that this is 270 // less than the global test timeout in Makefile. 271 if !waitFor(12*time.Second, isUp) { 272 t.Fatalf("Timeout waiting for consul server after %2.1f seconds", time.Since(start).Seconds()) 273 } 274 t.Logf("Consul is ready after %2.1f seconds", time.Since(start).Seconds()) 275 } else { 276 t.Log("Using existing consul server") 277 } 278 279 config, key, err := parseConsulURL(certURL) 280 if err != nil { 281 t.Fatalf("Failed to parse consul url: %s", err) 282 } 283 284 client, err := consulapi.NewClient(config) 285 if err != nil { 286 t.Fatalf("Failed to create consul client: %s", err) 287 } 288 defer func() { client.KV().DeleteTree(key, &consulapi.WriteOptions{}) }() 289 290 write := func(name string, value []byte) { 291 p := &consulapi.KVPair{Key: key + "/" + name, Value: value} 292 _, err := client.KV().Put(p, &consulapi.WriteOptions{}) 293 if err != nil { 294 t.Fatalf("Failed to write %q to consul: %s", p.Key, err) 295 } 296 } 297 298 certPEM, keyPEM := makePEM("localhost", time.Minute) 299 write("localhost-cert.pem", certPEM) 300 write("localhost-key.pem", keyPEM) 301 302 testSource(t, ConsulSource{CertURL: certURL}, makeCertPool(certPEM), 500*time.Millisecond) 303} 304 305// vaultServer starts a vault server in dev mode and waits until is ready. 306func vaultServer(t *testing.T, addr, rootToken string) (*exec.Cmd, *vaultapi.Client) { 307 vault := os.Getenv("VAULT_EXE") 308 if vault == "" { 309 vault = "vault" 310 } 311 312 version, err := exec.Command(vault, "--version").Output() 313 if err != nil { 314 t.Fatalf("Failed to run %s --version", vault) 315 } 316 t.Logf("Starting %s: %q", vault, string(version)) 317 318 cmd := exec.Command(vault, "server", "-dev", "-dev-root-token-id="+rootToken, "-dev-listen-address="+addr) 319 if err := cmd.Start(); err != nil { 320 t.Fatalf("Failed to start vault server. %s", err) 321 } 322 323 c, err := vaultapi.NewClient(&vaultapi.Config{Address: "http://" + addr}) 324 if err != nil { 325 cmd.Process.Kill() 326 t.Fatalf("NewClient failed: %s", err) 327 } 328 c.SetToken(rootToken) 329 330 isUp := func() bool { 331 ok, err := c.Sys().InitStatus() 332 return err == nil && ok 333 } 334 if !waitFor(time.Second, isUp) { 335 cmd.Process.Kill() 336 t.Fatal("Timeout waiting for vault server") 337 } 338 339 policy := ` 340 # Vault < 0.7 341 path "secret/fabio/cert" { 342 capabilities = ["list"] 343 } 344 # Vault >= 0.7. Note the trailing slash. 345 path "secret/fabio/cert/" { 346 capabilities = ["list"] 347 } 348 349 path "secret/fabio/cert/*" { 350 capabilities = ["read"] 351 } 352 353 # Vault >= 0.10. (KV Version 2) 354 path "secret/metadata/fabio/cert/" { 355 capabilities = ["list"] 356 } 357 358 path "secret/data/fabio/cert/*" { 359 capabilities = ["read"] 360 } 361 362 path "test-pki/issue/fabio" { 363 capabilities = ["update"] 364 } 365 ` 366 367 if err := c.Sys().PutPolicy("fabio", policy); err != nil { 368 cmd.Process.Kill() 369 t.Fatalf("Could not create policy: %s", err) 370 } 371 372 return cmd, c 373} 374 375func makeToken(t *testing.T, c *vaultapi.Client, wrapTTL string, req *vaultapi.TokenCreateRequest) string { 376 c.SetWrappingLookupFunc(func(string, string) string { return wrapTTL }) 377 378 resp, err := c.Auth().Token().Create(req) 379 if err != nil { 380 t.Fatalf("Could not create a token: %s", err) 381 } 382 383 if wrapTTL != "" { 384 if resp.WrapInfo == nil || resp.WrapInfo.Token == "" { 385 t.Fatalf("Could not create a wrapped token") 386 } 387 return resp.WrapInfo.Token 388 } 389 390 if resp.WrapInfo != nil && resp.WrapInfo.Token != "" { 391 t.Fatalf("Got a wrapped token but was not expecting one") 392 } 393 394 return resp.Auth.ClientToken 395} 396 397var vaultTestCases = []struct { 398 desc string 399 wrapTTL string 400 req *vaultapi.TokenCreateRequest 401 dropWarn bool 402}{ 403 { 404 desc: "renewable token", 405 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", Policies: []string{"fabio"}}, 406 }, 407 { 408 desc: "non-renewable token", 409 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", Renewable: new(bool), Policies: []string{"fabio"}}, 410 dropWarn: true, 411 }, 412 { 413 desc: "renewable orphan token", 414 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", NoParent: true, Policies: []string{"fabio"}}, 415 }, 416 { 417 desc: "non-renewable orphan token", 418 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", NoParent: true, Renewable: new(bool), Policies: []string{"fabio"}}, 419 dropWarn: true, 420 }, 421 { 422 desc: "renewable wrapped token", 423 wrapTTL: "10s", 424 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", Policies: []string{"fabio"}}, 425 }, 426 { 427 desc: "non-renewable wrapped token", 428 wrapTTL: "10s", 429 req: &vaultapi.TokenCreateRequest{Lease: "1m", TTL: "1m", Renewable: new(bool), Policies: []string{"fabio"}}, 430 dropWarn: true, 431 }, 432} 433 434func TestVaultSource(t *testing.T) { 435 const ( 436 addr = "127.0.0.1:58421" 437 rootToken = "token" 438 certPath = "secret/fabio/cert" 439 ) 440 441 // start a vault server 442 vault, client := vaultServer(t, addr, rootToken) 443 defer vault.Process.Kill() 444 445 // create a cert and store it in vault 446 certPEM, keyPEM := makePEM("localhost", time.Minute) 447 data := map[string]interface{}{"cert": string(certPEM), "key": string(keyPEM)} 448 449 var nilSource *VaultSource // for calling helper methods 450 451 mountPath, v2, err := nilSource.isKVv2(certPath, client) 452 if err != nil { 453 t.Fatal(err) 454 } 455 456 p := certPath + "/localhost" 457 if v2 { 458 t.Log("Vault: KV backend: V2") 459 data = map[string]interface{}{ 460 "data": data, 461 "options": map[string]interface{}{}, 462 } 463 p = nilSource.addPrefixToVKVPath(p, mountPath, "data") 464 } else { 465 t.Log("Vault: KV backend: V1") 466 } 467 if _, err := client.Logical().Write(p, data); err != nil { 468 t.Fatalf("logical.Write failed: %s", err) 469 } 470 471 pool := makeCertPool(certPEM) 472 timeout := 500 * time.Millisecond 473 for _, tt := range vaultTestCases { 474 tt := tt // capture loop var 475 t.Run(tt.desc, func(t *testing.T) { 476 src := &VaultSource{ 477 Client: &vaultClient{ 478 addr: "http://" + addr, 479 token: makeToken(t, client, tt.wrapTTL, tt.req), 480 }, 481 CertPath: certPath, 482 } 483 484 // suppress the log warning about a non-renewable token 485 // since this is the expected behavior. 486 dropNotRenewableWarning = tt.dropWarn 487 testSource(t, src, pool, timeout) 488 dropNotRenewableWarning = false 489 }) 490 } 491} 492 493func TestVaultPKISource(t *testing.T) { 494 const ( 495 addr = "127.0.0.1:58421" 496 rootToken = "token" 497 certPath = "test-pki/issue/fabio" 498 ) 499 500 // start a vault server 501 vault, client := vaultServer(t, addr, rootToken) 502 defer vault.Process.Kill() 503 504 // mount the PKI backend 505 err := client.Sys().Mount("test-pki", &vaultapi.MountInput{ 506 Type: "pki", 507 Config: vaultapi.MountConfigInput{ 508 DefaultLeaseTTL: "1h", // default validity period of issued certificates 509 MaxLeaseTTL: "2h", // maximum validity period of issued certificates 510 }, 511 }) 512 if err != nil { 513 t.Fatalf("Mount pki backend failed: %s", err) 514 } 515 516 // generate root CA cert 517 resp, err := client.Logical().Write("test-pki/root/generate/internal", map[string]interface{}{ 518 "common_name": "fabio-ca.com", 519 "ttl": "2h", 520 }) 521 if err != nil { 522 t.Fatalf("Generate root failed: %s", err) 523 } 524 caPool := makeCertPool([]byte(resp.Data["certificate"].(string))) 525 526 // create role 527 role := filepath.Base(certPath) 528 _, err = client.Logical().Write("test-pki/roles/"+role, map[string]interface{}{ 529 "allowed_domains": "", 530 "allow_localhost": true, 531 "allow_ip_sans": true, 532 "organization": "Fabio Test", 533 }) 534 if err != nil { 535 t.Fatalf("Write role failed: %s", err) 536 } 537 538 for _, tt := range vaultTestCases { 539 tt := tt // capture loop var 540 t.Run(tt.desc, func(t *testing.T) { 541 src := NewVaultPKISource() 542 src.Client = &vaultClient{ 543 addr: "http://" + addr, 544 token: makeToken(t, client, tt.wrapTTL, tt.req), 545 } 546 src.CertPath = certPath 547 548 // suppress the log warning about a non-renewable token 549 // since this is the expected behavior. 550 dropNotRenewableWarning = tt.dropWarn 551 testSource(t, src, caPool, 0) 552 dropNotRenewableWarning = false 553 }) 554 } 555} 556 557// testSource runs an integration test by making an HTTPS request 558// to https://localhost/ expecting that the source provides a valid 559// certificate for "localhost". rootCAs is expected to contain a 560// valid root certificate or the server certificate itself so that 561// the HTTPS client can validate the certificate presented by the 562// server. 563func testSource(t *testing.T, source Source, rootCAs *x509.CertPool, sleep time.Duration) { 564 const NoStrictMatch = false 565 srvConfig, err := TLSConfig(source, NoStrictMatch, 0, 0, nil) 566 if err != nil { 567 t.Fatalf("TLSConfig: got %q want nil", err) 568 } 569 570 // give the source some time to initialize if necessary 571 time.Sleep(sleep) 572 573 // create an http client that will accept the root CAs 574 // otherwise the HTTPS client will not verify the 575 // certificate presented by the server. 576 http11 := http11Client(rootCAs) 577 http20, err := http20Client(rootCAs) 578 if err != nil { 579 t.Fatal("http20Client: ", err) 580 } 581 582 // disable log output for the next call to prevent 583 // confusing log messages since they are expected 584 // http: TLS handshake error from 127.0.0.1:55044: remote error: bad certificate 585 log.SetOutput(ioutil.Discard) 586 defer log.SetOutput(os.Stderr) 587 588 // fail calls https://localhost.org/ for which certificate validation 589 // should fail since the hostname differs from the one in the certificate. 590 fail := func(client *http.Client) { 591 _, _, err := roundtrip("localhost.org", srvConfig, client) 592 got, want := err, "x509: certificate is valid for localhost, not localhost.org" 593 if got == nil || !strings.Contains(got.Error(), want) { 594 t.Fatalf("got %q want %q", got, want) 595 } 596 } 597 598 // succeed executes a roundtrip to https://localhost/ which 599 // should return 200 OK and wantBody. 600 succeed := func(client *http.Client, wantBody string) { 601 code, body, err := roundtrip("localhost", srvConfig, client) 602 if err != nil { 603 t.Fatalf("got %v want nil", err) 604 } 605 if got, want := code, 200; got != want { 606 t.Fatalf("got %v want %v", got, want) 607 } 608 if got, want := body, wantBody; got != want { 609 t.Fatalf("got %v want %v", got, want) 610 } 611 } 612 613 // make a call for which certificate validation succeeds. 614 succeed(http11, "OK HTTP/1.1") 615 succeed(http20, "OK HTTP/2.0") 616 617 // now make the call that should fail. 618 fail(http11) 619 fail(http20) 620} 621 622// roundtrip starts a TLS server with the given server configuration and 623// then sends an SNI request with the given serverName. 624func roundtrip(serverName string, srvConfig *tls.Config, client *http.Client) (code int, body string, err error) { 625 // create an HTTPS server and start it. It will be listening on 127.0.0.1 626 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 627 fmt.Fprint(w, "OK ", r.Proto) 628 })) 629 srv.TLS = srvConfig 630 srv.StartTLS() 631 defer srv.Close() 632 633 // configure SNI 634 client.Transport.(*http.Transport).TLSClientConfig.ServerName = serverName 635 636 // give the tls server some time to start up 637 time.Sleep(10 * time.Millisecond) 638 639 resp, err := client.Get(srv.URL) 640 if err != nil { 641 return 0, "", err 642 } 643 defer resp.Body.Close() 644 645 data, err := ioutil.ReadAll(resp.Body) 646 if err != nil { 647 return 0, "", err 648 } 649 return resp.StatusCode, string(data), nil 650} 651 652// http11Client returns an HTTP client which can only 653// execute HTTP/1.1 requests via TLS. 654func http11Client(rootCAs *x509.CertPool) *http.Client { 655 t := &http.Transport{ 656 TLSClientConfig: &tls.Config{ 657 RootCAs: rootCAs, 658 }, 659 } 660 return &http.Client{Transport: t} 661} 662 663// http20Client returns an HTTP client which can 664// execute HTTP/2.0 requests via TLS if the server 665// supports it. 666func http20Client(rootCAs *x509.CertPool) (*http.Client, error) { 667 t := &http.Transport{ 668 TLSClientConfig: &tls.Config{ 669 RootCAs: rootCAs, 670 }, 671 } 672 if err := http2.ConfigureTransport(t); err != nil { 673 return nil, err 674 } 675 return &http.Client{Transport: t}, nil 676} 677 678func tempDir() string { 679 dir, err := ioutil.TempDir("", "fabio") 680 if err != nil { 681 panic(err.Error()) 682 } 683 return dir 684} 685 686func writeFile(filename string, data []byte) { 687 if err := ioutil.WriteFile(filename, data, 0644); err != nil { 688 panic(err.Error()) 689 } 690} 691 692func makeCertPool(x ...[]byte) *x509.CertPool { 693 p := x509.NewCertPool() 694 for _, b := range x { 695 // https://github.com/fabiolb/fabio/issues/434 696 if ok := p.AppendCertsFromPEM(b); !ok { 697 panic("failed to add cert from PEM. Is the CN a DNS compatible name?") 698 } 699 } 700 return p 701} 702 703func saveCert(dir, host string, certPEM, keyPEM []byte) (certFile, keyFile string) { 704 certFile, keyFile = filepath.Join(dir, host+"-cert.pem"), filepath.Join(dir, host+"-key.pem") 705 writeFile(certFile, certPEM) 706 writeFile(keyFile, keyPEM) 707 return certFile, keyFile 708} 709 710// makePEM creates a self-signed RSA certificate as two PEM blocks. 711// taken from crypto/tls/generate_cert.go 712func makePEM(host string, validFor time.Duration) (certPEM, keyPEM []byte) { 713 const bits = 1024 714 priv, err := rsa.GenerateKey(rand.Reader, bits) 715 if err != nil { 716 panic("Failed to generate private key: " + err.Error()) 717 } 718 719 template := x509.Certificate{ 720 SerialNumber: big.NewInt(1), 721 Subject: pkix.Name{ 722 Organization: []string{"Fabio Co"}, 723 }, 724 NotBefore: time.Now(), 725 NotAfter: time.Now().Add(validFor), 726 IsCA: true, 727 DNSNames: []string{host}, 728 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 729 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 730 BasicConstraintsValid: true, 731 } 732 733 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 734 if err != nil { 735 panic("Failed to create certificate: " + err.Error()) 736 } 737 738 var cert, key bytes.Buffer 739 pem.Encode(&cert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 740 pem.Encode(&key, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 741 return cert.Bytes(), key.Bytes() 742} 743 744func makeCert(host string, validFor time.Duration) tls.Certificate { 745 certPEM, keyPEM := makePEM(host, validFor) 746 cert, err := tls.X509KeyPair(certPEM, keyPEM) 747 if err != nil { 748 panic("Failed to create certificate: " + err.Error()) 749 } 750 return cert 751} 752 753func waitFor(timeout time.Duration, up func() bool) bool { 754 until := time.Now().Add(timeout) 755 for { 756 if time.Now().After(until) { 757 return false 758 } 759 if up() { 760 return true 761 } 762 time.Sleep(100 * time.Millisecond) 763 } 764} 765