mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
feat: migrate to gopenpgp v2
This commit is contained in:
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
|
||||
2
test/testdata/user_key.json
vendored
2
test/testdata/user_key.json
vendored
@ -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\nxcMGBFRJbc0BCAC0mMLZPDBbtSCWvxwmOfXfJkE2+ssM3ux21LhD/bPiWefE\nWSHlCjJ8PqPHy7snSiUuxuj3f9AvXPvg+mjGLBwu1/QsnSP24sl3qD2onl39\nvPiLJXUqZs20ZRgnvX70gjkgEzMFBxINiy2MTIG+4RU8QA7y8KzWev0btqKi\nMeVa+GLEHhgZ2KPOn4Jv1q4bI9hV0C9NUe2tTXS6/Vv3vbCY7lRR0kbJ65T5\nc8CmpqJuASIJNrSXM/Q3NnnsY4kBYH0s5d2FgbASQvzrjuC2rngUg0EoPsrb\nDEVRA2/BCJonw7aASiNCrSP92lkZdtYlax/pcoE/mQ4WSwySFmcFT7yFABEB\nAAH+CQMIvzcDReuJkc9gnxAkfgmnkBFwRQrqT/4UAPOF8WGVo0uNvDo7Snlk\nqWsJS+54+/Xx6Jur/PdBWeEu+6+6GnppYuvsaT0D0nFdFhF6pjng+02IOxfG\nqlYXYcW4hRru3BfvJlSvU2LL/Z/ooBnw3T5vqd0eFHKrvabUuwf0x3+K/sru\nFp24rl2PU+bzQlUgKpWzKDmO+0RdKQ6KVCyCDMIXaAkALwNffAvYxI0wnb2y\nWAV/bGn1ODnszOYPk3pEMR6kKSxLLaO69kYx4eTERFyJ+1puAxEPCk3Cfeif\nyDWi4rU03YB16XH7hQLSFl61SKeIYlkKmkO5Hk1ybi/BhvOGBPVeGGbxWnwI\n46G8DfBHW0+uvD5cAQtk2d/q3Ge1I+DIyvuRCcSu0XSBNv/Bkpp4IbAUPBaW\nTIvf5p9oxw+AjrMtTtcdSiee1S6CvMMaHhVD7SI6qGA8GqwaXueeLuEXa0Ok\nBWlehx8wibMi4a9fLcQZtzJkmGhR1WzXcJfiEg32srILwIzPQYxuFdZZ2elb\ngYp/bMEIp4LKhi43IyM6peCDHDzEba8NuOSd0heEqFIm0vlXujMhkyMUvDBv\nH0V5On4aMuw/aSEKcAdbazppOru/W1ndyFa5ZHQIC19g72ZaDVyYjPyvNgOV\nAFqO4o3IbC5z31zMlTtMbAq2RG9svwUVejn0tmF6UPluTe0U1NuXFpLK6TCH\nwqocLz4ecptfJQulpYjClVLgzaYGDuKwQpIwPWg5G/DtKSCGNtEkfqB3aemH\nV5xmoYm1v5CQZAEvvsrLA6jxCk9lzqYV8QMivWNXUG+mneIEM35G0HOPzXca\nLLyB+N8Zxioc9DPGfdbcxXuVgOKRepbkq4xv1pUpMQ4BUmlkejDRSP+5SIR3\niEthg+FU6GRSQbORE6nhrKjGBk8fpNpozQZVc2VySUTCwHIEEAEIACYFAlRJ\nbc8GCwkIBwMCCRA+tiWe3yHfJAQVCAIKAxYCAQIbAwIeAQAA9J0H/RLR/Uwt\nCakrPKtfeGaNuOI45SRTNxM8TklC6tM28sJSzkX8qKPzvI1PxyLhs/i0/fCQ\n7Z5bU6n41oLuqUt2S9vy+ABlChKAeziOqCHUcMzHOtbKiPkKW88aO687nx+A\nol2XOnMTkVIC+edMUgnKp6tKtZnbO4ea6Cg88TFuli4hLHNXTfCECswuxHOc\nAO1OKDRrCd08iPI5CLNCIV60QnduitE1vF6ehgrH25Vl6LEdd8vPVlTYAvsa\n6ySk2RIrHNLUZZ3iII3MBFL8HyINp/XA1BQP+QbH801uSLq8agxM4iFT9C+O\nD147SawUGhjD5RG7T+YtqItzgA1V9l277EXHwwYEVEltzwEIAJD57uX6bOc4\nTgf3utfL/4hdyoqIMVHkYQOvE27wPsZxX08QsdlaNeGji9Ap2ifIDuckUqn6\nJi9jtZDKtOzdTBm6rnG5nPmkn6BJXPhnecQRP8N0XBISnAGmE4t+bxtts5Wb\nqeMdxJYqMiGqzrLBRJEIDTcg3+QF2Y3RywOqlcXqgG/xX++PsvR1Jiz0rEVP\nTcBc7ytyb/Av7mx1S802HRYGJHOFtVLoPTrtPCvv+DRDK8JzxQW2XSQLlI0M\n9s1tmYhCogYIIqKx9qOTd5mFJ1hJlL6i9xDkvE21qPFASFtww5tiYmUfFaxI\nLwbXPZlQ1I/8fuaUdOxctQ+g40ZgHPcAEQEAAf4JAwgdUg8ubE2BT2DITBD+\nXFgjrnUlQBilbN8/do/36KHuImSPO/GGLzKh4+oXxrvLc5fQLjeO+bzeen4u\nCOCBRO0hG7KpJPhQ6+T02uEF6LegE1sEz5hp6BpKUdPZ1+8799Rylb5kubC5\nIKnLqqpGDbH3hIsmSV3CG/ESkaGMLc/K0ZPt1JRWtUQ9GesXT0v6fdM5GB/L\ncZWFdDoYgZAw5BtymE44knIodfDAYJ4DHnPCh/oilWe1qVTQcNMdtkpBgkuo\nTHecqEmiODQz5EX8pVmS596XsnPO299Lo3TbaHUQo7EC6Au1Au9+b5hC1pDa\nFVCLcproi/Cgch0B/NOCFkVLYmp6BEljRj2dSZRWbO0vgl9kFmJEeiiH41+k\nEAI6PASSKZs3BYLFc2I8mBkcvt90kg4MTBjreuk0uWf1hdH2Rv8zprH4h5Uh\ngjx5nUDX8WXyeLxTU5EBKry+A2DIe0Gm0/waxp6lBlUl+7ra28KYEoHm8Nq/\nN9FCuEhFkFgw6EwUp7jsrFcqBKvmni6jyplm+mJXi3CK+IiNcqub4XPnBI97\nlR19fupB/Y6M7yEaxIM8fTQXmP+x/fe8zRphdo+7o+pJQ3hk5LrrNPK8GEZ6\nDLDOHjZzROhOgBvWtbxRktHk+f5YpuQL+xWd33IV1xYSSHuoAm0Zwt0QJxBs\noFBwJEq1NWM4FxXJBogvzV7KFhl/hXgtvx+GaMv3y8gucj+gE89xVv0XBXjl\n5dy5/PgCI0Id+KAFHyKpJA0N0h8O4xdJoNyIBAwDZ8LHt0vlnLGwcJFR9X7/\nPfWe0PFtC3d7cYY3RopDhnRP7MZs1Wo9nZ4IvlXoEsE2nPkWcns+Wv5Yaewr\ns2ra9ZIK7IIJhqKKgmQtCeiXyFwTq+kfunDnxeCavuWL3HuLKIOZf7P9vXXt\nXgEir9rCwF8EGAEIABMFAlRJbdIJED62JZ7fId8kAhsMAAD+LAf+KT1EpkwH\n0ivTHmYako+6qG6DCtzd3TibWw51cmbY20Ph13NIS/MfBo828S9SXm/sVUzN\n/r7qZgZYfI0/j57tG3BguVGm53qya4bINKyi1RjK6aKo/rrzRkh5ZVD5rVNO\nE2zzvyYAnLUWG9AV1OYDxcgLrXqEMWlqZAo+Wmg7VrTBmdCGs/BPvscNgQRr\n6Gpjgmv9ru6LjRL7vFhEcov/tkBLj+CtaWWFTd1s2vBLOs4rCsD9TT/23vfw\nCnokvvVjKYN5oviy61yhpqF1rWlOsxZ4+2sKW3Pq7JLBtmzsZegTONfcQAf7\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
|
||||
|
||||
Reference in New Issue
Block a user