Openpgp security update (V2) (#31)

* Change keyring unlock functionalities

* Add keyring#Lock, keyring#CheckIntegrity, tests

* Update helpers, fix bugs

* Update go.mod with ProtonMail/crypto commit

* Change key management system

* Clear keys from memory + tests

* Create SessionKey with direct encryption for datapackets. Move symmetrickey to password.

* Fix upstream dependencies

* Update module to V2, documentation

* Add linter

* Add v2 folder to .gitignore

* Minor changes to KeyID getters

* Remove old changelog

* Improve docs, remove compilation script
This commit is contained in:
wussler 2019-12-27 19:35:43 +01:00 committed by GitHub
parent 136c0a5495
commit 54f45d0471
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2588 additions and 1770 deletions

View file

@ -34,8 +34,14 @@ func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
if ap.err != nil {
return nil, ap.err
}
(*ap.w).Close()
(*ap.pipe).Close()
if err := (*ap.w).Close(); err != nil {
return nil, err
}
if err := (*ap.pipe).Close(); err != nil {
return nil, err
}
ap.done.Wait()
if ap.garbageCollector > 0 {
runtime.GC()

View file

@ -10,44 +10,46 @@ import (
// const testAttachmentEncrypted =
// `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
var testAttachmentKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
func TestAttachmentGetKey(t *testing.T) {
testKeyPacketsDecoded, err := base64.StdEncoding.DecodeString(readTestFile("attachment_keypacket", false))
if err != nil {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(testKeyPacketsDecoded)
if err != nil {
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
}
assert.Exactly(t, testSymmetricKey, symmetricKey)
assert.Exactly(t, testAttachmentKey, sessionKey.Key)
}
func TestAttachmentSetKey(t *testing.T) {
keyPackets, err := testPublicKeyRing.EncryptSessionKey(testSymmetricKey)
keyPackets, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Expected no error while encrypting attachment key, got:", err)
}
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(keyPackets)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}
assert.Exactly(t, testSymmetricKey, symmetricKey)
assert.Exactly(t, testSessionKey, sessionKey)
}
func TestAttachmentEncryptDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
redecData, err := testPrivateKeyRing.DecryptAttachment(encSplit)
redecData, err := keyRingTestPrivate.DecryptAttachment(encSplit)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
@ -59,14 +61,14 @@ func TestAttachmentEncrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
pgpMessage := NewPGPMessage(encSplit.GetBinary())
redecData, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0)
redecData, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
@ -78,7 +80,7 @@ func TestAttachmentDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encrypted, err := testPrivateKeyRing.Encrypt(message, nil)
encrypted, err := keyRingTestPrivate.Encrypt(message, nil)
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
@ -93,7 +95,7 @@ func TestAttachmentDecrypt(t *testing.T) {
t.Fatal("Expected no error while unarmoring, got:", err)
}
redecData, err := testPrivateKeyRing.DecryptAttachment(pgpSplitMessage)
redecData, err := keyRingTestPrivate.DecryptAttachment(pgpSplitMessage)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}

View file

@ -2,13 +2,21 @@ package crypto
import (
"io/ioutil"
"math/big"
"strings"
"testing"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/rsa"
"github.com/stretchr/testify/assert"
)
var err error
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
func readTestFile(name string, trimNewlines bool) string {
data, err := ioutil.ReadFile("testdata/" + name)
data, err := ioutil.ReadFile("testdata/" + name) //nolint
if err != nil {
panic(err)
}
@ -17,3 +25,48 @@ func readTestFile(name string, trimNewlines bool) string {
}
return string(data)
}
func init() {
UpdateTime(testTime) // 2019-05-13T13:37:07+00:00
initGenerateKeys()
initArmoredKeys()
initKeyRings()
}
func assertBigIntCleared(t *testing.T, x *big.Int) {
w := x.Bits()
for k := range w {
assert.Exactly(t, big.Word(0x00), w[k])
}
}
func assertMemCleared(t *testing.T, b []byte) {
for k := range b {
assert.Exactly(t, uint8(0x00), b[k])
}
}
func assertRSACleared(t *testing.T, rsaPriv *rsa.PrivateKey) {
assertBigIntCleared(t, rsaPriv.D)
for idx := range rsaPriv.Primes {
assertBigIntCleared(t, rsaPriv.Primes[idx])
}
assertBigIntCleared(t, rsaPriv.Precomputed.Qinv)
assertBigIntCleared(t, rsaPriv.Precomputed.Dp)
assertBigIntCleared(t, rsaPriv.Precomputed.Dq)
for idx := range rsaPriv.Precomputed.CRTValues {
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].Exp)
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].Coeff)
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].R)
}
}
func assertEdDSACleared(t *testing.T, priv ed25519.PrivateKey) {
assertMemCleared(t, priv)
}
func assertECDHCleared(t *testing.T, priv *ecdh.PrivateKey) {
assertMemCleared(t, priv.D)
}

View file

@ -4,54 +4,353 @@ import (
"bytes"
"crypto"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"strconv"
"strings"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
xarmor "golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func IsKeyExpired(publicKey []byte) (bool, error) {
now := getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
// Key contains a single private or public key
type Key struct {
// PGP entities in this keyring.
entity *openpgp.Entity
}
// --- Create Key object
// NewKeyFromArmoredReader reads an armored data into a key.
func NewKeyFromArmoredReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, true)
if err != nil {
return true, err
return nil, err
}
for _, e := range pubKeyEntries {
if _, ok := e.EncryptionKey(now); ok {
return key, nil
}
// NewKeyFromReader reads an binary data into Key
func NewKeyFromReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, false)
if err != nil {
return nil, err
}
return key, nil
}
// NewKey creates a new key from the first key in the unarmored binary data
func NewKey(binKeys []byte) (key *Key, err error) {
return NewKeyFromReader(bytes.NewReader(binKeys))
}
// NewKeyFromArmored creates a new key from the first key in an armored
func NewKeyFromArmored(armored string) (key *Key, err error) {
return NewKeyFromArmoredReader(strings.NewReader(armored))
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (*Key, error) {
return generateKey(name, email, "rsa", bits, primeone, primetwo, primethree, primefour)
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func GenerateKey(name, email string, keyType string, bits int) (*Key, error) {
return generateKey(name, email, keyType, bits, nil, nil, nil, nil)
}
// --- Operate on key
// Copy creates a deep copy of the key.
func (key *Key) Copy() (*Key, error) {
serialized, err := key.Serialize()
if err != nil {
return nil, err
}
return NewKey(serialized)
}
// Lock locks a copy of the key.
func (key *Key) Lock(passphrase []byte) (*Key, error) {
unlocked, err := key.IsUnlocked()
if err != nil {
return nil, err
}
if !unlocked {
return nil, errors.New("gopenpgp: key is not unlocked")
}
lockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = lockedKey.entity.PrivateKey.Encrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking key")
}
for _, sub := range lockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Encrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking sub key")
}
}
}
locked, err := lockedKey.IsLocked()
if err != nil {
return nil, err
}
if !locked {
return nil, errors.New("gopenpgp: unable to lock key")
}
return lockedKey, nil
}
// Unlock unlocks a copy of the key
func (key *Key) Unlock(passphrase []byte) (*Key, error) {
isLocked, err := key.IsLocked()
if err != nil {
return nil, err
}
if !isLocked {
return nil, errors.New("gopenpgp: key is not locked")
}
unlockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = unlockedKey.entity.PrivateKey.Decrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking key")
}
for _, sub := range unlockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Decrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking sub key")
}
}
}
isUnlocked, err := unlockedKey.IsUnlocked()
if err != nil {
return nil, err
}
if !isUnlocked {
return nil, errors.New("gopenpgp: unable to unlock key")
}
return unlockedKey, nil
}
// --- Export key
func (key *Key) Serialize() ([]byte, error) {
var buffer bytes.Buffer
var err error
if key.entity.PrivateKey == nil {
err = key.entity.Serialize(&buffer)
} else {
err = key.entity.SerializePrivateNoSign(&buffer, nil)
}
return buffer.Bytes(), err
}
func (key *Key) Armor() (string, error) {
serialized, err := key.Serialize()
if err != nil {
return "", err
}
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (key *Key) GetArmoredPublicKey() (s string, err error) {
var outBuf bytes.Buffer
aw, err := xarmor.Encode(&outBuf, openpgp.PublicKeyType, nil)
if err != nil {
return "", err
}
if err = key.entity.Serialize(aw); err != nil {
_ = aw.Close()
return "", err
}
err = aw.Close()
return outBuf.String(), err
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (key *Key) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = key.entity.Serialize(&outBuf); err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
// --- Key object properties
// IsExpired checks whether the key is expired.
func (key *Key) IsExpired() bool {
_, ok := key.entity.EncryptionKey(getNow())
return !ok
}
// IsPrivate returns true if the key is private
func (key *Key) IsPrivate() bool {
return key.entity.PrivateKey != nil
}
// IsLocked checks if a private key is locked
func (key *Key) IsLocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be locked")
}
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
return false, nil
}
}
return true, errors.New("keys expired")
return key.entity.PrivateKey.Encrypted, nil
}
// IsArmoredKeyExpired checks whether the given armored key is expired.
func IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
// IsUnlocked checks if a private key is unlocked
func (key *Key) IsUnlocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be unlocked")
}
return IsKeyExpired(rawPubKey)
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
return false, nil
}
}
return !key.entity.PrivateKey.Encrypted, nil
}
// Check verifies if the public keys match the private key parameters by signing and verifying
func (key *Key) Check() (bool, error) {
var err error
testSign := bytes.Repeat([]byte{0x01}, 64)
testReader := bytes.NewReader(testSign)
if !key.IsPrivate() {
return false, errors.New("gopenpgp: can check only private key")
}
var signBuf bytes.Buffer
if err = openpgp.DetachSign(&signBuf, key.entity, testReader, nil); err != nil {
return false, errors.New("gopenpgp: unable to sign with key")
}
testReader = bytes.NewReader(testSign)
signer, err := openpgp.CheckDetachedSignature(openpgp.EntityList{key.entity}, testReader, &signBuf, nil)
if signer == nil || err != nil {
return false, nil
}
return true, nil
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (key *Key) PrintFingerprints() {
for _, subKey := range key.entity.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:]))
}
// GetHexKeyID returns the key ID, hex encoded as a string
func (key *Key) GetHexKeyID() string {
return strconv.FormatUint(key.GetKeyID(), 16)
}
// GetKeyID returns the key ID, encoded as 8-byte int
func (key *Key) GetKeyID() uint64 {
return key.entity.PrimaryKey.KeyId
}
// GetFingerprint gets the fingerprint from the key
func (key *Key) GetFingerprint() string {
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:])
}
// --- Internal methods
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
func (key *Key) readFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
entities, err = openpgp.ReadArmoredKeyRing(r)
} else {
entities, err = openpgp.ReadKeyRing(r)
}
if err != nil {
return err
}
if len(entities) > 1 {
return errors.New("gopenpgp: the key contains too many entities")
}
if len(entities) == 0 {
return errors.New("gopenpgp: the key does not contain any entity")
}
key.entity = entities[0]
return nil
}
func generateKey(
name, email, passphrase, keyType string,
name, email string,
keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(email) <= 0 {
return "", errors.New("invalid email format")
) (*Key, error) {
if len(email) == 0 {
return nil, errors.New("gopenpgp: invalid email format")
}
if len(name) <= 0 {
return "", errors.New("invalid name format")
if len(name) == 0 {
return nil, errors.New("gopenpgp: invalid name format")
}
comments := ""
@ -84,114 +383,16 @@ func generateKey(
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
return nil, err
}
if err := newEntity.SelfSign(nil); err != nil {
return "", err
return nil, err
}
rawPwd := []byte(passphrase)
if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted {
if err := newEntity.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
if newEntity.PrivateKey == nil {
return nil, errors.New("gopenpgp: error in generating private key")
}
for _, sub := range newEntity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
}
}
w := bytes.NewBuffer(nil)
if err := newEntity.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return generateKey(name, email, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return generateKey(name, email, passphrase, keyType, bits, nil, nil, nil, nil)
}
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
privKey := strings.NewReader(privateKey)
privKeyEntries, err := openpgp.ReadArmoredKeyRing(privKey)
if err != nil {
return "", err
}
oldrawPwd := []byte(oldPassphrase)
newRawPwd := []byte(newPassphrase)
w := bytes.NewBuffer(nil)
for _, e := range privKeyEntries {
if e.PrivateKey != nil && e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
if err := e.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
}
if err := e.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {
return "", err
}
for _, e := range entries {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(e.PrimaryKey.Fingerprint[:]))
}
return "", nil
return &Key{newEntity}, nil
}

128
crypto/key_clear.go Normal file
View file

@ -0,0 +1,128 @@
package crypto
import (
"crypto/dsa"
"crypto/ecdsa"
"errors"
"math/big"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/openpgp/elgamal"
"golang.org/x/crypto/rsa"
)
func (sk *SessionKey) Clear() (ok bool) {
clearMem(sk.Key)
return true
}
func (key *Key) ClearPrivateParams() (ok bool) {
num := key.clearPrivateWithSubkeys()
key.entity.PrivateKey = nil
for k := range key.entity.Subkeys {
key.entity.Subkeys[k].PrivateKey = nil
}
return num > 0
}
func (key *Key) clearPrivateWithSubkeys() (num int) {
num = 0
if key.entity.PrivateKey != nil {
err := clearPrivateKey(key.entity.PrivateKey.PrivateKey)
if err == nil {
num++
}
}
for k := range key.entity.Subkeys {
if key.entity.Subkeys[k].PrivateKey != nil {
err := clearPrivateKey(key.entity.Subkeys[k].PrivateKey.PrivateKey)
if err == nil {
num++
}
}
}
return num
}
func clearPrivateKey(privateKey interface{}) error {
switch priv := privateKey.(type) {
case *rsa.PrivateKey:
return clearRSAPrivateKey(priv)
case *dsa.PrivateKey:
return clearDSAPrivateKey(priv)
case *elgamal.PrivateKey:
return clearElGamalPrivateKey(priv)
case *ecdsa.PrivateKey:
return clearECDSAPrivateKey(priv)
case ed25519.PrivateKey:
return clearEdDSAPrivateKey(priv)
case *ecdh.PrivateKey:
return clearECDHPrivateKey(priv)
default:
return errors.New("gopenpgp: unknown private key")
}
}
func clearBigInt(n *big.Int) {
w := n.Bits()
for k := range w {
w[k] = 0x00
}
}
func clearMem(w []byte) {
for k := range w {
w[k] = 0x00
}
}
func clearRSAPrivateKey(rsaPriv *rsa.PrivateKey) error {
clearBigInt(rsaPriv.D)
for idx := range rsaPriv.Primes {
clearBigInt(rsaPriv.Primes[idx])
}
clearBigInt(rsaPriv.Precomputed.Qinv)
clearBigInt(rsaPriv.Precomputed.Dp)
clearBigInt(rsaPriv.Precomputed.Dq)
for idx := range rsaPriv.Precomputed.CRTValues {
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Exp)
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Coeff)
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].R)
}
return nil
}
func clearDSAPrivateKey(priv *dsa.PrivateKey) error {
clearBigInt(priv.X)
return nil
}
func clearElGamalPrivateKey(priv *elgamal.PrivateKey) error {
clearBigInt(priv.X)
return nil
}
func clearECDSAPrivateKey(priv *ecdsa.PrivateKey) error {
clearBigInt(priv.D)
return nil
}
func clearEdDSAPrivateKey(priv ed25519.PrivateKey) error {
clearMem(priv)
return nil
}
func clearECDHPrivateKey(priv *ecdh.PrivateKey) error {
clearMem(priv.D)
return nil
}

View file

@ -2,131 +2,209 @@ package crypto
import (
"encoding/base64"
"io/ioutil"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/rsa"
"github.com/stretchr/testify/assert"
)
const name = "Richard M. Stallman"
const domain = "rms@protonmail.ch"
const keyTestName = "Max Mustermann"
const keyTestDomain = "max.mustermann@protonmail.ch"
var passphrase = "I love GNU"
var rsaKey, ecKey, rsaPublicKey, ecPublicKey string
var keyTestPassphrase = []byte("I love GNU")
var (
rsaPrivateKeyRing *KeyRing
ecPrivateKeyRing *KeyRing
rsaPublicKeyRing *KeyRing
ecPublicKeyRing *KeyRing
keyTestArmoredRSA string
keyTestArmoredEC string
keyTestRSA *Key
keyTestEC *Key
)
func TestGenerateKeys(t *testing.T) {
rsaKey, err = GenerateKey(name, domain, passphrase, "rsa", 1024)
func initGenerateKeys() {
var err error
keyTestRSA, err = GenerateKey(keyTestName, keyTestDomain, "rsa", 1024)
if err != nil {
t.Fatal("Cannot generate RSA key:", err)
panic("Cannot generate RSA key:" + err.Error())
}
ecKey, err = GenerateKey(name, domain, passphrase, "x25519", 256)
keyTestEC, err = GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
if err != nil {
t.Fatal("Cannot generate EC key:", err)
panic("Cannot generate EC key:" + err.Error())
}
}
func initArmoredKeys() {
var err error
lockedRSA, err := keyTestRSA.Lock(keyTestPassphrase)
if err != nil {
panic("Cannot lock RSA key:" + err.Error())
}
keyTestArmoredRSA, err = lockedRSA.Armor()
if err != nil {
panic("Cannot armor protected RSA key:" + err.Error())
}
lockedEC, err := keyTestEC.Lock(keyTestPassphrase)
if err != nil {
panic("Cannot lock EC key:" + err.Error())
}
keyTestArmoredEC, err = lockedEC.Armor()
if err != nil {
panic("Cannot armor protected EC key:" + err.Error())
}
}
func TestArmorKeys(t *testing.T) {
var err error
noPasswordRSA, err := keyTestRSA.Armor()
if err != nil {
t.Fatal("Cannot armor unprotected RSA key:" + err.Error())
}
noPasswordEC, err := keyTestEC.Armor()
if err != nil {
t.Fatal("Cannot armor unprotected EC key:" + err.Error())
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
assert.Regexp(t, rTest, rsaKey)
assert.Regexp(t, rTest, ecKey)
assert.Regexp(t, rTest, noPasswordRSA)
assert.Regexp(t, rTest, noPasswordEC)
assert.Regexp(t, rTest, keyTestArmoredRSA)
assert.Regexp(t, rTest, keyTestArmoredEC)
}
func TestGenerateKeyRings(t *testing.T) {
rsaPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaKey))
func TestLockUnlockKeys(t *testing.T) {
testLockUnlockKey(t, keyTestArmoredRSA, keyTestPassphrase)
testLockUnlockKey(t, keyTestArmoredEC, keyTestPassphrase)
testLockUnlockKey(t, readTestFile("keyring_privateKey", false), testMailboxPassword)
publicKey, err := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
if err != nil {
t.Fatal("Cannot read RSA key:", err)
t.Fatal("Cannot unarmor key:", err)
}
rsaPublicKey, err = rsaPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Cannot extract RSA public key:", err)
_, err = publicKey.IsLocked()
if err == nil {
t.Fatal("Should not be able to check locked on public key:")
}
rsaPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaPublicKey))
if err != nil {
t.Fatal("Cannot read RSA public key:", err)
_, err = publicKey.IsUnlocked()
if err == nil {
t.Fatal("Should not be able to check unlocked on public key:")
}
err = rsaPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
_, err = publicKey.Unlock(testMailboxPassword)
if err == nil {
t.Fatal("Should not be able to unlock public key:")
}
ecPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecKey))
if err != nil {
t.Fatal("Cannot read EC key:", err)
}
ecPublicKey, err = ecPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Cannot extract EC public key:", err)
}
ecPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecPublicKey))
if err != nil {
t.Fatal("Cannot read EC public key:", err)
}
err = ecPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt EC key:", err)
_, err = publicKey.Lock(keyTestPassphrase)
if err == nil {
t.Fatal("Should not be able to lock public key:")
}
}
func TestUpdatePrivateKeysPassphrase(t *testing.T) {
newPassphrase := "I like GNU"
rsaKey, err = UpdatePrivateKeyPassphrase(rsaKey, passphrase, newPassphrase)
func testLockUnlockKey(t *testing.T, armoredKey string, pass []byte) {
var err error
lockedKey, err := NewKeyFromArmored(armoredKey)
if err != nil {
t.Fatal("Error in changing RSA key's passphrase:", err)
t.Fatal("Cannot unarmor key:", err)
}
ecKey, err = UpdatePrivateKeyPassphrase(ecKey, passphrase, newPassphrase)
// Check if key is locked
locked, err := lockedKey.IsLocked()
if err != nil {
t.Fatal("Error in changing EC key's passphrase:", err)
t.Fatal("Cannot check if key is unlocked:", err)
}
passphrase = newPassphrase
if !locked {
t.Fatal("Key should be fully locked")
}
unlockedKey, err := lockedKey.Unlock(pass)
if err != nil {
t.Fatal("Cannot unlock key:", err)
}
// Check if key was successfully unlocked
unlocked, err := unlockedKey.IsUnlocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !unlocked {
t.Fatal("Key should be fully unlocked")
}
// Check if action is performed on copy
locked, err = lockedKey.IsLocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !locked {
t.Fatal("Key should be fully locked")
}
// re-lock key
relockedKey, err := unlockedKey.Lock(keyTestPassphrase)
if err != nil {
t.Fatal("Cannot lock key:", err)
}
// Check if key was successfully locked
relocked, err := relockedKey.IsLocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !relocked {
t.Fatal("Key should be fully locked")
}
// Check if action is performed on copy
unlocked, err = unlockedKey.IsUnlocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !unlocked {
t.Fatal("Key should be fully unlocked")
}
}
func ExamplePrintFingerprints() {
_, _ = PrintFingerprints(readTestFile("keyring_publicKey", false))
func ExampleKey_PrintFingerprints() {
keyringKey, _ := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
keyringKey.PrintFingerprints()
// Output:
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
}
func TestIsArmoredKeyExpired(t *testing.T) {
rsaRes, err := IsArmoredKeyExpired(rsaPublicKey)
func TestIsExpired(t *testing.T) {
assert.Exactly(t, false, keyTestRSA.IsExpired())
assert.Exactly(t, false, keyTestEC.IsExpired())
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
if err != nil {
t.Fatal("Error in checking expiration of RSA key:", err)
t.Fatal("Cannot unarmor expired key:", err)
}
ecRes, err := IsArmoredKeyExpired(ecPublicKey)
futureKey, err := NewKeyFromArmored(readTestFile("key_futureKey", false))
if err != nil {
t.Fatal("Error in checking expiration of EC key:", err)
t.Fatal("Cannot unarmor future key:", err)
}
assert.Exactly(t, false, rsaRes)
assert.Exactly(t, false, ecRes)
UpdateTime(1557754627) // 2019-05-13T13:37:07+00:00
expRes, expErr := IsArmoredKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := IsArmoredKeyExpired(readTestFile("key_futureKey", false))
assert.Exactly(t, true, expRes)
assert.Exactly(t, true, futureRes)
assert.EqualError(t, expErr, "keys expired")
assert.EqualError(t, futureErr, "keys expired")
assert.Exactly(t, true, expiredKey.IsExpired())
assert.Exactly(t, true, futureKey.IsExpired())
}
func TestGenerateKeyWithPrimes(t *testing.T) {
@ -139,24 +217,96 @@ func TestGenerateKeyWithPrimes(t *testing.T) {
prime4, _ := base64.StdEncoding.DecodeString(
"58UEDXTX29Q9JqvuE3Tn+Qj275CXBnJbA8IVM4d05cPYAZ6H43bPN01pbJqJTJw/cuFxs+8C+HNw3/MGQOExqw==")
staticRsaKey, err := GenerateRSAKeyWithPrimes(name, domain, passphrase, 1024, prime1, prime2, prime3, prime4)
staticRsaKey, err := GenerateRSAKeyWithPrimes(keyTestName, keyTestDomain, 1024, prime1, prime2, prime3, prime4)
if err != nil {
t.Fatal("Cannot generate RSA key:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
assert.Regexp(t, rTest, staticRsaKey)
staticRsaKeyRing, err := ReadArmoredKeyRing(strings.NewReader(staticRsaKey))
if err != nil {
t.Fatal("Cannot read RSA key:", err)
t.Fatal("Cannot generate RSA key with primes:", err)
}
err = staticRsaKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
}
pk := staticRsaKeyRing.GetEntities()[0].PrivateKey.PrivateKey.(*rsa.PrivateKey)
assert.Exactly(t, prime1, pk.Primes[1].Bytes())
assert.Exactly(t, prime2, pk.Primes[0].Bytes())
pk := staticRsaKey.entity.PrivateKey.PrivateKey.(*rsa.PrivateKey)
assert.Exactly(t, prime1, pk.Primes[0].Bytes())
assert.Exactly(t, prime2, pk.Primes[1].Bytes())
}
func TestCheckIntegrity(t *testing.T) {
isVerified, err := keyTestRSA.Check()
if err != nil {
t.Fatal("Expected no error while checking correct passphrase, got:", err)
}
assert.Exactly(t, true, isVerified)
}
func TestFailCheckIntegrity(t *testing.T) {
// This test is done with ECC because in an RSA key we would need to replace the primes, but maintaining the moduli,
// that is a private struct element.
k1, _ := GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
k2, _ := GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
k1.entity.PrivateKey.PrivateKey = k2.entity.PrivateKey.PrivateKey // Swap private keys
k3, err := k1.Copy()
if err != nil {
t.Fatal("Expected no error while locking keyring kr3, got:", err)
}
isVerified, err := k3.Check()
if err != nil {
t.Fatal("Expected no error while checking correct passphrase, got:", err)
}
assert.Exactly(t, false, isVerified)
}
func TestArmorPublicKey(t *testing.T) {
publicKey, err := keyTestRSA.GetPublicKey()
if err != nil {
t.Fatal("Expected no error while obtaining public key, got:", err)
}
decodedKey, err := NewKey(publicKey)
if err != nil {
t.Fatal("Expected no error while creating public key ring, got:", err)
}
privateFingerprint := keyTestRSA.GetFingerprint()
publicFingerprint := decodedKey.GetFingerprint()
assert.Exactly(t, privateFingerprint, publicFingerprint)
}
func TestGetArmoredPublicKey(t *testing.T) {
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
if err != nil {
t.Fatal("Expected no error while unarmouring private key, got:", err)
}
s, err := privateKey.GetArmoredPublicKey()
if err != nil {
t.Fatal("Expected no error while getting armored public key, got:", err)
}
// Decode armored keys
block, err := armor.Decode(strings.NewReader(s))
if err != nil {
t.Fatal("Expected no error while decoding armored public key, got:", err)
}
expected, err := armor.Decode(strings.NewReader(readTestFile("keyring_publicKey", false)))
if err != nil {
t.Fatal("Expected no error while decoding expected armored public key, got:", err)
}
assert.Exactly(t, expected.Type, block.Type)
b, err := ioutil.ReadAll(block.Body)
if err != nil {
t.Fatal("Expected no error while reading armored public key body, got:", err)
}
eb, err := ioutil.ReadAll(expected.Body)
if err != nil {
t.Fatal("Expected no error while reading expected armored public key body, got:", err)
}
assert.Exactly(t, eb, b)
}

View file

@ -2,19 +2,11 @@ package crypto
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"encoding/hex"
"errors"
"io"
"time"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
xrsa "golang.org/x/crypto/rsa"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
)
// KeyRing contains multiple private and public keys.
@ -32,13 +24,52 @@ type Identity struct {
Email string
}
// GetEntities returns openpgp entities contained in this KeyRing.
func (keyRing *KeyRing) GetEntities() openpgp.EntityList {
return keyRing.entities
// --- New keyrings
// NewKeyRing creates a new KeyRing, empty if key is nil
func NewKeyRing(key *Key) (*KeyRing, error) {
keyRing := &KeyRing{}
var err error
if key != nil {
err = keyRing.AddKey(key)
}
return keyRing, err
}
// GetSigningEntity returns first private unlocked signing entity from keyring.
func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
// --- Add keys to keyring
func (keyRing *KeyRing) AddKey(key *Key) error {
if key.IsPrivate() {
unlocked, err := key.IsUnlocked()
if err != nil || !unlocked {
return errors.New("gopenpgp: unable to add locked key to a keyring")
}
}
keyRing.appendKey(key)
return nil
}
// --- Extract keys from keyring
// GetKeys returns openpgp keys contained in this KeyRing.
func (keyRing *KeyRing) GetKeys() []*Key {
keys := make([]*Key, keyRing.CountEntities())
for i, entity := range keyRing.entities {
keys[i] = &Key{entity}
}
return keys
}
// GetKey returns the n-th openpgp key contained in this KeyRing.
func (keyRing *KeyRing) GetKey(n int) (*Key, error) {
if n >= keyRing.CountEntities() {
return nil, errors.New("gopenpgp: out of bound when fetching key")
}
return &Key{keyRing.entities[n]}, nil
}
// getSigningEntity returns first private unlocked signing entity from keyring.
func (keyRing *KeyRing) getSigningEntity() (*openpgp.Entity, error) {
var signEntity *openpgp.Entity
for _, e := range keyRing.entities {
@ -58,218 +89,20 @@ func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
return signEntity, nil
}
// Unlock tries to unlock as many keys as possible with the following password. Note
// that keyrings can contain keys locked with different passwords, and thus
// err == nil does not mean that all keys have been successfully decrypted.
// If err != nil, the password is wrong for every key, and err is the last error
// encountered.
func (keyRing *KeyRing) Unlock(passphrase []byte) error {
// Build a list of keys to decrypt
var keys []*packet.PrivateKey
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
keys = append(keys, e.PrivateKey)
}
// --- Extract info from key
// Entity.Subkeys can be used for encryption
for _, subKey := range e.Subkeys {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage ||
subKey.Sig.FlagEncryptCommunications) {
keys = append(keys, subKey.PrivateKey)
}
}
}
if len(keys) == 0 {
return errors.New("gopenpgp: cannot unlock key ring, no private key available")
}
var err error
var n int
for _, key := range keys {
if !key.Encrypted {
continue // Key already decrypted
}
if err = key.Decrypt(passphrase); err == nil {
n++
}
}
if n == 0 {
return err
}
return nil
// CountEntities returns the number of entities in the keyring
func (keyRing *KeyRing) CountEntities() int {
return len(keyRing.entities)
}
// UnlockWithPassphrase is a wrapper for Unlock that uses strings
func (keyRing *KeyRing) UnlockWithPassphrase(passphrase string) error {
return keyRing.Unlock([]byte(passphrase))
}
// WriteArmoredPublicKey outputs armored public keys from the keyring to w.
func (keyRing *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
aw, err := armor.Encode(w, openpgp.PublicKeyType, nil)
if err != nil {
return
}
for _, e := range keyRing.entities {
if err = e.Serialize(aw); err != nil {
aw.Close()
return
}
}
err = aw.Close()
return
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (keyRing *KeyRing) GetArmoredPublicKey() (s string, err error) {
b := &bytes.Buffer{}
if err = keyRing.WriteArmoredPublicKey(b); err != nil {
return
}
s = b.String()
return
}
// WritePublicKey outputs unarmored public keys from the keyring to w.
func (keyRing *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range keyRing.entities {
if err = e.Serialize(w); err != nil {
return
}
}
return
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (keyRing *KeyRing) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = keyRing.WritePublicKey(&outBuf); err != nil {
return
}
b = outBuf.Bytes()
return
}
// GetFingerprint gets the fingerprint from the keyring.
func (keyRing *KeyRing) GetFingerprint() (string, error) {
for _, entity := range keyRing.entities {
fp := entity.PrimaryKey.Fingerprint
return hex.EncodeToString(fp[:]), nil
}
return "", errors.New("can't find public key")
}
// CheckPassphrase checks if private key passphrase is correct for every sub key.
func (keyRing *KeyRing) CheckPassphrase(passphrase string) bool {
var keys []*packet.PrivateKey
for _, entity := range keyRing.entities {
keys = append(keys, entity.PrivateKey)
}
var decryptError error
var n int
for _, key := range keys {
if !key.Encrypted {
continue // Key already decrypted
}
if decryptError = key.Decrypt([]byte(passphrase)); decryptError == nil {
n++
}
}
return n != 0
}
// ReadFrom reads unarmored and armored keys from r and adds them to the keyring.
func (keyRing *KeyRing) ReadFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
entities, err = openpgp.ReadArmoredKeyRing(r)
} else {
entities, err = openpgp.ReadKeyRing(r)
}
for _, entity := range entities {
if entity.PrivateKey != nil {
switch entity.PrivateKey.PrivateKey.(type) {
// TODO: type mismatch after crypto lib update, fix this:
case *rsa.PrivateKey:
entity.PrimaryKey = packet.NewRSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
entity.PrimaryKey = packet.NewECDSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey != nil {
switch subkey.PrivateKey.PrivateKey.(type) {
case *rsa.PrivateKey:
subkey.PublicKey = packet.NewRSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
subkey.PublicKey = packet.NewECDSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
}
}
if err != nil {
return err
}
if len(entities) == 0 {
return errors.New("gopenpgp: key ring doesn't contain any key")
}
keyRing.entities = append(keyRing.entities, entities...)
return nil
}
// BuildKeyRing reads keyring from binary data
func BuildKeyRing(binKeys []byte) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
entriesReader := bytes.NewReader(binKeys)
err = keyRing.ReadFrom(entriesReader, false)
return
}
// BuildKeyRingNoError does not return error on fail
func BuildKeyRingNoError(binKeys []byte) (keyRing *KeyRing) {
keyRing, _ = BuildKeyRing(binKeys)
return
}
// BuildKeyRingArmored reads armored string and returns keyring
func BuildKeyRingArmored(key string) (keyRing *KeyRing, err error) {
keyRaw, err := armorUtils.Unarmor(key)
if err != nil {
return nil, err
}
keyReader := bytes.NewReader(keyRaw)
keyEntries, err := openpgp.ReadKeyRing(keyReader)
return &KeyRing{entities: keyEntries}, err
// CountDecryptionEntities returns the number of entities in the keyring
func (keyRing *KeyRing) CountDecryptionEntities() int {
return len(keyRing.entities.DecryptionKeys())
}
// Identities returns the list of identities associated with this key ring.
func (keyRing *KeyRing) Identities() []*Identity {
func (keyRing *KeyRing) GetIdentities() []*Identity {
var identities []*Identity
for _, e := range keyRing.entities {
for _, id := range e.Identities {
@ -282,28 +115,16 @@ func (keyRing *KeyRing) Identities() []*Identity {
return identities
}
// KeyIds returns array of IDs of keys in this KeyRing.
func (keyRing *KeyRing) KeyIds() []uint64 {
var res []uint64
for _, e := range keyRing.entities {
res = append(res, e.PrimaryKey.KeyId)
// GetKeyIDs returns array of IDs of keys in this KeyRing.
func (keyRing *KeyRing) GetKeyIDs() []uint64 {
var res = make([]uint64, len(keyRing.entities))
for id, e := range keyRing.entities {
res[id] = e.PrimaryKey.KeyId
}
return res
}
// ReadArmoredKeyRing reads an armored data into keyring.
func ReadArmoredKeyRing(r io.Reader) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.ReadFrom(r, true)
return
}
// ReadKeyRing reads an binary data into keyring.
func ReadKeyRing(r io.Reader) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.ReadFrom(r, false)
return
}
// --- Filter keyrings
// FilterExpiredKeys takes a given KeyRing list and it returns only those
// KeyRings which contain at least, one unexpired Key. It returns only unexpired
@ -316,7 +137,7 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
for _, contactKeyRing := range contactKeys {
keyRingHasUnexpiredEntity := false
keyRingHasTotallyExpiredEntity := false
for _, entity := range contactKeyRing.GetEntities() {
for _, entity := range contactKeyRing.entities {
hasExpired := false
hasUnexpired := false
for _, subkey := range entity.Subkeys {
@ -333,7 +154,12 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
}
if keyRingHasUnexpiredEntity {
filteredKeys = append(filteredKeys, contactKeyRing)
keyRingCopy, err := contactKeyRing.Copy()
if err != nil {
return nil, err
}
filteredKeys = append(filteredKeys, keyRingCopy)
} else if keyRingHasTotallyExpiredEntity {
hasExpiredEntity = true
}
@ -347,12 +173,57 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
// FirstKey returns a KeyRing with only the first key of the original one
func (keyRing *KeyRing) FirstKey() *KeyRing {
func (keyRing *KeyRing) FirstKey() (*KeyRing, error) {
if len(keyRing.entities) == 0 {
return nil
return nil, errors.New("gopenpgp: No key available in this keyring")
}
newKeyRing := &KeyRing{}
newKeyRing.entities = keyRing.entities[:1]
return newKeyRing
return newKeyRing.Copy()
}
// Copy creates a deep copy of the keyring
func (keyRing *KeyRing) Copy() (*KeyRing, error) {
newKeyRing := &KeyRing{}
entities := make([]*openpgp.Entity, len(keyRing.entities))
for id, entity := range keyRing.entities {
var buffer bytes.Buffer
var err error
if entity.PrivateKey == nil {
err = entity.Serialize(&buffer)
} else {
err = entity.SerializePrivateNoSign(&buffer, nil)
}
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in serializing entity")
}
bt := buffer.Bytes()
entities[id], err = openpgp.ReadEntity(packet.NewReader(bytes.NewReader(bt)))
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in reading entity")
}
}
newKeyRing.entities = entities
newKeyRing.FirstKeyID = keyRing.FirstKeyID
return newKeyRing, nil
}
func (keyRing *KeyRing) ClearPrivateParams() {
for _, key := range keyRing.GetKeys() {
key.ClearPrivateParams()
}
}
// INTERNAL FUNCTIONS
// append appends a key to the keyring
func (keyRing *KeyRing) appendKey(key *Key) {
keyRing.entities = append(keyRing.entities, key.entity)
}

View file

@ -39,7 +39,7 @@ func (keyRing *KeyRing) Decrypt(
// SignDetached generates and returns a PGPSignature for a given PlainMessage
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
signEntity, err := keyRing.GetSigningEntity()
signEntity, err := keyRing.getSigningEntity()
if err != nil {
return nil, err
}
@ -56,11 +56,9 @@ func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, erro
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
// and returns a SignatureVerificationError if fails
func (keyRing *KeyRing) VerifyDetached(
message *PlainMessage, signature *PGPSignature, verifyTime int64,
) (error) {
func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error {
return verifySignature(
keyRing.GetEntities(),
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
@ -78,7 +76,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
if privateKey != nil && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity()
signEntity, err = privateKey.getSigningEntity()
if err != nil {
return nil, err
}
@ -101,8 +99,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
}
_, err = encryptWriter.Write(data)
encryptWriter.Close()
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
@ -114,11 +115,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
func asymmetricDecrypt(
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
) (plaintext []byte, err error) {
privKeyEntries := privateKey.GetEntities()
privKeyEntries := privateKey.entities
var additionalEntries openpgp.EntityList
if verifyKey != nil {
additionalEntries = verifyKey.GetEntities()
additionalEntries = verifyKey.entities
}
if additionalEntries != nil {

View file

@ -2,31 +2,15 @@ package crypto
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp/packet"
)
// RandomToken generated a random token of the same size of the keysize of the default cipher.
func RandomToken() ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
return RandomTokenSize(config.DefaultCipher.KeySize())
}
// RandomTokenSize generates a random token with the specified key size
func RandomTokenSize(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
}
// DecryptSessionKey returns the decrypted session key from a binary encrypted session key packet.
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error) {
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
@ -57,18 +41,21 @@ func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, erro
return nil, errors.New("gopenpgp: unable to decrypt session key")
}
return newSymmetricKeyFromEncrypted(ek)
return newSessionKeyFromEncrypted(ek)
}
// EncryptSessionKey encrypts the session key with the unarmored
// publicKey and returns a binary public-key encrypted session key packet.
func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error) {
func (keyRing *KeyRing) EncryptSessionKey(sk *SessionKey) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
cf, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key")
}
var pub *packet.PublicKey
for _, e := range keyRing.GetEntities() {
for _, e := range keyRing.entities {
if encryptionKey, ok := e.EncryptionKey(getNow()); ok {
pub = encryptionKey.PublicKey
break
@ -78,7 +65,7 @@ func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, e
return nil, errors.New("cannot set key: no public key available")
}
if err := packet.SerializeEncryptedKey(outbuf, pub, cf, sessionSplit.Key, nil); err != nil {
if err := packet.SerializeEncryptedKey(outbuf, pub, cf, sk.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return nil, err
}

View file

@ -1,38 +1,27 @@
package crypto
import (
"encoding/base64"
"io/ioutil"
"strings"
"testing"
"golang.org/x/crypto/openpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/rsa"
)
var decodedSymmetricKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var testSymmetricKey = &SymmetricKey{
Key: decodedSymmetricKey,
Algo: constants.AES256,
}
var testWrongSymmetricKey = &SymmetricKey{
Key: []byte("WrongPass"),
Algo: constants.AES256,
}
var testSymmetricKey []byte
// Corresponding key in testdata/keyring_privateKey
const testMailboxPassword = "apple"
var testMailboxPassword = []byte("apple")
// Corresponding key in testdata/keyring_privateKeyLegacy
// const testMailboxPasswordLegacy = "123"
// const testMailboxPasswordLegacy = [][]byte{ []byte("123") }
var (
testPrivateKeyRing *KeyRing
testPublicKeyRing *KeyRing
keyRingTestPrivate *KeyRing
keyRingTestPublic *KeyRing
keyRingTestMultiple *KeyRing
)
var testIdentity = &Identity{
@ -40,75 +29,83 @@ var testIdentity = &Identity{
Email: "",
}
func init() {
func initKeyRings() {
var err error
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
testSymmetricKey, err = RandomToken(32)
if err != nil {
panic(err)
panic("Expected no error while generating random token, got:" + err.Error())
}
testPublicKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
if err != nil {
panic(err)
panic("Expected no error while unarmoring private key, got:" + err.Error())
}
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
panic(err)
}
}
func TestKeyRing_ArmoredPublicKeyString(t *testing.T) {
s, err := testPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Expected no error while getting armored public key, got:", err)
keyRingTestPrivate, err = NewKeyRing(privateKey)
if err == nil {
panic("Able to create a keyring with a locked key")
}
// Decode armored keys
block, err := armor.Decode(strings.NewReader(s))
unlockedKey, err := privateKey.Unlock(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error while decoding armored public key, got:", err)
panic("Expected no error while unlocking private key, got:" + err.Error())
}
expected, err := armor.Decode(strings.NewReader(readTestFile("keyring_publicKey", false)))
keyRingTestPrivate, err = NewKeyRing(unlockedKey)
if err != nil {
t.Fatal("Expected no error while decoding expected armored public key, got:", err)
panic("Expected no error while building private keyring, got:" + err.Error())
}
assert.Exactly(t, expected.Type, block.Type)
b, err := ioutil.ReadAll(block.Body)
publicKey, err := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
if err != nil {
t.Fatal("Expected no error while reading armored public key body, got:", err)
panic("Expected no error while unarmoring public key, got:" + err.Error())
}
eb, err := ioutil.ReadAll(expected.Body)
keyRingTestPublic, err = NewKeyRing(publicKey)
if err != nil {
t.Fatal("Expected no error while reading expected armored public key body, got:", err)
panic("Expected no error while building public keyring, got:" + err.Error())
}
assert.Exactly(t, eb, b)
}
keyRingTestMultiple, err = NewKeyRing(nil)
if err != nil {
panic("Expected no error while building empty keyring, got:" + err.Error())
}
func TestCheckPassphrase(t *testing.T) {
encryptedKeyRing, _ := BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
isCorrect := encryptedKeyRing.CheckPassphrase("Wrong password")
assert.Exactly(t, false, isCorrect)
err = keyRingTestMultiple.AddKey(keyTestRSA)
if err != nil {
panic("Expected no error while adding RSA key to keyring, got:" + err.Error())
}
isCorrect = encryptedKeyRing.CheckPassphrase(testMailboxPassword)
assert.Exactly(t, true, isCorrect)
err = keyRingTestMultiple.AddKey(keyTestEC)
if err != nil {
panic("Expected no error while adding EC key to keyring, got:" + err.Error())
}
err = keyRingTestMultiple.AddKey(unlockedKey)
if err != nil {
panic("Expected no error while adding unlocked key to keyring, got:" + err.Error())
}
}
func TestIdentities(t *testing.T) {
identities := testPrivateKeyRing.Identities()
identities := keyRingTestPrivate.GetIdentities()
assert.Len(t, identities, 1)
assert.Exactly(t, identities[0], testIdentity)
}
func TestFilterExpiredKeys(t *testing.T) {
expiredKey, _ := BuildKeyRingArmored(readTestFile("key_expiredKey", false))
keys := []*KeyRing{testPrivateKeyRing, expiredKey}
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
if err != nil {
t.Fatal("Cannot unarmor expired key:", err)
}
expiredKeyRing, err := NewKeyRing(expiredKey)
if err != nil {
t.Fatal("Cannot create keyring with expired key:", err)
}
keys := []*KeyRing{keyRingTestPrivate, expiredKeyRing}
unexpired, err := FilterExpiredKeys(keys)
if err != nil {
@ -116,60 +113,89 @@ func TestFilterExpiredKeys(t *testing.T) {
}
assert.Len(t, unexpired, 1)
assert.Exactly(t, unexpired[0], testPrivateKeyRing)
}
func TestGetPublicKey(t *testing.T) {
publicKey, err := testPrivateKeyRing.GetPublicKey()
if err != nil {
t.Fatal("Expected no error while obtaining public key, got:", err)
}
publicKeyRing, err := BuildKeyRing(publicKey)
if err != nil {
t.Fatal("Expected no error while creating public key ring, got:", err)
}
privateFingerprint, err := testPrivateKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting private fingerprint, got:", err)
}
publicFingerprint, err := publicKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting public fingerprint, got:", err)
}
assert.Exactly(t, privateFingerprint, publicFingerprint)
assert.Exactly(t, unexpired[0].GetKeyIDs(), keyRingTestPrivate.GetKeyIDs())
}
func TestKeyIds(t *testing.T) {
keyIDs := testPrivateKeyRing.KeyIds()
keyIDs := keyRingTestPrivate.GetKeyIDs()
var assertKeyIDs = []uint64{4518840640391470884}
assert.Exactly(t, assertKeyIDs, keyIDs)
}
func TestMutlipleKeyRing(t *testing.T) {
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
assert.Exactly(t, 1, len(testPublicKeyRing.entities))
func TestMultipleKeyRing(t *testing.T) {
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
assert.Exactly(t, 3, keyRingTestMultiple.CountEntities())
assert.Exactly(t, 3, keyRingTestMultiple.CountDecryptionEntities())
ids := testPublicKeyRing.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
assert.Exactly(t, 3, len(keyRingTestMultiple.GetKeys()))
err = testPublicKeyRing.ReadFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true)
testKey, err := keyRingTestMultiple.GetKey(1)
if err != nil {
t.Fatal("Expected no error while adding a key to the keyring, got:", err)
t.Fatal("Expected no error while extracting key, got:", err)
}
assert.Exactly(t, keyTestEC, testKey)
_, err = keyRingTestMultiple.GetKey(3)
assert.NotNil(t, err)
singleKeyRing, err := keyRingTestMultiple.FirstKey()
if err != nil {
t.Fatal("Expected no error while filtering the first key, got:", err)
}
assert.Exactly(t, 1, len(singleKeyRing.entities))
assert.Exactly(t, 1, singleKeyRing.CountEntities())
assert.Exactly(t, 1, singleKeyRing.CountDecryptionEntities())
}
func TestClearPrivateKey(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
assert.Exactly(t, 2, len(testPublicKeyRing.entities))
for _, key := range keyRingCopy.GetKeys() {
assert.Nil(t, clearPrivateKey(key.entity.PrivateKey.PrivateKey))
}
ids = testPublicKeyRing.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
assert.Exactly(t, uint64(0x374130b32ee1e5ea), ids[1])
singleKey := testPublicKeyRing.FirstKey()
assert.Exactly(t, 1, len(singleKey.entities))
ids = singleKey.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
keys := keyRingCopy.GetKeys()
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
}
func TestClearPrivateWithSubkeys(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
for _, key := range keyRingCopy.GetKeys() {
assert.Exactly(t, 2, key.clearPrivateWithSubkeys())
}
keys := keyRingCopy.GetKeys()
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertRSACleared(t, keys[0].entity.Subkeys[0].PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
assertECDHCleared(t, keys[1].entity.Subkeys[0].PrivateKey.PrivateKey.(*ecdh.PrivateKey))
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertRSACleared(t, keys[2].entity.Subkeys[0].PrivateKey.PrivateKey.(*rsa.PrivateKey))
}
func TestClearPrivateParams(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
for _, key := range keyRingCopy.GetKeys() {
assert.True(t, key.IsPrivate())
assert.True(t, key.ClearPrivateParams())
assert.False(t, key.IsPrivate())
assert.Nil(t, key.entity.PrivateKey)
assert.Nil(t, key.entity.Subkeys[0].PrivateKey)
assert.False(t, key.ClearPrivateParams())
}
}

View file

@ -10,9 +10,9 @@ import (
"regexp"
"runtime"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/internal"
"golang.org/x/crypto/openpgp/clearsign"
"golang.org/x/crypto/openpgp/packet"
@ -51,7 +51,7 @@ type PGPSplitMessage struct {
// A Cleartext message is a signed PGP message, that is not encrypted,
// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE-----
type ClearTextMessage struct {
Data []byte
Data []byte
Signature []byte
}
@ -146,7 +146,7 @@ func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
// NewClearTextMessage generates a new ClearTextMessage from data and signature
func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage {
return &ClearTextMessage{
Data: data,
Data: data,
Signature: signature,
}
}
@ -183,7 +183,7 @@ func (msg *PlainMessage) GetBase64() string {
return base64.StdEncoding.EncodeToString(msg.Data)
}
// NewReader returns a New io.Reader for the bianry data of the message
// NewReader returns a New io.Reader for the binary data of the message
func (msg *PlainMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
@ -203,7 +203,7 @@ func (msg *PGPMessage) GetBinary() []byte {
return msg.Data
}
// NewReader returns a New io.Reader for the unarmored bianry data of the message
// NewReader returns a New io.Reader for the unarmored binary data of the message
func (msg *PGPMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
@ -225,7 +225,7 @@ func (msg *PGPSplitMessage) GetBinaryKeyPacket() []byte {
// GetBinary returns the unarmored binary joined packets as a []byte
func (msg *PGPSplitMessage) GetBinary() []byte {
return append(msg.KeyPacket , msg.DataPacket...)
return append(msg.KeyPacket, msg.DataPacket...)
}
// GetArmored returns the armored message as a string, with joined data and key packets
@ -233,6 +233,11 @@ func (msg *PGPSplitMessage) GetArmored() (string, error) {
return armor.ArmorWithType(msg.GetBinary(), constants.PGPMessageHeader)
}
// GetPGPMessage joins asymmetric session key packet with the symmetric data packet to obtain a PGP message
func (msg *PGPSplitMessage) GetPGPMessage() *PGPMessage {
return NewPGPMessage(append(msg.KeyPacket, msg.DataPacket...))
}
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
// * estimatedLength is the estimate length of the message
// * garbageCollector > 0 activates the garbage collector
@ -244,7 +249,6 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// Store encrypted key and symmetrically encrypted packet separately
var encryptedKey *packet.EncryptedKey
var decryptErr error
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
@ -259,7 +263,7 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
encryptedKey = p
case *packet.SymmetricallyEncrypted:
// FIXME: add support for multiple keypackets
// TODO: add support for multiple keypackets
var b bytes.Buffer
// 2^16 is an estimation of the size difference between input and output, the size difference is most probably
// 16 bytes at a maximum though.
@ -267,8 +271,14 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
if _, err := b.Write(make([]byte, 6)); err != nil {
return nil, err
}
if err := b.WriteByte(byte(1)); err != nil {
return nil, err
}
actualLength := 1
block := make([]byte, 128)
for {
@ -276,7 +286,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
if err == io.EOF {
break
}
b.Write(block[:n])
if _, err := b.Write(block[:n]); err != nil {
return nil, err
}
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
@ -287,17 +299,18 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
switch {
case actualLength < 192:
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
case actualLength < 8384:
actualLength -= 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
default:
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
@ -305,13 +318,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
return nil, fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
}
if encryptedKey == nil {
return nil, errors.New("gopenpgp: packets don't include an encrypted key packet")
}

View file

@ -4,48 +4,47 @@ import (
"bytes"
"encoding/base64"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/openpgp/packet"
)
func TestTextMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestTextMessageEncryptionWithPassword(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
}
func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestBinaryMessageEncryptionWithPassword(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -55,21 +54,12 @@ func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestTextMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing)
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -80,28 +70,19 @@ func TestBinaryMessageEncryption(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing)
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
// Decrypt without verifying
decrypted, err = testPrivateKeyRing.Decrypt(ciphertext, nil, 0)
decrypted, err = keyRingTestPrivate.Decrypt(ciphertext, nil, 0)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -109,29 +90,40 @@ func TestBinaryMessageEncryption(t *testing.T) {
}
func TestIssue11(t *testing.T) {
myKeyring, err := BuildKeyRingArmored(readTestFile("issue11_privatekey", false))
var issue11Password = []byte("1234")
issue11Key, err := NewKeyFromArmored(readTestFile("issue11_privatekey", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}
issue11Key, err = issue11Key.Unlock(issue11Password)
if err != nil {
t.Fatal("Expected no error while unlocking private key, got:", err)
}
issue11Keyring, err := NewKeyRing(issue11Key)
if err != nil {
t.Fatal("Expected no error while bulding private keyring, got:", err)
}
err = myKeyring.UnlockWithPassphrase("1234");
senderKey, err := NewKeyFromArmored(readTestFile("issue11_publickey", false))
if err != nil {
t.Fatal("Expected no error while unlocking private keyring, got:", err)
t.Fatal("Expected no error while unarmoring public keyring, got:", err)
}
assert.Exactly(t, "643b3595e6ee4fdf", senderKey.GetHexKeyID())
senderKeyring, err := BuildKeyRingArmored(readTestFile("issue11_publickey", false))
senderKeyring, err := NewKeyRing(senderKey)
if err != nil {
t.Fatal("Expected no error while building public keyring, got:", err)
}
assert.Exactly(t, []uint64{0x643b3595e6ee4fdf}, senderKeyring.KeyIds())
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("issue11_message", false))
if err != nil {
t.Fatal("Expected no error while unlocking private keyring, got:", err)
}
plainMessage, err := myKeyring.Decrypt(pgpMessage, senderKeyring, 0)
plainMessage, err := issue11Keyring.Decrypt(pgpMessage, senderKeyring, 0)
if err != nil {
t.Fatal("Expected no error while decrypting/verifying, got:", err)
}
@ -140,20 +132,12 @@ func TestIssue11(t *testing.T) {
}
func TestSignedMessageDecryption(t *testing.T) {
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false))
if err != nil {
t.Fatal("Expected no error when unarmoring, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0)
decrypted, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -162,24 +146,9 @@ func TestSignedMessageDecryption(t *testing.T) {
func TestMultipleKeyMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
err = testPublicKeyRing.ReadFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true)
if err != nil {
t.Fatal("Expected no error adding second public key, got:", err)
}
assert.Exactly(t, 2, len(testPublicKeyRing.entities))
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing)
ciphertext, err := keyRingTestMultiple.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
@ -189,17 +158,15 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
err = nil
break
}
switch p.(type) {
case *packet.EncryptedKey:
numKeyPackets++
if _, ok := p.(*packet.EncryptedKey); ok {
numKeyPackets++
}
}
assert.Exactly(t, 2, numKeyPackets)
assert.Exactly(t, 3, numKeyPackets)
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}

View file

@ -77,7 +77,7 @@ func parseMIME(
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
if err == nil && verifierKey != nil {
err = signatureCollector.verified;
err = signatureCollector.verified
}
return bodyCollector,

View file

@ -7,7 +7,7 @@ import (
)
// Corresponding key in testdata/mime_privateKey
const privateKeyPassword = "test"
var MIMEKeyPassword = []byte("test")
// define call back interface
type Callbacks struct {
@ -37,13 +37,22 @@ func TestDecrypt(t *testing.T) {
callbacks := Callbacks{
Testing: t,
}
privateKeyRing, _ := BuildKeyRingArmored(readTestFile("mime_privateKey", false))
err = privateKeyRing.UnlockWithPassphrase(privateKeyPassword)
privateKey, err := NewKeyFromArmored(readTestFile("mime_privateKey", false))
if err != nil {
t.Fatal("Cannot unarmor private key:", err)
}
privateKey, err = privateKey.Unlock(MIMEKeyPassword)
if err != nil {
t.Fatal("Cannot unlock private key:", err)
}
privateKeyRing, err := NewKeyRing(privateKey)
if err != nil {
t.Fatal("Cannot create private keyring:", err)
}
message, err := NewPGPMessageFromArmored(readTestFile("mime_pgpMessage", false))
if err != nil {
t.Fatal("Cannot decode armored message:", err)
@ -60,7 +69,7 @@ func TestParse(t *testing.T) {
body, atts, attHeaders, err := parseMIME(readTestFile("mime_testMessage", false), nil)
if err != nil {
t.Error("Expected no error while parsing message, got:", err)
t.Fatal("Expected no error while parsing message, got:", err)
}
_ = atts

151
crypto/password.go Normal file
View file

@ -0,0 +1,151 @@
package crypto
import (
"bytes"
"io"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"github.com/pkg/errors"
)
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
// * message : The plain data as a PlainMessage
// * password: A password that will be derived into an encryption key
// * output : The encrypted data as PGPMessage
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
encrypted, err := passwordEncrypt(message.GetBinary(), password)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts password protected pgp binary messages
// * encrypted: The encrypted data as PGPMessage
// * password: A password that will be derived into an encryption key
// * output: The decrypted data as PlainMessage
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
decrypted, err := passwordDecrypt(message.NewReader(), password)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
}
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func DecryptSessionKeyWithPassword(keyPacket, password []byte) (*SessionKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
var symKeys []*packet.SymmetricKeyEncrypted
for {
var p packet.Packet
var err error
if p, err = packets.Next(); err != nil {
break
}
if p, ok := p.(*packet.SymmetricKeyEncrypted); ok {
symKeys = append(symKeys, p)
}
}
// Try the symmetric passphrase first
if len(symKeys) != 0 && password != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(password)
if err == nil {
return &SessionKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("gopenpgp: password incorrect")
}
// EncryptSessionKeyWithPassword encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
}
if len(password) == 0 {
return nil, errors.New("gopenpgp: password can't be empty")
}
config := &packet.Config{
DefaultCipher: cf,
}
err = packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sk.Key, password, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
}
return outbuf.Bytes(), nil
}
// ----- INTERNAL FUNCTIONS ------
func passwordEncrypt(message []byte, password []byte) ([]byte, error) {
var outBuf bytes.Buffer
config := &packet.Config{
Time: getTimeGenerator(),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, nil, config)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(message)
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) {
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return password, nil
}
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
Time: getTimeGenerator(),
}
md, err := openpgp.ReadMessage(encryptedIO, nil, prompt, config)
if err != nil {
return nil, err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return nil, err
}
return messageBuf.Bytes(), nil
}

View file

@ -1,74 +0,0 @@
package crypto
import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
var testRandomToken []byte
func TestRandomToken(t *testing.T) {
var err error
testRandomToken, err = RandomToken()
if err != nil {
t.Fatal("Expected no error while generating default length random token, got:", err)
}
token40, err := RandomTokenSize(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, testRandomToken, 32)
assert.Len(t, token40, 40)
}
func TestAsymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Algo: constants.AES256,
}
privateKeyRing, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
_ = privateKeyRing.UnlockWithPassphrase(testMailboxPassword)
keyPacket, err := privateKeyRing.EncryptSessionKey(symmetricKey)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
// Password defined in keyring_test
outputSymmetricKey, err := privateKeyRing.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, symmetricKey, outputSymmetricKey)
}
func TestSymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Algo: constants.AES256,
}
password := "I like encryption"
keyPacket, err := symmetricKey.EncryptToKeyPacket(password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = NewSymmetricKeyFromKeyPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := NewSymmetricKeyFromKeyPacket(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, symmetricKey, outputSymmetricKey)
}

221
crypto/sessionkey.go Normal file
View file

@ -0,0 +1,221 @@
package crypto
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SessionKey stores a decrypted session key.
type SessionKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SessionKey.
func (sk *SessionKey) GetCipherFunc() (packet.CipherFunction, error) {
cf, ok := symKeyAlgos[sk.Algo]
if !ok {
return cf, errors.New("gopenpgp: unsupported cipher function: " + sk.Algo)
}
return cf, nil
}
// GetBase64Key returns the session key as base64 encoded string.
func (sk *SessionKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(sk.Key)
}
// RandomToken generates a random token with the specified key size
func RandomToken(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
}
// GenerateSessionKeyAlgo generates a random key of the correct length for the specified algorithm
func GenerateSessionKeyAlgo(algo string) (sk *SessionKey, err error) {
cf, ok := symKeyAlgos[algo]
if !ok {
return nil, errors.New("gopenpgp: unknown symmetric key generation algorithm")
}
r, err := RandomToken(cf.KeySize())
if err != nil {
return nil, err
}
sk = &SessionKey{
Key: r,
Algo: algo,
}
return sk, nil
}
// GenerateSessionKey generates a random key for the default cipher
func GenerateSessionKey() (*SessionKey, error) {
return GenerateSessionKeyAlgo(constants.AES256)
}
func NewSessionKeyFromToken(token []byte, algo string) *SessionKey {
return &SessionKey{
Key: token,
Algo: algo,
}
}
func newSessionKeyFromEncrypted(ek *packet.EncryptedKey) (*SessionKey, error) {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
}
symmetricKey := &SessionKey{
Key: ek.Key,
Algo: algo,
}
return symmetricKey, nil
}
// Encrypt encrypts a PlainMessage to PGPMessage with a SessionKey
// * message : The plain data as a PlainMessage
// * output : The encrypted data as PGPMessage
func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
var encBuf bytes.Buffer
var encryptWriter io.WriteCloser
dc, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
}
config := &packet.Config{
Time: getTimeGenerator(),
DefaultCipher: dc,
}
encryptWriter, err = packet.SerializeSymmetricallyEncrypted(&encBuf, config.Cipher(), sk.Key, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt")
}
if algo := config.Compression(); algo != packet.CompressionNone {
encryptWriter, err = packet.SerializeCompressed(encryptWriter, algo, config.CompressionConfig)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in compression")
}
}
encryptWriter, err = packet.SerializeLiteral(encryptWriter, false, "", 0)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to serialize")
}
_, err = encryptWriter.Write(message.GetBinary())
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
return encBuf.Bytes(), nil
}
// Decrypt decrypts password protected pgp binary messages
// * encrypted: PGPMessage
// * output: PlainMessage
func (sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error) {
var messageReader = bytes.NewReader(dataPacket)
var decrypted io.ReadCloser
var decBuf bytes.Buffer
// Read symmetrically encrypted data packet
packets := packet.NewReader(messageReader)
p, err := packets.Next()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to read symmetric packet")
}
// Decrypt data packet
switch p := p.(type) {
case *packet.SymmetricallyEncrypted:
dc, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt with session key")
}
decrypted, err = p.Decrypt(dc, sk.Key)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt symmetric packet")
}
default:
return nil, errors.New("gopenpgp: invalid packet type")
}
_, err = decBuf.ReadFrom(decrypted)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to read from decrypted symmetric packet")
}
config := &packet.Config{
Time: getTimeGenerator(),
}
// Push decrypted packet as literal packet and use openpgp's reader
keyring := openpgp.EntityList{} // Ignore signatures, since we have no private key
md, err := openpgp.ReadMessage(&decBuf, keyring, nil, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decode symmetric packet")
}
messageBuf := new(bytes.Buffer)
_, err = messageBuf.ReadFrom(md.UnverifiedBody)
if err != nil {
return nil, err
}
return NewPlainMessage(messageBuf.Bytes()), nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}

145
crypto/sessionkey_test.go Normal file
View file

@ -0,0 +1,145 @@
package crypto
import (
"testing"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/stretchr/testify/assert"
)
var testSessionKey *SessionKey
func init() {
var err error
testSessionKey, err = GenerateSessionKey()
if err != nil {
panic("Expected no error while generating random session key with default algorithm, got:" + err.Error())
}
}
func TestRandomToken(t *testing.T) {
token40, err := RandomToken(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, token40, 40)
}
func TestGenerateSessionKey(t *testing.T) {
assert.Len(t, testSessionKey.Key, 32)
}
func TestAsymmetricKeyPacket(t *testing.T) {
keyPacket, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
// Password defined in keyring_test
outputSymmetricKey, err := keyRingTestPrivate.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, testSessionKey, outputSymmetricKey)
}
func TestSymmetricKeyPacket(t *testing.T) {
password := []byte("I like encryption")
keyPacket, err := EncryptSessionKeyWithPassword(testSessionKey, password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = DecryptSessionKeyWithPassword(keyPacket, []byte("Wrong password"))
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := DecryptSessionKeyWithPassword(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, testSessionKey, outputSymmetricKey)
}
func TestDataPacketEncryption(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with session key
dataPacket, err := testSessionKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong session key
wrongKey := SessionKey{
Key: []byte("wrong pass"),
Algo: constants.AES256,
}
_, err = wrongKey.Decrypt(dataPacket)
assert.NotNil(t, err)
// Decrypt data with the good session key
decrypted, err := testSessionKey.Decrypt(dataPacket)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
// Encrypt session key
keyPacket, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Unable to encrypt key packet, got:", err)
}
// Join key packet and data packet in single message
splitMessage := NewPGPSplitMessage(keyPacket, dataPacket)
// Armor and un-armor message. In alternative it can also be done with NewPgpMessage(splitMessage.GetBinary())
armored, err := splitMessage.GetArmored()
if err != nil {
t.Fatal("Unable to armor split message, got:", err)
}
pgpMessage, err := NewPGPMessageFromArmored(armored)
if err != nil {
t.Fatal("Unable to unarmor pgp message, got:", err)
}
// Test if final decryption succeeds
finalMessage, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Unable to decrypt joined keypacket and datapacket, got:", err)
}
assert.Exactly(t, message.GetString(), finalMessage.GetString())
}
func TestDataPacketDecryption(t *testing.T) {
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false))
if err != nil {
t.Fatal("Expected no error when unarmoring, got:", err)
}
split, err := pgpMessage.SeparateKeyAndData(1024, 0)
if err != nil {
t.Fatal("Expected no error when splitting, got:", err)
}
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(split.GetBinaryKeyPacket())
if err != nil {
t.Fatal("Expected no error when decrypting session key, got:", err)
}
decrypted, err := sessionKey.Decrypt(split.GetBinaryDataPacket())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, readTestFile("message_plaintext", true), decrypted.GetString())
}
func TestSessionKeyClear(t *testing.T) {
testSessionKey.Clear()
assertMemCleared(t, testSessionKey.Key)
}

View file

@ -8,16 +8,16 @@ import (
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/internal"
)
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
type SignatureVerificationError struct {
Status int
Status int
Message string
}
@ -32,7 +32,7 @@ func (e SignatureVerificationError) Error() string {
// newSignatureFailed creates a new SignatureVerificationError, type SIGNATURE_FAILED
func newSignatureFailed() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_FAILED,
"Invalid signature",
}
@ -40,7 +40,7 @@ func newSignatureFailed() SignatureVerificationError {
// newSignatureNotSigned creates a new SignatureVerificationError, type SIGNATURE_NOT_SIGNED
func newSignatureNotSigned() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_NOT_SIGNED,
"Missing signature",
}
@ -48,7 +48,7 @@ func newSignatureNotSigned() SignatureVerificationError {
// newSignatureNoVerifier creates a new SignatureVerificationError, type SIGNATURE_NO_VERIFIER
func newSignatureNoVerifier() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_NO_VERIFIER,
"No matching signature",
}

View file

@ -85,7 +85,8 @@ func (sc *SignatureCollector) Accept(
if err != nil {
return err
}
buffer, err = gomime.DecodeCharset(buffer, params)
mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type"))
buffer, err = gomime.DecodeCharset(buffer, mediaType, params)
if err != nil {
return err
}

View file

@ -2,39 +2,23 @@ package crypto
import (
"regexp"
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/stretchr/testify/assert"
)
const signedPlainText = "Signed message\n"
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
var signingKeyRing *KeyRing
var textSignature, binSignature *PGPSignature
var message *PlainMessage
var signatureTest = regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
var signedMessageTest = regexp.MustCompile(
"(?s)^-----BEGIN PGP SIGNED MESSAGE-----.*-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
func TestSignTextDetached(t *testing.T) {
var err error
signingKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
if err != nil {
t.Fatal("Cannot read private key:", err)
}
// Password defined in keyring_test
err = signingKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Cannot decrypt private key:", err)
}
message = NewPlainMessageFromString(signedPlainText)
textSignature, err = signingKeyRing.SignDetached(message)
textSignature, err = keyRingTestPrivate.SignDetached(message)
if err != nil {
t.Fatal("Cannot generate signature:", err)
}
@ -48,15 +32,15 @@ func TestSignTextDetached(t *testing.T) {
}
func TestVerifyTextDetachedSig(t *testing.T) {
verificationError := signingKeyRing.VerifyDetached(message, textSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(message, textSignature, testTime)
if verificationError != nil {
t.Fatal("Cannot verify plaintext signature:", err)
t.Fatal("Cannot verify plaintext signature:", verificationError)
}
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
fakeMessage := NewPlainMessageFromString("wrong text")
verificationError := signingKeyRing.VerifyDetached(fakeMessage, textSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(fakeMessage, textSignature, testTime)
assert.EqualError(t, verificationError, "Signature Verification Error: Invalid signature")
@ -67,7 +51,7 @@ func TestVerifyTextDetachedSigWrong(t *testing.T) {
func TestSignBinDetached(t *testing.T) {
var err error
binSignature, err = signingKeyRing.SignDetached(NewPlainMessage([]byte(signedPlainText)))
binSignature, err = keyRingTestPrivate.SignDetached(NewPlainMessage([]byte(signedPlainText)))
if err != nil {
t.Fatal("Cannot generate signature:", err)
}
@ -81,8 +65,8 @@ func TestSignBinDetached(t *testing.T) {
}
func TestVerifyBinDetachedSig(t *testing.T) {
verificationError := signingKeyRing.VerifyDetached(message, binSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(message, binSignature, testTime)
if verificationError != nil {
t.Fatal("Cannot verify binary signature:", err)
t.Fatal("Cannot verify binary signature:", verificationError)
}
}

View file

@ -1,225 +0,0 @@
package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SymmetricKey stores a decrypted session key.
type SymmetricKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SymmetricKey.
func (symmetricKey *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[symmetricKey.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + symmetricKey.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (symmetricKey *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(symmetricKey.Key)
}
func NewSymmetricKeyFromToken(passphrase, algo string) *SymmetricKey {
return &SymmetricKey{
Key: []byte(passphrase),
Algo: algo,
}
}
func newSymmetricKeyFromEncrypted(ek *packet.EncryptedKey) (*SymmetricKey, error) {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
}
symmetricKey := &SymmetricKey{
Key: ek.Key,
Algo: algo,
}
return symmetricKey, nil
}
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
// * message : The plain data as a PlainMessage
// * output : The encrypted data as PGPMessage
func (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, error) {
encrypted, err := symmetricEncrypt(message.GetBinary(), symmetricKey)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts password protected pgp binary messages
// * encrypted: PGPMessage
// * output: PlainMessage
func (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error) {
decrypted, err := symmetricDecrypt(message.NewReader(), symmetricKey)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
}
// NewSymmetricKeyFromKeyPacket decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func NewSymmetricKeyFromKeyPacket(keyPacket []byte, password string) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
var symKeys []*packet.SymmetricKeyEncrypted
for {
var p packet.Packet
var err error
if p, err = packets.Next(); err != nil {
break
}
switch p := p.(type) {
case *packet.SymmetricKeyEncrypted:
symKeys = append(symKeys, p)
}
}
pwdRaw := []byte(password)
// Try the symmetric passphrase first
if len(symKeys) != 0 && pwdRaw != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(pwdRaw)
if err == nil {
return &SymmetricKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("gopenpgp: password incorrect")
}
// EncryptToKeyPacket encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func (symmetricKey *SymmetricKey) EncryptToKeyPacket(password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := symmetricKey.GetCipherFunc()
if len(password) <= 0 {
return nil, errors.New("gopenpgp: password can't be empty")
}
pwdRaw := []byte(password)
config := &packet.Config{
DefaultCipher: cf,
}
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, symmetricKey.Key, pwdRaw, config)
if err != nil {
return nil, err
}
return outbuf.Bytes(), nil
}
// ----- INTERNAL FUNCTIONS ------
func symmetricEncrypt(message []byte, sk *SymmetricKey) ([]byte, error) {
var outBuf bytes.Buffer
config := &packet.Config{
Time: getTimeGenerator(),
DefaultCipher: sk.GetCipherFunc(),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, sk.Key, nil, config)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(message)
encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func symmetricDecrypt(encryptedIO io.Reader, sk *SymmetricKey) ([]byte, error) {
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return sk.Key, nil
}
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
Time: getTimeGenerator(),
}
md, err := openpgp.ReadMessage(encryptedIO, nil, prompt, config)
if err != nil {
return nil, err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return nil, err
}
return messageBuf.Bytes(), nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}

View file

@ -1,8 +1,8 @@
package crypto
import (
"time"
"errors"
"time"
)
// UpdateTime updates cached time
@ -31,7 +31,7 @@ func getNow() time.Time {
return time.Now()
}
return time.Unix(pgp.latestServerTime + extrapolate, 0)
return time.Unix(pgp.latestServerTime+extrapolate, 0)
}
func getDiff() (int64, error) {
@ -40,12 +40,10 @@ func getDiff() (int64, error) {
return int64(time.Since(pgp.latestClientTime).Seconds()), nil
}
return 0, errors.New("Latest server time not available")
return 0, errors.New("gopenpgp: latest server time not available")
}
// getTimeGenerator Returns a time generator function
func getTimeGenerator() func() time.Time {
return func() time.Time {
return getNow()
}
return getNow
}