GODT-2034: Basic vault migration ability (proof of concept)
This commit is contained in:
parent
4c4c592f31
commit
6bf67917fb
|
@ -15,6 +15,9 @@ issues:
|
||||||
- at least one file in a package should have a package comment
|
- at least one file in a package should have a package comment
|
||||||
# Package comments.
|
# Package comments.
|
||||||
- "package-comments: should have a package comment"
|
- "package-comments: should have a package comment"
|
||||||
|
# Migration uses underscores to make versions clearer.
|
||||||
|
- "var-naming: don't use underscores in Go names"
|
||||||
|
- "ST1003: should not use underscores in Go names"
|
||||||
|
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Version int
|
||||||
|
|
||||||
|
const (
|
||||||
|
v2_3_x Version = iota
|
||||||
|
v2_4_x
|
||||||
|
v2_5_x
|
||||||
|
|
||||||
|
Current = v2_5_x
|
||||||
|
)
|
||||||
|
|
||||||
|
// upgrade migrates the vault from the given version to the next version.
|
||||||
|
func upgrade(v Version, b []byte) ([]byte, error) {
|
||||||
|
switch v {
|
||||||
|
case v2_3_x:
|
||||||
|
return upgrade_2_3_x(b)
|
||||||
|
|
||||||
|
case v2_4_x:
|
||||||
|
return upgrade_2_4_x(b)
|
||||||
|
|
||||||
|
case Current:
|
||||||
|
return nil, fmt.Errorf("already at current version %d", Current)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown version %d", v)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/sha256"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrate(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
// Create a v2.3.x vault.
|
||||||
|
b := newLegacyVault(t, []byte("my secret key"), v2_3_x, Data_2_3_x{
|
||||||
|
Settings: Settings_2_3_x{
|
||||||
|
GluonDir: "v2.3.x-gluon-dir",
|
||||||
|
IMAPPort: "1234", // string in v2.3.x, current version uses int
|
||||||
|
SMTPPort: "5678", // string in v2.3.x, current version uses int
|
||||||
|
},
|
||||||
|
Users: []UserData_2_3_x{{
|
||||||
|
ID: "user-id", // called "ID" in v2.3.x, current version has "UserID"
|
||||||
|
Name: "user-name", // called "Name" in v2.3.x, current version has "Username"
|
||||||
|
GluonKey: []byte("gluon-key"), // []byte in v2.3.x and current version, string in v2.4.x (intermediate)
|
||||||
|
SplitMode: true, // bool in v2.3.x and v2.4.x, enum in current version
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write the vault to disk.
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "vault.enc"), b, 0600))
|
||||||
|
|
||||||
|
// Migrate the vault.
|
||||||
|
s, corrupt, err := New(dir, "default-gluon-dir", []byte("my secret key"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, corrupt)
|
||||||
|
|
||||||
|
// Check the migrated vault.
|
||||||
|
require.Equal(t, "v2.3.x-gluon-dir", s.GetGluonDir())
|
||||||
|
require.Equal(t, 1234, s.GetIMAPPort())
|
||||||
|
require.Equal(t, 5678, s.GetSMTPPort())
|
||||||
|
|
||||||
|
// The user should be migrated.
|
||||||
|
userIDs := s.GetUserIDs()
|
||||||
|
require.Len(t, userIDs, 1)
|
||||||
|
|
||||||
|
// The migrated user should be correct.
|
||||||
|
require.NoError(t, s.GetUser("user-id", func(user *User) {
|
||||||
|
require.Equal(t, "user-id", user.UserID())
|
||||||
|
require.Equal(t, "user-name", user.Username())
|
||||||
|
require.Equal(t, []byte("gluon-key"), user.GluonKey())
|
||||||
|
require.Equal(t, SplitMode, user.AddressMode())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLegacyVault[T any](t *testing.T, key []byte, version Version, data T) []byte {
|
||||||
|
hash256 := sha256.Sum256(key)
|
||||||
|
|
||||||
|
aes, err := aes.NewCipher(hash256[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(aes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
b, err := msgpack.Marshal(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dec, err := msgpack.Marshal(File{Version: version, Data: b})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nonce, err := crypto.RandomToken(gcm.NonceSize())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return gcm.Seal(nonce, nonce, dec, nil)
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data_2_3_x struct {
|
||||||
|
Settings Settings_2_3_x
|
||||||
|
Users []UserData_2_3_x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data Data_2_3_x) migrate() Data_2_4_x {
|
||||||
|
return Data_2_4_x{
|
||||||
|
Settings: data.Settings.migrate(),
|
||||||
|
Users: xslices.Map(data.Users, func(user UserData_2_3_x) UserData_2_4_x { return user.migrate() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Settings_2_3_x struct {
|
||||||
|
GluonDir string
|
||||||
|
|
||||||
|
IMAPPort string
|
||||||
|
SMTPPort string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings Settings_2_3_x) migrate() Settings_2_4_x {
|
||||||
|
imapPort, err := strconv.Atoi(settings.IMAPPort)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
smtpPort, err := strconv.Atoi(settings.SMTPPort)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings_2_4_x{
|
||||||
|
GluonDir: settings.GluonDir,
|
||||||
|
|
||||||
|
IMAPPort: imapPort,
|
||||||
|
SMTPPort: smtpPort,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserData_2_3_x struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
GluonKey []byte
|
||||||
|
SplitMode bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user UserData_2_3_x) migrate() UserData_2_4_x {
|
||||||
|
return UserData_2_4_x{
|
||||||
|
UserID: user.ID,
|
||||||
|
Username: user.Name,
|
||||||
|
GluonKey: string(user.GluonKey),
|
||||||
|
SplitMode: user.SplitMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade_2_3_x(b []byte) ([]byte, error) {
|
||||||
|
var old Data_2_3_x
|
||||||
|
|
||||||
|
if err := msgpack.Unmarshal(b, &old); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgpack.Marshal(old.migrate())
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data_2_4_x struct {
|
||||||
|
Settings Settings_2_4_x
|
||||||
|
Users []UserData_2_4_x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data Data_2_4_x) migrate() Data {
|
||||||
|
return Data{
|
||||||
|
Settings: data.Settings.migrate(),
|
||||||
|
Users: xslices.Map(data.Users, func(user UserData_2_4_x) UserData { return user.migrate() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Settings_2_4_x struct {
|
||||||
|
GluonDir string
|
||||||
|
|
||||||
|
IMAPPort int
|
||||||
|
SMTPPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (settings Settings_2_4_x) migrate() Settings {
|
||||||
|
newSettings := newDefaultSettings(settings.GluonDir)
|
||||||
|
|
||||||
|
newSettings.IMAPPort = settings.IMAPPort
|
||||||
|
newSettings.SMTPPort = settings.SMTPPort
|
||||||
|
|
||||||
|
return newSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserData_2_4_x struct {
|
||||||
|
UserID string
|
||||||
|
Username string
|
||||||
|
|
||||||
|
GluonKey string
|
||||||
|
SplitMode bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user UserData_2_4_x) migrate() UserData {
|
||||||
|
var mode AddressMode
|
||||||
|
|
||||||
|
if user.SplitMode {
|
||||||
|
mode = SplitMode
|
||||||
|
} else {
|
||||||
|
mode = CombinedMode
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserData{
|
||||||
|
UserID: user.UserID,
|
||||||
|
Username: user.Username,
|
||||||
|
GluonKey: []byte(user.GluonKey),
|
||||||
|
AddressMode: mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade_2_4_x(b []byte) ([]byte, error) {
|
||||||
|
var old Data_2_4_x
|
||||||
|
|
||||||
|
if err := msgpack.Unmarshal(b, &old); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgpack.Marshal(old.migrate())
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import "github.com/ProtonMail/proton-bridge/v2/internal/certs"
|
||||||
|
|
||||||
|
type Certs struct {
|
||||||
|
Bridge Cert
|
||||||
|
Installed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cert struct {
|
||||||
|
Cert, Key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultCerts() Certs {
|
||||||
|
return Certs{
|
||||||
|
Bridge: newTLSCert(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTLSCert() Cert {
|
||||||
|
template, err := certs.NewTLSTemplate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPEM, keyPEM, err := certs.GenerateCert(template)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cert{
|
||||||
|
Cert: certPEM,
|
||||||
|
Key: keyPEM,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Settings Settings
|
||||||
|
Users []UserData
|
||||||
|
Cookies []byte
|
||||||
|
Certs Certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultData(gluonDir string) Data {
|
||||||
|
return Data{
|
||||||
|
Settings: newDefaultSettings(gluonDir),
|
||||||
|
Certs: newDefaultCerts(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File holds a versioned, serialized data.
|
||||||
|
type File struct {
|
||||||
|
Version Version
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalFile[T any](gcm cipher.AEAD, enc []byte, data *T) error {
|
||||||
|
dec, err := gcm.Open(nil, enc[:gcm.NonceSize()], enc[gcm.NonceSize():], nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var f File
|
||||||
|
|
||||||
|
if err := msgpack.Unmarshal(dec, &f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for v := f.Version; v < Current; v++ {
|
||||||
|
b, err := upgrade(v, f.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Data = b
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := msgpack.Unmarshal(f.Data, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalFile[T any](gcm cipher.AEAD, t T) ([]byte, error) {
|
||||||
|
b, err := msgpack.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := msgpack.Marshal(File{Version: Current, Data: b})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, err := crypto.RandomToken(gcm.NonceSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gcm.Seal(nonce, nonce, dec, nil), nil
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail 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.
|
||||||
|
//
|
||||||
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
GluonDir string
|
||||||
|
|
||||||
|
IMAPPort int
|
||||||
|
SMTPPort int
|
||||||
|
IMAPSSL bool
|
||||||
|
SMTPSSL bool
|
||||||
|
|
||||||
|
UpdateChannel updater.Channel
|
||||||
|
UpdateRollout float64
|
||||||
|
|
||||||
|
ColorScheme string
|
||||||
|
ProxyAllowed bool
|
||||||
|
ShowAllMail bool
|
||||||
|
Autostart bool
|
||||||
|
AutoUpdate bool
|
||||||
|
|
||||||
|
LastVersion string
|
||||||
|
FirstStart bool
|
||||||
|
FirstStartGUI bool
|
||||||
|
|
||||||
|
SyncWorkers int
|
||||||
|
SyncBuffer int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultSettings(gluonDir string) Settings {
|
||||||
|
return Settings{
|
||||||
|
GluonDir: gluonDir,
|
||||||
|
|
||||||
|
IMAPPort: 1143,
|
||||||
|
SMTPPort: 1025,
|
||||||
|
IMAPSSL: false,
|
||||||
|
SMTPSSL: false,
|
||||||
|
|
||||||
|
UpdateChannel: updater.DefaultUpdateChannel,
|
||||||
|
UpdateRollout: rand.Float64(), //nolint:gosec
|
||||||
|
|
||||||
|
ColorScheme: "",
|
||||||
|
ProxyAllowed: true,
|
||||||
|
ShowAllMail: true,
|
||||||
|
Autostart: false,
|
||||||
|
AutoUpdate: true,
|
||||||
|
|
||||||
|
LastVersion: "0.0.0",
|
||||||
|
FirstStart: true,
|
||||||
|
FirstStartGUI: true,
|
||||||
|
|
||||||
|
SyncWorkers: runtime.NumCPU(),
|
||||||
|
SyncBuffer: runtime.NumCPU(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,81 +17,7 @@
|
||||||
|
|
||||||
package vault
|
package vault
|
||||||
|
|
||||||
import (
|
import "github.com/ProtonMail/gluon/imap"
|
||||||
"math/rand"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/gluon/imap"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Data struct {
|
|
||||||
Settings Settings
|
|
||||||
Users []UserData
|
|
||||||
Cookies []byte
|
|
||||||
Certs Certs
|
|
||||||
}
|
|
||||||
|
|
||||||
type Certs struct {
|
|
||||||
Bridge Cert
|
|
||||||
Installed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cert struct {
|
|
||||||
Cert, Key []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type Settings struct {
|
|
||||||
GluonDir string
|
|
||||||
|
|
||||||
IMAPPort int
|
|
||||||
SMTPPort int
|
|
||||||
IMAPSSL bool
|
|
||||||
SMTPSSL bool
|
|
||||||
|
|
||||||
UpdateChannel updater.Channel
|
|
||||||
UpdateRollout float64
|
|
||||||
|
|
||||||
ColorScheme string
|
|
||||||
ProxyAllowed bool
|
|
||||||
ShowAllMail bool
|
|
||||||
Autostart bool
|
|
||||||
AutoUpdate bool
|
|
||||||
|
|
||||||
LastVersion string
|
|
||||||
FirstStart bool
|
|
||||||
FirstStartGUI bool
|
|
||||||
|
|
||||||
SyncWorkers int
|
|
||||||
SyncBuffer int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDefaultSettings(gluonDir string) Settings {
|
|
||||||
return Settings{
|
|
||||||
GluonDir: gluonDir,
|
|
||||||
|
|
||||||
IMAPPort: 1143,
|
|
||||||
SMTPPort: 1025,
|
|
||||||
IMAPSSL: false,
|
|
||||||
SMTPSSL: false,
|
|
||||||
|
|
||||||
UpdateChannel: updater.DefaultUpdateChannel,
|
|
||||||
UpdateRollout: rand.Float64(), //nolint:gosec
|
|
||||||
|
|
||||||
ColorScheme: "",
|
|
||||||
ProxyAllowed: true,
|
|
||||||
ShowAllMail: true,
|
|
||||||
Autostart: false,
|
|
||||||
AutoUpdate: true,
|
|
||||||
|
|
||||||
LastVersion: "0.0.0",
|
|
||||||
FirstStart: true,
|
|
||||||
FirstStartGUI: true,
|
|
||||||
|
|
||||||
SyncWorkers: runtime.NumCPU(),
|
|
||||||
SyncBuffer: runtime.NumCPU(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserData holds information about a single bridge user.
|
// UserData holds information about a single bridge user.
|
||||||
// The user may or may not be logged in.
|
// The user may or may not be logged in.
|
|
@ -20,7 +20,6 @@ package vault
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -29,10 +28,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
|
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/vmihailenco/msgpack/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Vault is an encrypted data vault that stores bridge and user data.
|
// Vault is an encrypted data vault that stores bridge and user data.
|
||||||
|
@ -228,9 +225,7 @@ func newVault(path, gluonDir string, gcm cipher.AEAD) (*Vault, bool, error) {
|
||||||
|
|
||||||
var corrupt bool
|
var corrupt bool
|
||||||
|
|
||||||
if dec, err := decrypt(gcm, enc); err != nil {
|
if err := unmarshalFile(gcm, enc, new(Data)); err != nil {
|
||||||
corrupt = true
|
|
||||||
} else if err := msgpack.Unmarshal(dec, new(Data)); err != nil {
|
|
||||||
corrupt = true
|
corrupt = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,14 +250,9 @@ func (vault *Vault) get() Data {
|
||||||
vault.encLock.RLock()
|
vault.encLock.RLock()
|
||||||
defer vault.encLock.RUnlock()
|
defer vault.encLock.RUnlock()
|
||||||
|
|
||||||
dec, err := decrypt(vault.gcm, vault.enc)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var data Data
|
var data Data
|
||||||
|
|
||||||
if err := msgpack.Unmarshal(dec, &data); err != nil {
|
if err := unmarshalFile(vault.gcm, vault.enc, &data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,25 +263,15 @@ func (vault *Vault) mod(fn func(data *Data)) error {
|
||||||
vault.encLock.Lock()
|
vault.encLock.Lock()
|
||||||
defer vault.encLock.Unlock()
|
defer vault.encLock.Unlock()
|
||||||
|
|
||||||
dec, err := decrypt(vault.gcm, vault.enc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var data Data
|
var data Data
|
||||||
|
|
||||||
if err := msgpack.Unmarshal(dec, &data); err != nil {
|
if err := unmarshalFile(vault.gcm, vault.enc, &data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(&data)
|
fn(&data)
|
||||||
|
|
||||||
mod, err := msgpack.Marshal(data)
|
enc, err := marshalFile(vault.gcm, data)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := encrypt(vault.gcm, mod)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -318,23 +298,7 @@ func (vault *Vault) modUser(userID string, fn func(userData *UserData)) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initVault(path, gluonDir string, gcm cipher.AEAD) ([]byte, error) {
|
func initVault(path, gluonDir string, gcm cipher.AEAD) ([]byte, error) {
|
||||||
bridgeCert, err := newTLSCert()
|
enc, err := marshalFile(gcm, newDefaultData(gluonDir))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := msgpack.Marshal(Data{
|
|
||||||
Settings: newDefaultSettings(gluonDir),
|
|
||||||
|
|
||||||
Certs: Certs{
|
|
||||||
Bridge: bridgeCert,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := encrypt(gcm, dec)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -345,34 +309,3 @@ func initVault(path, gluonDir string, gcm cipher.AEAD) ([]byte, error) {
|
||||||
|
|
||||||
return enc, nil
|
return enc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(gcm cipher.AEAD, enc []byte) ([]byte, error) {
|
|
||||||
return gcm.Open(nil, enc[:gcm.NonceSize()], enc[gcm.NonceSize():], nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encrypt(gcm cipher.AEAD, data []byte) ([]byte, error) {
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
|
||||||
|
|
||||||
if _, err := rand.Read(nonce); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gcm.Seal(nonce, nonce, data, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLSCert() (Cert, error) {
|
|
||||||
template, err := certs.NewTLSTemplate()
|
|
||||||
if err != nil {
|
|
||||||
return Cert{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
certPEM, keyPEM, err := certs.GenerateCert(template)
|
|
||||||
if err != nil {
|
|
||||||
return Cert{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Cert{
|
|
||||||
Cert: certPEM,
|
|
||||||
Key: keyPEM,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue