forked from Silverfish/proton-bridge
336 lines
8.3 KiB
Go
336 lines
8.3 KiB
Go
// Copyright (c) 2022 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package pmapi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io"
|
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type PMKey struct {
|
|
ID string
|
|
Version int
|
|
Flags int
|
|
Fingerprint string
|
|
PrivateKey *crypto.Key
|
|
Primary int
|
|
Token string
|
|
Active Boolean
|
|
Signature string
|
|
}
|
|
|
|
type clearable []byte
|
|
|
|
func (c *clearable) UnmarshalJSON(b []byte) error {
|
|
b = bytes.Trim(b, "\"")
|
|
b = bytes.ReplaceAll(b, []byte("\\n"), []byte("\n"))
|
|
b = bytes.ReplaceAll(b, []byte("\\r"), []byte("\r"))
|
|
*c = b
|
|
return nil
|
|
}
|
|
|
|
func (c *clearable) clear() {
|
|
for i := range *c {
|
|
(*c)[i] = 0
|
|
}
|
|
}
|
|
|
|
func (key *PMKey) UnmarshalJSON(b []byte) (err error) {
|
|
type _PMKey PMKey
|
|
|
|
rawKey := struct {
|
|
_PMKey
|
|
PrivateKey clearable
|
|
}{}
|
|
|
|
defer rawKey.PrivateKey.clear()
|
|
|
|
if err = json.Unmarshal(b, &rawKey); err != nil {
|
|
return
|
|
}
|
|
|
|
*key = PMKey(rawKey._PMKey)
|
|
|
|
if key.PrivateKey, err = crypto.NewKeyFromArmoredReader(bytes.NewReader(rawKey.PrivateKey)); err != nil {
|
|
return errors.Wrap(err, "failed to create crypto key from armored private key")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (key PMKey) getPassphraseFromToken(kr *crypto.KeyRing) (passphrase []byte, err error) {
|
|
if kr == nil {
|
|
return nil, errors.New("no user key was provided")
|
|
}
|
|
|
|
msg, err := crypto.NewPGPMessageFromArmored(key.Token)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
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 no keys could 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 {
|
|
if !key.Active {
|
|
logrus.WithField("fingerprint", key.Fingerprint).Warn("Skipping inactive key")
|
|
continue
|
|
}
|
|
|
|
var secret []byte
|
|
|
|
if key.Token == "" || key.Signature == "" {
|
|
secret = passphrase
|
|
} else if secret, err = key.getPassphraseFromToken(userKey); err != nil {
|
|
return
|
|
}
|
|
|
|
k, unlockErr := key.unlock(secret)
|
|
if unlockErr != nil {
|
|
logrus.WithError(unlockErr).WithField("fingerprint", key.Fingerprint).Warn("Failed to unlock key")
|
|
continue
|
|
}
|
|
|
|
if addKeyErr := kr.AddKey(k); addKeyErr != nil {
|
|
logrus.WithError(addKeyErr).Warn("Failed to add key to keyring")
|
|
continue
|
|
}
|
|
}
|
|
|
|
if kr.CountEntities() == 0 {
|
|
err = errors.New("no keys could be unlocked")
|
|
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 encrypt(encrypter *crypto.KeyRing, plain string, signer *crypto.KeyRing) (armored string, err error) {
|
|
if encrypter == nil {
|
|
return "", ErrNoKeyringAvailable
|
|
}
|
|
|
|
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 := firstKey.Encrypt(plainMessage, signer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return pgpMessage.GetArmored()
|
|
}
|
|
|
|
func (c *client) decrypt(armored string) (plain []byte, err error) {
|
|
return decrypt(c.userKeyRing, armored)
|
|
}
|
|
|
|
func decrypt(decrypter *crypto.KeyRing, armored string) (plainBody []byte, err error) {
|
|
if decrypter == nil {
|
|
return nil, ErrNoKeyringAvailable
|
|
}
|
|
pgpMessage, err := crypto.NewPGPMessageFromArmored(armored)
|
|
if err != nil {
|
|
return
|
|
}
|
|
plainMessage, err := decrypter.Decrypt(pgpMessage, nil, 0)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return plainMessage.GetBinary(), nil
|
|
}
|
|
|
|
func (c *client) verify(plain, amroredSignature string) (err error) {
|
|
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.userKeyRing.VerifyDetached(plainMessage, pgpSignature, verifyTime)
|
|
}
|
|
|
|
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 := io.ReadAll(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
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 := firstKey.EncryptAttachment(plainMessage, filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
packets := append(pgpSplitMessage.KeyPacket, pgpSplitMessage.DataPacket...) //nolint:gocritic
|
|
|
|
return bytes.NewReader(packets), nil
|
|
}
|
|
|
|
func decryptAttachment(kr *crypto.KeyRing, keyPackets []byte, data io.Reader) (decrypted io.Reader, err error) {
|
|
if kr == nil {
|
|
return nil, ErrNoKeyringAvailable
|
|
}
|
|
dataBytes, err := io.ReadAll(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
pgpSplitMessage := crypto.NewPGPSplitMessage(keyPackets, dataBytes)
|
|
plainMessage, err := kr.DecryptAttachment(pgpSplitMessage)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return plainMessage.NewReader(), nil
|
|
}
|
|
|
|
func signAttachment(encrypter *crypto.KeyRing, data io.Reader) (signature io.Reader, err error) {
|
|
if encrypter == nil {
|
|
return nil, ErrNoKeyringAvailable
|
|
}
|
|
dataBytes, err := io.ReadAll(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
plainMessage := crypto.NewPlainMessage(dataBytes)
|
|
sig, err := encrypter.SignDetached(plainMessage)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return bytes.NewReader(sig.GetBinary()), nil
|
|
}
|
|
|
|
func encryptAndEncodeSessionKeys(
|
|
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)
|
|
if err != nil {
|
|
return
|
|
}
|
|
bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
|
|
|
|
// Encrypt attachment keys.
|
|
attachmentPackets = make(map[string]string)
|
|
for id, attkey := range attkeys {
|
|
var packets []byte
|
|
if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
|
|
return
|
|
}
|
|
attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
|
|
}
|
|
return
|
|
}
|
|
|
|
func encryptSymmDecryptKey(
|
|
kr *crypto.KeyRing,
|
|
textToEncrypt string,
|
|
) (decryptedKey *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).
|
|
firstKey, err := kr.FirstKey()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
pgpSplitMessage, err := pgpMessage.SplitMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
decryptedKey, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
|
|
|
|
return
|
|
}
|