From 47b8c2b766bb807d55a365a040c7760c63125cf1 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 13 May 2025 16:43:27 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20autog=C3=A9n=C3=A9rer=20certificat=20TL?= =?UTF-8?q?S=20si=20non-fourni?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- server.go | 29 +++++++++++++++++++-- x509.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 x509.go diff --git a/Dockerfile b/Dockerfile index c2840ea..07cf33f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY LICENSE cmd.go config.go db.go entity.go flag.go go.mod go.sum handler.go server.go ./ +COPY LICENSE cmd.go config.go db.go entity.go flag.go go.mod go.sum handler.go server.go x509.go ./ ADD cmd/ cmd/ ADD queries/ queries/ diff --git a/server.go b/server.go index fdd02a3..e583d80 100644 --- a/server.go +++ b/server.go @@ -3,7 +3,9 @@ package presences import ( "context" "crypto/subtle" + "crypto/tls" "fmt" + "log" "net/http" "git.agecem.com/bottin/bottin/v11" @@ -69,7 +71,30 @@ func RunUIServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient address := fmt.Sprintf(":%d", cfg.Port) - return e.StartTLS(address, cfg.TLS.Cert, cfg.TLS.Key) - } + switch { + case cfg.TLS.Cert != "" && cfg.TLS.Key != "": + return e.StartTLS(address, cfg.TLS.Cert, cfg.TLS.Key) + case cfg.TLS.Cert != "" && cfg.TLS.Key == "": + return fmt.Errorf("found TLS certificate but missing associated TLS private key") + case cfg.TLS.Cert == "" && cfg.TLS.Key != "": + return fmt.Errorf("found TLS private key but missing associated TLS certificate") + default: + log.Println("No TLS pair was provided. Generating self-signed pair.") + tlsPair, err := newTLSPair() + if err != nil { + return err + } + + server := &http.Server{ + Addr: address, + Handler: e, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{tlsPair}, + }, + } + + return server.ListenAndServeTLS("", "") + } + } } diff --git a/x509.go b/x509.go new file mode 100644 index 0000000..434c720 --- /dev/null +++ b/x509.go @@ -0,0 +1,74 @@ +package presences + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" +) + +func newTLSPair() (tls.Certificate, error) { + //TODO revise this code to make sure it is satisfying + + // Generate an ECDSA private key using P256. + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to generate private key: %v", err) + } + + // Generate a random serial number. + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to generate serial number: %v", err) + } + + // Create a certificate template. + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"AGECEM-bottin-presences"}, + CommonName: "localhost", // common name localhost for local development + }, + NotBefore: time.Now().Add(-time.Hour), // valid from 1 hour ago + NotAfter: time.Now().Add(365 * 24 * time.Hour), // valid for 1 year + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, // self-signed, so we can mark it as CA (optional) + } + + // Self-sign the certificate. + derCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %v", err) + } + + // Encode the certificate PEM block. + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: derCert, + }) + // Encode the private key PEM block. + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Unable to marshal ECDSA private key: %v", err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: keyBytes, + }) + + // Load the generated certificate into a tls.Certificate. + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to load X509 key pair: %v", err) + } + + return tlsCert, nil +}