From 0576dcddff82d080d12c986ec4f28caa2f48157a Mon Sep 17 00:00:00 2001 From: Lukasz Janyst Date: Tue, 3 May 2022 16:27:58 +0200 Subject: [PATCH] bridge: Remove the X509 generation and management code Issue #6 --- pkg/bridge/bridge.go | 10 +- pkg/bridge/utils.go | 50 ++++----- pkg/config/settings/settings.go | 6 +- pkg/config/tls/cert_store_darwin.go | 53 --------- pkg/config/tls/cert_store_linux.go | 26 ----- pkg/config/tls/cert_store_windows.go | 26 ----- pkg/config/tls/tls.go | 155 --------------------------- pkg/config/tls/tls_test.go | 77 ------------- 8 files changed, 29 insertions(+), 374 deletions(-) delete mode 100644 pkg/config/tls/cert_store_darwin.go delete mode 100644 pkg/config/tls/cert_store_linux.go delete mode 100644 pkg/config/tls/cert_store_windows.go delete mode 100644 pkg/config/tls/tls.go delete mode 100644 pkg/config/tls/tls_test.go diff --git a/pkg/bridge/bridge.go b/pkg/bridge/bridge.go index 7e3a194..be97d1c 100644 --- a/pkg/bridge/bridge.go +++ b/pkg/bridge/bridge.go @@ -28,7 +28,6 @@ import ( cacheCfg "github.com/ljanyst/peroxide/pkg/config/cache" "github.com/ljanyst/peroxide/pkg/config/settings" - "github.com/ljanyst/peroxide/pkg/config/tls" "github.com/ljanyst/peroxide/pkg/cookies" "github.com/ljanyst/peroxide/pkg/events" "github.com/ljanyst/peroxide/pkg/imap" @@ -52,7 +51,7 @@ var ErrLocalCacheUnavailable = errors.New("local cache is unavailable") type Bridge struct { Users *users.Users - settings SettingsProvider + settings *settings.Settings clientManager pmapi.Manager cacheProvider *cacheCfg.Cache cache cache.Cache @@ -133,9 +132,10 @@ func (b *Bridge) Configure(configFile string) error { } func (b *Bridge) Run() error { - tlsCfg := tls.New(b.settings.Get(settings.TLSDir)) - - tlsConfig, err := loadTLSConfig(tlsCfg) + tlsConfig, err := loadTlsConfig( + b.settings.Get(settings.X509Cert), + b.settings.Get(settings.X509Key), + ) if err != nil { return err } diff --git a/pkg/bridge/utils.go b/pkg/bridge/utils.go index 9e5ca09..aa37bc1 100644 --- a/pkg/bridge/utils.go +++ b/pkg/bridge/utils.go @@ -20,14 +20,14 @@ package bridge import ( "crypto/tls" + "crypto/x509" + "time" cfgCache "github.com/ljanyst/peroxide/pkg/config/cache" "github.com/ljanyst/peroxide/pkg/config/settings" - pkgTLS "github.com/ljanyst/peroxide/pkg/config/tls" "github.com/ljanyst/peroxide/pkg/store" "github.com/ljanyst/peroxide/pkg/store/cache" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) const ( @@ -36,42 +36,32 @@ const ( inMemoryCacheLimnit = 100 * (1 << 20) ) -func loadTLSConfig(cfg *pkgTLS.TLS) (*tls.Config, error) { - if !cfg.HasCerts() { - if err := generateTLSCerts(cfg); err != nil { - return nil, err - } - } - - tlsConfig, err := cfg.GetConfig() - if err == nil { - return tlsConfig, nil - } - - logrus.WithError(err).Error("Failed to load TLS config, regenerating certificates") - - if err := generateTLSCerts(cfg); err != nil { - return nil, err +// GetConfig tries to load TLS config or generate new one which is then returned. +func loadTlsConfig(certPath, keyPath string) (*tls.Config, error) { + c, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, errors.Wrap(err, "Failed to load cert and key") } - return cfg.GetConfig() -} - -func generateTLSCerts(cfg *pkgTLS.TLS) error { - template, err := pkgTLS.NewTLSTemplate() + c.Leaf, err = x509.ParseCertificate(c.Certificate[0]) if err != nil { - return errors.Wrap(err, "failed to generate TLS template") + return nil, errors.Wrap(err, "Failed to parse the certificate") } - if err := cfg.GenerateCerts(template); err != nil { - return errors.Wrap(err, "failed to generate TLS certs") + if time.Now().Add(31 * 24 * time.Hour).After(c.Leaf.NotAfter) { + return nil, errors.Wrap(err, "The X509 certificate is about to expire") } - if err := cfg.InstallCerts(); err != nil { - return errors.Wrap(err, "failed to install TLS certs") - } + caCertPool := x509.NewCertPool() + caCertPool.AddCert(c.Leaf) - return nil + return &tls.Config{ + Certificates: []tls.Certificate{c}, + ServerName: c.Leaf.Subject.CommonName, + ClientAuth: tls.VerifyClientCertIfGiven, + RootCAs: caCertPool, + ClientCAs: caCertPool, + }, nil } // loadMessageCache loads local cache in case it is enabled in settings and available. diff --git a/pkg/config/settings/settings.go b/pkg/config/settings/settings.go index e516e41..38c5ad8 100644 --- a/pkg/config/settings/settings.go +++ b/pkg/config/settings/settings.go @@ -38,7 +38,8 @@ const ( FetchWorkers = "FetchWorkers" AttachmentWorkers = "AttachmentWorkers" CacheDir = "CacheDir" - TLSDir = "TlsDir" + X509Key = "X509Key" + X509Cert = "X509Cert" CookieJar = "CookieJar" ) @@ -81,6 +82,7 @@ func (s *Settings) setDefaultValues() { s.setDefault(SMTPPortKey, DefaultSMTPPort) s.setDefault(CacheDir, filepath.Join(s.settingsDir, "cache")) - s.setDefault(TLSDir, s.settingsDir) + s.setDefault(X509Key, filepath.Join(s.settingsDir, "key.pem")) + s.setDefault(X509Cert, filepath.Join(s.settingsDir, "cert.pem")) s.setDefault(CookieJar, filepath.Join(s.settingsDir, "cookies.json")) } diff --git a/pkg/config/tls/cert_store_darwin.go b/pkg/config/tls/cert_store_darwin.go deleted file mode 100644 index 0ecb958..0000000 --- a/pkg/config/tls/cert_store_darwin.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2022 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package tls - -import "os/exec" - -func addTrustedCert(certPath string) error { - return exec.Command( // nolint[gosec] - "/usr/bin/security", - "execute-with-privileges", - "/usr/bin/security", - "add-trusted-cert", - "-d", - "-r", "trustRoot", - "-p", "ssl", - "-k", "/Library/Keychains/System.keychain", - certPath, - ).Run() -} - -func removeTrustedCert(certPath string) error { - return exec.Command( // nolint[gosec] - "/usr/bin/security", - "execute-with-privileges", - "/usr/bin/security", - "remove-trusted-cert", - "-d", - certPath, - ).Run() -} - -func (t *TLS) InstallCerts() error { - return addTrustedCert(t.getTLSCertPath()) -} - -func (t *TLS) UninstallCerts() error { - return removeTrustedCert(t.getTLSCertPath()) -} diff --git a/pkg/config/tls/cert_store_linux.go b/pkg/config/tls/cert_store_linux.go deleted file mode 100644 index 7d1d12d..0000000 --- a/pkg/config/tls/cert_store_linux.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package tls - -func (t *TLS) InstallCerts() error { - return nil // Linux doesn't have a root cert store. -} - -func (t *TLS) UninstallCerts() error { - return nil // Linux doesn't have a root cert store. -} diff --git a/pkg/config/tls/cert_store_windows.go b/pkg/config/tls/cert_store_windows.go deleted file mode 100644 index 1a93b32..0000000 --- a/pkg/config/tls/cert_store_windows.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package tls - -func (t *TLS) InstallCerts() error { - return nil // NOTE(GODT-986): Install certs to root cert store? -} - -func (t *TLS) UninstallCerts() error { - return nil // NOTE(GODT-986): Uninstall certs from root cert store? -} diff --git a/pkg/config/tls/tls.go b/pkg/config/tls/tls.go deleted file mode 100644 index 677cdb8..0000000 --- a/pkg/config/tls/tls.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2022 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package tls - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "math/big" - "net" - "os" - "path/filepath" - "time" - - "github.com/pkg/errors" -) - -type TLS struct { - settingsPath string -} - -func New(settingsPath string) *TLS { - return &TLS{ - settingsPath: settingsPath, - } -} - -// NewTLSTemplate creates a new TLS template certificate with a random serial number. -func NewTLSTemplate() (*x509.Certificate, error) { - serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - if err != nil { - return nil, errors.Wrap(err, "failed to generate serial number") - } - - return &x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Country: []string{"CH"}, - Organization: []string{"Proton Technologies AG"}, - OrganizationalUnit: []string{"ProtonMail"}, - CommonName: "127.0.0.1", - }, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - IsCA: true, - IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(20 * 365 * 24 * time.Hour), - }, nil -} - -var ErrTLSCertExpiresSoon = fmt.Errorf("TLS certificate will expire soon") - -// getTLSCertPath returns path to certificate; used for TLS servers (IMAP, SMTP). -func (t *TLS) getTLSCertPath() string { - return filepath.Join(t.settingsPath, "cert.pem") -} - -// getTLSKeyPath returns path to private key; used for TLS servers (IMAP, SMTP). -func (t *TLS) getTLSKeyPath() string { - return filepath.Join(t.settingsPath, "key.pem") -} - -// HasCerts returns whether TLS certs have been generated. -func (t *TLS) HasCerts() bool { - if _, err := os.Stat(t.getTLSCertPath()); err != nil { - return false - } - - if _, err := os.Stat(t.getTLSKeyPath()); err != nil { - return false - } - - return true -} - -// GenerateCerts generates certs from the given template. -func (t *TLS) GenerateCerts(template *x509.Certificate) error { - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return errors.Wrap(err, "failed to generate private key") - } - - derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) - if err != nil { - return errors.Wrap(err, "failed to create certificate") - } - - certOut, err := os.Create(t.getTLSCertPath()) - if err != nil { - return err - } - defer certOut.Close() // nolint[errcheck] - - if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return err - } - - keyOut, err := os.OpenFile(t.getTLSKeyPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer keyOut.Close() // nolint[errcheck] - - return pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) -} - -// GetConfig tries to load TLS config or generate new one which is then returned. -func (t *TLS) GetConfig() (*tls.Config, error) { - c, err := tls.LoadX509KeyPair(t.getTLSCertPath(), t.getTLSKeyPath()) - if err != nil { - return nil, errors.Wrap(err, "failed to load keypair") - } - - c.Leaf, err = x509.ParseCertificate(c.Certificate[0]) - if err != nil { - return nil, errors.Wrap(err, "failed to parse certificate") - } - - if time.Now().Add(31 * 24 * time.Hour).After(c.Leaf.NotAfter) { - return nil, ErrTLSCertExpiresSoon - } - - caCertPool := x509.NewCertPool() - caCertPool.AddCert(c.Leaf) - - // nolint[gosec]: We need to support older TLS versions for AppleMail and Outlook. - return &tls.Config{ - Certificates: []tls.Certificate{c}, - ServerName: "127.0.0.1", - ClientAuth: tls.VerifyClientCertIfGiven, - RootCAs: caCertPool, - ClientCAs: caCertPool, - }, nil -} diff --git a/pkg/config/tls/tls_test.go b/pkg/config/tls/tls_test.go deleted file mode 100644 index f6f27f8..0000000 --- a/pkg/config/tls/tls_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2022 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package tls - -import ( - "io/ioutil" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestGetOldConfig(t *testing.T) { - dir, err := ioutil.TempDir("", "test-tls") - require.NoError(t, err) - - // Create new tls object. - tls := New(dir) - - // Create new TLS template. - tlsTemplate, err := NewTLSTemplate() - require.NoError(t, err) - - // Make the template be an old key. - tlsTemplate.NotBefore = time.Now().Add(-365 * 24 * time.Hour) - tlsTemplate.NotAfter = time.Now() - - // Generate the certs from the template. - require.NoError(t, tls.GenerateCerts(tlsTemplate)) - - // Generate the config from the certs -- it's going to expire soon so we don't want to use it. - _, err = tls.GetConfig() - require.Equal(t, err, ErrTLSCertExpiresSoon) -} - -func TestGetValidConfig(t *testing.T) { - dir, err := ioutil.TempDir("", "test-tls") - require.NoError(t, err) - - // Create new tls object. - tls := New(dir) - - // Create new TLS template. - tlsTemplate, err := NewTLSTemplate() - require.NoError(t, err) - - // Make the template be a new key. - tlsTemplate.NotBefore = time.Now() - tlsTemplate.NotAfter = time.Now().Add(2 * 365 * 24 * time.Hour) - - // Generate the certs from the template. - require.NoError(t, tls.GenerateCerts(tlsTemplate)) - - // Generate the config from the certs -- it's not going to expire soon so we want to use it. - config, err := tls.GetConfig() - require.NoError(t, err) - require.Equal(t, len(config.Certificates), 1) - - // Check the cert is valid. - now, notValidAfter := time.Now(), config.Certificates[0].Leaf.NotAfter - require.False(t, now.After(notValidAfter), "new certificate expected to be valid at %v but have valid until %v", now, notValidAfter) -}