├── .gitignore ├── Makefile ├── README.md ├── generate.go └── testdata ├── request.py └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | /*.crt 2 | /*.pem 3 | /*.key 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | release: 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Better self-signed certificates 2 | 3 | Here's a script that helps you generate self signed certificates. It will 4 | generate both a root certificate and a leaf. 5 | 6 | (The TLS certificates generated by `crypto/tls/generate_cert.go` act both as a 7 | CA and as a leaf certificate. Some TLS clients have a problem with that scheme.) 8 | 9 | This script modifies `crypto/tls/generate_cert.go` slightly: 10 | 11 | - A leaf certificate and a root certificate are generated. 12 | 13 | - the only supported key type is ecdsa P256. 14 | 15 | - Better usage instructions are generated. 16 | 17 | Credit comes from [Adam Langley](https://www.imperialviolet.org/), who provided 18 | the initial version of this script on a golang-nuts message thread. 19 | 20 | ## Installation 21 | 22 | ```bash 23 | go get github.com/Shyp/generate-tls-cert 24 | ``` 25 | 26 | ## Usage 27 | 28 | Running `generate-tls-cert` will give you nine files. Three of them are the 29 | most important: 30 | 31 | - `root.pem`: The public key of the root CA. Add this as a CA in clients to 32 | connect to your self-signed server (see "Client" below). 33 | 34 | - `leaf.key` and `leaf.pem` - The public and private key for terminating TLS 35 | with your self signed certificate. 36 | 37 | ``` 38 | $ generate-tls-cert --host=localhost,127.0.0.1 39 | Successfully generated certificates! Here's what you generated. 40 | 41 | # Root CA 42 | 43 | root.key 44 | The private key for the root Certificate Authority. Keep this private. 45 | 46 | root.pem 47 | The public key for the root Certificate Authority. Clients should load the 48 | certificate in this file to connect to the server. 49 | 50 | root.debug.crt 51 | Debug information about the generated certificate. 52 | 53 | # Leaf Certificate - Use these to serve TLS traffic. 54 | 55 | leaf.key 56 | Private key (PEM-encoded) for terminating TLS traffic on the server. 57 | 58 | leaf.pem 59 | Public key for terminating TLS traffic on the server. 60 | 61 | leaf.debug.crt 62 | Debug information about the generated certificate 63 | 64 | # Client Certificate - You probably don't need these. 65 | 66 | client.key: Secret key for TLS client authentication 67 | client.pem: Public key for TLS client authentication 68 | ``` 69 | 70 | Add the following instructions to your Makefile, and all your users will have to 71 | do to get started is run `make generate_cert` to download the binary and load 72 | TLS certificates. 73 | 74 | ```make 75 | GENERATE_TLS_CERT = $(GOPATH)/bin/generate-tls-cert 76 | 77 | $(GENERATE_TLS_CERT): 78 | go get -u github.com/Shyp/generate-tls-cert 79 | 80 | certs/leaf.pem: | $(GENERATE_TLS_CERT) 81 | mkdir -p certs 82 | cd certs && $(GENERATE_TLS_CERT) --host=localhost,127.0.0.1 83 | 84 | # Generate TLS certificates for local development. 85 | generate_cert: certs/leaf.pem | $(GENERATE_TLS_CERT) 86 | ``` 87 | 88 | ## Client Side 89 | 90 | Here's how to make requests that validate, using your new TLS certificates. 91 | 92 | ### Go 93 | 94 | ```go 95 | rootPEM, err := ioutil.ReadFile("path/to/root.pem") 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | roots := x509.NewCertPool() 100 | ok := roots.AppendCertsFromPEM(rootPEM) 101 | if !ok { 102 | panic("failed to parse root certificate") 103 | } 104 | 105 | // Use the tls.Config here in http.Transport.TLSClientConfig 106 | conn, err := tls.Dial("tcp", "yourhost:yourport", &tls.Config{ 107 | RootCAs: roots, 108 | }) 109 | if err != nil { 110 | panic("failed to connect: " + err.Error()) 111 | } 112 | conn.Close() 113 | ``` 114 | 115 | ### Javascript 116 | 117 | ```javascript 118 | var fs = require('fs'); 119 | var https = require('https'); 120 | 121 | var get = https.request({ 122 | path: '/', hostname: 'yourhost', port: yourport, 123 | ca: fs.readFileSync('path/to/root.pem'), 124 | agent: false, 125 | rejectUnauthorized: true, 126 | }, function(response) { 127 | response.on('data', (d) => { 128 | process.stdout.write(d); 129 | }); 130 | }); 131 | 132 | get.on('error', function(e) { 133 | console.error(e) 134 | console.error("error", e) 135 | console.error("error", JSON.stringify(e)) 136 | }); 137 | 138 | get.end(); 139 | ``` 140 | 141 | ### Curl 142 | 143 | ```bash 144 | curl --cacert path/to/root.pem https://yourhost:yourport 145 | ``` 146 | 147 | ### Python Requests 148 | 149 | ```python 150 | import requests 151 | 152 | r = requests.get("https://yourhost:yourport", verify='root.pem') 153 | print(r.status_code) 154 | ``` 155 | 156 | ### OpenSSL 157 | 158 | ```bash 159 | openssl s_client -showcerts -servername localhost -CAfile path/to/root.pem -connect yourhost:yourport 160 | ``` 161 | 162 | ## Server Side 163 | 164 | Here's how to integrate the generated certificates into different server 165 | architectures. 166 | 167 | ### Go 168 | 169 | Start the Go server with the leaf public and private keys. 170 | 171 | ```go 172 | http.ListenAndServeTLS(":7252", "leaf.pem", "leaf.key", nil) 173 | ``` 174 | 175 | ### Node.js 176 | 177 | Start a Node server with the leaf public and private keys. 178 | 179 | ```javascript 180 | const https = require('https'); 181 | const fs = require('fs'); 182 | 183 | const options = { 184 | key: fs.readFileSync('leaf.key'), 185 | cert: fs.readFileSync('leaf.pem'), 186 | }; 187 | 188 | https.createServer(options, (req, res) => { 189 | res.writeHead(200); 190 | res.end('hello world\n'); 191 | }).listen(8000); 192 | ``` 193 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | // generate-tls-cert generates root, leaf, and client TLS certificates. 2 | package main 3 | 4 | import ( 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "crypto/rand" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "flag" 12 | "fmt" 13 | "log" 14 | "math/big" 15 | "net" 16 | "os" 17 | "os/exec" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | var ( 23 | host = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for") 24 | validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011 (default now)") 25 | validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for") 26 | version = flag.Bool("version", false, "Print the version string") 27 | ) 28 | 29 | const Version = "0.1" 30 | 31 | func main() { 32 | flag.Parse() 33 | if *version { 34 | fmt.Fprintf(os.Stderr, "generate-tls-cert version %s\n", Version) 35 | os.Exit(2) 36 | } 37 | if len(*host) == 0 { 38 | log.Fatalf("Missing required --host parameter") 39 | } 40 | var err error 41 | var notBefore time.Time 42 | if len(*validFrom) == 0 { 43 | notBefore = time.Now() 44 | } else { 45 | notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err) 48 | os.Exit(1) 49 | } 50 | } 51 | 52 | notAfter := notBefore.Add(*validFor) 53 | 54 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 55 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 56 | if err != nil { 57 | log.Fatalf("failed to generate serial number: %s", err) 58 | } 59 | rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 60 | if err != nil { 61 | panic(err) 62 | } 63 | keyToFile("root.key", rootKey) 64 | 65 | rootTemplate := x509.Certificate{ 66 | SerialNumber: serialNumber, 67 | Subject: pkix.Name{ 68 | Organization: []string{"Acme Co"}, 69 | CommonName: "Root CA", 70 | }, 71 | NotBefore: notBefore, 72 | NotAfter: notAfter, 73 | KeyUsage: x509.KeyUsageCertSign, 74 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 75 | BasicConstraintsValid: true, 76 | IsCA: true, 77 | } 78 | 79 | derBytes, err := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootKey.PublicKey, rootKey) 80 | if err != nil { 81 | panic(err) 82 | } 83 | debugCertToFile("root.debug.crt", derBytes) 84 | certToFile("root.pem", derBytes) 85 | 86 | leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 87 | if err != nil { 88 | panic(err) 89 | } 90 | keyToFile("leaf.key", leafKey) 91 | 92 | serialNumber, err = rand.Int(rand.Reader, serialNumberLimit) 93 | if err != nil { 94 | log.Fatalf("failed to generate serial number: %s", err) 95 | } 96 | leafTemplate := x509.Certificate{ 97 | SerialNumber: serialNumber, 98 | Subject: pkix.Name{ 99 | Organization: []string{"Acme Co"}, 100 | CommonName: "test_cert_1", 101 | }, 102 | NotBefore: notBefore, 103 | NotAfter: notAfter, 104 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, 105 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 106 | BasicConstraintsValid: true, 107 | IsCA: false, 108 | } 109 | hosts := strings.Split(*host, ",") 110 | for _, h := range hosts { 111 | if ip := net.ParseIP(h); ip != nil { 112 | leafTemplate.IPAddresses = append(leafTemplate.IPAddresses, ip) 113 | } else { 114 | leafTemplate.DNSNames = append(leafTemplate.DNSNames, h) 115 | } 116 | } 117 | 118 | derBytes, err = x509.CreateCertificate(rand.Reader, &leafTemplate, &rootTemplate, &leafKey.PublicKey, rootKey) 119 | if err != nil { 120 | panic(err) 121 | } 122 | debugCertToFile("leaf.debug.crt", derBytes) 123 | certToFile("leaf.pem", derBytes) 124 | 125 | clientKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 126 | if err != nil { 127 | panic(err) 128 | } 129 | keyToFile("client.key", clientKey) 130 | 131 | clientTemplate := x509.Certificate{ 132 | SerialNumber: new(big.Int).SetInt64(4), 133 | Subject: pkix.Name{ 134 | Organization: []string{"Acme Co"}, 135 | CommonName: "client_auth_test_cert", 136 | }, 137 | NotBefore: notBefore, 138 | NotAfter: notAfter, 139 | KeyUsage: x509.KeyUsageDigitalSignature, 140 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 141 | BasicConstraintsValid: true, 142 | IsCA: false, 143 | } 144 | 145 | derBytes, err = x509.CreateCertificate(rand.Reader, &clientTemplate, &rootTemplate, &clientKey.PublicKey, rootKey) 146 | if err != nil { 147 | panic(err) 148 | } 149 | debugCertToFile("client.debug.crt", derBytes) 150 | certToFile("client.pem", derBytes) 151 | 152 | fmt.Fprintf(os.Stdout, `Successfully generated certificates! Here's what you generated. 153 | 154 | # Root CA 155 | 156 | root.key 157 | The private key for the root Certificate Authority. Keep this private. 158 | 159 | root.pem 160 | The public key for the root Certificate Authority. Clients should load the 161 | certificate in this file to connect to the server. 162 | 163 | root.debug.crt 164 | Debug information about the generated certificate. 165 | 166 | # Leaf Certificate - Use these to serve TLS traffic. 167 | 168 | leaf.key 169 | Private key (PEM-encoded) for terminating TLS traffic on the server. 170 | 171 | leaf.pem 172 | Public key for terminating TLS traffic on the server. 173 | 174 | leaf.debug.crt 175 | Debug information about the generated certificate 176 | 177 | # Client Certificate - You probably don't need these. 178 | 179 | client.key: Secret key for TLS client authentication 180 | client.pem: Public key for TLS client authentication 181 | 182 | See https://github.com/Shyp/generate-tls-cert for examples of how to use in code. 183 | `) 184 | } 185 | 186 | // keyToFile writes a PEM serialization of |key| to a new file called 187 | // |filename|. 188 | func keyToFile(filename string, key *ecdsa.PrivateKey) { 189 | file, err := os.Create(filename) 190 | if err != nil { 191 | panic(err) 192 | } 193 | defer file.Close() 194 | b, err := x509.MarshalECPrivateKey(key) 195 | if err != nil { 196 | fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) 197 | os.Exit(2) 198 | } 199 | if err := pem.Encode(file, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}); err != nil { 200 | panic(err) 201 | } 202 | } 203 | 204 | func certToFile(filename string, derBytes []byte) { 205 | certOut, err := os.Create(filename) 206 | if err != nil { 207 | log.Fatalf("failed to open cert.pem for writing: %s", err) 208 | } 209 | if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 210 | log.Fatalf("failed to write data to cert.pem: %s", err) 211 | } 212 | if err := certOut.Close(); err != nil { 213 | log.Fatalf("error closing cert.pem: %s", err) 214 | } 215 | } 216 | 217 | // debugCertToFile writes a PEM serialization and OpenSSL debugging dump of 218 | // |derBytes| to a new file called |filename|. 219 | func debugCertToFile(filename string, derBytes []byte) { 220 | cmd := exec.Command("openssl", "x509", "-text", "-inform", "DER") 221 | 222 | file, err := os.Create(filename) 223 | if err != nil { 224 | panic(err) 225 | } 226 | defer file.Close() 227 | cmd.Stdout = file 228 | cmd.Stderr = os.Stderr 229 | 230 | stdin, err := cmd.StdinPipe() 231 | if err != nil { 232 | panic(err) 233 | } 234 | 235 | if err := cmd.Start(); err != nil { 236 | panic(err) 237 | } 238 | if _, err := stdin.Write(derBytes); err != nil { 239 | panic(err) 240 | } 241 | stdin.Close() 242 | if err := cmd.Wait(); err != nil { 243 | panic(err) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /testdata/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | r = requests.get("https://localhost:7252", verify='root.pem') 4 | print(r.status_code) 5 | -------------------------------------------------------------------------------- /testdata/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/kevinburke/handlers" 8 | ) 9 | 10 | func main() { 11 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 12 | io.WriteString(w, "Hello World") 13 | }) 14 | http.ListenAndServeTLS(":7252", "leaf.pem", "leaf.key", handlers.Log(http.DefaultServeMux)) 15 | } 16 | --------------------------------------------------------------------------------