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:
parent
136c0a5495
commit
54f45d0471
46 changed files with 2588 additions and 1770 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
455
crypto/key.go
455
crypto/key.go
|
|
@ -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
128
crypto/key_clear.go
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
151
crypto/password.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
221
crypto/sessionkey.go
Normal 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
145
crypto/sessionkey_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue