Add initial tls support
Some checks are pending
/ docker (push) Waiting to run

- Uses certificate and key files provided by admin in config
- Let's Encrypt integration planned, but not even close to working
- Initial HTTP3 Support added
This commit is contained in:
Melody Becker 2025-05-27 14:06:08 +02:00
parent 68d7a5e8c3
commit 9151bfb3be
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
6 changed files with 250 additions and 42 deletions

View file

@ -5,8 +5,10 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"database/sql"
"errors"
"os"
"strings"
"time"
@ -40,62 +42,99 @@ func (l *leUser) GetPrivateKey() crypto.PrivateKey {
return l.Key
}
func TlsFromConfig() (*tls.Config, error) {
// Return the path to the certificate file (.crt), the key file (.key) and any errors
func TlsFromConfig() (string, string, error) {
if config.GlobalConfig.SSL.UseLetsEncrypt != nil && *config.GlobalConfig.SSL.UseLetsEncrypt {
return tlsFromLetsEncrypt()
} else {
return tlsFromFile()
}
panic("Not implemented")
}
func tlsFromLetsEncrypt() (*tls.Config, error) {
// Verifies that the relevant config entries are set and are paths to readable files
func tlsFromFile() (string, string, error) {
sslConf := config.GlobalConfig.SSL
if sslConf.CertificateFile == nil {
return "", "", errors.New("need a path to a certificate file to use for tls")
}
if sslConf.CertKeyFile == nil {
return "", "", errors.New("need a path to a certificate key file to use for tls")
}
certInfo, err := os.Stat(*sslConf.CertificateFile)
if err != nil {
return "", "", err
}
if certInfo.IsDir() {
return "", "", errors.New("certificate file is a directory, needs to be a file")
}
keyInfo, err := os.Stat(*sslConf.CertKeyFile)
if err != nil {
return "", "", err
}
if keyInfo.IsDir() {
return "", "", errors.New("key file is a directory, needs to be a file")
}
return *sslConf.CertificateFile, *sslConf.CertKeyFile, nil
}
// Get the LetsEncrypt generated certifiates and store them in temporary files for the server to use
// If the certificate is too old, requests a new one.
func tlsFromLetsEncrypt() (string, string, error) {
metadata, err := dbgen.ServerMetadata.First()
if err != nil {
return nil, err
return "", "", err
}
if !metadata.LELastUpdate.Valid {
return generateLetsEncryptCert(metadata)
if err = generateLetsEncryptCert(metadata); err != nil {
return "", "", err
}
} else if metadata.LELastUpdate.Time.Add(time.Hour * 24 * 60).Before(time.Now()) {
// Let's Encrypt certificates last for 90 days. It is recommended to renew every 60 days
// See https://letsencrypt.org/docs/faq/#what-is-the-lifetime-for-let-s-encrypt-certificates-for-how-long-are-they-valid
return generateLetsEncryptCert(metadata)
if err = generateLetsEncryptCert(metadata); err != nil {
return "", "", err
}
}
panic("Not implemented")
}
// (Re-)Generate a Lets Encrypt certificate using the provided details in the config
// Automatically stores the newly generated certificate in the db, encrypting beforehand
func generateLetsEncryptCert(metadata *models.ServerMetadata) (*tls.Config, error) {
// (Re-)Generate a Lets Encrypt certificate using the provided details in the config.
// Automatically stores the newly generated certificate in the db, encrypting beforehand.
// Also updates the metadata with the new data.
// Does not write the cert to a temporary file.
func generateLetsEncryptCert(metadata *models.ServerMetadata) error {
encKey := []byte(config.GlobalConfig.Storage.EncryptionKey)
// Follow https://go-acme.github.io/lego/usage/library/index.html
var userPrivateKey *ecdsa.PrivateKey
var err error
if len(metadata.LEUserPrivKey) == 0 {
userPrivateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
return err
}
marshalled, err := x509.MarshalECPrivateKey(userPrivateKey)
if err != nil {
return nil, err
return err
}
encryptedKey, err := Encrypt(
[]byte(config.GlobalConfig.Storage.EncryptionKey),
encKey,
marshalled,
)
if err != nil {
return nil, err
return err
}
metadata.LEUserPrivKey = encryptedKey
} else {
decryptedKey, err := Decrypt([]byte(config.GlobalConfig.Storage.EncryptionKey), metadata.LEUserPrivKey)
decryptedKey, err := Decrypt(encKey, metadata.LEUserPrivKey)
if err != nil {
return nil, err
return err
}
userPrivateKey, err = x509.ParseECPrivateKey(decryptedKey)
if err != nil {
return nil, err
return err
}
}
user := leUser{Email: *config.GlobalConfig.SSL.AdminMail}
user := leUser{Email: *config.GlobalConfig.SSL.AdminMail, Key: userPrivateKey}
leConfig := lego.NewConfig(&user)
leConfig.CADirURL = strings.Replace(
config.GlobalConfig.General.GetFullPublicUrl(),
@ -107,22 +146,22 @@ func generateLetsEncryptCert(metadata *models.ServerMetadata) (*tls.Config, erro
leClient, err := lego.NewClient(leConfig)
if err != nil {
return nil, err
return err
}
// TODO: Figure out these servers
err = leClient.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
if err != nil {
return nil, err
return err
}
err = leClient.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001"))
if err != nil {
return nil, err
return err
}
reg, err := leClient.Registration.Register(
registration.RegisterOptions{TermsOfServiceAgreed: true},
)
if err != nil {
return nil, err
return err
}
user.Registration = reg
req := certificate.ObtainRequest{
@ -131,9 +170,35 @@ func generateLetsEncryptCert(metadata *models.ServerMetadata) (*tls.Config, erro
}
certificates, err := leClient.Certificate.Obtain(req)
if err != nil {
return nil, err
return err
}
_ = certificates
// TODO: Do something with certificates here
panic("Not implemented")
metadata.LELastUpdate = sql.NullTime{Valid: true, Time: time.Now()}
metadata.LEDomain = certificates.Domain
metadata.LECertStableUrl = certificates.CertStableURL
metadata.LECertUrl = certificates.CertURL
metadata.LECSR, err = Encrypt(encKey, certificates.CSR)
if err != nil {
return err
}
metadata.LECertificate, err = Encrypt(encKey, certificates.Certificate)
if err != nil {
return err
}
metadata.LECertificate, err = Encrypt(encKey, certificates.Certificate)
if err != nil {
return err
}
metadata.LEPrivateKey, err = Encrypt(encKey, certificates.PrivateKey)
if err != nil {
return err
}
metadata.LEIssuerCertificate, err = Encrypt(encKey, certificates.IssuerCertificate)
if err != nil {
return err
}
err = dbgen.ServerMetadata.Save(metadata)
if err != nil {
return err
}
return nil
}