Skip to content

Commit

Permalink
Update TLS certificates, generate certificate if not found.
Browse files Browse the repository at this point in the history
We now notice that a TLS certificate on disk has changed, and we
generate a self-signed certificate if none is found.
  • Loading branch information
jech committed Feb 24, 2021
1 parent 6cf82f6 commit b1babf5
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 12 deletions.
16 changes: 11 additions & 5 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@

CGO_ENABLED=0 go build -ldflags='-s -w'

## Create a server certificate

mkdir data
openssl req -newkey rsa:2048 -nodes -keyout data/key.pem -x509 -days 365 -out data/cert.pem

## Set the server administrator credentials

This step is optional.

mkdir data
echo 'god:topsecret' > data/passwd

## Set up a group
Expand Down Expand Up @@ -110,6 +106,16 @@ For a 32-bit MIPS board with no hardware floating point (WNDR3800, etc.):
Set up a user *galene* on your server, then do:

rsync -a galene static data groups galene@server.example.org:

If you don't have a TLS certificate, Galène will generate a self-signed
certificate automatically (and print a warning to the logs). If you have
a certificate, install it in the files `data/cert.pem` and `data/key.pem`:

ssh galene@server.example.org
sudo cp /etc/letsencrypt/live/server.example.org/fullchain.pem data/cert.pem
sudo cp /etc/letsencrypt/live/server.example.org/key.pem data/key.pem
sudo chown galene:galene data/*.pem
sudo chmod go-rw data/key.pem

Now run the binary on the server:

Expand Down
98 changes: 98 additions & 0 deletions webserver/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package webserver

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"log"
"math/big"
"os"
"path/filepath"
"sync/atomic"
"time"
)

type certInfo struct {
certificate *tls.Certificate
keyTime time.Time
certTime time.Time
}

var certificate atomic.Value

func generateCertificate(dataDir string) (tls.Certificate, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return tls.Certificate{}, err
}

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
now := time.Now()

template := x509.Certificate{
SerialNumber: serialNumber,
NotBefore: now,
NotAfter: now.Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
bytes, err := x509.CreateCertificate(
rand.Reader, &template, &template, &priv.PublicKey, priv,
)
if err != nil {
return tls.Certificate{}, err
}

return tls.Certificate{
Certificate: [][]byte{bytes},
PrivateKey: priv,
}, nil
}

func fileTime(filename string) time.Time {
fi, err := os.Stat(filename)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("%v: %v", filename, err)
}
return time.Time{}
}
return fi.ModTime()
}

func getCertificate(dataDir string) (*tls.Certificate, error) {
info, ok := certificate.Load().(*certInfo)

certFile := filepath.Join(dataDir, "cert.pem")
keyFile := filepath.Join(dataDir, "key.pem")
certTime := fileTime(certFile)
keyTime := fileTime(keyFile)

if !ok || !info.certTime.Equal(certTime) || !info.keyTime.Equal(keyTime) {
var cert tls.Certificate
if certTime.Equal(time.Time{}) || keyTime.Equal(time.Time{}) {
log.Printf("Generating self-signed certificate")
var err error
cert, err = generateCertificate(dataDir)
if err != nil {
return nil, err
}
} else {
var err error
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
}
info = &certInfo{
certificate: &cert,
certTime: certTime,
keyTime: keyTime,
}
certificate.Store(info)
}
return info.certificate, nil
}
19 changes: 12 additions & 7 deletions webserver/webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package webserver
import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -55,6 +56,13 @@ func Serve(address string, dataDir string) error {
ReadHeaderTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
}
if !Insecure {
s.TLSConfig = &tls.Config{
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return getCertificate(dataDir)
},
}
}
s.RegisterOnShutdown(func() {
group.Range(func(g *group.Group) bool {
go g.Shutdown("server is shutting down")
Expand All @@ -67,10 +75,7 @@ func Serve(address string, dataDir string) error {
var err error

if !Insecure {
err = s.ListenAndServeTLS(
filepath.Join(dataDir, "cert.pem"),
filepath.Join(dataDir, "key.pem"),
)
err = s.ListenAndServeTLS("", "")
} else {
err = s.ListenAndServe()
}
Expand Down Expand Up @@ -273,8 +278,8 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return
}

if r.URL.Path != "/group/" + name {
http.Redirect(w, r, "/group/" + name,
if r.URL.Path != "/group/"+name {
http.Redirect(w, r, "/group/"+name,
http.StatusPermanentRedirect)
return
}
Expand Down Expand Up @@ -433,7 +438,7 @@ func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) {
fmt.Fprintf(w, "</body></html>\n")
}

var wsUpgrader = websocket.Upgrader {
var wsUpgrader = websocket.Upgrader{
HandshakeTimeout: 30 * time.Second,
}

Expand Down

0 comments on commit b1babf5

Please sign in to comment.