// genCertPair generates a key/cert pair to the paths provided. func genCertPair(certFile, keyFile, certFileWallet, keyFileWallet string) error { org := "rpctest autogenerated cert" validUntil := time.Now().Add(10 * 365 * 24 * time.Hour) cert, key, err := dcrutil.NewTLSCertPair(org, validUntil, nil) if err != nil { return err } // Write cert and key files. if err = ioutil.WriteFile(certFile, cert, 0666); err != nil { return err } if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { if errR := os.Remove(certFile); errR != nil { fmt.Printf("Failed to remove %s, %v", certFile, err) } return err } if err = ioutil.WriteFile(certFileWallet, cert, 0666); err != nil { return err } if err = ioutil.WriteFile(keyFileWallet, key, 0600); err != nil { if errR := os.Remove(certFile); errR != nil { fmt.Printf("Failed to remove %s, %v", certFile, err) } return err } return nil }
func main() { cfg := config{ Years: 10, Organization: "gencerts", } parser := flags.NewParser(&cfg, flags.Default) _, err := parser.Parse() if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { parser.WriteHelp(os.Stderr) } return } if cfg.Directory == "" { var err error cfg.Directory, err = os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "no directory specified and cannot get working directory\n") os.Exit(1) } } cfg.Directory = cleanAndExpandPath(cfg.Directory) certFile := filepath.Join(cfg.Directory, "rpc.cert") keyFile := filepath.Join(cfg.Directory, "rpc.key") if !cfg.Force { if fileExists(certFile) || fileExists(keyFile) { fmt.Fprintf(os.Stderr, "%v: certificate and/or key files exist; use -f to force\n", cfg.Directory) os.Exit(1) } } validUntil := time.Now().Add(time.Duration(cfg.Years) * 365 * 24 * time.Hour) cert, key, err := dcrutil.NewTLSCertPair(cfg.Organization, validUntil, cfg.ExtraHosts) if err != nil { fmt.Fprintf(os.Stderr, "cannot generate certificate pair: %v\n", err) os.Exit(1) } // Write cert and key files. if err = ioutil.WriteFile(certFile, cert, 0666); err != nil { fmt.Fprintf(os.Stderr, "cannot write cert: %v\n", err) os.Exit(1) } if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { os.Remove(certFile) fmt.Fprintf(os.Stderr, "cannot write key: %v\n", err) os.Exit(1) } }
// generateRPCKeyPair generates a new RPC TLS keypair and writes the cert and // possibly also the key in PEM format to the paths specified by the config. If // successful, the new keypair is returned. func generateRPCKeyPair(writeKey bool) (tls.Certificate, error) { log.Infof("Generating TLS certificates...") // Create directories for cert and key files if they do not yet exist. certDir, _ := filepath.Split(cfg.RPCCert) keyDir, _ := filepath.Split(cfg.RPCKey) err := os.MkdirAll(certDir, 0700) if err != nil { return tls.Certificate{}, err } err = os.MkdirAll(keyDir, 0700) if err != nil { return tls.Certificate{}, err } // Generate cert pair. org := "dcrwallet autogenerated cert" validUntil := time.Now().Add(time.Hour * 24 * 365 * 10) cert, key, err := dcrutil.NewTLSCertPair(org, validUntil, nil) if err != nil { return tls.Certificate{}, err } keyPair, err := tls.X509KeyPair(cert, key) if err != nil { return tls.Certificate{}, err } // Write cert and (potentially) the key files. err = ioutil.WriteFile(cfg.RPCCert, cert, 0600) if err != nil { return tls.Certificate{}, err } if writeKey { err = ioutil.WriteFile(cfg.RPCKey, key, 0600) if err != nil { rmErr := os.Remove(cfg.RPCCert) if rmErr != nil { log.Warnf("Cannot remove written certificates: %v", rmErr) } return tls.Certificate{}, err } } log.Info("Done generating TLS certificates") return keyPair, nil }
// TestNewTLSCertPair ensures the NewTLSCertPair function works as expected. func TestNewTLSCertPair(t *testing.T) { // Certs don't support sub-second precision, so truncate it now to // ensure the checks later don't fail due to nanosecond precision // differences. validUntil := time.Unix(time.Now().Add(10*365*24*time.Hour).Unix(), 0) org := "test autogenerated cert" extraHosts := []string{"testtlscert.bogus", "localhost", "127.0.0.1"} cert, key, err := dcrutil.NewTLSCertPair(org, validUntil, extraHosts) if err != nil { t.Fatalf("failed with unexpected error: %v", err) } // Ensure the PEM-encoded cert that is returned can be decoded. pemCert, _ := pem.Decode(cert) if pemCert == nil { t.Fatalf("pem.Decode was unable to decode the certificate") } // Ensure the PEM-encoded key that is returned can be decoded. pemKey, _ := pem.Decode(key) if pemCert == nil { t.Fatalf("pem.Decode was unable to decode the key") } // Ensure the DER-encoded key bytes can be successfully parsed. _, err = x509.ParseECPrivateKey(pemKey.Bytes) if err != nil { t.Fatalf("failed with unexpected error: %v", err) } // Ensure the DER-encoded cert bytes can be successfully into an X.509 // certificate. x509Cert, err := x509.ParseCertificate(pemCert.Bytes) if err != nil { t.Fatalf("failed with unexpected error: %v", err) } // Ensure the specified organization is correct. x509Orgs := x509Cert.Subject.Organization if len(x509Orgs) == 0 || x509Orgs[0] != org { x509Org := "<no organization>" if len(x509Orgs) > 0 { x509Org = x509Orgs[0] } t.Fatalf("generated cert organization field mismatch, got "+ "'%v', want '%v'", x509Org, org) } // Ensure the specified valid until value is correct. if !x509Cert.NotAfter.Equal(validUntil) { t.Fatalf("generated cert valid until field mismatch, got %v, "+ "want %v", x509Cert.NotAfter, validUntil) } // Ensure the specified extra hosts are present. for _, host := range extraHosts { if err := x509Cert.VerifyHostname(host); err != nil { t.Fatalf("failed to verify extra host '%s'", host) } } // Ensure that the Common Name is also the first SAN DNS name. cn := x509Cert.Subject.CommonName san0 := x509Cert.DNSNames[0] if cn != san0 { t.Errorf("common name %s does not match first SAN %s", cn, san0) } // Ensure there are no duplicate hosts or IPs. hostCounts := make(map[string]int) for _, host := range x509Cert.DNSNames { hostCounts[host]++ } ipCounts := make(map[string]int) for _, ip := range x509Cert.IPAddresses { ipCounts[string(ip)]++ } for host, count := range hostCounts { if count != 1 { t.Errorf("host %s appears %d times in certificate", host, count) } } for ipStr, count := range ipCounts { if count != 1 { t.Errorf("ip %s appears %d times in certificate", net.IP(ipStr), count) } } // Ensure the cert can be use for the intended purposes. if !x509Cert.IsCA { t.Fatal("generated cert is not a certificate authority") } if x509Cert.KeyUsage&x509.KeyUsageKeyEncipherment == 0 { t.Fatal("generated cert can't be used for key encipherment") } if x509Cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 { t.Fatal("generated cert can't be used for digital signatures") } if x509Cert.KeyUsage&x509.KeyUsageCertSign == 0 { t.Fatal("generated cert can't be used for signing other certs") } if !x509Cert.BasicConstraintsValid { t.Fatal("generated cert does not have valid basic constraints") } }