forked from Silverfish/proton-bridge
feat: migrate to gopenpgp v2
This commit is contained in:
@ -19,10 +19,9 @@ package pmapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
// Address statuses.
|
||||
@ -86,11 +85,6 @@ type AddressesRes struct {
|
||||
Addresses AddressList
|
||||
}
|
||||
|
||||
// KeyRing returns the (possibly unlocked) PMKeys KeyRing.
|
||||
func (a *Address) KeyRing() *pmcrypto.KeyRing {
|
||||
return a.Keys.KeyRing
|
||||
}
|
||||
|
||||
// ByID returns an address by id. Returns nil if no address is found.
|
||||
func (l AddressList) ByID(id string) *Address {
|
||||
for _, addr := range l {
|
||||
@ -202,31 +196,33 @@ func (c *client) Addresses() AddressList {
|
||||
return c.addresses
|
||||
}
|
||||
|
||||
// UnlockAddresses unlocks all keys for all addresses of current user.
|
||||
func (c *client) UnlockAddresses(passphrase []byte) (err error) {
|
||||
// unlockAddresses unlocks all keys for all addresses of current user.
|
||||
func (c *client) unlockAddresses(passphrase []byte) (err error) {
|
||||
for _, a := range c.addresses {
|
||||
if a.HasKeys == MissingKeys {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unlock the address token using the UserKey, use the unlocked token to unlock the keyring.
|
||||
if err = a.Keys.unlockKeyRing(c.kr, passphrase, c.keyLocker); err != nil {
|
||||
err = fmt.Errorf("pmapi: cannot unlock private key of address %v: %v", a.Email, err)
|
||||
if c.addrKeyRing[a.ID] != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var kr *crypto.KeyRing
|
||||
|
||||
if kr, err = a.Keys.UnlockAll(passphrase, c.userKeyRing); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.addrKeyRing[a.ID] = kr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *client) KeyRingForAddressID(addrID string) (*pmcrypto.KeyRing, error) {
|
||||
if addr := c.addresses.ByID(addrID); addr != nil {
|
||||
return addr.KeyRing(), nil
|
||||
func (c *client) KeyRingForAddressID(addrID string) (*crypto.KeyRing, error) {
|
||||
if kr, ok := c.addrKeyRing[addrID]; ok {
|
||||
return kr, nil
|
||||
}
|
||||
|
||||
if addr := c.addresses.Main(); addr != nil {
|
||||
return addr.KeyRing(), nil
|
||||
}
|
||||
|
||||
return nil, errors.New("no such address ID")
|
||||
return nil, errors.New("no keyring available")
|
||||
}
|
||||
|
||||
@ -52,12 +52,6 @@ func routeGetAddresses(tb testing.TB, w http.ResponseWriter, r *http.Request) st
|
||||
return "addresses/get_response.json"
|
||||
}
|
||||
|
||||
func routeGetSalts(tb testing.TB, w http.ResponseWriter, r *http.Request) string {
|
||||
Ok(tb, checkMethodAndPath(r, "GET", "/keys/salts"))
|
||||
Ok(tb, isAuthReq(r, testUID, testAccessToken))
|
||||
return "keys/salts/get_response.json"
|
||||
}
|
||||
|
||||
func TestAddressList(t *testing.T) {
|
||||
input := "1"
|
||||
addr := testAddressList.ByID(input)
|
||||
|
||||
@ -26,7 +26,7 @@ import (
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
type header textproto.MIMEHeader
|
||||
@ -114,7 +114,7 @@ func (a *Attachment) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
// Decrypt decrypts this attachment's data from r using the keys from kr.
|
||||
func (a *Attachment) Decrypt(r io.Reader, kr *pmcrypto.KeyRing) (decrypted io.Reader, err error) {
|
||||
func (a *Attachment) Decrypt(r io.Reader, kr *crypto.KeyRing) (decrypted io.Reader, err error) {
|
||||
keyPackets, err := base64.StdEncoding.DecodeString(a.KeyPackets)
|
||||
if err != nil {
|
||||
return
|
||||
@ -123,11 +123,11 @@ func (a *Attachment) Decrypt(r io.Reader, kr *pmcrypto.KeyRing) (decrypted io.Re
|
||||
}
|
||||
|
||||
// Encrypt encrypts an attachment.
|
||||
func (a *Attachment) Encrypt(kr *pmcrypto.KeyRing, att io.Reader) (encrypted io.Reader, err error) {
|
||||
func (a *Attachment) Encrypt(kr *crypto.KeyRing, att io.Reader) (encrypted io.Reader, err error) {
|
||||
return encryptAttachment(kr, att, a.Name)
|
||||
}
|
||||
|
||||
func (a *Attachment) DetachedSign(kr *pmcrypto.KeyRing, att io.Reader) (signed io.Reader, err error) {
|
||||
func (a *Attachment) DetachedSign(kr *crypto.KeyRing, att io.Reader) (signed io.Reader, err error) {
|
||||
return signAttachment(kr, att)
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
||||
)
|
||||
|
||||
@ -112,7 +111,6 @@ type Auth struct {
|
||||
Scope string
|
||||
uid string // Read from AuthRes.
|
||||
RefreshToken string
|
||||
KeySalt string
|
||||
EventID string
|
||||
PasswordMode int
|
||||
TwoFA *TwoFactorInfo `json:"2FA,omitempty"`
|
||||
@ -145,10 +143,6 @@ func (s *Auth) HasMailboxPassword() bool {
|
||||
return s.PasswordMode == 2
|
||||
}
|
||||
|
||||
func (s *Auth) hasFullScope() bool {
|
||||
return strings.Contains(s.Scope, "full")
|
||||
}
|
||||
|
||||
type AuthRes struct {
|
||||
Res
|
||||
Auth
|
||||
@ -327,15 +321,6 @@ func (c *client) Auth(username, password string, info *AuthInfo) (auth *Auth, er
|
||||
auth = authRes.getAuth()
|
||||
c.sendAuth(auth)
|
||||
|
||||
// Auth has to be fully unlocked to get key salt. During `Auth` it can happen
|
||||
// only to accounts without 2FA. For 2FA accounts, it's done in `Auth2FA`.
|
||||
if auth.hasFullScope() {
|
||||
err = c.setKeySaltToAuth(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return auth, err
|
||||
}
|
||||
|
||||
@ -367,55 +352,9 @@ func (c *client) Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.setKeySaltToAuth(auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return auth2FARes.getAuth2FA(), nil
|
||||
}
|
||||
|
||||
func (c *client) setKeySaltToAuth(auth *Auth) error {
|
||||
// KeySalt already set up, no need to do it again.
|
||||
if auth.KeySalt != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user, err := c.CurrentUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
salts, err := c.GetKeySalts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range salts {
|
||||
if s.ID == user.KeyRing().FirstKeyID {
|
||||
auth.KeySalt = s.KeySalt
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock decrypts the key ring.
|
||||
// If the password is invalid, IsUnlockError(err) will return true.
|
||||
func (c *client) Unlock(password string) (kr *pmcrypto.KeyRing, err error) {
|
||||
if _, err = c.CurrentUser(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.keyLocker.Lock()
|
||||
defer c.keyLocker.Unlock()
|
||||
|
||||
kr = c.user.KeyRing()
|
||||
if err = unlockKeyRingNoErrorWhenAlreadyUnlocked(kr, []byte(password)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.kr = kr
|
||||
return kr, err
|
||||
}
|
||||
|
||||
// AuthRefresh will refresh an expired access token.
|
||||
func (c *client) AuthRefresh(uidAndRefreshToken string) (auth *Auth, err error) {
|
||||
// If we don't yet have a saved access token, save this one in case the refresh fails!
|
||||
@ -466,6 +405,25 @@ func (c *client) AuthRefresh(uidAndRefreshToken string) (auth *Auth, err error)
|
||||
return auth, err
|
||||
}
|
||||
|
||||
func (c *client) AuthSalt() (string, error) {
|
||||
salts, err := c.GetKeySalts()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := c.CurrentUser(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range salts {
|
||||
if s.ID == c.user.Keys[0].ID {
|
||||
return s.KeySalt, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("no matching salt found")
|
||||
}
|
||||
|
||||
// Logout instructs the client manager to log this client out.
|
||||
func (c *client) Logout() {
|
||||
c.cm.LogoutClient(c.userID)
|
||||
@ -499,7 +457,18 @@ func (c *client) IsConnected() bool {
|
||||
func (c *client) ClearData() {
|
||||
c.uid = ""
|
||||
c.accessToken = ""
|
||||
c.kr = nil
|
||||
c.addresses = nil
|
||||
c.user = nil
|
||||
|
||||
if c.userKeyRing != nil {
|
||||
c.userKeyRing.ClearPrivateParams()
|
||||
c.userKeyRing = nil
|
||||
}
|
||||
|
||||
for addrID, addr := range c.addrKeyRing {
|
||||
if addr != nil {
|
||||
addr.ClearPrivateParams()
|
||||
delete(c.addrKeyRing, addrID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -33,7 +33,7 @@ import (
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testIdentity = &pmcrypto.Identity{
|
||||
var testIdentity = &crypto.Identity{
|
||||
Name: "UserID",
|
||||
Email: "",
|
||||
}
|
||||
@ -131,22 +131,16 @@ func TestClient_Auth(t *testing.T) {
|
||||
|
||||
return "/auth/post_response.json"
|
||||
},
|
||||
routeGetUsers,
|
||||
routeGetAddresses,
|
||||
routeGetSalts,
|
||||
)
|
||||
defer finish()
|
||||
|
||||
auth, err := c.Auth(testUsername, testAPIPassword, testAuthInfo)
|
||||
r.Nil(t, err)
|
||||
|
||||
r.True(t, c.user.KeyRing().FirstKeyID != "", "Parsing First key ID issue")
|
||||
|
||||
exp := &Auth{}
|
||||
*exp = *testAuth
|
||||
exp.accessToken = testAccessToken
|
||||
exp.RefreshToken = testRefreshToken
|
||||
exp.KeySalt = "abc"
|
||||
a.Equal(t, exp, auth)
|
||||
}
|
||||
|
||||
@ -161,9 +155,6 @@ func TestClient_Auth2FA(t *testing.T) {
|
||||
|
||||
return "/auth/2fa/post_response.json"
|
||||
},
|
||||
routeGetUsers,
|
||||
routeGetAddresses,
|
||||
routeGetSalts,
|
||||
)
|
||||
defer finish()
|
||||
|
||||
@ -224,16 +215,16 @@ func TestClient_Unlock(t *testing.T) {
|
||||
c.uid = testUID
|
||||
c.accessToken = testAccessToken
|
||||
|
||||
_, err := c.Unlock("wrong")
|
||||
a.True(t, IsUnlockError(err), "expected error, pasword is wrong")
|
||||
err := c.Unlock([]byte("wrong"))
|
||||
a.Error(t, err, "expected error, pasword is wrong")
|
||||
|
||||
_, err = c.Unlock(testMailboxPassword)
|
||||
err = c.Unlock([]byte(testMailboxPassword))
|
||||
a.Nil(t, err)
|
||||
a.Equal(t, testUID, c.uid)
|
||||
a.Equal(t, testAccessToken, c.accessToken)
|
||||
|
||||
// second try should not fail because there is an unlocked key already
|
||||
_, err = c.Unlock("wrong")
|
||||
err = c.Unlock([]byte("wrong"))
|
||||
a.Nil(t, err)
|
||||
}
|
||||
|
||||
@ -246,7 +237,7 @@ func TestClient_Unlock_EncPrivKey(t *testing.T) {
|
||||
c.uid = testUID
|
||||
c.accessToken = testAccessToken
|
||||
|
||||
_, err := c.Unlock(testMailboxPassword)
|
||||
err := c.Unlock([]byte(testMailboxPassword))
|
||||
Ok(t, err)
|
||||
Equals(t, testUID, c.uid)
|
||||
Equals(t, testAccessToken, c.accessToken)
|
||||
@ -280,7 +271,6 @@ func TestClient_AuthRefresh(t *testing.T) {
|
||||
*exp = *testAuth
|
||||
exp.uid = testUID // AuthRefresh will not return UID (only Auth returns the UID) we should set testUID to be able to generate token, see `GetToken`
|
||||
exp.accessToken = testAccessToken
|
||||
exp.KeySalt = ""
|
||||
exp.EventID = ""
|
||||
exp.ExpiresIn = 360000
|
||||
exp.RefreshToken = testRefreshTokenNew
|
||||
@ -313,7 +303,6 @@ func TestClient_AuthRefresh_HasUID(t *testing.T) {
|
||||
exp := &Auth{}
|
||||
*exp = *testAuth
|
||||
exp.accessToken = testAccessToken
|
||||
exp.KeySalt = ""
|
||||
exp.EventID = ""
|
||||
exp.ExpiresIn = 360000
|
||||
exp.RefreshToken = testRefreshTokenNew
|
||||
@ -336,7 +325,7 @@ func TestClient_Logout(t *testing.T) {
|
||||
c.Logout()
|
||||
|
||||
r.Eventually(t, func() bool {
|
||||
return c.IsConnected() == false && c.kr == nil && c.addresses == nil && c.user == nil
|
||||
return c.IsConnected() == false && c.userKeyRing == nil && c.addresses == nil && c.user == nil
|
||||
}, 10*time.Second, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/jaytaylor/html2text"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -104,63 +104,6 @@ type ClientConfig struct {
|
||||
MinBytesPerSecond int64
|
||||
}
|
||||
|
||||
// Client defines the interface of a PMAPI client.
|
||||
type Client interface {
|
||||
Auth(username, password string, info *AuthInfo) (*Auth, error)
|
||||
AuthInfo(username string) (*AuthInfo, error)
|
||||
AuthRefresh(token string) (*Auth, error)
|
||||
Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error)
|
||||
Logout()
|
||||
DeleteAuth() error
|
||||
IsConnected() bool
|
||||
ClearData()
|
||||
|
||||
CurrentUser() (*User, error)
|
||||
UpdateUser() (*User, error)
|
||||
Unlock(mailboxPassword string) (kr *pmcrypto.KeyRing, err error)
|
||||
UnlockAddresses(passphrase []byte) error
|
||||
|
||||
GetAddresses() (addresses AddressList, err error)
|
||||
Addresses() AddressList
|
||||
ReorderAddresses(addressIDs []string) error
|
||||
|
||||
GetEvent(eventID string) (*Event, error)
|
||||
|
||||
SendMessage(string, *SendMessageReq) (sent, parent *Message, err error)
|
||||
CreateDraft(m *Message, parent string, action int) (created *Message, err error)
|
||||
Import([]*ImportMsgReq) ([]*ImportMsgRes, error)
|
||||
|
||||
CountMessages(addressID string) ([]*MessagesCount, error)
|
||||
ListMessages(filter *MessagesFilter) ([]*Message, int, error)
|
||||
GetMessage(apiID string) (*Message, error)
|
||||
DeleteMessages(apiIDs []string) error
|
||||
LabelMessages(apiIDs []string, labelID string) error
|
||||
UnlabelMessages(apiIDs []string, labelID string) error
|
||||
MarkMessagesRead(apiIDs []string) error
|
||||
MarkMessagesUnread(apiIDs []string) error
|
||||
|
||||
ListLabels() ([]*Label, error)
|
||||
CreateLabel(label *Label) (*Label, error)
|
||||
UpdateLabel(label *Label) (*Label, error)
|
||||
DeleteLabel(labelID string) error
|
||||
EmptyFolder(labelID string, addressID string) error
|
||||
|
||||
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
|
||||
SendSimpleMetric(category, action, label string) error
|
||||
|
||||
GetMailSettings() (MailSettings, error)
|
||||
GetContactEmailByEmail(string, int, int) ([]ContactEmail, error)
|
||||
GetContactByID(string) (Contact, error)
|
||||
DecryptAndVerifyCards([]Card) ([]Card, error)
|
||||
|
||||
GetAttachment(id string) (att io.ReadCloser, err error)
|
||||
CreateAttachment(att *Attachment, r io.Reader, sig io.Reader) (created *Attachment, err error)
|
||||
DeleteAttachment(attID string) (err error)
|
||||
|
||||
KeyRingForAddressID(string) (kr *pmcrypto.KeyRing, err error)
|
||||
GetPublicKeysForEmail(string) ([]PublicKey, bool, error)
|
||||
}
|
||||
|
||||
// client is a client of the protonmail API. It implements the Client interface.
|
||||
type client struct {
|
||||
cm *ClientManager
|
||||
@ -170,11 +113,12 @@ type client struct {
|
||||
accessToken string
|
||||
userID string
|
||||
requestLocker sync.Locker
|
||||
keyLocker sync.Locker
|
||||
|
||||
user *User
|
||||
addresses AddressList
|
||||
kr *pmcrypto.KeyRing
|
||||
user *User
|
||||
addresses AddressList
|
||||
userKeyRing *crypto.KeyRing
|
||||
addrKeyRing map[string]*crypto.KeyRing
|
||||
keyRingLock sync.Locker
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
@ -186,7 +130,8 @@ func newClient(cm *ClientManager, userID string) *client {
|
||||
hc: getHTTPClient(cm.config, cm.roundTripper),
|
||||
userID: userID,
|
||||
requestLocker: &sync.Mutex{},
|
||||
keyLocker: &sync.Mutex{},
|
||||
keyRingLock: &sync.Mutex{},
|
||||
addrKeyRing: make(map[string]*crypto.KeyRing),
|
||||
log: logrus.WithField("pkg", "pmapi").WithField("userID", userID),
|
||||
}
|
||||
}
|
||||
@ -199,6 +144,39 @@ func getHTTPClient(cfg *ClientConfig, rt http.RoundTripper) (hc *http.Client) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) IsUnlocked() bool {
|
||||
return c.userKeyRing != nil
|
||||
}
|
||||
|
||||
// Unlock unlocks all the user and address keys using the given passphrase.
|
||||
func (c *client) Unlock(passphrase []byte) (err error) {
|
||||
c.keyRingLock.Lock()
|
||||
defer c.keyRingLock.Unlock()
|
||||
|
||||
// If the user already has a keyring, we already unlocked, so no need to try again.
|
||||
if c.userKeyRing != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = c.CurrentUser(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.user == nil || c.addresses == nil {
|
||||
return errors.New("user data is not loaded")
|
||||
}
|
||||
|
||||
if err = c.unlockUser(passphrase); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
if err = c.unlockAddresses(passphrase); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock addresses")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Do makes an API request. It does not check for HTTP status code errors.
|
||||
func (c *client) Do(req *http.Request, retryUnauthorized bool) (res *http.Response, err error) {
|
||||
// Copy the request body in case we need to retry it.
|
||||
@ -258,7 +236,7 @@ func (c *client) doBuffered(req *http.Request, bodyBuffer []byte, retryUnauthori
|
||||
resDate := res.Header.Get("Date")
|
||||
if resDate != "" {
|
||||
if serverTime, err := http.ParseTime(resDate); err == nil {
|
||||
pmcrypto.GetGopenPGP().UpdateTime(serverTime.Unix())
|
||||
crypto.UpdateTime(serverTime.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
82
pkg/pmapi/client_types.go
Normal file
82
pkg/pmapi/client_types.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 pmapi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
// Client defines the interface of a PMAPI client.
|
||||
type Client interface {
|
||||
Auth(username, password string, info *AuthInfo) (*Auth, error)
|
||||
AuthInfo(username string) (*AuthInfo, error)
|
||||
AuthRefresh(token string) (*Auth, error)
|
||||
Auth2FA(twoFactorCode string, auth *Auth) (*Auth2FA, error)
|
||||
AuthSalt() (salt string, err error)
|
||||
Logout()
|
||||
DeleteAuth() error
|
||||
IsConnected() bool
|
||||
ClearData()
|
||||
|
||||
CurrentUser() (*User, error)
|
||||
UpdateUser() (*User, error)
|
||||
Unlock(passphrase []byte) (err error)
|
||||
IsUnlocked() bool
|
||||
|
||||
GetAddresses() (addresses AddressList, err error)
|
||||
Addresses() AddressList
|
||||
ReorderAddresses(addressIDs []string) error
|
||||
|
||||
GetEvent(eventID string) (*Event, error)
|
||||
|
||||
SendMessage(string, *SendMessageReq) (sent, parent *Message, err error)
|
||||
CreateDraft(m *Message, parent string, action int) (created *Message, err error)
|
||||
Import([]*ImportMsgReq) ([]*ImportMsgRes, error)
|
||||
|
||||
CountMessages(addressID string) ([]*MessagesCount, error)
|
||||
ListMessages(filter *MessagesFilter) ([]*Message, int, error)
|
||||
GetMessage(apiID string) (*Message, error)
|
||||
DeleteMessages(apiIDs []string) error
|
||||
LabelMessages(apiIDs []string, labelID string) error
|
||||
UnlabelMessages(apiIDs []string, labelID string) error
|
||||
MarkMessagesRead(apiIDs []string) error
|
||||
MarkMessagesUnread(apiIDs []string) error
|
||||
|
||||
ListLabels() ([]*Label, error)
|
||||
CreateLabel(label *Label) (*Label, error)
|
||||
UpdateLabel(label *Label) (*Label, error)
|
||||
DeleteLabel(labelID string) error
|
||||
EmptyFolder(labelID string, addressID string) error
|
||||
|
||||
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
|
||||
SendSimpleMetric(category, action, label string) error
|
||||
|
||||
GetMailSettings() (MailSettings, error)
|
||||
GetContactEmailByEmail(string, int, int) ([]ContactEmail, error)
|
||||
GetContactByID(string) (Contact, error)
|
||||
DecryptAndVerifyCards([]Card) ([]Card, error)
|
||||
|
||||
GetAttachment(id string) (att io.ReadCloser, err error)
|
||||
CreateAttachment(att *Attachment, r io.Reader, sig io.Reader) (created *Attachment, err error)
|
||||
DeleteAttachment(attID string) (err error)
|
||||
|
||||
KeyRingForAddressID(string) (kr *crypto.KeyRing, err error)
|
||||
GetPublicKeysForEmail(string) ([]PublicKey, bool, error)
|
||||
}
|
||||
@ -655,7 +655,7 @@ var testCardsCleartext = []Card{
|
||||
|
||||
func TestClient_Encrypt(t *testing.T) {
|
||||
c := newTestClient(newTestClientManager(testClientConfig))
|
||||
c.kr = testPrivateKeyRing
|
||||
c.userKeyRing = testPrivateKeyRing
|
||||
|
||||
cardEncrypted, err := c.EncryptAndSignCards(testCardsCleartext)
|
||||
assert.Nil(t, err)
|
||||
@ -669,7 +669,7 @@ func TestClient_Encrypt(t *testing.T) {
|
||||
|
||||
func TestClient_Decrypt(t *testing.T) {
|
||||
c := newTestClient(newTestClientManager(testClientConfig))
|
||||
c.kr = testPrivateKeyRing
|
||||
c.userKeyRing = testPrivateKeyRing
|
||||
|
||||
cardCleartext, err := c.DecryptAndVerifyCards(testCardsEncrypted)
|
||||
assert.Nil(t, err)
|
||||
|
||||
@ -21,9 +21,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
// Flags
|
||||
@ -46,12 +45,13 @@ type PublicKey struct {
|
||||
}
|
||||
|
||||
// PublicKeys returns the public keys of the given email addresses.
|
||||
func (c *client) PublicKeys(emails []string) (keys map[string]*pmcrypto.KeyRing, err error) {
|
||||
func (c *client) PublicKeys(emails []string) (keys map[string]*crypto.Key, err error) {
|
||||
if len(emails) == 0 {
|
||||
err = fmt.Errorf("pmapi: cannot get public keys: no email address provided")
|
||||
return
|
||||
}
|
||||
keys = map[string]*pmcrypto.KeyRing{}
|
||||
|
||||
keys = make(map[string]*crypto.Key)
|
||||
|
||||
for _, email := range emails {
|
||||
email = url.QueryEscape(email)
|
||||
@ -66,13 +66,15 @@ func (c *client) PublicKeys(emails []string) (keys map[string]*pmcrypto.KeyRing,
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range res.Keys {
|
||||
if key.Flags&UseToEncryptFlag == UseToEncryptFlag {
|
||||
var kr *pmcrypto.KeyRing
|
||||
if kr, err = pmcrypto.ReadArmoredKeyRing(strings.NewReader(key.PublicKey)); err != nil {
|
||||
for _, rawKey := range res.Keys {
|
||||
if rawKey.Flags&UseToEncryptFlag == UseToEncryptFlag {
|
||||
var key *crypto.Key
|
||||
|
||||
if key, err = crypto.NewKeyFromArmored(rawKey.PublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
keys[email] = kr
|
||||
|
||||
keys[email] = key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,183 +20,152 @@ package pmapi
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// clearableKey is a region of memory intended to hold a private key and which can be securely
|
||||
// cleared by calling clear().
|
||||
type clearableKey []byte
|
||||
|
||||
// UnmarshalJSON Removes quotation and unescapes CR, LF.
|
||||
func (pk *clearableKey) UnmarshalJSON(b []byte) (err error) {
|
||||
b = bytes.Trim(b, "\"")
|
||||
b = bytes.ReplaceAll(b, []byte("\\n"), []byte("\n"))
|
||||
b = bytes.ReplaceAll(b, []byte("\\r"), []byte("\r"))
|
||||
*pk = b
|
||||
return
|
||||
}
|
||||
|
||||
// clear irreversibly destroys the full range of `clearableKey` by filling it with zeros to ensure
|
||||
// nobody can see what was in there (e.g. while waiting for the garbage collector to clean it up).
|
||||
func (pk *clearableKey) clear() {
|
||||
for b := range *pk {
|
||||
(*pk)[b] = 0
|
||||
}
|
||||
}
|
||||
|
||||
type PMKey struct {
|
||||
ID string
|
||||
Version int
|
||||
Flags int
|
||||
Fingerprint string
|
||||
PrivateKey *crypto.Key
|
||||
Primary int
|
||||
Token *string `json:",omitempty"`
|
||||
Signature *string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type PMKeys struct {
|
||||
Keys []PMKey
|
||||
KeyRing *pmcrypto.KeyRing
|
||||
}
|
||||
func (key *PMKey) UnmarshalJSON(b []byte) (err error) {
|
||||
type _PMKey PMKey
|
||||
|
||||
func (k *PMKeys) UnmarshalJSON(b []byte) (err error) {
|
||||
var rawKeys []struct {
|
||||
PMKey
|
||||
PrivateKey clearableKey
|
||||
}
|
||||
if err = json.Unmarshal(b, &rawKeys); err != nil {
|
||||
rawKey := struct {
|
||||
_PMKey
|
||||
PrivateKey string
|
||||
}{}
|
||||
|
||||
if err = json.Unmarshal(b, &rawKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
k.KeyRing = &pmcrypto.KeyRing{}
|
||||
for _, rawKey := range rawKeys {
|
||||
err = k.KeyRing.ReadFrom(bytes.NewReader(rawKey.PrivateKey), true)
|
||||
rawKey.PrivateKey.clear()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
k.Keys = append(k.Keys, rawKey.PMKey)
|
||||
}
|
||||
if len(k.Keys) > 0 {
|
||||
k.KeyRing.FirstKeyID = k.Keys[0].ID
|
||||
*key = PMKey(rawKey._PMKey)
|
||||
|
||||
if key.PrivateKey, err = crypto.NewKeyFromArmored(rawKey.PrivateKey); err != nil {
|
||||
return errors.Wrap(err, "failed to create crypto key from armored private key")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// unlockKeyRing tries to unlock them with the provided keyRing using the token
|
||||
// and if the token is not available it will use passphrase. It will not fail
|
||||
// if keyring contains at least one unlocked private key.
|
||||
func (k *PMKeys) unlockKeyRing(userKeyring *pmcrypto.KeyRing, passphrase []byte, locker sync.Locker) (err error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
func (key PMKey) getPassphraseFromToken(kr *crypto.KeyRing) (passphrase []byte, err error) {
|
||||
if kr == nil {
|
||||
return nil, errors.New("no user key was provided")
|
||||
}
|
||||
|
||||
if k == nil {
|
||||
err = errors.New("keys is a nil object")
|
||||
msg, err := crypto.NewPGPMessageFromArmored(*key.Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range k.Keys {
|
||||
sig, err := crypto.NewPGPSignatureFromArmored(*key.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := kr.Decrypt(msg, nil, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = kr.VerifyDetached(token, sig, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return token.GetBinary(), nil
|
||||
}
|
||||
|
||||
func (key PMKey) unlock(passphrase []byte) (unlockedKey *crypto.Key, err error) {
|
||||
if unlockedKey, err = key.PrivateKey.Unlock(passphrase); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := unlockedKey.Check()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
err = errors.New("private and public keys do not match")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type PMKeys []PMKey
|
||||
|
||||
// UnlockAll goes through each key and unlocks it, returning a keyring containing all unlocked keys,
|
||||
// or an error if at least one could not be unlocked.
|
||||
// The passphrase is used to unlock the key unless the key's token and signature are both non-nil,
|
||||
// in which case the given userkey is used to deduce the passphrase.
|
||||
func (keys *PMKeys) UnlockAll(passphrase []byte, userKey *crypto.KeyRing) (kr *crypto.KeyRing, err error) {
|
||||
if kr, err = crypto.NewKeyRing(nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range *keys {
|
||||
var secret []byte
|
||||
|
||||
if key.Token == nil || key.Signature == nil {
|
||||
if err = unlockKeyRingNoErrorWhenAlreadyUnlocked(k.KeyRing, passphrase); err != nil {
|
||||
return
|
||||
}
|
||||
secret = passphrase
|
||||
} else if secret, err = key.getPassphraseFromToken(userKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var k *crypto.Key
|
||||
|
||||
if k, err = key.unlock(secret); err != nil {
|
||||
logrus.WithError(err).Warn("Failed to unlock key")
|
||||
continue
|
||||
}
|
||||
|
||||
message, err := pmcrypto.NewPGPMessageFromArmored(*key.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signature, err := pmcrypto.NewPGPSignatureFromArmored(*key.Signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userKeyring == nil {
|
||||
return errors.New("userkey required to decrypt tokens but wasn't provided")
|
||||
}
|
||||
token, err := userKeyring.Decrypt(message, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = userKeyring.VerifyDetached(token, signature, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = unlockKeyRingNoErrorWhenAlreadyUnlocked(k.KeyRing, token.GetBinary())
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong token: %v", err)
|
||||
if err = kr.AddKey(k); err != nil {
|
||||
logrus.WithError(err).Warn("Failed to add key to keyring")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type unlockError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (err *unlockError) Error() string {
|
||||
return "Invalid mailbox password (" + err.error.Error() + ")"
|
||||
}
|
||||
|
||||
// IsUnlockError checks whether the error is due to failure to unlock (which is represented by an unexported type).
|
||||
func IsUnlockError(err error) bool {
|
||||
_, ok := err.(*unlockError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func unlockKeyRingNoErrorWhenAlreadyUnlocked(kr *pmcrypto.KeyRing, passphrase []byte) (err error) {
|
||||
if err = kr.Unlock(passphrase); err != nil {
|
||||
// Do not fail if it has already unlocked keys.
|
||||
hasUnlockedKey := false
|
||||
for _, e := range kr.GetEntities() {
|
||||
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
|
||||
hasUnlockedKey = true
|
||||
break
|
||||
}
|
||||
for _, se := range e.Subkeys {
|
||||
if se.PrivateKey != nil && (!se.Sig.FlagsValid || se.Sig.FlagEncryptStorage || se.Sig.FlagEncryptCommunications) && !e.PrivateKey.Encrypted {
|
||||
hasUnlockedKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasUnlockedKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUnlockedKey {
|
||||
err = &unlockError{err}
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
if kr.CountEntities() == 0 {
|
||||
err = errors.New("no keys could be unlocked")
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
return kr, err
|
||||
}
|
||||
|
||||
// ErrNoKeyringAvailable represents an error caused by a keyring being nil or having no entities.
|
||||
var ErrNoKeyringAvailable = errors.New("no keyring available")
|
||||
|
||||
func (c *client) encrypt(plain string, signer *pmcrypto.KeyRing) (armored string, err error) {
|
||||
return encrypt(c.kr, plain, signer)
|
||||
func (c *client) encrypt(plain string, signer *crypto.KeyRing) (armored string, err error) {
|
||||
return encrypt(c.userKeyRing, plain, signer)
|
||||
}
|
||||
|
||||
func encrypt(encrypter *pmcrypto.KeyRing, plain string, signer *pmcrypto.KeyRing) (armored string, err error) {
|
||||
if encrypter == nil || encrypter.FirstKey() == nil {
|
||||
func encrypt(encrypter *crypto.KeyRing, plain string, signer *crypto.KeyRing) (armored string, err error) {
|
||||
if encrypter == nil {
|
||||
return "", ErrNoKeyringAvailable
|
||||
}
|
||||
plainMessage := pmcrypto.NewPlainMessageFromString(plain)
|
||||
|
||||
firstKey, err := encrypter.FirstKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plainMessage := crypto.NewPlainMessageFromString(plain)
|
||||
|
||||
// We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
|
||||
pgpMessage, err := encrypter.FirstKey().Encrypt(plainMessage, signer)
|
||||
pgpMessage, err := firstKey.Encrypt(plainMessage, signer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -204,14 +173,14 @@ func encrypt(encrypter *pmcrypto.KeyRing, plain string, signer *pmcrypto.KeyRing
|
||||
}
|
||||
|
||||
func (c *client) decrypt(armored string) (plain string, err error) {
|
||||
return decrypt(c.kr, armored)
|
||||
return decrypt(c.userKeyRing, armored)
|
||||
}
|
||||
|
||||
func decrypt(decrypter *pmcrypto.KeyRing, armored string) (plainBody string, err error) {
|
||||
func decrypt(decrypter *crypto.KeyRing, armored string) (plainBody string, err error) {
|
||||
if decrypter == nil {
|
||||
return "", ErrNoKeyringAvailable
|
||||
}
|
||||
pgpMessage, err := pmcrypto.NewPGPMessageFromArmored(armored)
|
||||
pgpMessage, err := crypto.NewPGPMessageFromArmored(armored)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -223,11 +192,11 @@ func decrypt(decrypter *pmcrypto.KeyRing, armored string) (plainBody string, err
|
||||
}
|
||||
|
||||
func (c *client) sign(plain string) (armoredSignature string, err error) {
|
||||
if c.kr == nil {
|
||||
if c.userKeyRing == nil {
|
||||
return "", ErrNoKeyringAvailable
|
||||
}
|
||||
plainMessage := pmcrypto.NewPlainMessageFromString(plain)
|
||||
pgpSignature, err := c.kr.SignDetached(plainMessage)
|
||||
plainMessage := crypto.NewPlainMessageFromString(plain)
|
||||
pgpSignature, err := c.userKeyRing.SignDetached(plainMessage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -235,34 +204,44 @@ func (c *client) sign(plain string) (armoredSignature string, err error) {
|
||||
}
|
||||
|
||||
func (c *client) verify(plain, amroredSignature string) (err error) {
|
||||
plainMessage := pmcrypto.NewPlainMessageFromString(plain)
|
||||
pgpSignature, err := pmcrypto.NewPGPSignatureFromArmored(amroredSignature)
|
||||
plainMessage := crypto.NewPlainMessageFromString(plain)
|
||||
pgpSignature, err := crypto.NewPGPSignatureFromArmored(amroredSignature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
verifyTime := int64(0) // By default it will use current timestamp.
|
||||
return c.kr.VerifyDetached(plainMessage, pgpSignature, verifyTime)
|
||||
return c.userKeyRing.VerifyDetached(plainMessage, pgpSignature, verifyTime)
|
||||
}
|
||||
|
||||
func encryptAttachment(kr *pmcrypto.KeyRing, data io.Reader, filename string) (encrypted io.Reader, err error) {
|
||||
if kr == nil || kr.FirstKey() == nil {
|
||||
func encryptAttachment(kr *crypto.KeyRing, data io.Reader, filename string) (encrypted io.Reader, err error) {
|
||||
if kr == nil {
|
||||
return nil, ErrNoKeyringAvailable
|
||||
}
|
||||
|
||||
firstKey, err := kr.FirstKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataBytes, err := ioutil.ReadAll(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plainMessage := pmcrypto.NewPlainMessage(dataBytes)
|
||||
|
||||
plainMessage := crypto.NewPlainMessage(dataBytes)
|
||||
|
||||
// We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
|
||||
pgpSplitMessage, err := kr.FirstKey().EncryptAttachment(plainMessage, filename)
|
||||
pgpSplitMessage, err := firstKey.EncryptAttachment(plainMessage, filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
packets := append(pgpSplitMessage.KeyPacket, pgpSplitMessage.DataPacket...)
|
||||
|
||||
return bytes.NewReader(packets), nil
|
||||
}
|
||||
|
||||
func decryptAttachment(kr *pmcrypto.KeyRing, keyPackets []byte, data io.Reader) (decrypted io.Reader, err error) {
|
||||
func decryptAttachment(kr *crypto.KeyRing, keyPackets []byte, data io.Reader) (decrypted io.Reader, err error) {
|
||||
if kr == nil {
|
||||
return nil, ErrNoKeyringAvailable
|
||||
}
|
||||
@ -270,7 +249,7 @@ func decryptAttachment(kr *pmcrypto.KeyRing, keyPackets []byte, data io.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pgpSplitMessage := pmcrypto.NewPGPSplitMessage(keyPackets, dataBytes)
|
||||
pgpSplitMessage := crypto.NewPGPSplitMessage(keyPackets, dataBytes)
|
||||
plainMessage, err := kr.DecryptAttachment(pgpSplitMessage)
|
||||
if err != nil {
|
||||
return
|
||||
@ -278,7 +257,7 @@ func decryptAttachment(kr *pmcrypto.KeyRing, keyPackets []byte, data io.Reader)
|
||||
return plainMessage.NewReader(), nil
|
||||
}
|
||||
|
||||
func signAttachment(encrypter *pmcrypto.KeyRing, data io.Reader) (signature io.Reader, err error) {
|
||||
func signAttachment(encrypter *crypto.KeyRing, data io.Reader) (signature io.Reader, err error) {
|
||||
if encrypter == nil {
|
||||
return nil, ErrNoKeyringAvailable
|
||||
}
|
||||
@ -286,7 +265,7 @@ func signAttachment(encrypter *pmcrypto.KeyRing, data io.Reader) (signature io.R
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plainMessage := pmcrypto.NewPlainMessage(dataBytes)
|
||||
plainMessage := crypto.NewPlainMessage(dataBytes)
|
||||
sig, err := encrypter.SignDetached(plainMessage)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@ -19,11 +19,9 @@ package pmapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -38,11 +36,16 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
||||
addrKeysPrimaryHasToken := loadPMKeys(readTestFile("keyring_addressKeysPrimaryHasToken_JSON", false))
|
||||
addrKeysSecondaryHasToken := loadPMKeys(readTestFile("keyring_addressKeysSecondaryHasToken_JSON", false))
|
||||
|
||||
userKey, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_userKey", false)))
|
||||
key, err := crypto.NewKeyFromArmored(readTestFile("keyring_userKey", false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
userKey, err := crypto.NewKeyRing(key)
|
||||
assert.NoError(t, err, "Expected not to receive an error unlocking user key")
|
||||
|
||||
type args struct {
|
||||
userKeyring *pmcrypto.KeyRing
|
||||
userKeyring *crypto.KeyRing
|
||||
passphrase []byte
|
||||
}
|
||||
tests := []struct {
|
||||
@ -73,21 +76,26 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempLocker := &sync.Mutex{}
|
||||
|
||||
err := tt.keys.unlockKeyRing(tt.args.userKeyring, tt.args.passphrase, tempLocker) // nolint[scopelint]
|
||||
kr, err := tt.keys.UnlockAll(tt.args.passphrase, tt.args.userKeyring) // nolint[scopelint]
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// assert at least one key has been decrypted
|
||||
atLeastOneDecrypted := false
|
||||
for _, e := range tt.keys.KeyRing.GetEntities() { // nolint[scopelint]
|
||||
if !e.PrivateKey.Encrypted {
|
||||
|
||||
for _, k := range kr.GetKeys() { // nolint[scopelint]
|
||||
ok, err := k.IsUnlocked()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
atLeastOneDecrypted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, atLeastOneDecrypted)
|
||||
})
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
@ -250,7 +250,7 @@ func (m *Message) IsLegacyMessage() bool {
|
||||
strings.Contains(m.Body, MessageTail)
|
||||
}
|
||||
|
||||
func (m *Message) Decrypt(kr *pmcrypto.KeyRing) (err error) {
|
||||
func (m *Message) Decrypt(kr *crypto.KeyRing) (err error) {
|
||||
if m.IsLegacyMessage() {
|
||||
return m.DecryptLegacy(kr)
|
||||
}
|
||||
@ -269,7 +269,7 @@ func (m *Message) Decrypt(kr *pmcrypto.KeyRing) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Message) DecryptLegacy(kr *pmcrypto.KeyRing) (err error) {
|
||||
func (m *Message) DecryptLegacy(kr *crypto.KeyRing) (err error) {
|
||||
randomKeyStart := strings.Index(m.Body, RandomKeyHeader) + len(RandomKeyHeader)
|
||||
randomKeyEnd := strings.Index(m.Body, RandomKeyTail)
|
||||
randomKey := m.Body[randomKeyStart:randomKeyEnd]
|
||||
@ -341,7 +341,7 @@ func decodeBase64UTF8(input string) (output []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Message) Encrypt(encrypter, signer *pmcrypto.KeyRing) (err error) {
|
||||
func (m *Message) Encrypt(encrypter, signer *crypto.KeyRing) (err error) {
|
||||
if m.IsBodyEncrypted() {
|
||||
err = errors.New("pmapi: trying to encrypt an already encrypted message")
|
||||
return
|
||||
|
||||
@ -20,10 +20,9 @@ package pmapi
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -142,10 +141,15 @@ func TestMessage_Decrypt(t *testing.T) {
|
||||
|
||||
func TestMessage_Decrypt_Legacy(t *testing.T) {
|
||||
testPrivateKeyLegacy := readTestFile("testPrivateKeyLegacy", false)
|
||||
testPrivateKeyRingLegacy, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPrivateKeyLegacy))
|
||||
|
||||
key, err := crypto.NewKeyFromArmored(testPrivateKeyLegacy)
|
||||
Ok(t, err)
|
||||
|
||||
Ok(t, testPrivateKeyRingLegacy.Unlock([]byte(testMailboxPasswordLegacy)))
|
||||
unlockedKey, err := key.Unlock([]byte(testMailboxPasswordLegacy))
|
||||
Ok(t, err)
|
||||
|
||||
testPrivateKeyRingLegacy, err := crypto.NewKeyRing(unlockedKey)
|
||||
Ok(t, err)
|
||||
|
||||
msg := &Message{Body: testMessageEncryptedLegacy}
|
||||
|
||||
@ -163,7 +167,10 @@ func TestMessage_Decrypt_signed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMessage_Encrypt(t *testing.T) {
|
||||
signer, err := pmcrypto.ReadArmoredKeyRing(strings.NewReader(testMessageSigner))
|
||||
key, err := crypto.NewKeyFromArmored(testMessageSigner)
|
||||
Ok(t, err)
|
||||
|
||||
signer, err := crypto.NewKeyRing(key)
|
||||
Ok(t, err)
|
||||
|
||||
msg := &Message{Body: testMessageCleartext}
|
||||
@ -173,7 +180,7 @@ func TestMessage_Encrypt(t *testing.T) {
|
||||
Ok(t, err)
|
||||
|
||||
Equals(t, testMessageCleartext, msg.Body)
|
||||
Equals(t, testIdentity, signer.Identities()[0])
|
||||
Equals(t, testIdentity, signer.GetIdentities()[0])
|
||||
}
|
||||
|
||||
func routeLabelMessages(tb testing.TB, w http.ResponseWriter, r *http.Request) string {
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
crypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
crypto "github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
@ -110,6 +110,21 @@ func (mr *MockClientMockRecorder) AuthRefresh(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRefresh", reflect.TypeOf((*MockClient)(nil).AuthRefresh), arg0)
|
||||
}
|
||||
|
||||
// AuthSalt mocks base method
|
||||
func (m *MockClient) AuthSalt() (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AuthSalt")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AuthSalt indicates an expected call of AuthSalt
|
||||
func (mr *MockClientMockRecorder) AuthSalt() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthSalt", reflect.TypeOf((*MockClient)(nil).AuthSalt))
|
||||
}
|
||||
|
||||
// ClearData mocks base method
|
||||
func (m *MockClient) ClearData() {
|
||||
m.ctrl.T.Helper()
|
||||
@ -432,6 +447,20 @@ func (mr *MockClientMockRecorder) IsConnected() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockClient)(nil).IsConnected))
|
||||
}
|
||||
|
||||
// IsUnlocked mocks base method
|
||||
func (m *MockClient) IsUnlocked() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsUnlocked")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsUnlocked indicates an expected call of IsUnlocked
|
||||
func (mr *MockClientMockRecorder) IsUnlocked() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnlocked", reflect.TypeOf((*MockClient)(nil).IsUnlocked))
|
||||
}
|
||||
|
||||
// KeyRingForAddressID mocks base method
|
||||
func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -605,12 +634,11 @@ func (mr *MockClientMockRecorder) UnlabelMessages(arg0, arg1 interface{}) *gomoc
|
||||
}
|
||||
|
||||
// Unlock mocks base method
|
||||
func (m *MockClient) Unlock(arg0 string) (*crypto.KeyRing, error) {
|
||||
func (m *MockClient) Unlock(arg0 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Unlock", arg0)
|
||||
ret0, _ := ret[0].(*crypto.KeyRing)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Unlock indicates an expected call of Unlock
|
||||
@ -619,20 +647,6 @@ func (mr *MockClientMockRecorder) Unlock(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockClient)(nil).Unlock), arg0)
|
||||
}
|
||||
|
||||
// UnlockAddresses mocks base method
|
||||
func (m *MockClient) UnlockAddresses(arg0 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnlockAddresses", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnlockAddresses indicates an expected call of UnlockAddresses
|
||||
func (mr *MockClientMockRecorder) UnlockAddresses(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockAddresses", reflect.TypeOf((*MockClient)(nil).UnlockAddresses), arg0)
|
||||
}
|
||||
|
||||
// UpdateLabel mocks base method
|
||||
func (m *MockClient) UpdateLabel(arg0 *pmapi.Label) (*pmapi.Label, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@ -24,12 +24,12 @@ import (
|
||||
"github.com/jameskeane/bcrypt"
|
||||
)
|
||||
|
||||
func HashMailboxPassword(password, keySalt string) (hashedPassword string, err error) {
|
||||
if keySalt == "" {
|
||||
func HashMailboxPassword(password, salt string) (hashedPassword string, err error) {
|
||||
if salt == "" {
|
||||
hashedPassword = password
|
||||
return
|
||||
}
|
||||
decodedSalt, err := base64.StdEncoding.DecodeString(keySalt)
|
||||
decodedSalt, err := base64.StdEncoding.DecodeString(salt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -21,15 +21,15 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
const testMailboxPassword = "apple"
|
||||
const testMailboxPasswordLegacy = "123"
|
||||
|
||||
var (
|
||||
testPrivateKeyRing *pmcrypto.KeyRing
|
||||
testPublicKeyRing *pmcrypto.KeyRing
|
||||
testPrivateKeyRing *crypto.KeyRing
|
||||
testPublicKeyRing *crypto.KeyRing
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -37,15 +37,27 @@ func init() {
|
||||
testPublicKey := readTestFile("testPublicKey", false)
|
||||
|
||||
var err error
|
||||
if testPrivateKeyRing, err = pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPrivateKey)); err != nil {
|
||||
|
||||
privKey, err := crypto.NewKeyFromArmored(testPrivateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if testPublicKeyRing, err = pmcrypto.ReadArmoredKeyRing(strings.NewReader(testPublicKey)); err != nil {
|
||||
privKeyUnlocked, err := privKey.Unlock([]byte(testMailboxPassword))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := testPrivateKeyRing.Unlock([]byte(testMailboxPassword)); err != nil {
|
||||
pubKey, err := crypto.NewKeyFromArmored(testPublicKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if testPrivateKeyRing, err = crypto.NewKeyRing(privKeyUnlocked); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if testPublicKeyRing, err = crypto.NewKeyRing(pubKey); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@
|
||||
package pmapi
|
||||
|
||||
import (
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Role values.
|
||||
@ -68,15 +68,17 @@ type User struct {
|
||||
Private int
|
||||
Subscribed int
|
||||
Services int
|
||||
VPN struct {
|
||||
Deliquent int
|
||||
|
||||
Keys PMKeys
|
||||
|
||||
VPN struct {
|
||||
Status int
|
||||
ExpirationTime int
|
||||
PlanName string
|
||||
MaxConnect int
|
||||
MaxTier int
|
||||
}
|
||||
Deliquent int
|
||||
Keys PMKeys
|
||||
}
|
||||
|
||||
// UserRes holds structure of JSON response.
|
||||
@ -86,9 +88,17 @@ type UserRes struct {
|
||||
User *User
|
||||
}
|
||||
|
||||
// KeyRing returns the (possibly unlocked) PMKeys KeyRing.
|
||||
func (u *User) KeyRing() *pmcrypto.KeyRing {
|
||||
return u.Keys.KeyRing
|
||||
// unlockUser unlocks all the client's user keys using the given passphrase.
|
||||
func (c *client) unlockUser(passphrase []byte) (err error) {
|
||||
if c.userKeyRing != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.userKeyRing, err = c.user.Keys.UnlockAll(passphrase, nil); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user keys")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateUser retrieves details about user and loads its addresses.
|
||||
|
||||
@ -23,7 +23,7 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
r "github.com/stretchr/testify/require"
|
||||
@ -72,9 +72,9 @@ func TestClient_CurrentUser(t *testing.T) {
|
||||
r.Nil(t, err)
|
||||
|
||||
// Ignore KeyRings during the check because they have unexported fields and cannot be compared
|
||||
r.True(t, cmp.Equal(user, testCurrentUser, cmpopts.IgnoreTypes(&pmcrypto.KeyRing{})))
|
||||
r.True(t, cmp.Equal(user, testCurrentUser, cmpopts.IgnoreTypes(&crypto.Key{})))
|
||||
|
||||
r.Nil(t, c.UnlockAddresses([]byte(testMailboxPassword)))
|
||||
r.Nil(t, c.Unlock([]byte(testMailboxPassword)))
|
||||
}
|
||||
|
||||
func TestClient_PublicKeys(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user