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 }