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
204 lines
5.7 KiB
Go
204 lines
5.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"database/sql"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-acme/lego/v4/certcrypto"
|
|
"github.com/go-acme/lego/v4/certificate"
|
|
"github.com/go-acme/lego/v4/challenge/http01"
|
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
|
"github.com/go-acme/lego/v4/lego"
|
|
"github.com/go-acme/lego/v4/registration"
|
|
|
|
"git.mstar.dev/mstar/linstrom/config"
|
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
|
)
|
|
|
|
type leUser struct {
|
|
Email string
|
|
Registration *registration.Resource
|
|
Key crypto.PrivateKey
|
|
}
|
|
|
|
func (l *leUser) GetEmail() string {
|
|
return l.Email
|
|
}
|
|
|
|
func (l *leUser) GetRegistration() *registration.Resource {
|
|
return l.Registration
|
|
}
|
|
|
|
func (l *leUser) GetPrivateKey() crypto.PrivateKey {
|
|
return l.Key
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
// 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 "", "", err
|
|
}
|
|
if !metadata.LELastUpdate.Valid {
|
|
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
|
|
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.
|
|
// 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 err
|
|
}
|
|
marshalled, err := x509.MarshalECPrivateKey(userPrivateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
encryptedKey, err := Encrypt(
|
|
encKey,
|
|
marshalled,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
metadata.LEUserPrivKey = encryptedKey
|
|
} else {
|
|
decryptedKey, err := Decrypt(encKey, metadata.LEUserPrivKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userPrivateKey, err = x509.ParseECPrivateKey(decryptedKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
user := leUser{Email: *config.GlobalConfig.SSL.AdminMail, Key: userPrivateKey}
|
|
leConfig := lego.NewConfig(&user)
|
|
leConfig.CADirURL = strings.Replace(
|
|
config.GlobalConfig.General.GetFullPublicUrl(),
|
|
"https",
|
|
"http",
|
|
1,
|
|
) + "/directory"
|
|
leConfig.Certificate.KeyType = certcrypto.RSA2048
|
|
|
|
leClient, err := lego.NewClient(leConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO: Figure out these servers
|
|
err = leClient.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = leClient.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
reg, err := leClient.Registration.Register(
|
|
registration.RegisterOptions{TermsOfServiceAgreed: true},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
user.Registration = reg
|
|
req := certificate.ObtainRequest{
|
|
Domains: []string{config.GlobalConfig.General.GetFullDomain()},
|
|
Bundle: true,
|
|
}
|
|
certificates, err := leClient.Certificate.Obtain(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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
|
|
}
|