Skip to content

Commit

Permalink
Module generates keys and certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
joshrivers committed May 25, 2021
1 parent 2ed73c0 commit 09ce5c0
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 1 deletion.
18 changes: 18 additions & 0 deletions .github/workflows/test_and_coverage.yml
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)
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
golang 1.16.4
11 changes: 11 additions & 0 deletions Makefile
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
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,58 @@
# selfsignedcertgen
Generate self signed certificates for Golang web server

[![codecov](https://codecov.io/gh/./branch/master/graph/badge.svg)](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)
}
}
```
53 changes: 53 additions & 0 deletions doc.go
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
12 changes: 12 additions & 0 deletions example_test.go
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)
}
34 changes: 34 additions & 0 deletions examples/example_1/main.go
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)
}
}
96 changes: 96 additions & 0 deletions generate_key_and_certificate.go
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()}
}
5 changes: 5 additions & 0 deletions go.mod
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
Loading

0 comments on commit 09ce5c0

Please sign in to comment.