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

@ -29,9 +29,8 @@ import (
)
const (
testUserKey = "user_key.json"
testAddressKey = "address_key.json"
testKeyPassphrase = "testpassphrase"
testUserKey = "user_key.json"
testAddressKey = "address_key.json"
)
type TestAccount struct {
@ -78,14 +77,9 @@ func newTestAccount(
}
func (a *TestAccount) initKeys() {
if a.user.Keys.Keys != nil {
return
}
userKeys := loadPMKeys(readTestFile(testUserKey))
_ = userKeys.KeyRing.Unlock([]byte(testKeyPassphrase))
addressKeys := loadPMKeys(readTestFile(testAddressKey))
_ = addressKeys.KeyRing.Unlock([]byte(testKeyPassphrase))
a.user.Keys = *userKeys
for _, addressEmail := range a.Addresses().ActiveEmails() {

View File

@ -27,7 +27,8 @@
"ID": "userAddress",
"Email": "user@pm.me",
"Order": 1,
"Receive": 1
"Receive": 1,
"HasKeys": 1
}
},
"user2fa": {
@ -35,7 +36,8 @@
"ID": "user2faAddress",
"Email": "user@pm.me",
"Order": 1,
"Receive": 1
"Receive": 1,
"HasKeys": 1
}
},
"userAddressWithCapitalLetter": {
@ -43,7 +45,8 @@
"ID": "userAddressWithCapitalLetterAddress",
"Email": "uSeR@pm.me",
"Order": 1,
"Receive": 1
"Receive": 1,
"HasKeys": 1
}
},
"userMoreAddresses": {
@ -51,13 +54,15 @@
"ID": "primary",
"Email": "primaryaddress@pm.me",
"Order": 1,
"Receive": 1
"Receive": 1,
"HasKeys": 1
},
"secondary": {
"ID": "secondary",
"Email": "secondaryaddress@pm.me",
"Order": 2,
"Receive": 1
"Receive": 1,
"HasKeys": 1
},
"disabled": {
"ID": "disabled",
@ -75,9 +80,10 @@
},
"secondary": {
"ID": "secondary",
"Email": "user@pm.me",
"Email": "secondaryaddress@pm.me",
"Order": 2,
"Receive": 1
"Receive": 1,
"HasKeys": 1
}
}
},
@ -89,11 +95,11 @@
"userDisabledPrimaryAddress": "password"
},
"mailboxPasswords": {
"user": "password",
"user2fa": "password",
"userAddressWithCapitalLetter": "password",
"userMoreAddresses": "password",
"userDisabledPrimaryAddress": "password"
"user": "testpassphrase",
"user2fa": "testpassphrase",
"userAddressWithCapitalLetter": "testpassphrase",
"userMoreAddresses": "testpassphrase",
"userDisabledPrimaryAddress": "testpassphrase"
},
"twoFAs": {
"user": false,
@ -102,4 +108,4 @@
"userMoreAddresses": false,
"userDisabledPrimaryAddress": false
}
}
}

View File

@ -147,6 +147,14 @@ func (api *FakePMAPI) AuthRefresh(token string) (*pmapi.Auth, error) {
return auth, nil
}
func (api *FakePMAPI) AuthSalt() (string, error) {
if err := api.checkInternetAndRecordCall(GET, "/keys/salts", nil); err != nil {
return "", err
}
return "", nil
}
func (api *FakePMAPI) Logout() {
api.controller.clientManager.LogoutClient(api.userID)
}
@ -164,5 +172,17 @@ func (api *FakePMAPI) DeleteAuth() error {
}
func (api *FakePMAPI) ClearData() {
if api.userKeyRing != nil {
api.userKeyRing.ClearPrivateParams()
api.userKeyRing = nil
}
for addrID, addr := range api.addrKeyRing {
if addr != nil {
addr.ClearPrivateParams()
delete(api.addrKeyRing, addrID)
}
}
api.unsetUser()
}

View File

@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/sirupsen/logrus"
)
@ -33,12 +34,14 @@ type FakePMAPI struct {
controller *Controller
eventIDGenerator idGenerator
auths chan<- *pmapi.Auth
user *pmapi.User
addresses *pmapi.AddressList
labels []*pmapi.Label
messages []*pmapi.Message
events []*pmapi.Event
auths chan<- *pmapi.Auth
user *pmapi.User
userKeyRing *crypto.KeyRing
addresses *pmapi.AddressList
addrKeyRing map[string]*crypto.KeyRing
labels []*pmapi.Label
messages []*pmapi.Message
events []*pmapi.Event
// uid represents the API UID. It is the unique session ID.
uid, lastToken string
@ -48,9 +51,10 @@ type FakePMAPI struct {
func New(controller *Controller, userID string) *FakePMAPI {
fakePMAPI := &FakePMAPI{
controller: controller,
log: logrus.WithField("pkg", "fakeapi"),
userID: userID,
controller: controller,
log: logrus.WithField("pkg", "fakeapi"),
userID: userID,
addrKeyRing: make(map[string]*crypto.KeyRing),
}
fakePMAPI.addEvent(&pmapi.Event{

View File

@ -18,7 +18,7 @@
package fakeapi
import (
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
@ -29,13 +29,37 @@ func (api *FakePMAPI) GetMailSettings() (pmapi.MailSettings, error) {
return pmapi.MailSettings{}, nil
}
func (api *FakePMAPI) Unlock(mailboxPassword string) (*pmcrypto.KeyRing, error) {
return &pmcrypto.KeyRing{
FirstKeyID: "key",
}, nil
func (api *FakePMAPI) IsUnlocked() bool {
return api.userKeyRing != nil
}
func (api *FakePMAPI) UnlockAddresses(password []byte) error {
func (api *FakePMAPI) Unlock(passphrase []byte) (err error) {
if api.userKeyRing != nil {
return
}
if api.userKeyRing, err = api.user.Keys.UnlockAll(passphrase, nil); err != nil {
return
}
for _, a := range *api.addresses {
if a.HasKeys == pmapi.MissingKeys {
continue
}
if api.addrKeyRing[a.ID] != nil {
continue
}
var kr *crypto.KeyRing
if kr, err = a.Keys.UnlockAll(passphrase, api.userKeyRing); err != nil {
return
}
api.addrKeyRing[a.ID] = kr
}
return nil
}
@ -47,6 +71,7 @@ func (api *FakePMAPI) UpdateUser() (*pmapi.User, error) {
if err := api.checkAndRecordCall(GET, "/users", nil); err != nil {
return nil, err
}
return api.user, nil
}
@ -84,8 +109,6 @@ func (api *FakePMAPI) Addresses() pmapi.AddressList {
return *api.addresses
}
func (api *FakePMAPI) KeyRingForAddressID(addrID string) (*pmcrypto.KeyRing, error) {
return &pmcrypto.KeyRing{
FirstKeyID: "key",
}, nil
func (api *FakePMAPI) KeyRingForAddressID(addrID string) (*crypto.KeyRing, error) {
return api.addrKeyRing[addrID], nil
}

View File

@ -71,7 +71,7 @@ Feature: IMAP auth
@ignore-live
Scenario: Authenticates with disabled primary address
Given there is connected user "userDisabledPrimaryAddress"
When IMAP client authenticates "userDisabledPrimaryAddress" with address "primary"
When IMAP client authenticates "userDisabledPrimaryAddress" with address "disabled"
Then IMAP response is "OK"
Scenario: Authenticates two users

View File

@ -48,7 +48,7 @@ Feature: SMTP auth
@ignore-live
Scenario: Authenticates with disabled primary address
Given there is connected user "userDisabledPrimaryAddress"
When SMTP client authenticates "userDisabledPrimaryAddress" with address "primary"
When SMTP client authenticates "userDisabledPrimaryAddress" with address "secondary"
Then SMTP response is "OK"
Scenario: Authenticates two users

View File

@ -80,11 +80,10 @@ func buildMessage(client pmapi.Client, message *pmapi.Message) (*bytes.Buffer, e
}
func encryptMessage(client pmapi.Client, message *pmapi.Message) error {
addresses, err := client.GetAddresses()
kr, err := client.KeyRingForAddressID(message.AddressID)
if err != nil {
return errors.Wrap(err, "failed to get address")
return errors.Wrap(err, "failed to get keyring for address")
}
kr := addresses.ByID(message.AddressID).KeyRing()
if err = message.Encrypt(kr, nil); err != nil {
return errors.Wrap(err, "failed to encrypt message body")

View File

@ -34,21 +34,25 @@ func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, p
if err != nil {
return errors.Wrap(err, "failed to get auth info")
}
auth, err := client.Auth(user.Name, password, authInfo)
_, err = client.Auth(user.Name, password, authInfo)
if err != nil {
return errors.Wrap(err, "failed to auth user")
}
mailboxPassword, err := pmapi.HashMailboxPassword(password, auth.KeySalt)
salt, err := client.AuthSalt()
if err != nil {
return errors.Wrap(err, "failed to get salt")
}
mailboxPassword, err := pmapi.HashMailboxPassword(password, salt)
if err != nil {
return errors.Wrap(err, "failed to hash mailbox password")
}
if _, err := client.Unlock(mailboxPassword); err != nil {
if err := client.Unlock([]byte(mailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := client.UnlockAddresses([]byte(mailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock addresses")
}
if err := cleanup(client, addresses); err != nil {
return errors.Wrap(err, "failed to clean user")

View File

@ -2,7 +2,7 @@
{
"ID": "IlnTbqicN-2HfUGIn-ki8bqZfLqNj5ErUB0z24Qx5g-4NvrrIc6GLvEpj2EPfwGDv28aKYVRRrSgEFhR_zhlkA==",
"Version": 3,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: OpenPGP.js v0.7.1\r\nComment: http://openpgpjs.org\r\n\r\n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\nqqGRQm3MxoTdgQUShAwbNwNNQR9cInfMnA==\r\n=2wIY\r\n-----END PGP PRIVATE KEY BLOCK-----\r\n",
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n\r\n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\r\na7RmMPBR30nWAMrvteun5z2n8rizgEZHcmE=\r\n=2e5K\r\n-----END PGP PRIVATE KEY BLOCK-----",
"Fingerprint": "c93f767df53b0ca8395cfde90483475164ec6353",
"Activation": null,
"Primary": 1