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)
-}