-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Module generates keys and certificates
- Loading branch information
1 parent
2ed73c0
commit 09ce5c0
Showing
13 changed files
with
539 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: Test and coverage | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 2 | ||
- uses: actions/setup-go@v2 | ||
with: | ||
go-version: '1.16' | ||
- name: Run coverage | ||
run: go test -race -coverprofile=coverage.out -covermode=atomic | ||
- name: Upload coverage to Codecov | ||
run: bash <(curl -s https://codecov.io/bash) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
golang 1.16.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
run: | ||
go run examples/example_1/main.go | ||
|
||
test: | ||
go test -v . | ||
|
||
coverage: | ||
go test -race -covermode=atomic -coverprofile=coverage.out | ||
|
||
readme: | ||
goreadme -credit=false -badge-codecov > README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,58 @@ | ||
# selfsignedcertgen | ||
Generate self signed certificates for Golang web server | ||
|
||
[](https://codecov.io/gh/.) | ||
|
||
Generate self signed certificates for use in a Golang web server. | ||
|
||
You can generate a new TLS private key and sign it with a self-signed certificate authority with a simple one-liner: | ||
|
||
```go | ||
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/C=US/ST=OR/L=Portland/O=test/OU=example/CN=www.example.com" | ||
``` | ||
|
||
Unfortunately this requires openssl to be installed and some pre-launch execution for a containerized application. | ||
|
||
This library will generate the equivalent key and certificate files in Go at runtime to allow a TLS server to start. It is implemented using RSA keys for simplicity and current compatibility requirements. | ||
|
||
## Note on security | ||
|
||
If possible, you should never use self-sigened certificates. These days it is pretty easy to use Let's Encrypt and there are a number of Go livraries that will autoprovision an TLS certificate using the Let's Encrypt api. If possible you should use one of those and not self-signed certificates. Self-signed certificates often create a number of security problems that leave you open to a lower level of security than using plain old HTTP. Avoid them if possible. Remember that you need `chrome://flags/#allow-insecure-localhost` enabled to hit insecure HTTP on localhost now. | ||
|
||
Example | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/joshrivers/selfsignedcertgen" | ||
) | ||
|
||
func hello(w http.ResponseWriter, req *http.Request) { | ||
|
||
fmt.Fprintf(w, "hello\n") | ||
} | ||
|
||
func main() { | ||
fmt.Print("Listening for [https://localhost:8443/hello](https://localhost:8443/hello)\n") | ||
http.HandleFunc("/hello", hello) | ||
|
||
signer := selfsignedcertgen.NewSelfSigner() | ||
signer.Hosts = []string{"www.example.net", "replica.example.net"} | ||
signer.Country = "US" | ||
signer.Locality = "Los Angeles" | ||
signer.Organization = "Tyrell Corporation" | ||
signer.OrganizationUnit = "Nexus Design" | ||
signer.RsaKeyBits = 4096 | ||
signer.ValidFor = 10 * 365 * 24 * time.Hour | ||
keyLocations := signer.GenerateKeyAndCertificate() | ||
err := http.ListenAndServeTLS("localhost:8443", keyLocations.CertPath, keyLocations.KeyPath, nil) | ||
if err != nil { | ||
log.Fatalf("Failed to start server with error: %!(NOVERB)v", err) | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
Generate self signed certificates for use in a Golang web server. | ||
You can generate a new TLS private key and sign it with a self-signed certificate authority with a simple one-liner: | ||
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/C=US/ST=OR/L=Portland/O=test/OU=example/CN=www.example.com" | ||
Unfortunately this requires openssl to be installed and some pre-launch execution for a containerized application. | ||
This library will generate the equivalent key and certificate files in Go at runtime to allow a TLS server to start. It is implemented using RSA keys for simplicity and current compatibility requirements. | ||
Note on security | ||
If possible, you should never use self-sigened certificates. These days it is pretty easy to use Let's Encrypt and there are a number of Go livraries that will autoprovision an TLS certificate using the Let's Encrypt api. If possible you should use one of those and not self-signed certificates. Self-signed certificates often create a number of security problems that leave you open to a lower level of security than using plain old HTTP. Avoid them if possible. Remember that you need `chrome://flags/#allow-insecure-localhost` enabled to hit insecure HTTP on localhost now. | ||
Example | ||
package main | ||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"time" | ||
"github.com/joshrivers/selfsignedcertgen" | ||
) | ||
func hello(w http.ResponseWriter, req *http.Request) { | ||
fmt.Fprintf(w, "hello\n") | ||
} | ||
func main() { | ||
fmt.Print("Listening for https://localhost:8443/hello\n") | ||
http.HandleFunc("/hello", hello) | ||
signer := selfsignedcertgen.NewSelfSigner() | ||
signer.Hosts = []string{"www.example.net", "replica.example.net"} | ||
signer.Country = "US" | ||
signer.Locality = "Los Angeles" | ||
signer.Organization = "Tyrell Corporation" | ||
signer.OrganizationUnit = "Nexus Design" | ||
signer.RsaKeyBits = 4096 | ||
signer.ValidFor = 10 * 365 * 24 * time.Hour | ||
keyLocations := signer.GenerateKeyAndCertificate() | ||
err := http.ListenAndServeTLS("localhost:8443", keyLocations.CertPath, keyLocations.KeyPath, nil) | ||
if err != nil { | ||
log.Fatalf("Failed to start server with error: %v", err) | ||
} | ||
} | ||
*/ | ||
package selfsignedcertgen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package selfsignedcertgen_test | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/joshrivers/selfsignedcertgen" | ||
) | ||
|
||
func ExampleNewSelfSigner() { | ||
keyLocations := selfsignedcertgen.NewSelfSigner().GenerateKeyAndCertificate() | ||
http.ListenAndServeTLS(":443", keyLocations.CertPath, keyLocations.KeyPath, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/joshrivers/selfsignedcertgen" | ||
) | ||
|
||
func hello(w http.ResponseWriter, req *http.Request) { | ||
|
||
fmt.Fprintf(w, "hello\n") | ||
} | ||
|
||
func main() { | ||
fmt.Print("Listening for https://localhost:8443/hello\n") | ||
http.HandleFunc("/hello", hello) | ||
|
||
signer := selfsignedcertgen.NewSelfSigner() | ||
signer.Hosts = []string{"www.example.net", "replica.example.net"} | ||
signer.Country = "US" | ||
signer.Locality = "Los Angeles" | ||
signer.Organization = "Tyrell Corporation" | ||
signer.OrganizationUnit = "Nexus Design" | ||
signer.RsaKeyBits = 4096 | ||
signer.ValidFor = 10 * 365 * 24 * time.Hour | ||
keyLocations := signer.GenerateKeyAndCertificate() | ||
err := http.ListenAndServeTLS("localhost:8443", keyLocations.CertPath, keyLocations.KeyPath, nil) | ||
if err != nil { | ||
log.Fatalf("Failed to start server with error: %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package selfsignedcertgen | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"io/ioutil" | ||
"log" | ||
"math/big" | ||
"net" | ||
"os" | ||
) | ||
|
||
// Generates a key and certificate from SelfSigner spec and | ||
// stores them in temporary files. Returns a struct containing | ||
// the path to the generated temporary files. | ||
func (ss SelfSigner) GenerateKeyAndCertificate() KeyLocations { | ||
notAfter := ss.ValidFrom.Add(ss.ValidFor) | ||
|
||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | ||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
if err != nil { | ||
log.Fatalf("Failed to generate serial number: %v", err) | ||
} | ||
|
||
template := x509.Certificate{ | ||
SerialNumber: serialNumber, | ||
Subject: pkix.Name{ | ||
Country: []string{ss.Country}, | ||
CommonName: ss.Hosts[0], | ||
Locality: []string{ss.Locality}, | ||
Organization: []string{ss.Organization}, | ||
OrganizationalUnit: []string{ss.OrganizationUnit}, | ||
}, | ||
NotBefore: ss.ValidFrom, | ||
NotAfter: notAfter, | ||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
BasicConstraintsValid: true, | ||
} | ||
|
||
for _, h := range ss.Hosts { | ||
if ip := net.ParseIP(h); ip != nil { | ||
template.IPAddresses = append(template.IPAddresses, ip) | ||
} else { | ||
template.DNSNames = append(template.DNSNames, h) | ||
} | ||
} | ||
|
||
priv, err := rsa.GenerateKey(rand.Reader, ss.RsaKeyBits) | ||
if err != nil { | ||
log.Fatalf("Failed to generate private key: %v", err) | ||
} | ||
|
||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) | ||
if err != nil { | ||
log.Fatalf("Failed to create certificate: %v", err) | ||
} | ||
certOut, err := ioutil.TempFile("", "cert.*.pem") | ||
if err != nil { | ||
log.Fatalf("Failed to open certificate file for writing: %v", err) | ||
} | ||
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) | ||
if err != nil { | ||
log.Fatalf("Failed to write data to certificate file: %v", err) | ||
} | ||
err = certOut.Close() | ||
if err != nil { | ||
log.Fatalf("Error closing certificate file: %v", err) | ||
} | ||
|
||
keyOut, err := ioutil.TempFile("", "key.*.pem") | ||
if err != nil { | ||
log.Fatalf("Failed to open key file: %v", err) | ||
} | ||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv) | ||
if err != nil { | ||
log.Fatalf("Unable to format private key: %v", err) | ||
} | ||
err = pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) | ||
if err != nil { | ||
log.Fatalf("Unable to write data to key file: %v", err) | ||
} | ||
err = keyOut.Close() | ||
if err != nil { | ||
log.Fatalf("Error closing key file: %v", err) | ||
} | ||
err = os.Chmod(keyOut.Name(), 0600) | ||
if err != nil { | ||
log.Fatalf("Unable to restrict key file premissions: %v", err) | ||
} | ||
|
||
return KeyLocations{KeyPath: keyOut.Name(), CertPath: certOut.Name()} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/joshrivers/selfsignedcertgen | ||
|
||
go 1.16 | ||
|
||
require github.com/posener/goreadme v1.4.0 // indirect |
Oops, something went wrong.