feat: migrate to gopenpgp v2

This commit is contained in:
James Houlahan
2020-06-05 09:33:37 +02:00
parent de16f6f2d1
commit c19bb0fa97
54 changed files with 928 additions and 684 deletions

View File

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./credits.sh at Thu 07 May 2020 08:24:48 PM CEST. DO NOT EDIT.
// Code generated by ./credits.sh at Fri 05 Jun 2020 09:09:44 AM CEST. DO NOT EDIT.
package bridge
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/danieljoos/wincred;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./release-notes.sh at Thu 07 May 2020 08:46:50 PM CEST. DO NOT EDIT.
// Code generated by ./release-notes.sh at Tue 09 Jun 2020 10:21:50 AM CEST. DO NOT EDIT.
package bridge

View File

@ -32,7 +32,7 @@ import (
"text/template"
"time"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/imap/cache"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/pkg/message"
@ -82,7 +82,11 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
return errors.New("no available address for encryption")
}
m.AddressID = addr.ID
kr := addr.KeyRing()
kr, err := im.user.client().KeyRingForAddressID(addr.ID)
if err != nil {
return err
}
// Handle imported messages which have no "Sender" address.
// This sometimes occurs with outlook which reports errors as imported emails or for drafts.
@ -184,7 +188,7 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
}
func (im *imapMailbox) importMessage(m *pmapi.Message, readers []io.Reader, kr *pmcrypto.KeyRing) (err error) { // nolint[funlen]
func (im *imapMailbox) importMessage(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (err error) { // nolint[funlen]
b := &bytes.Buffer{}
// Overwrite content for main header for import.
@ -240,7 +244,7 @@ func (im *imapMailbox) importMessage(m *pmapi.Message, readers []io.Reader, kr *
}
// Create encrypted writer.
pgpMessage, err := kr.Encrypt(pmcrypto.NewPlainMessage(data), nil)
pgpMessage, err := kr.Encrypt(crypto.NewPlainMessage(data), nil)
if err != nil {
return err
}
@ -722,7 +726,7 @@ func (im *imapMailbox) buildMessage(m *pmapi.Message) (structure *message.BodySt
return structure, msgBody, err
}
func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *pmcrypto.KeyRing) (structure *message.BodyStructure, msgBody []byte, err error) { // nolint[funlen]
func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (structure *message.BodyStructure, msgBody []byte, err error) { // nolint[funlen]
multipartType, err := im.setMessageContentType(m)
if err != nil {
return

View File

@ -21,7 +21,7 @@ import (
"io"
"net/mail"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
@ -35,7 +35,7 @@ type storeUserProvider interface {
GetAddress(addressID string) (storeAddressProvider, error)
CreateDraft(
kr *pmcrypto.KeyRing,
kr *crypto.KeyRing,
message *pmapi.Message,
attachmentReaders []io.Reader,
attachedPublicKey,

View File

@ -0,0 +1,76 @@
// Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
package smtp
import (
"testing"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
func TestThing(t *testing.T) {
// Load the key.
key, err := crypto.NewKeyFromArmored(testPublicKey)
if err != nil {
panic(err)
}
// Put it in a keyring.
keyRing, err := crypto.NewKeyRing(key)
if err != nil {
panic(err)
}
// Filter out expired ones.
validKeyRings, err := crypto.FilterExpiredKeys([]*crypto.KeyRing{keyRing})
if err != nil {
panic(err)
}
// Filtering shouldn't make them unequal.
assert.True(t, isEqual(keyRing, validKeyRings[0]))
}
func isEqual(a, b *crypto.KeyRing) bool {
if a == nil && b == nil {
return true
}
if a == nil && b != nil || a != nil && b == nil {
return false
}
aKeys, bKeys := a.GetKeys(), b.GetKeys()
if len(aKeys) != len(bKeys) {
return false
}
for i := range aKeys {
aFPs := aKeys[i].GetSHA256Fingerprints()
bFPs := bKeys[i].GetSHA256Fingerprints()
if !cmp.Equal(aFPs, bFPs) {
return false
}
}
return true
}

View File

@ -20,7 +20,7 @@ package smtp
import (
"errors"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/algo"
"github.com/ProtonMail/proton-bridge/pkg/listener"
@ -37,7 +37,7 @@ type SendingInfo struct {
Sign bool
Scheme int
MIMEType string
PublicKey *pmcrypto.KeyRing
PublicKey *crypto.KeyRing
}
func generateSendingInfo(
@ -46,10 +46,10 @@ func generateSendingInfo(
isInternal bool,
composeMode string,
apiKeys,
contactKeys []*pmcrypto.KeyRing,
contactKeys []*crypto.KeyRing,
settingsSign bool,
settingsPgpScheme int) (sendingInfo SendingInfo, err error) {
contactKeys, err = pmcrypto.FilterExpiredKeys(contactKeys)
contactKeys, err = crypto.FilterExpiredKeys(contactKeys)
if err != nil {
return
}
@ -72,7 +72,7 @@ func generateInternalSendingInfo(
contactMeta *ContactMetadata,
composeMode string,
apiKeys,
contactKeys []*pmcrypto.KeyRing,
contactKeys []*crypto.KeyRing,
settingsSign bool, //nolint[unparam]
settingsPgpScheme int) (sendingInfo SendingInfo, err error) { //nolint[unparam]
// If sending internally, there should always be a public key; if not, there's an error.
@ -125,7 +125,7 @@ func generateExternalSendingInfo(
contactMeta *ContactMetadata,
composeMode string,
apiKeys,
contactKeys []*pmcrypto.KeyRing,
contactKeys []*crypto.KeyRing,
settingsSign bool,
settingsPgpScheme int) (sendingInfo SendingInfo, err error) {
// The default settings, unless overridden by presence of a saved contact.
@ -230,14 +230,27 @@ func schemeAndMIME(contact *ContactMetadata, settingsScheme int, settingsMIMETyp
// checkContactKeysAgainstAPI keeps only those contact keys which are up to date and have
// an ID that matches an API key's ID.
func checkContactKeysAgainstAPI(contactKeys, apiKeys []*pmcrypto.KeyRing) (filteredKeys []*pmcrypto.KeyRing, err error) { //nolint[unparam]
func checkContactKeysAgainstAPI(contactKeys, apiKeys []*crypto.KeyRing) (filteredKeys []*crypto.KeyRing, err error) { //nolint[unparam]
keyIDsAreEqual := func(a, b interface{}) bool {
aKey, bKey := a.(*pmcrypto.KeyRing), b.(*pmcrypto.KeyRing)
return aKey.GetEntities()[0].PrimaryKey.KeyId == bKey.GetEntities()[0].PrimaryKey.KeyId
aKey, bKey := a.(*crypto.KeyRing), b.(*crypto.KeyRing)
aFirst, getKeyErr := aKey.GetKey(0)
if getKeyErr != nil {
err = errors.New("missing primary key")
return false
}
bFirst, getKeyErr := bKey.GetKey(0)
if getKeyErr != nil {
err = errors.New("missing primary key")
return false
}
return aFirst.GetKeyID() == bFirst.GetKeyID()
}
for _, v := range algo.SetIntersection(contactKeys, apiKeys, keyIDsAreEqual) {
filteredKeys = append(filteredKeys, v.(*pmcrypto.KeyRing))
filteredKeys = append(filteredKeys, v.(*crypto.KeyRing))
}
return

View File

@ -18,15 +18,15 @@
package smtp
import (
"strings"
"testing"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
@ -46,8 +46,8 @@ func initMocks(t *testing.T) mocks {
type args struct {
eventListener listener.Listener
contactMeta *ContactMetadata
apiKeys []*pmcrypto.KeyRing
contactKeys []*pmcrypto.KeyRing
apiKeys []*crypto.KeyRing
contactKeys []*crypto.KeyRing
composeMode string
settingsPgpScheme int
settingsSign bool
@ -68,18 +68,61 @@ func (tt *testData) runTest(t *testing.T) {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, gotSendingInfo, tt.wantSendingInfo)
assert.Equal(t, gotSendingInfo.Encrypt, tt.wantSendingInfo.Encrypt)
assert.Equal(t, gotSendingInfo.Sign, tt.wantSendingInfo.Sign)
assert.Equal(t, gotSendingInfo.Scheme, tt.wantSendingInfo.Scheme)
assert.Equal(t, gotSendingInfo.MIMEType, tt.wantSendingInfo.MIMEType)
assert.True(t, keyRingsAreEqual(gotSendingInfo.PublicKey, tt.wantSendingInfo.PublicKey))
}
})
}
func keyRingFromKey(publicKey string) *crypto.KeyRing {
key, err := crypto.NewKeyFromArmored(publicKey)
if err != nil {
panic(err)
}
kr, err := crypto.NewKeyRing(key)
if err != nil {
panic(err)
}
return kr
}
func keyRingsAreEqual(a, b *crypto.KeyRing) bool {
if a == nil && b == nil {
return true
}
if a == nil && b != nil || a != nil && b == nil {
return false
}
aKeys, bKeys := a.GetKeys(), b.GetKeys()
if len(aKeys) != len(bKeys) {
return false
}
for i := range aKeys {
aFPs := aKeys[i].GetSHA256Fingerprints()
bFPs := bKeys[i].GetSHA256Fingerprints()
if !cmp.Equal(aFPs, bFPs) {
return false
}
}
return true
}
func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
m := initMocks(t)
pubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPublicKey))
if err != nil {
panic(err)
}
pubKey := keyRingFromKey(testPublicKey)
tests := []testData{
{
@ -88,8 +131,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -107,8 +150,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPInlinePackage,
},
@ -126,8 +169,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -145,8 +188,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPInlinePackage,
},
@ -164,8 +207,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: false,
settingsPgpScheme: pmapi.PGPInlinePackage,
},
@ -183,8 +226,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
eventListener: m.eventListener,
contactMeta: nil,
isInternal: true,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{pubKey},
},
wantSendingInfo: SendingInfo{},
wantErr: true,
@ -195,8 +238,8 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
contactMeta: nil,
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -217,20 +260,11 @@ func TestGenerateSendingInfo_WithoutContact(t *testing.T) {
func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
m := initMocks(t)
pubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPublicKey))
if err != nil {
panic(err)
}
pubKey := keyRingFromKey(testPublicKey)
preferredPubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPublicKey))
if err != nil {
panic(err)
}
preferredPubKey := keyRingFromKey(testPublicKey)
differentPubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testDifferentPublicKey))
if err != nil {
panic(err)
}
differentPubKey := keyRingFromKey(testDifferentPublicKey)
m.eventListener.EXPECT().Emit(events.NoActiveKeyForRecipientEvent, "badkey@email.com")
@ -241,8 +275,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -260,8 +294,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{pubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -279,8 +313,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{preferredPubKey},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{preferredPubKey},
contactKeys: []*crypto.KeyRing{pubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -299,8 +333,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Email: "badkey@email.com", Encrypt: true, Scheme: "pgp-mime"},
isInternal: true,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{differentPubKey},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{differentPubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -313,8 +347,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -332,8 +366,8 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{pubKey},
contactKeys: []*pmcrypto.KeyRing{differentPubKey},
apiKeys: []*crypto.KeyRing{pubKey},
contactKeys: []*crypto.KeyRing{differentPubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -352,15 +386,9 @@ func TestGenerateSendingInfo_Contact_Internal(t *testing.T) {
}
func TestGenerateSendingInfo_Contact_External(t *testing.T) {
pubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPublicKey))
if err != nil {
panic(err)
}
pubKey := keyRingFromKey(testPublicKey)
expiredPubKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testExpiredPublicKey))
if err != nil {
panic(err)
}
expiredPubKey := keyRingFromKey(testExpiredPublicKey)
tests := []testData{
{
@ -369,8 +397,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -388,8 +416,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{expiredPubKey},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{expiredPubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -407,8 +435,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-mime"},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{pubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -426,8 +454,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true, Scheme: "pgp-inline"},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{pubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -445,8 +473,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{Encrypt: true},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{pubKey},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{pubKey},
settingsSign: true,
settingsPgpScheme: pmapi.PGPMIMEPackage,
},
@ -464,8 +492,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPInlinePackage,
},
@ -483,8 +511,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{MIMEType: pmapi.ContentTypePlainText},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPInlinePackage,
},
@ -502,8 +530,8 @@ func TestGenerateSendingInfo_Contact_External(t *testing.T) {
contactMeta: &ContactMetadata{SignMissing: true},
isInternal: false,
composeMode: pmapi.ContentTypeHTML,
apiKeys: []*pmcrypto.KeyRing{},
contactKeys: []*pmcrypto.KeyRing{},
apiKeys: []*crypto.KeyRing{},
contactKeys: []*crypto.KeyRing{},
settingsSign: true,
settingsPgpScheme: pmapi.PGPInlinePackage,
},

View File

@ -20,13 +20,13 @@ package smtp
import (
"io"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
type storeUserProvider interface {
CreateDraft(
kr *pmcrypto.KeyRing,
kr *crypto.KeyRing,
message *pmapi.Message,
attachmentReaders []io.Reader,
attachedPublicKey,

View File

@ -20,7 +20,6 @@
package smtp
import (
"bytes"
"encoding/base64"
"fmt"
"io"
@ -32,7 +31,7 @@ import (
"strings"
"time"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/message"
@ -93,16 +92,26 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
err = errors.New("backend: invalid email address: not owned by user")
return
}
kr := addr.KeyRing()
kr, err := su.client().KeyRingForAddressID(addr.ID)
if err != nil {
return
}
var attachedPublicKey string
var attachedPublicKeyName string
if mailSettings.AttachPublicKey > 0 {
attachedPublicKey, err = kr.GetArmoredPublicKey()
firstKey, err := kr.GetKey(0)
if err != nil {
return err
}
attachedPublicKeyName = "publickey - " + kr.Identities()[0].Name
attachedPublicKey, err = firstKey.GetArmoredPublicKey()
if err != nil {
return err
}
attachedPublicKeyName = "publickey - " + kr.GetIdentities()[0].Name
}
message, mimeBody, plainBody, attReaders, err := message.Parse(messageReader, attachedPublicKey, attachedPublicKeyName)
@ -171,7 +180,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
atts = append(atts, message.Attachments...)
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
attkeys := make(map[string]*pmcrypto.SymmetricKey)
attkeys := make(map[string]*crypto.SessionKey)
attkeysEncoded := make(map[string]pmapi.AlgoKey)
for _, att := range atts {
@ -203,7 +212,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
// PMEL 3.
composeMode := message.MIMEType
var plainKey, htmlKey, mimeKey *pmcrypto.SymmetricKey
var plainKey, htmlKey, mimeKey *crypto.SessionKey
var plainData, htmlData, mimeData []byte
containsUnencryptedRecipients := false
@ -219,7 +228,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
return err
}
var contactMeta *ContactMetadata
var contactKeys []*pmcrypto.KeyRing
var contactKeyRings []*crypto.KeyRing
for _, contactEmail := range contactEmails {
if contactEmail.Defaults == 1 { // WARNING: in doc it says _ignore for now, future feature_
continue
@ -236,12 +245,19 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
if err != nil {
return err
}
contactKeyRing, err := crypto.NewKeyRing(nil)
if err != nil {
return err
}
for _, contactRawKey := range contactMeta.Keys {
contactKey, err := pmcrypto.ReadKeyRing(bytes.NewBufferString(contactRawKey))
contactKey, err := crypto.NewKeyFromArmored(contactRawKey)
if err != nil {
return err
}
contactKeys = append(contactKeys, contactKey)
if err := contactKeyRing.AddKey(contactKey); err != nil {
return err
}
contactKeyRings = append(contactKeyRings, contactKeyRing)
}
break // We take the first hit where Defaults == 0, see "How to find the right contact" of PMEL
@ -254,16 +270,22 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
return err
}
var apiKeys []*pmcrypto.KeyRing
var apiKeyRings []*crypto.KeyRing
for _, apiRawKey := range apiRawKeyList {
var kr *pmcrypto.KeyRing
if kr, err = pmcrypto.ReadArmoredKeyRing(strings.NewReader(apiRawKey.PublicKey)); err != nil {
key, err := crypto.NewKeyFromArmored(apiRawKey.PublicKey)
if err != nil {
return err
}
apiKeys = append(apiKeys, kr)
kr, err := crypto.NewKeyRing(key)
if err != nil {
return err
}
apiKeyRings = append(apiKeyRings, kr)
}
sendingInfo, err := generateSendingInfo(su.eventListener, contactMeta, isInternal, composeMode, apiKeys, contactKeys, settingsSign, settingsPgpScheme)
sendingInfo, err := generateSendingInfo(su.eventListener, contactMeta, isInternal, composeMode, apiKeyRings, contactKeyRings, settingsSign, settingsPgpScheme)
if !sendingInfo.Encrypt {
containsUnencryptedRecipients = true
}
@ -284,7 +306,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
}
}
if sendingInfo.Scheme == pmapi.PGPMIMEPackage {
mimeBodyPacket, _, err := createPackets(sendingInfo.PublicKey, mimeKey, map[string]*pmcrypto.SymmetricKey{})
mimeBodyPacket, _, err := createPackets(sendingInfo.PublicKey, mimeKey, map[string]*crypto.SessionKey{})
if err != nil {
return err
}

View File

@ -21,7 +21,7 @@ import (
"encoding/base64"
"regexp"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
@ -37,9 +37,9 @@ func looksLikeEmail(e string) bool {
}
func createPackets(
pubkey *pmcrypto.KeyRing,
bodyKey *pmcrypto.SymmetricKey,
attkeys map[string]*pmcrypto.SymmetricKey,
pubkey *crypto.KeyRing,
bodyKey *crypto.SessionKey,
attkeys map[string]*crypto.SessionKey,
) (bodyPacket string, attachmentPackets map[string]string, err error) {
// Encrypt message body keys.
packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
@ -61,24 +61,33 @@ func createPackets(
}
func encryptSymmetric(
kr *pmcrypto.KeyRing,
kr *crypto.KeyRing,
textToEncrypt string,
canonicalizeText bool, // nolint[unparam]
) (key *pmcrypto.SymmetricKey, symEncryptedData []byte, err error) {
) (key *crypto.SessionKey, symEncryptedData []byte, err error) {
// We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
pgpMessage, err := kr.FirstKey().Encrypt(pmcrypto.NewPlainMessageFromString(textToEncrypt), kr)
firstKey, err := kr.FirstKey()
if err != nil {
return
}
pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
if err != nil {
return
}
pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
if err != nil {
return
}
key, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
if err != nil {
return
}
symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
return
}
@ -87,7 +96,7 @@ func buildPackage(
sharedScheme int,
mimeType string,
bodyData []byte,
bodyKey *pmcrypto.SymmetricKey,
bodyKey *crypto.SessionKey,
attKeys map[string]pmapi.AlgoKey,
) (pkg *pmapi.MessagePackage) {
if len(addressMap) == 0 {

View File

@ -26,7 +26,7 @@ import (
"net/textproto"
"strings"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -37,7 +37,7 @@ import (
// If `attachedPublicKey` is passed, it's added to attachments.
// Both draft and attachments are encrypted with passed `kr` key.
func (store *Store) CreateDraft(
kr *pmcrypto.KeyRing,
kr *crypto.KeyRing,
message *pmapi.Message,
attachmentReaders []io.Reader,
attachedPublicKey,
@ -92,7 +92,7 @@ func (store *Store) getDraftAction(message *pmapi.Message) int {
return pmapi.DraftActionReply
}
func (store *Store) createAttachment(kr *pmcrypto.KeyRing, attachment *pmapi.Attachment, attachmentBody []byte) (*pmapi.Attachment, error) {
func (store *Store) createAttachment(kr *crypto.KeyRing, attachment *pmapi.Attachment, attachmentBody []byte) (*pmapi.Attachment, error) {
r := bytes.NewReader(attachmentBody)
sigReader, err := attachment.DetachedSign(kr, r)
if err != nil {

View File

@ -53,9 +53,6 @@ type User struct {
lock sync.RWMutex
isAuthorized bool
unlockingKeyringLock sync.Mutex
wasKeyringUnlocked bool
}
// newUser creates a new user.
@ -99,10 +96,6 @@ func (u *User) client() pmapi.Client {
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
// something in the store changed).
func (u *User) init(idleUpdates chan imapBackend.Update) (err error) {
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
u.log.Info("Initialising user")
// Reload the user's credentials (if they log out and back in we need the new
@ -197,22 +190,14 @@ func (u *User) authorizeIfNecessary(emitEvent bool) (err error) {
// unlockIfNecessary will not trigger keyring unlocking if it was already successfully unlocked.
func (u *User) unlockIfNecessary() error {
u.unlockingKeyringLock.Lock()
defer u.unlockingKeyringLock.Unlock()
if u.wasKeyringUnlocked {
if u.client().IsUnlocked() {
return nil
}
if _, err := u.client().Unlock(u.creds.MailboxPassword); err != nil {
if err := u.client().Unlock([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
u.wasKeyringUnlocked = true
return nil
}
@ -228,14 +213,10 @@ func (u *User) authorizeAndUnlock() (err error) {
return errors.Wrap(err, "failed to refresh API auth")
}
if _, err = u.client().Unlock(u.creds.MailboxPassword); err != nil {
if err := u.client().Unlock([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err = u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
return nil
}
@ -432,12 +413,8 @@ func (u *User) UpdateUser() error {
return err
}
if _, err = u.client().Unlock(u.creds.MailboxPassword); err != nil {
return err
}
if err := u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return err
if err = u.client().Unlock([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
emails := u.client().Addresses().ActiveEmails()
@ -514,10 +491,6 @@ func (u *User) Logout() (err error) {
return
}
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
u.client().Logout()
if err = u.credStorer.Logout(u.userID); err != nil {

View File

@ -35,12 +35,11 @@ func TestUpdateUser(t *testing.T) {
defer cleanUpUserData(user)
gomock.InOrder(
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().UpdateUser().Return(nil, nil),
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}),
@ -155,8 +154,8 @@ func TestCheckBridgeLoginOK(t *testing.T) {
defer cleanUpUserData(user)
gomock.InOrder(
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
)
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
@ -166,6 +165,28 @@ func TestCheckBridgeLoginOK(t *testing.T) {
assert.NoError(t, err)
}
func TestCheckBridgeLoginTwiceOK(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
gomock.InOrder(
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().IsUnlocked().Return(true),
)
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
waitForEvents()
assert.NoError(t, err)
err = user.CheckBridgeLogin(testCredentials.BridgePassword)
waitForEvents()
assert.NoError(t, err)
}
func TestCheckBridgeLoginUpgradeApplication(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
@ -220,8 +241,8 @@ func TestCheckBridgeLoginBadPassword(t *testing.T) {
defer cleanUpUserData(user)
gomock.InOrder(
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
)
err := user.CheckBridgeLogin("wrong!")

View File

@ -114,33 +114,7 @@ func TestNewUserUnlockFails(t *testing.T) {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, errors.New("bad password")),
m.credentialsStore.EXPECT().Logout("user").Return(nil),
m.pmapiClient.EXPECT().Logout(),
m.credentialsStore.EXPECT().Logout("user").Return(nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
)
checkNewUserHasCredentials(testCredentialsDisconnected, m)
}
func TestNewUserUnlockAddressesFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
gomock.InOrder(
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(errors.New("bad password")),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(errors.New("bad password")),
m.credentialsStore.EXPECT().Logout("user").Return(nil),
m.pmapiClient.EXPECT().Logout(),
m.credentialsStore.EXPECT().Logout("user").Return(nil),

View File

@ -182,17 +182,8 @@ func (u *Users) closeAllConnections() {
}
}
// Login authenticates a user.
// The login flow:
// * Authenticate user:
// client, auth, err := users.Authenticate(username, password)
//
// * In case user `auth.HasTwoFactor()`, ask for it and fully authenticate the user.
// auth2FA, err := client.Auth2FA(twoFactorCode)
//
// * In case user `auth.HasMailboxPassword()`, ask for it, otherwise use `password`
// and then finish the login procedure.
// user, err := users.FinishLogin(client, auth, mailboxPassword)
// Login authenticates a user by username/password, returning an authorised client and an auth object.
// The authorisation scope may not yet be full if the user has 2FA enabled.
func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
u.crashBandicoot(username)
@ -214,7 +205,6 @@ func (u *Users) Login(username, password string) (authClient pmapi.Client, auth
}
// FinishLogin finishes the login procedure and adds the user into the credentials store.
// See `Login` for more details of the login flow.
func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *User, err error) { //nolint[funlen]
defer func() {
if err == pmapi.ErrUpgradeApplication {
@ -230,20 +220,22 @@ func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPasswor
authClient.Logout()
}()
apiUser, hashedPassword, err := getAPIUser(authClient, auth, mbPassword)
apiUser, passphrase, err := getAPIUser(authClient, mbPassword)
if err != nil {
log.WithError(err).Error("Failed to get API user")
return
}
log.Info("Got API user")
var ok bool
if user, ok = u.hasUser(apiUser.ID); ok {
if err = u.connectExistingUser(user, auth, hashedPassword); err != nil {
if err = u.connectExistingUser(user, auth, passphrase); err != nil {
log.WithError(err).Error("Failed to connect existing user")
return
}
} else {
if err = u.addNewUser(apiUser, auth, hashedPassword); err != nil {
if err = u.addNewUser(apiUser, auth, passphrase); err != nil {
log.WithError(err).Error("Failed to add new user")
return
}
@ -255,13 +247,15 @@ func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPasswor
}
// connectExistingUser connects an existing user.
func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassword string) (err error) {
func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, passphrase string) (err error) {
if user.IsConnected() {
return errors.New("user is already connected")
}
log.Info("Connecting existing user")
// Update the user's password in the cred store in case they changed it.
if err = u.credStorer.UpdatePassword(user.ID(), hashedPassword); err != nil {
if err = u.credStorer.UpdatePassword(user.ID(), passphrase); err != nil {
return errors.Wrap(err, "failed to update password of user in credentials store")
}
@ -283,7 +277,7 @@ func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassword
}
// addNewUser adds a new user.
func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassword string) (err error) {
func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, passphrase string) (err error) {
u.lock.Lock()
defer u.lock.Unlock()
@ -299,7 +293,7 @@ func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassword
activeEmails := client.Addresses().ActiveEmails()
if _, err = u.credStorer.Add(apiUser.ID, apiUser.Name, auth.GenToken(), hashedPassword, activeEmails); err != nil {
if _, err = u.credStorer.Add(apiUser.ID, apiUser.Name, auth.GenToken(), passphrase, activeEmails); err != nil {
return errors.Wrap(err, "failed to add user to credentials store")
}
@ -321,21 +315,27 @@ func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassword
return err
}
func getAPIUser(client pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *pmapi.User, hashedPassword string, err error) {
hashedPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt)
func getAPIUser(client pmapi.Client, mbPassword string) (user *pmapi.User, passphrase string, err error) {
salt, err := client.AuthSalt()
if err != nil {
log.WithError(err).Error("Could not get salt")
return
}
passphrase, err = pmapi.HashMailboxPassword(mbPassword, salt)
if err != nil {
log.WithError(err).Error("Could not hash mailbox password")
return
}
// We unlock the user's PGP key here to detect if the user's mailbox password is wrong.
if _, err = client.Unlock(hashedPassword); err != nil {
if err = client.Unlock([]byte(passphrase)); err != nil {
log.WithError(err).Error("Wrong mailbox password")
return
}
if user, err = client.CurrentUser(); err != nil {
log.WithError(err).Error("Could not load API user")
log.WithError(err).Error("Could not load user data")
return
}

View File

@ -39,7 +39,8 @@ func TestUsersFinishLoginBadMailboxPassword(t *testing.T) {
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
// Set up mocks for FinishLogin.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, err),
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(err),
m.pmapiClient.EXPECT().DeleteAuth(),
m.pmapiClient.EXPECT().Logout(),
)
@ -57,7 +58,8 @@ func TestUsersFinishLoginUpgradeApplication(t *testing.T) {
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
// Set up mocks for FinishLogin.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, pmapi.ErrUpgradeApplication),
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(pmapi.ErrUpgradeApplication),
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, ""),
m.pmapiClient.EXPECT().DeleteAuth().Return(err),
@ -70,7 +72,6 @@ func TestUsersFinishLoginUpgradeApplication(t *testing.T) {
func refreshWithToken(token string) *pmapi.Auth {
return &pmapi.Auth{
RefreshToken: token,
KeySalt: "", // No salting in tests.
}
}
@ -93,7 +94,8 @@ func TestUsersFinishLoginNewUser(t *testing.T) {
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
// getAPIUser() loads user info from API (e.g. userID).
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
// addNewUser()
@ -106,8 +108,7 @@ func TestUsersFinishLoginNewUser(t *testing.T) {
// user.init() in addNewUser
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil),
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil),
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
// store.New() in user.init
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
@ -158,7 +159,8 @@ func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
m.pmapiClient.EXPECT().Addresses().Return(nil),
// getAPIUser() loads user info from API (e.g. userID).
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
// connectExistingUser()
@ -169,8 +171,7 @@ func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
// user.init() in connectExistingUser
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil),
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil),
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
// store.New() in user.init
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
@ -206,7 +207,8 @@ func TestUsersFinishLoginConnectedUser(t *testing.T) {
// Then, try to log in again...
gomock.InOrder(
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
m.pmapiClient.EXPECT().DeleteAuth(),
m.pmapiClient.EXPECT().Logout(),

View File

@ -93,8 +93,7 @@ func mockConnectedUser(m mocks) {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
// Set up mocks for store initialisation for the authorized user.
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),

View File

@ -49,11 +49,9 @@ func TestMain(m *testing.M) {
var (
testAuth = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "tok",
KeySalt: "", // No salting in tests.
}
testAuthRefresh = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "reftok",
KeySalt: "", // No salting in tests.
}
testCredentials = &credentials.Credentials{ //nolint[gochecknoglobals]
@ -209,8 +207,7 @@ func testNewUsersWithUsers(t *testing.T, m mocks) *Users {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
@ -219,8 +216,7 @@ func testNewUsersWithUsers(t *testing.T, m mocks) *Users {
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil),
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil),
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil),
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses),