Refactor api (#6)

* Refactor library, remove duplicates

* Rebuild structure to use Messages and Signature models

* Use PGPSplitMessage

* Remove signature model

* Various fixes

* Add helpers with tests

* Fixes, add some docs, add tests

* Add attachment helpers

* Add helpers Symmetric encryption

* Edit docs + examples

* Rename kr to keyRing

* Various fixes for documentation

* Edit JSON handling functions, add decrypt keyring via token

* Add proposal changes doc

* Fix CI

* Drop *Message functions, join CleartextMessage and BinaryMessage

* Change canonicalization and trimming only to text signatures

* Add cleartextsignature, detach signature from message model, move helpers

* Documentation, remove optional parameters

* Move verification to separate model

* Don't return message in VerifyDetached

* Update table of contents in readme

* Appease golint

* Run go fmt

* Rename Encrypt/DecryptMessageWithPassword to ..WithToken

These functions shouldn't be used with user-provided passwords,
as they don't do any key-stretching.

* Change key generation usernames
This commit is contained in:
wussler 2019-06-03 17:00:01 +02:00 committed by GitHub
parent 82d49bf235
commit e65ed17b41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2573 additions and 1478 deletions

View file

@ -2,15 +2,11 @@ package crypto
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"runtime"
"sync"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
@ -21,7 +17,7 @@ type AttachmentProcessor struct {
w *io.WriteCloser
pipe *io.PipeWriter
done sync.WaitGroup
split *models.EncryptedSplit
split *PGPSplitMessage
garbageCollector int
err error
}
@ -34,7 +30,7 @@ func (ap *AttachmentProcessor) Process(plainData []byte) {
}
// Finish closes the attachment and returns the encrypted data
func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
if ap.err != nil {
return nil, ap.err
}
@ -47,10 +43,10 @@ func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
return ap.split, nil
}
// encryptAttachment creates an AttachmentProcessor which can be used to encrypt
// newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
// a file. It takes an estimatedSize and fileName as hints about the file.
func (pgp *GopenPGP) encryptAttachment(
estimatedSize int, fileName string, publicKey *KeyRing, garbageCollector int,
func (keyRing *KeyRing) newAttachmentProcessor(
estimatedSize int, fileName string, garbageCollector int,
) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{}
// You could also add these one at a time if needed.
@ -70,17 +66,18 @@ func (pgp *GopenPGP) encryptAttachment(
go func() {
defer attachmentProc.done.Done()
split, splitError := SeparateKeyAndData(nil, reader, estimatedSize, garbageCollector)
ciphertext, _ := ioutil.ReadAll(reader)
message := NewPGPMessage(ciphertext)
split, splitError := message.SeparateKeyAndData(estimatedSize, garbageCollector)
if attachmentProc.err != nil {
attachmentProc.err = splitError
}
split.Algo = constants.AES256
attachmentProc.split = split
}()
var ew io.WriteCloser
var encryptErr error
ew, encryptErr = openpgp.Encrypt(writer, publicKey.entities, nil, hints, config)
ew, encryptErr = openpgp.Encrypt(writer, keyRing.entities, nil, hints, config)
if encryptErr != nil {
return nil, encryptErr
}
@ -90,15 +87,14 @@ func (pgp *GopenPGP) encryptAttachment(
return attachmentProc, nil
}
// EncryptAttachment encrypts a file. fileName
func (pgp *GopenPGP) EncryptAttachment(
plainData []byte, fileName string, publicKey *KeyRing,
) (*models.EncryptedSplit, error) {
ap, err := pgp.encryptAttachment(len(plainData), fileName, publicKey, -1)
// EncryptAttachment encrypts a file given a PlainMessage and a fileName.
// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error) {
ap, err := keyRing.newAttachmentProcessor(len(message.GetBinary()), fileName, -1)
if err != nil {
return nil, err
}
ap.Process(plainData)
ap.Process(message.GetBinary())
split, err := ap.Finish()
if err != nil {
return nil, err
@ -106,47 +102,23 @@ func (pgp *GopenPGP) EncryptAttachment(
return split, nil
}
// EncryptAttachmentLowMemory creates an AttachmentProcessor which can be used
// NewLowMemoryAttachmentProcessor creates an AttachmentProcessor which can be used
// to encrypt a file. It takes an estimatedSize and fileName as hints about the
// file. It is optimized for low-memory environments and collects garbage every
// megabyte.
func (pgp *GopenPGP) EncryptAttachmentLowMemory(
estimatedSize int, fileName string, publicKey *KeyRing,
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
estimatedSize int, fileName string,
) (*AttachmentProcessor, error) {
return pgp.encryptAttachment(estimatedSize, fileName, publicKey, 1<<20)
return keyRing.newAttachmentProcessor(estimatedSize, fileName, 1<<20)
}
// SplitArmor is a helper method which splits an armored message into its
// session key packet and symmetrically encrypted data packet.
func SplitArmor(encrypted string) (*models.EncryptedSplit, error) {
var err error
// DecryptAttachment takes a PGPSplitMessage, containing a session key packet and symmetrically encrypted data
// and returns a decrypted PlainMessage
func (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error) {
privKeyEntries := keyRing.entities
encryptedRaw, err := armorUtils.Unarmor(encrypted)
if err != nil {
return nil, err
}
encryptedReader := bytes.NewReader(encryptedRaw)
return SeparateKeyAndData(nil, encryptedReader, len(encrypted), -1)
}
// DecryptAttachment takes a session key packet and symmetrically encrypted data
// packet. privateKeys is a KeyRing that can contain multiple keys. The
// passphrase is used to unlock keys in privateKeys.
func (pgp *GopenPGP) DecryptAttachment(
keyPacket, dataPacket []byte,
kr *KeyRing, passphrase string,
) ([]byte, error) {
privKeyEntries := kr.entities
if err := kr.Unlock([]byte(passphrase)); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt attachment: %v", err)
return nil, err
}
keyReader := bytes.NewReader(keyPacket)
dataReader := bytes.NewReader(dataPacket)
keyReader := bytes.NewReader(message.GetKeyPacket())
dataReader := bytes.NewReader(message.GetDataPacket())
encryptedReader := io.MultiReader(keyReader, dataReader)
@ -163,5 +135,5 @@ func (pgp *GopenPGP) DecryptAttachment(
return nil, err
}
return b, nil
return NewPlainMessage(b), nil
}

View file

@ -2,7 +2,6 @@ package crypto
import (
"encoding/base64"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -13,54 +12,45 @@ import (
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)
}
split, err := SeparateKeyAndData(
testPrivateKeyRing,
strings.NewReader(string(testKeyPacketsDecoded)),
len(testKeyPacketsDecoded),
-1)
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
}
assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, symmetricKey)
}
func TestAttachmentSetKey(t *testing.T) {
packets, err := testPublicKeyRing.EncryptKey(testSymmetricKey)
keyPackets, err := testPublicKeyRing.EncryptSessionKey(testSymmetricKey)
if err != nil {
t.Fatal("Expected no error while encrypting attachment key, got:", err)
}
keyPackets, err := base64.StdEncoding.DecodeString(packets)
if err != nil {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}
split, err := SeparateKeyAndData(testPrivateKeyRing, strings.NewReader(string(keyPackets)), len(keyPackets), -1)
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}
assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, symmetricKey)
}
func TestAttachnentEncryptDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := pgp.EncryptAttachment([]byte(testAttachmentCleartext), "s.txt", testPrivateKeyRing)
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
redecData, err := pgp.DecryptAttachment(encSplit.KeyPacket, encSplit.DataPacket, testPrivateKeyRing, "")
redecData, err := testPrivateKeyRing.DecryptAttachment(encSplit)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
assert.Exactly(t, testAttachmentCleartext, string(redecData))
assert.Exactly(t, message, redecData)
}

View file

@ -3,278 +3,22 @@ package crypto
import (
"bytes"
"crypto"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"runtime"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"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 (sk *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[sk.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + sk.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (sk *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(sk.Key)
}
func newSymmetricKey(ek *packet.EncryptedKey) *SymmetricKey {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
panic(fmt.Sprintf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc))
}
return &SymmetricKey{
Key: ek.Key, //base64.StdEncoding.EncodeToString(ek.Key),
Algo: algo,
}
}
// DecryptAttKey decrypts a public-key encrypted session key and returns the
// decrypted symmetric session key.
func DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error) {
r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(keyPacket))
packets := packet.NewReader(r)
var p packet.Packet
if p, err = packets.Next(); err != nil {
return
}
ek := p.(*packet.EncryptedKey)
var decryptErr error
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return
}
key = newSymmetricKey(ek)
return
}
// SeparateKeyAndData reads a binary PGP message from r and splits it into its
// session key packet and symmetrically encrypted data packet.
func SeparateKeyAndData(
kr *KeyRing, r io.Reader,
estimatedLength, garbageCollector int,
) (outSplit *models.EncryptedSplit, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(r)
outSplit = &models.EncryptedSplit{}
gcCounter := 0
// Store encrypted key and symmetrically encrypted packet separately
var ek *packet.EncryptedKey
var decryptErr error
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
err = nil
break
}
switch p := p.(type) {
case *packet.EncryptedKey:
// We got an encrypted key. Try to decrypt it with each available key
if ek != nil && ek.Key != nil {
break
}
ek = p
if kr != nil {
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
}
case *packet.SymmetricallyEncrypted:
// The code below is optimized to not
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.
// We need to avoid triggering a grow from the system as this will allocate too much memory causing problems
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
actualLength := 1
block := make([]byte, 128)
for {
n, err := p.Contents.Read(block)
if err == io.EOF {
break
}
b.Write(block[:n])
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
runtime.GC()
gcCounter = 0
}
}
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
symEncryptedData[3] = byte(actualLength >> 16)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return nil, err
}
if ek == nil {
err = errors.New("gopenpgp: packets don't include an encrypted key packet")
return nil, err
}
if kr == nil {
var buf bytes.Buffer
if err := ek.Serialize(&buf); err != nil {
err = fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
return nil, err
}
outSplit.KeyPacket = buf.Bytes()
} else {
key := newSymmetricKey(ek)
outSplit.KeyPacket = key.Key
outSplit.Algo = key.Algo
}
return outSplit, nil
}
// EncryptKey encrypts the provided key.
func (kr *KeyRing) EncryptKey(symKey *SymmetricKey) (packets string, err error) {
b := &bytes.Buffer{}
w := base64.NewEncoder(base64.StdEncoding, b)
cf := symKey.GetCipherFunc()
if len(kr.entities) == 0 {
err = fmt.Errorf("gopenpgp: cannot set key: key ring is empty")
return
}
var pub *packet.PublicKey
for _, e := range kr.entities {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
pub = subKey.PublicKey
break
}
}
if pub == nil && len(e.Identities) > 0 {
var i *openpgp.Identity
for _, i = range e.Identities {
break
}
if i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptStorage || i.SelfSignature.FlagEncryptCommunications {
pub = e.PrimaryKey
}
}
if pub != nil {
break
}
}
if pub == nil {
err = fmt.Errorf("gopenpgp: cannot set key: no public key available")
return "", err
}
if err = packet.SerializeEncryptedKey(w, pub, cf, symKey.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
if err = w.Close(); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
return b.String(), nil
}
// IsKeyExpiredBin checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey []byte) (bool, error) {
now := pgp.getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
@ -325,34 +69,26 @@ func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
return true, errors.New("keys expired")
}
const (
ok = 0
notSigned = 1
noVerifier = 2
failed = 3
)
// IsKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey string) (bool, error) {
// IsArmoredKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
}
return pgp.IsKeyExpiredBin(rawPubKey)
return pgp.IsKeyExpired(rawPubKey)
}
func (pgp *GopenPGP) generateKey(
userName, domain, passphrase, keyType string,
name, email, passphrase, keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(userName) <= 0 {
return "", errors.New("invalid user name format")
if len(email) <= 0 {
return "", errors.New("invalid email format")
}
var email = userName
if len(domain) > 0 {
email = email + "@" + domain
if len(name) <= 0 {
return "", errors.New("invalid name format")
}
comments := ""
@ -383,7 +119,7 @@ func (pgp *GopenPGP) generateKey(
cfg.RSAPrimes = bigPrimes[:]
}
newEntity, err := openpgp.NewEntity(email, comments, email, cfg)
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
}
@ -417,23 +153,22 @@ func (pgp *GopenPGP) generateKey(
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func (pgp *GopenPGP) GenerateRSAKeyWithPrimes(
userName, domain, passphrase string,
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return pgp.generateKey(userName, domain, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
return pgp.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 (pgp *GopenPGP) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) {
return pgp.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil)
// 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 (pgp *GopenPGP) GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return pgp.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.
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
@ -479,9 +214,8 @@ func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// CheckKey is a debug helper function that prints the key and subkey
// fingerprints.
func (pgp *GopenPGP) CheckKey(pubKey string) (string, error) {
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (pgp *GopenPGP) PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {

View file

@ -6,12 +6,13 @@ import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/rsa"
)
const name = "richard.stallman"
const domain = "protonmail.ch"
const name = "Richard M. Stallman"
const domain = "rms@protonmail.ch"
var passphrase = "I love GNU"
var rsaKey, ecKey, rsaPublicKey, ecPublicKey string
@ -55,7 +56,7 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot read RSA public key:", err)
}
err = rsaPrivateKeyRing.Unlock([]byte(passphrase))
err = rsaPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
}
@ -75,40 +76,12 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot read EC public key:", err)
}
err = ecPrivateKeyRing.Unlock([]byte(passphrase))
err = ecPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt EC key:", err)
}
}
func TestEncryptDecryptKeys(t *testing.T) {
var pass, _ = base64.StdEncoding.DecodeString("H2CAwzpdexjxXucVYMERDiAc/td8aGPrr6ZhfMnZlLI=")
var testSymmetricKey = &SymmetricKey{
Key: pass,
Algo: constants.AES256,
}
packet, err := rsaPublicKeyRing.EncryptKey(testSymmetricKey)
if err != nil {
t.Fatal("Cannot encrypt keypacket with RSA keyring", err)
}
rsaTestSymmetricKey, err := DecryptAttKey(rsaPrivateKeyRing, packet)
if err != nil {
t.Fatal("Cannot decrypt keypacket with RSA keyring", err)
}
assert.Exactly(t, testSymmetricKey, rsaTestSymmetricKey)
packet, err = ecPublicKeyRing.EncryptKey(testSymmetricKey)
if err != nil {
t.Fatal("Cannot encrypt keypacket with EC keyring", err)
}
ecTestSymmetricKey, err := DecryptAttKey(ecPrivateKeyRing, packet)
if err != nil {
t.Fatal("Cannot decrypt keypacket with EC keyring", err)
}
assert.Exactly(t, testSymmetricKey, ecTestSymmetricKey)
}
func TestUpdatePrivateKeysPassphrase(t *testing.T) {
newPassphrase := "I like GNU"
rsaKey, err = pgp.UpdatePrivateKeyPassphrase(rsaKey, passphrase, newPassphrase)
@ -124,20 +97,20 @@ func TestUpdatePrivateKeysPassphrase(t *testing.T) {
passphrase = newPassphrase
}
func ExampleCheckKeys() {
_, _ = pgp.CheckKey(readTestFile("keyring_publicKey", false))
func ExamplePrintFingerprints() {
_, _ = pgp.PrintFingerprints(readTestFile("keyring_publicKey", false))
// Output:
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
}
func TestIsKeyExpired(t *testing.T) {
rsaRes, err := pgp.IsKeyExpired(rsaPublicKey)
func TestIsArmoredKeyExpired(t *testing.T) {
rsaRes, err := pgp.IsArmoredKeyExpired(rsaPublicKey)
if err != nil {
t.Fatal("Error in checking expiration of RSA key:", err)
}
ecRes, err := pgp.IsKeyExpired(ecPublicKey)
ecRes, err := pgp.IsArmoredKeyExpired(ecPublicKey)
if err != nil {
t.Fatal("Error in checking expiration of EC key:", err)
}
@ -147,11 +120,43 @@ func TestIsKeyExpired(t *testing.T) {
pgp.UpdateTime(1557754627) // 2019-05-13T13:37:07+00:00
expRes, expErr := pgp.IsKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := pgp.IsKeyExpired(readTestFile("key_futureKey", false))
expRes, expErr := pgp.IsArmoredKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := pgp.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")
}
func TestGenerateKeyWithPrimes(t *testing.T) {
prime1, _ := base64.StdEncoding.DecodeString(
"/thF8zjjk6fFx/y9NId35NFx8JTA7jvHEl+gI0dp9dIl9trmeZb+ESZ8f7bNXUmTI8j271kyenlrVJiqwqk80Q==")
prime2, _ := base64.StdEncoding.DecodeString(
"0HyyG/TShsw7yObD+DDP9Ze39ye1Redljx+KOZ3iNDmuuwwI1/5y44rD/ezAsE7A188NsotMDTSy5xtfHmu0xQ==")
prime3, _ := base64.StdEncoding.DecodeString(
"3OyJpAdnQXNjPNzI1u3BWDmPrzWw099E0UfJj5oJJILSbsAg/DDrmrdrIZDt7f24d06HCnTErCNWjvFJ3Kdq4w==")
prime4, _ := base64.StdEncoding.DecodeString(
"58UEDXTX29Q9JqvuE3Tn+Qj275CXBnJbA8IVM4d05cPYAZ6H43bPN01pbJqJTJw/cuFxs+8C+HNw3/MGQOExqw==")
staticRsaKey, err := pgp.GenerateRSAKeyWithPrimes(name, domain, passphrase, 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)
}
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())
}

View file

@ -8,31 +8,35 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"regexp"
"strings"
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
pgperrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
xrsa "golang.org/x/crypto/rsa"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
)
// KeyRing contains multiple private and public keys.
type KeyRing struct {
// PGP entities in this keyring.
entities openpgp.EntityList
// FirstKeyID as obtained from API to match salt
FirstKeyID string
}
// A keypair contains a private key and a public key.
type pgpKeyObject struct {
ID string
Version int
Flags int
Fingerprint string
PublicKey string `json:",omitempty"`
PrivateKey string
Primary int
ID string
Version int
Flags int
PrivateKey string
Primary int
Token *string `json:",omitempty"`
Signature *string `json:",omitempty"`
}
// PrivateKeyReader
@ -46,87 +50,22 @@ type Identity struct {
Email string
}
// Signature is be used to check a signature. Because the signature is checked
// when the reader is consumed, Signature must only be used after EOF has been
// seen. A signature is only valid if s.Err() returns nil, otherwise the
// sender's identity cannot be trusted.
type Signature struct {
md *openpgp.MessageDetails
}
// SignedString wraps string with a Signature
type SignedString struct {
String string
Signed *Signature
}
var errKeyringNotUnlocked = errors.New("gopenpgp: cannot sign message, key ring is not unlocked")
// Err returns a non-nil error if the signature is invalid.
func (s *Signature) Err() error {
return s.md.SignatureError
}
// KeyRing returns the key ring that was used to produce the signature, if
// available.
func (s *Signature) KeyRing() *KeyRing {
if s.md.SignedBy == nil {
return nil
}
return &KeyRing{
entities: openpgp.EntityList{s.md.SignedBy.Entity},
}
}
// IsBy returns true if the signature has been created by kr's owner.
func (s *Signature) IsBy(kr *KeyRing) bool {
// Use fingerprint if possible
if s.md.SignedBy != nil {
for _, e := range kr.entities {
if e.PrimaryKey.Fingerprint == s.md.SignedBy.PublicKey.Fingerprint {
return true
}
}
return false
}
for _, e := range kr.entities {
if e.PrimaryKey.KeyId == s.md.SignedByKeyId {
return true
}
}
return false
}
// KeyRing contains multiple private and public keys.
type KeyRing struct {
// PGP entities in this keyring.
entities openpgp.EntityList
// FirstKeyID as obtained from API to match salt
FirstKeyID string
}
// GetEntities returns openpgp entities contained in this KeyRing.
func (kr *KeyRing) GetEntities() openpgp.EntityList {
return kr.entities
func (keyRing *KeyRing) GetEntities() openpgp.EntityList {
return keyRing.entities
}
// GetSigningEntity returns first private unlocked signing entity from keyring.
func (kr *KeyRing) GetSigningEntity(passphrase string) (*openpgp.Entity, error) {
func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
var signEntity *openpgp.Entity
for _, e := range kr.entities {
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
if e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
continue
}
if !e.PrivateKey.Encrypted {
signEntity = e
break
}
signEntity = e
break
}
}
if signEntity == nil {
@ -137,184 +76,15 @@ func (kr *KeyRing) GetSigningEntity(passphrase string) (*openpgp.Entity, error)
return signEntity, nil
}
// Encrypt encrypts data to this keyring's owner. If sign is not nil, it also
// signs data with it. The keyring sign must be unlocked to be able to sign data,
// if not an error will be returned.
func (kr *KeyRing) Encrypt(w io.Writer, sign *KeyRing, filename string, canonicalizeText bool) (io.WriteCloser, error) {
// The API returns keys sorted by descending priority
// Only encrypt to the first one
var encryptEntities []*openpgp.Entity
for _, e := range kr.entities {
encryptEntities = append(encryptEntities, e)
break
}
var signEntity *openpgp.Entity
if sign != nil {
// To sign a message, the private key must be decrypted
for _, e := range sign.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
signEntity = e
break
}
}
if signEntity == nil {
return nil, errKeyringNotUnlocked
}
}
return EncryptCore(
w,
encryptEntities,
signEntity,
filename,
canonicalizeText,
func() time.Time { return GetGopenPGP().GetTime() })
}
// EncryptCore is lower-level encryption method used by KeyRing.Encrypt.
func EncryptCore(w io.Writer, encryptEntities []*openpgp.Entity, signEntity *openpgp.Entity, filename string,
canonicalizeText bool, timeGenerator func() time.Time) (io.WriteCloser, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: timeGenerator}
hints := &openpgp.FileHints{
IsBinary: !canonicalizeText,
FileName: filename,
}
if canonicalizeText {
return openpgp.EncryptText(w, encryptEntities, signEntity, hints, config)
}
return openpgp.Encrypt(w, encryptEntities, signEntity, hints, config)
}
// An io.WriteCloser that both encrypts and armors data.
type armorEncryptWriter struct {
aw io.WriteCloser // Armored writer
ew io.WriteCloser // Encrypted writer
}
// Write encrypted data
func (w *armorEncryptWriter) Write(b []byte) (n int, err error) {
return w.ew.Write(b)
}
// Close armor and encryption io.WriteClose
func (w *armorEncryptWriter) Close() (err error) {
if err = w.ew.Close(); err != nil {
return
}
err = w.aw.Close()
return
}
// EncryptArmored encrypts and armors data to the keyring's owner.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptArmored(w io.Writer, sign *KeyRing) (wc io.WriteCloser, err error) {
aw, err := armorUtils.ArmorWithTypeBuffered(w, constants.PGPMessageHeader)
if err != nil {
return
}
ew, err := kr.Encrypt(aw, sign, "", false)
if err != nil {
aw.Close()
return
}
wc = &armorEncryptWriter{aw: aw, ew: ew}
return
}
// EncryptMessage encrypts and armors a string to the keyring's owner.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptMessage(s string, sign *KeyRing) (encrypted string, err error) {
var b bytes.Buffer
w, err := kr.EncryptArmored(&b, sign)
if err != nil {
return
}
if _, err = w.Write([]byte(s)); err != nil {
return
}
if err = w.Close(); err != nil {
return
}
encrypted = b.String()
return
}
// EncryptSymmetric data using generated symmetric key encrypted with this KeyRing.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit,
err error) {
var encryptedWriter io.WriteCloser
buffer := &bytes.Buffer{}
if encryptedWriter, err = kr.Encrypt(buffer, kr, "msg.txt", canonicalizeText); err != nil {
return
}
if _, err = io.Copy(encryptedWriter, bytes.NewBufferString(textToEncrypt)); err != nil {
return
}
encryptedWriter.Close()
if outSplit, err = SeparateKeyAndData(kr, buffer, len(textToEncrypt), -1); err != nil {
return
}
return
}
// DecryptMessage decrypts an armored string sent to the keypair's owner.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptMessage(encrypted string) (SignedString, error) {
r, signed, err := kr.DecryptArmored(strings.NewReader(encrypted))
if err != nil && err != pgperrors.ErrSignatureExpired {
return SignedString{String: encrypted, Signed: nil}, err
}
b, err := ioutil.ReadAll(r)
if err != nil && err != pgperrors.ErrSignatureExpired {
return SignedString{String: encrypted, Signed: nil}, err
}
s := string(b)
return SignedString{String: s, Signed: signed}, nil
}
// DecryptMessageIfNeeded data if has armored PGP message format, if not return original data.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptMessageIfNeeded(data string) (decrypted string, err error) {
if re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----"); re.MatchString(data) {
var signed SignedString
signed, err = kr.DecryptMessage(data)
decrypted = signed.String
} else {
decrypted = data
}
return
}
// 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 (kr *KeyRing) Unlock(passphrase []byte) error {
func (keyRing *KeyRing) Unlock(passphrase []byte) error {
// Build a list of keys to decrypt
var keys []*packet.PrivateKey
for _, e := range kr.entities {
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
keys = append(keys, e.PrivateKey)
@ -352,48 +122,19 @@ func (kr *KeyRing) Unlock(passphrase []byte) error {
return nil
}
// Decrypt decrypts a message sent to the keypair's owner. If the message is not
// signed, signed will be nil.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) Decrypt(r io.Reader) (decrypted io.Reader, signed *Signature, err error) {
md, err := openpgp.ReadMessage(r, kr.entities, nil, nil)
if err != nil && err != pgperrors.ErrSignatureExpired {
return
}
decrypted = md.UnverifiedBody
if md.IsSigned {
signed = &Signature{md}
}
return
}
// DecryptArmored decrypts an armored message sent to the keypair's owner.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptArmored(r io.Reader) (decrypted io.Reader, signed *Signature, err error) {
block, err := armor.Decode(r)
if err != nil && err != pgperrors.ErrSignatureExpired {
return
}
if block.Type != constants.PGPMessageHeader {
err = errors.New("gopenpgp: not an armored PGP message")
return
}
return kr.Decrypt(block.Body)
// 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 (kr *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
func (keyRing *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
aw, err := armor.Encode(w, openpgp.PublicKeyType, nil)
if err != nil {
return
}
for _, e := range kr.entities {
for _, e := range keyRing.entities {
if err = e.Serialize(aw); err != nil {
aw.Close()
return
@ -405,9 +146,9 @@ func (kr *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (kr *KeyRing) GetArmoredPublicKey() (s string, err error) {
func (keyRing *KeyRing) GetArmoredPublicKey() (s string, err error) {
b := &bytes.Buffer{}
if err = kr.WriteArmoredPublicKey(b); err != nil {
if err = keyRing.WriteArmoredPublicKey(b); err != nil {
return
}
@ -416,8 +157,8 @@ func (kr *KeyRing) GetArmoredPublicKey() (s string, err error) {
}
// WritePublicKey outputs unarmored public keys from the keyring to w.
func (kr *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range kr.entities {
func (keyRing *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range keyRing.entities {
if err = e.Serialize(w); err != nil {
return
}
@ -427,9 +168,9 @@ func (kr *KeyRing) WritePublicKey(w io.Writer) (err error) {
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (kr *KeyRing) GetPublicKey() (b []byte, err error) {
func (keyRing *KeyRing) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = kr.WritePublicKey(&outBuf); err != nil {
if err = keyRing.WritePublicKey(&outBuf); err != nil {
return
}
@ -438,8 +179,8 @@ func (kr *KeyRing) GetPublicKey() (b []byte, err error) {
}
// GetFingerprint gets the fingerprint from the keyring.
func (kr *KeyRing) GetFingerprint() (string, error) {
for _, entity := range kr.entities {
func (keyRing *KeyRing) GetFingerprint() (string, error) {
for _, entity := range keyRing.entities {
fp := entity.PrimaryKey.Fingerprint
return hex.EncodeToString(fp[:]), nil
}
@ -447,10 +188,10 @@ func (kr *KeyRing) GetFingerprint() (string, error) {
}
// CheckPassphrase checks if private key passphrase is correct for every sub key.
func (kr *KeyRing) CheckPassphrase(passphrase string) bool {
func (keyRing *KeyRing) CheckPassphrase(passphrase string) bool {
var keys []*packet.PrivateKey
for _, entity := range kr.entities {
for _, entity := range keyRing.entities {
keys = append(keys, entity.PrivateKey)
}
var decryptError error
@ -468,7 +209,7 @@ func (kr *KeyRing) CheckPassphrase(passphrase string) bool {
}
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
func (kr *KeyRing) readFrom(r io.Reader, armored bool) error {
func (keyRing *KeyRing) readFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
@ -515,27 +256,27 @@ func (kr *KeyRing) readFrom(r io.Reader, armored bool) error {
return errors.New("gopenpgp: key ring doesn't contain any key")
}
kr.entities = append(kr.entities, entities...)
keyRing.entities = append(keyRing.entities, entities...)
return nil
}
// BuildKeyRing reads keyring from binary data
func (pgp *GopenPGP) BuildKeyRing(binKeys []byte) (kr *KeyRing, err error) {
kr = &KeyRing{}
func (pgp *GopenPGP) BuildKeyRing(binKeys []byte) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
entriesReader := bytes.NewReader(binKeys)
err = kr.readFrom(entriesReader, false)
err = keyRing.readFrom(entriesReader, false)
return
}
// BuildKeyRingNoError does not return error on fail
func (pgp *GopenPGP) BuildKeyRingNoError(binKeys []byte) (kr *KeyRing) {
kr, _ = pgp.BuildKeyRing(binKeys)
func (pgp *GopenPGP) BuildKeyRingNoError(binKeys []byte) (keyRing *KeyRing) {
keyRing, _ = pgp.BuildKeyRing(binKeys)
return
}
// BuildKeyRingArmored reads armored string and returns keyring
func (pgp *GopenPGP) BuildKeyRingArmored(key string) (kr *KeyRing, err error) {
func (pgp *GopenPGP) BuildKeyRingArmored(key string) (keyRing *KeyRing, err error) {
keyRaw, err := armorUtils.Unarmor(key)
if err != nil {
return nil, err
@ -545,36 +286,98 @@ func (pgp *GopenPGP) BuildKeyRingArmored(key string) (kr *KeyRing, err error) {
return &KeyRing{entities: keyEntries}, err
}
// UnmarshalJSON implements encoding/json.Unmarshaler.
func (kr *KeyRing) UnmarshalJSON(b []byte) (err error) {
kr.entities = nil
keyObjs := []pgpKeyObject{}
if err = json.Unmarshal(b, &keyObjs); err != nil {
return
// ReadFromJSON reads multiple keys from a json array and fills the keyring
func (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error) {
keyObjs, err := unmarshalJSON(jsonData)
if err != nil {
return err
}
if len(keyObjs) == 0 {
return
return keyRing.newKeyRingFromPGPKeyObject(keyObjs)
}
// UnlockJSONKeyRing reads keys from a JSON array, creates a newKeyRing,
// then tries to unlock them with the provided keyRing using the token in the structure.
// If the token is not available it will fall back to just reading the keys, and leave them locked.
func (keyRing *KeyRing) UnlockJSONKeyRing(jsonData []byte) (newKeyRing *KeyRing, err error) {
keyObjs, err := unmarshalJSON(jsonData)
newKeyRing = &KeyRing{}
err = newKeyRing.newKeyRingFromPGPKeyObject(keyObjs)
if err != nil {
return nil, err
}
for _, ko := range keyObjs {
if ko.Token == nil || ko.Signature == nil {
continue
}
message, err := NewPGPMessageFromArmored(*ko.Token)
if err != nil {
return nil, err
}
signature, err := NewPGPSignatureFromArmored(*ko.Signature)
if err != nil {
return nil, err
}
token, _, err := keyRing.Decrypt(message, nil, 0)
if err != nil {
return nil, err
}
ver, err := keyRing.VerifyDetached(token, signature, 0)
if err != nil {
return nil, err
}
if !ver.IsValid() {
return nil, errors.New("gopenpgp: unable to verify token")
}
err = newKeyRing.Unlock(token.GetBinary())
if err != nil {
return nil, errors.New("gopenpgp: wrong token")
}
}
return newKeyRing, nil
}
// newKeyRingFromPGPKeyObject fills a KeyRing given an array of pgpKeyObject
func (keyRing *KeyRing) newKeyRingFromPGPKeyObject(keyObjs []pgpKeyObject) error {
keyRing.entities = nil
for i, ko := range keyObjs {
if i == 0 {
kr.FirstKeyID = ko.ID
keyRing.FirstKeyID = ko.ID
}
err = kr.readFrom(ko.PrivateKeyReader(), true)
err := keyRing.readFrom(ko.PrivateKeyReader(), true)
if err != nil {
return err
}
}
return nil
}
// unmarshalJSON implements encoding/json.Unmarshaler.
func unmarshalJSON(jsonData []byte) ([]pgpKeyObject, error) {
keyObjs := []pgpKeyObject{}
if err := json.Unmarshal(jsonData, &keyObjs); err != nil {
return nil, err
}
if len(keyObjs) == 0 {
return nil, errors.New("gopenpgp: no key found")
}
return keyObjs, nil
}
// Identities returns the list of identities associated with this key ring.
func (kr *KeyRing) Identities() []*Identity {
func (keyRing *KeyRing) Identities() []*Identity {
var identities []*Identity
for _, e := range kr.entities {
for _, e := range keyRing.entities {
for _, id := range e.Identities {
identities = append(identities, &Identity{
Name: id.UserId.Name,
@ -586,25 +389,25 @@ func (kr *KeyRing) Identities() []*Identity {
}
// KeyIds returns array of IDs of keys in this KeyRing.
func (kr *KeyRing) KeyIds() []uint64 {
func (keyRing *KeyRing) KeyIds() []uint64 {
var res []uint64
for _, e := range kr.entities {
for _, e := range keyRing.entities {
res = append(res, e.PrimaryKey.KeyId)
}
return res
}
// ReadArmoredKeyRing reads an armored data into keyring.
func ReadArmoredKeyRing(r io.Reader) (kr *KeyRing, err error) {
kr = &KeyRing{}
err = kr.readFrom(r, true)
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) (kr *KeyRing, err error) {
kr = &KeyRing{}
err = kr.readFrom(r, false)
func ReadKeyRing(r io.Reader) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.readFrom(r, false)
return
}
@ -643,7 +446,7 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
if len(filteredKeys) == 0 && hasExpiredEntity {
return filteredKeys, errors.New("all contacts keys are expired")
return filteredKeys, errors.New("gopenpgp: all contacts keys are expired")
}
return filteredKeys, nil

255
crypto/keyring_message.go Normal file
View file

@ -0,0 +1,255 @@
package crypto
import (
"bytes"
"crypto"
"errors"
"io"
"io/ioutil"
"math"
"time"
"golang.org/x/crypto/openpgp"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
)
// Encrypt encrypts a PlainMessage, outputs a PGPMessage.
// If an unlocked private key is also provided it will also sign the message.
// message : The plaintext input as a PlainMessage
// privateKey : (optional) an unlocked private keyring to include signature in the message
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
encrypted, err := asymmetricEncrypt(message.GetBinary(), keyRing, privateKey, true)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage
// message : The encrypted input as a PGPMessage
// verifyKey : Public key for signature verification (optional)
// verifyTime : Time at verification (necessary only if verifyKey is not nil)
func (keyRing *KeyRing) Decrypt(
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
) (*PlainMessage, *Verification, error) {
decrypted, verifyStatus, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
if err != nil {
return nil, nil, err
}
return NewPlainMessage(decrypted), newVerification(verifyStatus), nil
}
// SignDetached generates and returns a PGPSignature for a given PlainMessage
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
signEntity, err := keyRing.GetSigningEntity()
if err != nil {
return nil, err
}
config := &packet.Config{DefaultHash: crypto.SHA512, Time: pgp.getTimeGenerator()}
var outBuf bytes.Buffer
//sign bin
if err := openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config); err != nil {
return nil, err
}
return NewPGPSignature(outBuf.Bytes()), nil
}
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
// and returns a Verification with the filled Verified field.
func (keyRing *KeyRing) VerifyDetached(
message *PlainMessage, signature *PGPSignature, verifyTime int64,
) (*Verification, error) {
var err error
verifyVal, err := verifySignature(
keyRing.GetEntities(),
message.NewReader(),
signature.GetBinary(),
verifyTime,
)
return newVerification(verifyVal), err
}
// ------ INTERNAL FUNCTIONS -------
// Core for encryption+signature functions
func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isBinary bool) ([]byte, error) {
var outBuf bytes.Buffer
var encryptWriter io.WriteCloser
var signEntity *openpgp.Entity
var err error
if privateKey != nil && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity()
if err != nil {
return nil, err
}
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
hints := &openpgp.FileHints{
IsBinary: isBinary,
FileName: "",
}
if isBinary {
encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
} else {
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config)
}
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(data)
encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
// Core for decryption+verification functions
func asymmetricDecrypt(
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
) (plaintext []byte, verified int, err error) {
privKeyEntries := privateKey.GetEntities()
var additionalEntries openpgp.EntityList
if verifyKey != nil {
additionalEntries = verifyKey.GetEntities()
}
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
messageDetails, err := openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
if err != nil {
return nil, constants.SIGNATURE_NOT_SIGNED, err
}
if verifyKey != nil {
processSignatureExpiration(messageDetails, verifyTime)
}
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
if err != nil {
return nil, constants.SIGNATURE_NOT_SIGNED, err
}
if verifyKey != nil {
verifyStatus, verifyError := verifyDetailsSignature(messageDetails, verifyKey)
if verifyStatus == constants.SIGNATURE_FAILED {
return nil, verifyStatus, errors.New(verifyError)
}
return body, verifyStatus, nil
}
return body, constants.SIGNATURE_NOT_SIGNED, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
// creationTime check.
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
if md.SignatureError == pgpErrors.ErrSignatureExpired {
if verifyTime > 0 {
created := md.Signature.CreationTime.Unix()
expires := int64(math.MaxInt64)
if md.Signature.SigLifetimeSecs != nil {
expires = int64(*md.Signature.SigLifetimeSecs) + created
}
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
md.SignatureError = nil
}
} else {
// verifyTime = 0: time check disabled, everything is okay
md.SignatureError = nil
}
}
}
// Verify signature from message details
func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) (int, string) {
if md.IsSigned {
if md.SignedBy != nil {
if len(verifierKey.entities) > 0 {
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
if len(matches) > 0 {
if md.SignatureError == nil {
return constants.SIGNATURE_OK, ""
}
return constants.SIGNATURE_FAILED, md.SignatureError.Error()
}
} else {
return constants.SIGNATURE_NO_VERIFIER, ""
}
} else {
return constants.SIGNATURE_NO_VERIFIER, ""
}
}
return constants.SIGNATURE_NOT_SIGNED, ""
}
// verifySignature verifies if a signature is valid with the entity list
func verifySignature(
pubKeyEntries openpgp.EntityList, origText io.Reader, signature []byte, verifyTime int64) (int, error) {
config := &packet.Config{}
if verifyTime == 0 {
config.Time = func() time.Time {
return time.Unix(0, 0)
}
} else {
config.Time = func() time.Time {
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
}
}
signatureReader := bytes.NewReader(signature)
signer, err := openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err == pgpErrors.ErrSignatureExpired && signer != nil {
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
// Maybe the creation time offset pushed it over the edge
// Retry with the actual verification time
config.Time = func() time.Time {
return time.Unix(verifyTime, 0)
}
_, err = signatureReader.Seek(0, io.SeekStart)
if err != nil {
return constants.SIGNATURE_FAILED, err
}
signer, err = openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err != nil {
return constants.SIGNATURE_FAILED, err
}
}
}
if signer == nil {
return constants.SIGNATURE_FAILED, errors.New("gopenpgp: signer is empty")
}
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
// return false, errors.New("signer is nil")
// }
return constants.SIGNATURE_OK, nil
}

View file

@ -19,14 +19,17 @@ var testSymmetricKey = &SymmetricKey{
Algo: constants.AES256,
}
var testWrongSymmetricKey = &SymmetricKey{
Key: []byte("WrongPass"),
Algo: constants.AES256,
}
// Corresponding key in testdata/keyring_privateKey
const testMailboxPassword = "apple"
// Corresponding key in testdata/keyring_privateKeyLegacy
// const testMailboxPasswordLegacy = "123"
const testToken = "d79ca194a22810a5363eeddfdef7dfbc327c6229"
var (
testPrivateKeyRing *KeyRing
testPublicKeyRing *KeyRing
@ -50,43 +53,12 @@ func init() {
panic(err)
}
err = testPrivateKeyRing.Unlock([]byte(testMailboxPassword))
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
panic(err)
}
}
func TestKeyRing_Decrypt(t *testing.T) {
decString, err := testPrivateKeyRing.DecryptMessageIfNeeded(readTestFile("keyring_token", false))
if err != nil {
t.Fatal("Cannot decrypt token:", err)
}
assert.Exactly(t, testToken, decString)
}
func TestKeyRing_Encrypt(t *testing.T) {
encrypted, err := testPublicKeyRing.EncryptMessage(testToken, testPrivateKeyRing)
if err != nil {
t.Fatal("Cannot encrypt token:", err)
}
// We can't just check if encrypted == testEncryptedToken
// Decrypt instead
ss, err := testPrivateKeyRing.DecryptMessage(encrypted)
if err != nil {
t.Fatal("Cannot decrypt token:", err)
}
assert.Exactly(t, testToken, ss.String)
signatureKeyRing := ss.Signed.KeyRing()
assert.Exactly(t, testPrivateKeyRing, signatureKeyRing)
isby := ss.Signed.IsBy(testPublicKeyRing)
assert.Exactly(t, true, isby)
}
func TestKeyRing_ArmoredPublicKeyString(t *testing.T) {
s, err := testPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
@ -134,10 +106,9 @@ func TestIdentities(t *testing.T) {
assert.Exactly(t, identities[0], testIdentity)
}
func TestFilterExpiredKeys(t *testing.T) {
expiredKey, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("key_expiredKey", false)))
keys := []*KeyRing {testPrivateKeyRing, expiredKey}
keys := []*KeyRing{testPrivateKeyRing, expiredKey}
unexpired, err := FilterExpiredKeys(keys)
if err != nil {
@ -147,3 +118,69 @@ 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 := pgp.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)
}
func TestKeyIds(t *testing.T) {
keyIDs := testPrivateKeyRing.KeyIds()
var assertKeyIDs = []uint64{4518840640391470884}
assert.Exactly(t, assertKeyIDs, keyIDs)
}
func TestReadFromJson(t *testing.T) {
decodedKeyRing := &KeyRing{}
err = decodedKeyRing.ReadFromJSON([]byte(readTestFile("keyring_jsonKeys", false)))
if err != nil {
t.Fatal("Expected no error while reading JSON, got:", err)
}
fingerprint, err := decodedKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting fingerprint, got:", err)
}
assert.Exactly(t, "91eacacca6837890efa7000470e569d5c182bef6", fingerprint)
}
func TestUnlockJson(t *testing.T) {
userKeyRing, err := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_userKey", false)))
if err != nil {
t.Fatal("Expected no error while creating keyring, got:", err)
}
err = userKeyRing.UnlockWithPassphrase("testpassphrase")
if err != nil {
t.Fatal("Expected no error while creating keyring, got:", err)
}
addressKeyRing, err := userKeyRing.UnlockJSONKeyRing([]byte(readTestFile("keyring_newJSONKeys", false)))
if err != nil {
t.Fatal("Expected no error while reading and decrypting JSON, got:", err)
}
for _, e := range addressKeyRing.entities {
assert.Exactly(t, false, e.PrivateKey.Encrypted)
}
}

View file

@ -2,271 +2,326 @@ package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"time"
"regexp"
"runtime"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp/packet"
)
// DecryptMessageStringKey decrypts encrypted message use private key (string)
// encryptedText : string armored encrypted
// privateKey : armored private use to decrypt message
// passphrase : match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessageStringKey(
encryptedText, privateKey, passphrase string,
) (string, error) {
privKeyRaw, err := armorUtils.Unarmor(privateKey)
if err != nil {
return "", err
}
privKeyReader := bytes.NewReader(privKeyRaw)
privKeyEntries, err := openpgp.ReadKeyRing(privKeyReader)
if err != nil {
return "", err
}
// ---- MODELS -----
return pgp.DecryptMessage(encryptedText, &KeyRing{entities: privKeyEntries}, passphrase)
// PlainMessage stores an unencrypted message.
type PlainMessage struct {
// The content of the message
Data []byte
// if the content is text or binary
TextType bool
}
// DecryptMessage decrypts encrypted string using keyring
// encryptedText : string armored encrypted
// privateKey : keyring with private key to decrypt message, could be multiple keys
// passphrase : match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessage(encryptedText string, privateKey *KeyRing, passphrase string) (string, error) {
md, err := decryptCore(encryptedText, nil, privateKey, passphrase, pgp.getTimeGenerator())
if err != nil {
return "", err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
if err != nil {
return "", err
}
return string(b), nil
// Verification for a PlainMessage
type Verification struct {
// If the decoded message was correctly signed. See constants.SIGNATURE* for all values.
Verified int
}
func decryptCore(
encryptedText string, additionalEntries openpgp.EntityList,
privKey *KeyRing, passphrase string,
timeFunc func() time.Time,
) (*openpgp.MessageDetails, error) {
rawPwd := []byte(passphrase)
if err := privKey.Unlock(rawPwd); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt passphrase: %v", err)
return nil, err
}
privKeyEntries := privKey.entities
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
encryptedio, err := internal.Unarmor(encryptedText)
if err != nil {
return nil, err
}
config := &packet.Config{Time: timeFunc}
md, err := openpgp.ReadMessage(encryptedio.Body, privKeyEntries, nil, config)
return md, err
// PGPMessage stores a PGP-encrypted message.
type PGPMessage struct {
// The content of the message
Data []byte
}
// DecryptMessageVerify decrypts message and verify the signature
// encryptedText: string armored encrypted
// verifierKey []byte: unarmored verifier keys
// privateKeyRing []byte: unarmored private key to decrypt. could be multiple
// passphrase: match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessageVerify(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, verifyTime int64,
) (*models.DecryptSignedVerify, error) {
out := &models.DecryptSignedVerify{}
out.Verify = failed
// PGPSignature stores a PGP-encoded detached signature.
type PGPSignature struct {
// The content of the signature
Data []byte
}
var verifierEntries openpgp.EntityList
if len(verifierKey.entities) == 0 {
out.Verify = noVerifier
// PGPSplitMessage contains a separate session key packet and symmetrically
// encrypted data packet.
type PGPSplitMessage struct {
DataPacket []byte
KeyPacket []byte
}
// ---- GENERATORS -----
// NewPlainMessage generates a new binary PlainMessage ready for encryption,
// signature, or verification from the unencrypted binary data.
func NewPlainMessage(data []byte) *PlainMessage {
return &PlainMessage{
Data: data,
TextType: false,
}
}
md, err := decryptCore(
encryptedText,
verifierEntries,
privateKeyRing,
passphrase,
func() time.Time { return time.Unix(0, 0) }) // TODO: I doubt this time is correct
// NewPlainMessageFromString generates a new text PlainMessage,
// ready for encryption, signature, or verification from an unencrypted string.
func NewPlainMessageFromString(text string) *PlainMessage {
return &PlainMessage{
Data: []byte(text),
TextType: true,
}
}
// newVerification returns a new instance of *Verification with the specified value
func newVerification(value int) *Verification {
return &Verification{
Verified: value,
}
}
// NewPGPMessage generates a new PGPMessage from the unarmored binary data.
func NewPGPMessage(data []byte) *PGPMessage {
return &PGPMessage{
Data: data,
}
}
// NewPGPMessageFromArmored generates a new PGPMessage from an armored string ready for decryption.
func NewPGPMessageFromArmored(armored string) (*PGPMessage, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
message, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
processSignatureExpiration(md, verifyTime)
return &PGPMessage{
Data: message,
}, nil
}
out.Plaintext = string(b)
if md.IsSigned {
if md.SignedBy != nil {
if len(verifierKey.entities) > 0 {
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
if len(matches) > 0 {
if md.SignatureError == nil {
out.Verify = ok
} else {
out.Message = md.SignatureError.Error()
out.Verify = failed
}
// NewPGPSplitMessage generates a new PGPSplitMessage from the binary unarmored keypacket,
// datapacket, and encryption algorithm.
func NewPGPSplitMessage(keyPacket []byte, dataPacket []byte) *PGPSplitMessage {
return &PGPSplitMessage{
KeyPacket: keyPacket,
DataPacket: dataPacket,
}
}
// NewPGPSplitMessageFromArmored generates a new PGPSplitMessage by splitting an armored message into its
// session key packet and symmetrically encrypted data packet.
func NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error) {
message, err := NewPGPMessageFromArmored(encrypted)
if err != nil {
return nil, err
}
return message.SeparateKeyAndData(len(encrypted), -1)
}
// NewPGPSignature generates a new PGPSignature from the unarmored binary data.
func NewPGPSignature(data []byte) *PGPSignature {
return &PGPSignature{
Data: data,
}
}
// NewPGPSignatureFromArmored generates a new PGPSignature from the armored string ready for verification.
func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
signature, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
return &PGPSignature{
Data: signature,
}, nil
}
// ---- MODEL METHODS -----
// GetBinary returns the binary content of the message as a []byte
func (msg *PlainMessage) GetBinary() []byte {
return msg.Data
}
// GetString returns the content of the message as a string
func (msg *PlainMessage) GetString() string {
return string(msg.Data)
}
// GetBase64 returns the base-64 encoded binary content of the message as a string
func (msg *PlainMessage) GetBase64() string {
return base64.StdEncoding.EncodeToString(msg.Data)
}
// GetVerification returns the verification status of a verification,
// to use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
// The int value returned is to compare to constants.SIGNATURE*.
func (ver *Verification) GetVerification() int {
return ver.Verified
}
// IsValid returns true if the message is signed and the signature is valid.
// To use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
func (ver *Verification) IsValid() bool {
return ver.Verified == constants.SIGNATURE_OK
}
// NewReader returns a New io.Reader for the bianry data of the message
func (msg *PlainMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// IsText returns whether the message is a text message
func (msg *PlainMessage) IsText() bool {
return msg.TextType
}
// IsBinary returns whether the message is a binary message
func (msg *PlainMessage) IsBinary() bool {
return !msg.TextType
}
// GetBinary returns the unarmored binary content of the message as a []byte
func (msg *PGPMessage) GetBinary() []byte {
return msg.Data
}
// NewReader returns a New io.Reader for the unarmored bianry data of the message
func (msg *PGPMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// GetArmored returns the armored message as a string
func (msg *PGPMessage) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
}
// GetDataPacket returns the unarmored binary datapacket as a []byte
func (msg *PGPSplitMessage) GetDataPacket() []byte {
return msg.DataPacket
}
// GetKeyPacket returns the unarmored binary keypacket as a []byte
func (msg *PGPSplitMessage) GetKeyPacket() []byte {
return msg.KeyPacket
}
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(bytes.NewReader(msg.Data))
outSplit = &PGPSplitMessage{}
gcCounter := 0
// 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 {
err = nil
break
}
switch p := p.(type) {
case *packet.EncryptedKey:
if encryptedKey != nil && encryptedKey.Key != nil {
break
}
encryptedKey = p
case *packet.SymmetricallyEncrypted:
// FIXME: 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.
// We need to avoid triggering a grow from the system as this will allocate too much memory causing problems
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
actualLength := 1
block := make([]byte, 128)
for {
n, err := p.Contents.Read(block)
if err == io.EOF {
break
}
b.Write(block[:n])
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
runtime.GC()
gcCounter = 0
}
}
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
out.Verify = noVerifier
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
symEncryptedData[3] = byte(actualLength >> 16)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
} else {
out.Verify = noVerifier
}
} else {
out.Verify = notSigned
}
return out, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
// creationTime check.
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
if md.SignatureError == pgpErrors.ErrSignatureExpired {
if verifyTime > 0 {
created := md.Signature.CreationTime.Unix()
expires := int64(math.MaxInt64)
if md.Signature.SigLifetimeSecs != nil {
expires = int64(*md.Signature.SigLifetimeSecs) + created
}
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
md.SignatureError = nil
}
} else {
// verifyTime = 0: time check disabled, everything is okay
md.SignatureError = nil
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")
}
var buf bytes.Buffer
if err := encryptedKey.Serialize(&buf); err != nil {
return nil, fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
}
outSplit.KeyPacket = buf.Bytes()
return outSplit, nil
}
// EncryptMessageWithPassword encrypts a plain text to pgp message with a password
// plainText string: clear text
// output string: armored pgp message
func (pgp *GopenPGP) EncryptMessageWithPassword(plainText string, password string) (string, error) {
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
plaintext, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, config)
if err != nil {
return "", err
}
message := []byte(plainText)
_, err = plaintext.Write(message)
if err != nil {
return "", err
}
err = plaintext.Close()
if err != nil {
return "", err
}
w.Close()
return outBuf.String(), nil
// GetBinary returns the unarmored binary content of the signature as a []byte
func (msg *PGPSignature) GetBinary() []byte {
return msg.Data
}
// EncryptMessage encrypts message with unarmored public key, if pass private key and passphrase will also sign
// the message
// publicKey : bytes unarmored public key
// plainText : the input
// privateKey : optional required when you want to sign
// passphrase : optional required when you pass the private key and this passphrase should decrypt the private key
// trim : bool true if need to trim new lines
func (pgp *GopenPGP) EncryptMessage(
plainText string, publicKey, privateKey *KeyRing,
passphrase string, trim bool,
) (string, error) {
if trim {
plainText = internal.TrimNewlines(plainText)
}
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
var signEntity *openpgp.Entity
if len(passphrase) > 0 && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
}
ew, err := EncryptCore(w, publicKey.entities, signEntity, "", false, pgp.getTimeGenerator())
if err != nil {
return "", err
}
_, err = ew.Write([]byte(plainText))
ew.Close()
w.Close()
return outBuf.String(), err
// GetArmored returns the armored signature as a string
func (msg *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
}
// DecryptMessageWithPassword decrypts a pgp message with a password
// encrypted string : armored pgp message
// output string : clear text
func (pgp *GopenPGP) DecryptMessageWithPassword(encrypted string, password string) (string, error) {
encryptedio, err := internal.Unarmor(encrypted)
if err != nil {
return "", err
}
// ---- UTILS -----
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return []byte(password), nil
}
return nil, errors.New("password incorrect")
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
md, err := openpgp.ReadMessage(encryptedio.Body, nil, prompt, config)
if err != nil {
return "", err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return "", err
}
return messageBuf.String(), nil
// IsPGPMessage checks if data if has armored PGP message format.
func (pgp *GopenPGP) IsPGPMessage(data string) bool {
re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----")
return re.MatchString(data)
}

View file

@ -1,49 +1,104 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"encoding/base64"
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
func TestMessageEncryptionWithPassword(t *testing.T) {
var pgp = GopenPGP{}
const password = "my secret password"
func TestTextMessageEncryptionWithSymmetricKey(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with password
armor, err := pgp.EncryptMessageWithPassword("my message", password)
encrypted, err := testSymmetricKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = pgp.DecryptMessageWithPassword(armor, "wrong password")
_, err = testWrongSymmetricKey.Decrypt(encrypted)
assert.NotNil(t, err)
// Decrypt data with the good password
text, err := pgp.DecryptMessageWithPassword(armor, password)
decrypted, err := testSymmetricKey.Decrypt(encrypted)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, "my message", text)
assert.Exactly(t, message.GetString(), decrypted.GetString())
}
func TestMessageEncryption(t *testing.T) {
var pgp = GopenPGP{}
var (
message = "plain text"
)
func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
_ = testPrivateKeyRing.Unlock([]byte(testMailboxPassword))
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
armor, err := pgp.EncryptMessage(message, testPublicKeyRing, testPrivateKeyRing, testMailboxPassword, false)
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
plainText, err := pgp.DecryptMessage(armor, testPrivateKeyRing, testMailboxPassword)
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message, plainText)
assert.Exactly(t, message, decrypted)
}
func TestTextMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(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)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, ver, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, pgp.GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
assert.Exactly(t, constants.SIGNATURE_OK, ver.GetVerification())
assert.Exactly(t, true, ver.IsValid())
}
func TestBinaryMessageEncryption(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(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)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, ver, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, pgp.GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
assert.Exactly(t, constants.SIGNATURE_OK, ver.GetVerification())
assert.Exactly(t, true, ver.IsValid())
}

View file

@ -8,11 +8,52 @@ import (
"strings"
gomime "github.com/ProtonMail/go-mime"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// MIMECallbacks defines callback methods to process a MIME message.
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
OnEncryptedHeaders(headers string)
OnVerified(verified int)
OnError(err error)
}
// DecryptMIMEMessage decrypts a MIME message.
func (keyRing *KeyRing) DecryptMIMEMessage(
message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64,
) {
decryptedMessage, verification, err := keyRing.Decrypt(message, verifyKey, verifyTime)
if err != nil {
callbacks.OnError(err)
return
}
body, verified, attachments, attachmentHeaders, err := pgp.parseMIME(decryptedMessage.GetString(), verifyKey)
if err != nil {
callbacks.OnError(err)
return
}
bodyContent, bodyMimeType := body.GetBody()
callbacks.OnBody(bodyContent, bodyMimeType)
for i := 0; i < len(attachments); i++ {
callbacks.OnAttachment(attachmentHeaders[i], []byte(attachments[i]))
}
callbacks.OnEncryptedHeaders("")
if verification.GetVerification() != constants.SIGNATURE_NOT_SIGNED {
callbacks.OnVerified(verification.GetVerification())
} else {
callbacks.OnVerified(verified)
}
}
// ----- INTERNAL FUNCTIONS -----
func (pgp GopenPGP) parseMIME(
mimeBody string, verifierKey *KeyRing,
) (*gomime.BodyCollector, int, []string, []string, error) {
@ -49,42 +90,3 @@ func (pgp GopenPGP) parseMIME(
return body, verified, atts, attHeaders, err
}
// MIMECallbacks defines callback methods to process a MIME message.
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
OnEncryptedHeaders(headers string)
OnVerified(verified int)
OnError(err error)
}
// DecryptMIMEMessage decrypts a MIME message.
func (pgp *GopenPGP) DecryptMIMEMessage(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, callbacks MIMECallbacks, verifyTime int64,
) {
decsignverify, err := pgp.DecryptMessageVerify(encryptedText, verifierKey, privateKeyRing, passphrase, verifyTime)
if err != nil {
callbacks.OnError(err)
return
}
body, verified, attachments, attachmentHeaders, err := pgp.parseMIME(decsignverify.Plaintext, verifierKey)
if err != nil {
callbacks.OnError(err)
return
}
bodyContent, bodyMimeType := body.GetBody()
callbacks.OnBody(bodyContent, bodyMimeType)
for i := 0; i < len(attachments); i++ {
callbacks.OnAttachment(attachmentHeaders[i], []byte(attachments[i]))
}
callbacks.OnEncryptedHeaders("")
if decsignverify.Verify == notSigned {
callbacks.OnVerified(verified)
} else {
callbacks.OnVerified(decsignverify.Verify)
}
}

View file

@ -1,8 +1,8 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
@ -48,18 +48,26 @@ func TestDecrypt(t *testing.T) {
block, err = internal.Unarmor(readTestFile("mime_privateKey", false))
if err != nil {
t.Fatal("Cannot unarmor private key: ", err)
t.Fatal("Cannot unarmor private key:", err)
}
privateKeyUnarmored, _ := ioutil.ReadAll(block.Body)
privateKeyRing, _ := pgp.BuildKeyRing(privateKeyUnarmored)
err = privateKeyRing.UnlockWithPassphrase(privateKeyPassword)
if err != nil {
t.Fatal("Cannot unlock private key:", err)
}
pgp.DecryptMIMEMessage(
readTestFile("mime_pgpMessage", false),
message, err := NewPGPMessageFromArmored(readTestFile("mime_pgpMessage", false))
if err != nil {
t.Fatal("Cannot decode armored message:", err)
}
privateKeyRing.DecryptMIMEMessage(
message,
pgp.BuildKeyRingNoError(publicKeyUnarmored),
pgp.BuildKeyRingNoError(privateKeyUnarmored),
privateKeyPassword,
&callbacks,
pgp.GetTimeUnix())
pgp.GetUnixTime())
}
func TestParse(t *testing.T) {

View file

@ -6,26 +6,18 @@ import (
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// RandomToken generates a random token with the key size of the default cipher.
// RandomToken generated a random token of the same size of the keysize of the default cipher.
func (pgp *GopenPGP) RandomToken() ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
keySize := config.DefaultCipher.KeySize()
symKey := make([]byte, keySize)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
return pgp.RandomTokenSize(config.DefaultCipher.KeySize())
}
// RandomTokenWith generates a random token with the given key size.
func (pgp *GopenPGP) RandomTokenWith(size int) ([]byte, error) {
// RandomTokenSize generates a random token with the specified key size
func (pgp *GopenPGP) RandomTokenSize(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
@ -34,12 +26,8 @@ func (pgp *GopenPGP) RandomTokenWith(size int) ([]byte, error) {
return symKey, nil
}
// GetSessionFromKeyPacket returns the decrypted session key from a binary
// public-key encrypted session key packet.
func (pgp *GopenPGP) GetSessionFromKeyPacket(
keyPacket []byte, privateKey *KeyRing, passphrase string,
) (*SymmetricKey,
error) {
// DecryptSessionKey returns the decrypted session key from a binary encrypted session key packet.
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
@ -50,15 +38,11 @@ func (pgp *GopenPGP) GetSessionFromKeyPacket(
}
ek := p.(*packet.EncryptedKey)
rawPwd := []byte(passphrase)
var decryptErr error
for _, key := range privateKey.entities.DecryptionKeys() {
for _, key := range keyRing.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
if err := priv.Decrypt(rawPwd); err != nil {
continue
}
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
@ -70,38 +54,22 @@ func (pgp *GopenPGP) GetSessionFromKeyPacket(
return nil, decryptErr
}
return getSessionSplit(ek)
}
// KeyPacketWithPublicKey encrypts the session key with the armored publicKey
// and returns a binary public-key encrypted session key packet.
func (pgp *GopenPGP) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey string) ([]byte, error) {
pubkeyRaw, err := armor.Unarmor(publicKey)
if err != nil {
return nil, err
if ek == nil {
return nil, errors.New("gopenpgp: unable to decrypt session key")
}
return pgp.KeyPacketWithPublicKeyBin(sessionSplit, pubkeyRaw)
return newSymmetricKeyFromEncrypted(ek)
}
// KeyPacketWithPublicKeyBin encrypts the session key with the unarmored
// EncryptSessionKey encrypts the session key with the unarmored
// publicKey and returns a binary public-key encrypted session key packet.
func (pgp *GopenPGP) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publicKey []byte) ([]byte, error) {
publicKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(publicKeyReader)
if err != nil {
return nil, err
}
func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
if len(pubKeyEntries) == 0 {
return nil, errors.New("cannot set key: key ring is empty")
}
var pub *packet.PublicKey
for _, e := range pubKeyEntries {
for _, e := range keyRing.GetEntities() {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
pub = subKey.PublicKey
@ -125,106 +93,9 @@ func (pgp *GopenPGP) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publi
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, sessionSplit.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return nil, err
}
return outbuf.Bytes(), nil
}
// GetSessionFromSymmetricPacket decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func (pgp *GopenPGP) GetSessionFromSymmetricPacket(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("password incorrect")
}
// SymmetricKeyPacketWithPassword encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func (pgp *GopenPGP) SymmetricKeyPacketWithPassword(sessionSplit *SymmetricKey, password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
if len(password) <= 0 {
return nil, errors.New("password can't be empty")
}
pwdRaw := []byte(password)
config := &packet.Config{
DefaultCipher: cf,
}
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sessionSplit.Key, pwdRaw, config)
if err != nil {
return nil, err
}
return outbuf.Bytes(), nil
}
func getSessionSplit(ek *packet.EncryptedKey) (*SymmetricKey, error) {
if ek == nil {
return nil, errors.New("can't decrypt key packet")
}
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if ek.Key == nil {
return nil, errors.New("can't decrypt key packet key is nil")
}
return &SymmetricKey{
Key: ek.Key,
Algo: algo,
}, nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}

View file

@ -4,46 +4,44 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
var testRandomToken []byte
func TestRandomToken(t *testing.T) {
var err error
testRandomToken, err = pgp.RandomToken()
if err != nil {
t.Fatal("Expected no error while generating default length random token, got:", err)
}
token40, err := pgp.RandomTokenSize(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, testRandomToken, 32)
}
func TestRandomTokenWith(t *testing.T) {
token, err := pgp.RandomTokenWith(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, token, 40)
assert.Len(t, token40, 40)
}
func TestAsymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Key: testRandomToken,
Algo: constants.AES256,
}
privateKeyRing, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
publicKey, _ := testPrivateKeyRing.GetArmoredPublicKey()
_ = privateKeyRing.UnlockWithPassphrase(testMailboxPassword)
keyPacket, err := pgp.KeyPacketWithPublicKey(symmetricKey, publicKey)
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 := pgp.GetSessionFromKeyPacket(keyPacket, privateKeyRing, testMailboxPassword)
outputSymmetricKey, err := privateKeyRing.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
@ -53,21 +51,21 @@ func TestAsymmetricKeyPacket(t *testing.T) {
func TestSymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Key: testRandomToken,
Algo: constants.AES256,
}
password := "I like encryption"
keyPacket, err := pgp.SymmetricKeyPacketWithPassword(symmetricKey, password)
keyPacket, err := symmetricKey.EncryptToKeyPacket(password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = pgp.GetSessionFromSymmetricPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "password incorrect")
_, err = NewSymmetricKeyFromKeyPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := pgp.GetSessionFromSymmetricPacket(keyPacket, password)
outputSymmetricKey, err := NewSymmetricKeyFromKeyPacket(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}

View file

@ -1,128 +0,0 @@
package crypto
import (
"bytes"
"errors"
"io"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/internal"
"golang.org/x/crypto/openpgp"
errorsPGP "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
)
// SignTextDetached creates an armored detached signature of a given string.
func (kr *KeyRing) SignTextDetached(plainText string, passphrase string, trimNewlines bool) (string, error) {
signEntity, err := kr.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
if trimNewlines {
plainText = internal.TrimNewlines(plainText)
}
att := strings.NewReader(plainText)
var outBuf bytes.Buffer
//SignText
if err := openpgp.ArmoredDetachSignText(&outBuf, signEntity, att, config); err != nil {
return "", err
}
return outBuf.String(), nil
}
// SignBinDetached creates an armored detached signature of binary data.
func (kr *KeyRing) SignBinDetached(plainData []byte, passphrase string) (string, error) {
//sign with 0x00
signEntity, err := kr.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
att := bytes.NewReader(plainData)
var outBuf bytes.Buffer
//sign bin
if err := openpgp.ArmoredDetachSign(&outBuf, signEntity, att, config); err != nil {
return "", err
}
return outBuf.String(), nil
}
// VerifyTextDetachedSig verifies an armored detached signature given the plaintext as a string.
func (kr *KeyRing) VerifyTextDetachedSig(
signature string, plainText string, verifyTime int64, trimNewlines bool,
) (bool, error) {
if trimNewlines {
plainText = internal.TrimNewlines(plainText)
}
origText := bytes.NewReader(bytes.NewBufferString(plainText).Bytes())
return verifySignature(kr.GetEntities(), origText, signature, verifyTime)
}
// VerifyBinDetachedSig verifies an armored detached signature given the plaintext as binary data.
func (kr *KeyRing) VerifyBinDetachedSig(signature string, plainData []byte, verifyTime int64) (bool, error) {
origText := bytes.NewReader(plainData)
return verifySignature(kr.GetEntities(), origText, signature, verifyTime)
}
// Internal
func verifySignature(
pubKeyEntries openpgp.EntityList, origText *bytes.Reader,
signature string, verifyTime int64,
) (bool, error) {
config := &packet.Config{}
if verifyTime == 0 {
config.Time = func() time.Time {
return time.Unix(0, 0)
}
} else {
config.Time = func() time.Time {
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
}
}
signatureReader := strings.NewReader(signature)
signer, err := openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err == errorsPGP.ErrSignatureExpired && signer != nil {
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
// Maybe the creation time offset pushed it over the edge
// Retry with the actual verification time
config.Time = func() time.Time {
return time.Unix(verifyTime, 0)
}
_, err = signatureReader.Seek(0, io.SeekStart)
if err != nil {
return false, err
}
signer, err = openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err != nil {
return false, err
}
}
}
if signer == nil {
return false, errors.New("gopenpgp: signer is empty")
}
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
// return false, errors.New("signer is nil")
// }
return true, nil
}

View file

@ -8,6 +8,7 @@ import (
"net/textproto"
gomime "github.com/ProtonMail/go-mime"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
@ -51,7 +52,7 @@ func (sc *SignatureCollector) Accept(
}
}
if len(multiparts) != 2 {
sc.verified = notSigned
sc.verified = constants.SIGNATURE_NOT_SIGNED
// Invalid multipart/signed format just pass along
_, err = ioutil.ReadAll(rawBody)
if err != nil {
@ -96,12 +97,12 @@ func (sc *SignatureCollector) Accept(
_, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), sc.config)
if err != nil {
sc.verified = failed
sc.verified = constants.SIGNATURE_FAILED
} else {
sc.verified = ok
sc.verified = constants.SIGNATURE_OK
}
} else {
sc.verified = noVerifier
sc.verified = constants.SIGNATURE_NO_VERIFIER
}
return nil
}

View file

@ -5,87 +5,86 @@ import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
const signedPlainText = "Signed message"
const signedPlainText = "Signed message\n"
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
var signingKeyRing *KeyRing
var signature, signatureBin string
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) {
signingKeyRing, err := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
var err error
signingKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
if err != nil {
t.Fatal("Cannot read private key:", err)
}
signature, err = signingKeyRing.SignTextDetached(signedPlainText, "", true)
assert.EqualError(t, err, "gopenpgp: cannot sign message, unable to unlock signer key")
// Password defined in keyring_test
signature, err = signingKeyRing.SignTextDetached(signedPlainText, testMailboxPassword, true)
if err != nil {
t.Fatal("Cannot generate signature with encrypted key:", err)
}
// Reset keyring to locked state
signingKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
// Password defined in keyring_test
err = signingKeyRing.Unlock([]byte(testMailboxPassword))
err = signingKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Cannot decrypt private key:", err)
}
signatureDec, err := signingKeyRing.SignTextDetached(signedPlainText, "", true)
message = NewPlainMessageFromString(signedPlainText)
textSignature, err = signingKeyRing.SignDetached(message)
if err != nil {
t.Fatal("Cannot generate signature with decrypted key:", err)
t.Fatal("Cannot generate signature:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
assert.Regexp(t, rTest, signature)
assert.Exactly(t, signatureDec, signature)
armoredSignature, err := textSignature.GetArmored()
if err != nil {
t.Fatal("Cannot armor signature:", err)
}
assert.Regexp(t, signatureTest, armoredSignature)
}
func TestVerifyTextDetachedSig(t *testing.T) {
signedMessage, err := signingKeyRing.VerifyDetached(message, textSignature, testTime)
if err != nil {
t.Fatal("Cannot verify plaintext signature:", err)
}
assert.Exactly(t, constants.SIGNATURE_OK, signedMessage.GetVerification())
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
fakeMessage := NewPlainMessageFromString("wrong text")
signedMessage, err := signingKeyRing.VerifyDetached(fakeMessage, textSignature, testTime)
assert.EqualError(t, err, "gopenpgp: signer is empty")
assert.Exactly(t, constants.SIGNATURE_FAILED, signedMessage.GetVerification())
}
func TestSignBinDetached(t *testing.T) {
var err error
// Reset keyring to locked state
signingKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
signatureBin, err = signingKeyRing.SignBinDetached([]byte(signedPlainText), "")
assert.EqualError(t, err, "gopenpgp: cannot sign message, unable to unlock signer key")
// Password defined in keyring_test
signatureBin, err = signingKeyRing.SignBinDetached([]byte(signedPlainText), testMailboxPassword)
binSignature, err = signingKeyRing.SignDetached(NewPlainMessage([]byte(signedPlainText)))
if err != nil {
t.Fatal("Cannot generate signature with encrypted key:", err)
t.Fatal("Cannot generate signature:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
assert.Regexp(t, rTest, signatureBin)
}
func TestVerifyTextDetachedSig(t *testing.T) {
verified, err := signingKeyRing.VerifyTextDetachedSig(signature, signedPlainText, testTime, true)
armoredSignature, err := binSignature.GetArmored()
if err != nil {
t.Fatal("Cannot verify plaintext signature:", err)
t.Fatal("Cannot armor signature:", err)
}
assert.Exactly(t, true, verified)
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
verified, err := signingKeyRing.VerifyTextDetachedSig(signature, "wrong text", testTime, true)
assert.EqualError(t, err, "gopenpgp: signer is empty")
assert.Exactly(t, false, verified)
assert.Regexp(t, signatureTest, armoredSignature)
}
func TestVerifyBinDetachedSig(t *testing.T) {
verified, err := signingKeyRing.VerifyBinDetachedSig(signatureBin, []byte(signedPlainText), testTime)
signedMessage, err := signingKeyRing.VerifyDetached(message, binSignature, testTime)
if err != nil {
t.Fatal("Cannot verify binary signature:", err)
}
assert.Exactly(t, true, verified)
assert.Exactly(t, constants.SIGNATURE_OK, signedMessage.GetVerification())
}

224
crypto/symmetrickey.go Normal file
View file

@ -0,0 +1,224 @@
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 NewSymmetricKey([]byte(passphrase), algo)
}
func NewSymmetricKey(key []byte, algo string) *SymmetricKey {
return &SymmetricKey{
Key: key,
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)
}
return NewSymmetricKey(ek.Key, algo), 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: pgp.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: pgp.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
}

20
crypto/testdata/keyring_jsonKeys vendored Normal file
View file

@ -0,0 +1,20 @@
[
{
"ID": "qpRBAbx3s0gw5fdpS4VHPmxFZG42NPWGZW2g0fDhwHeO7duCQgOdnxcSDUxTwfLjfpeIHuPzUQPKeEN8q4TsRA==",
"Primary": 1,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\n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\nSmpbVZj1NSvPN4xFOnZaytD99Yt/8ZFonY9Rjg==\n=5ACd\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": null
},
{
"ID": "88xeHAObMEZp8b15R3643qJFbRoAXPm-FNuHxWO_VBVEqm294wuKCbIdoytQwHq2uwTxAXB-c4Jh0R47F34qig==",
"Primary": 0,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nxYYEXMcTAxYJKwYBBAHaRw8BAQdAjYhUufSrYtCsWhJMjCaEkdJb1Zuauh8P\nS0WipcZO2dD+CQMIxbf+ePNBNFVgXUTPHWmy2PpBS2FHQWgONx0UQLRi+JyT\nMJmxS/CToS54Eo0sWEjaZROZr8f5smCgBZqZsRWv4CA35qBG/RxJMkVH2lKQ\nuM0zZWxlY3Ryb25AcHJvdG9ubWFpbC5ibHVlIDxlbGVjdHJvbkBwcm90b25t\nYWlsLmJsdWU+wncEEBYKAB8FAlzaAdIGCwkHCAMCBBUICgIDFgIBAhkBAhsD\nAh4BAAoJEP7+7R3IrxGwxh0BAKbR76lG0OH83tI5quvyNf7yt+ck/uV8MrPZ\ntcBLHwO3AQCd5CHU3Rx7Huw0QsVkJYr7t3JEYfSaTK2wTxUFKudSDceLBFzH\nEwMSCisGAQQBl1UBBQEBB0Dijq86qjP8AEv98lTBqw69HAAxo0S4Eqh1P4qU\n1irhLwMBCAf+CQMIXvD746WZtFtglCNKwMqcHMFJ0sadSDo6ntYdiQwSM42E\n0jdfdM+JUIfDw9cOXflCcdW8yUJSRaBL1BXwtCcr686pkPZ/79qxuqYY6+Nq\nzMJhBBgWCAAJBQJc2gHSAhsMAAoJEP7+7R3IrxGwKzUA/j/00OybZkE3oTDO\n2fLjBZtlKa7T1n4+vZb+T8dvl2wnAP9Ln3wTzY9oXN/n/WgSi8Q5iT2to7zx\n25aU/PlFqHQmBQ==\n=3PyZ\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": null
}
]

12
crypto/testdata/keyring_newJSONKeys vendored Normal file
View file

@ -0,0 +1,12 @@
[
{
"ID": "hKRtZeTDhvzfAaycb5BOVx6Y3hc3gs4QvET8H_YZBTwAQBPp3h6FI4nnkJePCYuM9CG0zf7TQzOJeB2rPi0YmQ==",
"Primary": 1,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nxcMGBFzGzj4BCADh9uuhG3mDhlbFMgQpvP6AReyJYa6KSV9a6AJsNI0/DkVz\nFenIodav/r4RBgSoblqxCa/QH6galbX0eB8Vtxx4xTXCcHHZzTM9oAb4s8r6\ndGchNsSkoLspVwmc2fYfDRCTcapmeza7Jym8gD9wFUSWSwpC49jDeT6VIcOd\n+qNXgIJX1V36BOAB53lKenMYnxfzzAyJDeL/sQ/SWE9h/X+wGdkVmk7gk3pa\nQ9URoxAf7MO808hHyQYcjTV4tOHD/9k+AZC6sqmr2KAefYCxCMoCDj1wQp3J\nN6MfK2Zvc1r5KFQM2YzUXrMNrycAjo7gGeZnsvz758Q+8ouSP7iXGRl/ABEB\nAAH+CQMIQt3QQIJZYjRgTVgIQNSEkhMn4GJ5lQQPBXD0/YyK3xP0bm/bsX8S\nz9dAm1nkP0l8Q9z3lR6OcAX2O/KsqJdQGmJEZAaadz2je+EEWo1FDYaL76E6\n6z+AuteP8UtP61jBt3pTfIlhW8o7o2SlM1lvytpvc5FQplZ/iUuwfsIltCiD\n9VDkprNOVrRsjoh8BV4TrrIvU8qga58Aeg7SjOmm+3oMZ7yPTYirr11Tx/m3\nj1uzdEDfiPk4LmvlzSsWwKZuy6fSul+n92+9qN81wmdts/I2ucuKvOINZim4\nlk/p2AOsPjWpAgkefpTVLZnu2IH+VAyaXt1Fl84badXx4N921nPs7ova1Ud5\n2RddBc7b/01DtOyBSWDoNskLGpsc2mqz9kdkwwQKNjChzZc9nmY9M+AIfgT3\n+2DSQIuoJYPX69DKi/bZDwRzoHmiwiHT6Us7qxd6kD1dzCIHTptxwZQp4Tow\nnN6lmtK4S6O2B47+ROn8s0N+EH9GR8F6mvOTayNLH5yicpR3M4Of0ClvFa0G\n+JUKBXIQXUvF03G3nTPU17nLibC81UmbK3zobfbrLfuU2gU+sY+OfE1E/+GO\nSFpZcrkoRqRr39CfLkLk+GjU7RCLNddb5LxgaurVZo5h0Y7Rr8VvOQMWjjl/\nvTAG7gU/HtXi24TijNC0fP6j9w43K4b6t1SZYn5us7RRlFlWGKMSPf5Q9j6/\nryo3xULUQv0lTCQPtfQQ5UWE5ZpQ4Kjt/k5+/YtfuOcMbrDU4qa+H+rrisBu\nko7f4Wn0iYjRwRIuWh1NfUM3rIbNhq7/wonasEFOeFdPwprzMaawx3rL0Pq5\n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\nV0GB3IX8qBHjAfPmqN9JPxBIn3/6ag==\n=U1yD\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": "-----BEGIN PGP MESSAGE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwcBMAxW/uQhm1PKxAQf8D4BAoXqnYgoTr7jEiabP9NkeuD6Z88fZDgUzXte5\n6aYMmCtPr0Vef1eDl2x0S0q/YJgR+A5Icmjxk+jG9nMSnRPlAozJfTCXu/Oy\nfkI4CcClGQv7U1EzfknBVNEKmuPO8XlEkGZ5lW/TzWk8ZtQqJsVuqZlZ5qQE\n/p8FNYEZ5RHPhhKlotA+T6XKh23z9mcN/JNEsSNcx+guERbivtTwnd5JpxQJ\nMNWzUYh+K0W7LgxoAo2jevDd4CZ/0sAAViIl+QrShodseV9agbldGUUoIMzn\nHUuIV6VYi7X82eKQXDInrtPc9IHekbDDFoncLnsGGrEqD/8O/qOHHOPwNwU+\n+cHATAOUTDv/ccWq1QEIALl8SNOQBmCuanAceUTSwCkM1fC3Ddqa8ZmnMMyG\nnDWDI7XkFU31CO10lN20/kAWjdN2073B/NT+17cp8fCqbQ1pAFJHpabdqmbI\ntm3pCC5M6otTN+MhjgFYcBuxo0rq9qtuEzz5j4Ub9MIIJTurUHMEPMI462Dc\ndK/d8BvDkU7q67Lkp65vpe9e/pv0lMMrQjdohnTHNgbZhbI/Z5LU0ApD//Ye\ncSC26BRUMJITiGKb9pKGAi0/ig0jJfzykgEarzOsY/v0W7016AMka+NsHuNc\n7Qfyg2LApF5s9Z6aK+Uy851haUk+p+abjBdcWACziAmimVGjlRYm49ra4UJ7\nc6LSeAElM584e4Z3VnqajAJWbmWt2atgBQmcwEBBsdNAtzIagNydMbBBIELA\n5wGUbMXfQxrMOo/Mdac/5EMTHT0/kVQNIBHtpX5SknWvbc5DjIHoH3+BbL87\neaxhKW94hMubKNKT3dbm4PKHtsMiS3TGkFZ3GjvA0Cy/Kw==\n=b1N8\n-----END PGP MESSAGE-----\n",
"Signature": "-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwsBcBAEBCAAGBQJcxtSkAAoJEGfweL9m9sQefz8IAJhpEOvG7d+PVgq3bEL+\nXzY8yTUKB1ZXEMbcR/uHUWSfu0F8zy4CNtSG5HUVjNxx1xzzJVJcxWB/ljO+\n/bJSFOFexTyNh8i/xU+CiBfm5RhAFTYF9xFwfD3LKp5gaalJAhWhArk1/Wuh\nWTDpFpk39uzBRKNwcnSgiJYPxOjAZxj+w/hhHPwmco5cUwMiMR5MNrfzKf+x\nwX3Cfs9fsiiCDzohBzbK0FFsMnJ8aXNVsDBjEA1KrB7sdyaf8FnaM0RsFRDb\njxtIgfcFBbyNJh4414Unt4AYTIrIWhK4OOXI3AfsJy8p6KRBKQUcUkKcDxab\nPTXOPZsZ+UgQ5MevyVnP1zfCwFwEAQEIAAYFAlzG1KQACgkQm1DMMBzxetx/\nPwf9F04uHtix0zDpP1IvG4VYlor4rjYTdfXqxxiFXHO6MZXJoigS1E71E8r4\nsqZ6PoQ5/xCj2A01KRhuF1Bon3mEZEwaIUuBqTV91sLsVWfoxgyPAYpr6gK/\n1W9JhVNNrVRMGox42LQUjyiq0ESrfWmqC8SuMfZMoUoBZycHicA50RbyOUnT\nLO57ArL3JIVmYtyosaXM3idzmNHmaXSkcGt4cvTVysJZQrneaxmikfm5CH2O\n1z2goLBNnzsbRionoV6gCukZOiM/d14yiyeYtsFJ7u/vodkI5y0M0sF8VnN/\njL9keP4ZpHiJ4MBd6tyIH0pLueDRlurFL3fcHsEzD5SrOA==\n=wGCk\n-----END PGP SIGNATURE-----\n"
}
]

62
crypto/testdata/keyring_userKey vendored Normal file
View file

@ -0,0 +1,62 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.4.5
Comment: testpassphrase
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
uaSC3IcBmBsj1fNb4eYXElILjQ==
=fMOl
-----END PGP PRIVATE KEY BLOCK-----

18
crypto/testdata/message_expired vendored Normal file
View file

@ -0,0 +1,18 @@
-----BEGIN PGP MESSAGE-----
Comment: GPGTools - https://gpgtools.org
owEBWwKk/ZANAwAKAeyAexA3gWZ0AawUYgloZWxsby50eHRaX2WpaGVsbG+JAjME
AAEKAB0WIQTxcIn7ZRrhwd51IZbsgHsQN4FmdAUCWl9lqQAKCRDsgHsQN4FmdCln
D/44x1bcrOXg+DbRStSrC75wFa+cvPEmaTZyqN6d7qlQCMxOcPlq6lbZ74QWfEq7
i1ZYHp4AU8jALw0QqBQQE5FvABleQKpVfY22s83Bqy+P0DB9ntpD+t+oZrxGCLmL
MbZJNFnGro48gHt+/OQKLuftiVwE2opHfgogVKNL74FmYA0hMItdzpn4OPNFkP8t
Iq/m0hkXlTAKqBPITVLv1FN16v+Sm1iC317eP/HOTYqVZdJN3svVF8ZBfg29a8p6
6nl67fZhXgrt0OB6KSNIZEwMTWjFAqi365mtTssqAA0un94+cQ/WvAC5QcMM8g5S
i3G7vny9AsXor+GDU1z7UDWs3wBV4mVRdj7bBIS6PK+6oe012aNpRObcI2bU2BT/
H/7uHZWfwEmpfvH9RVZgoeETA3vSx7MDrNyDt3gwv2hxOHEd7nnVQ3EKG33173o1
/5/oEmn2USujKGhHJ2Zo3aWNRuUWZlvBaYw+PwB2R0UiuJbi0KofNYPssNdpw4sg
Qs7Nb2/Ilo1zn5bDh+WDrUrn6zHKAfBytBPpwPFWPZ8W10HUlC5vMZSKH5/UZhj5
kLlUC1zKjFPpRhO27ImTJuImil4lR2/CFjB1duG3JGJQaYIq8RFJOjvTVY29wl0i
pFy6y1Ofv2lLHB9K7N7dvvee2nvpUMkLEL52oFQ6Jc7sdg==
=Q4tk
-----END PGP MESSAGE-----

View file

@ -17,8 +17,8 @@ func (pgp *GopenPGP) UpdateTime(newTime int64) {
pgp.latestClientTime = time.Now()
}
// GetTimeUnix gets latest cached time
func (pgp *GopenPGP) GetTimeUnix() int64 {
// GetUnixTime gets latest cached time
func (pgp *GopenPGP) GetUnixTime() int64 {
return pgp.getNow().Unix()
}
@ -27,6 +27,9 @@ func (pgp *GopenPGP) GetTime() time.Time {
return pgp.getNow()
}
// ----- INTERNAL FUNCTIONS -----
// getNow returns current time
func (pgp *GopenPGP) getNow() time.Time {
if pgp.latestServerTime > 0 && !pgp.latestClientTime.IsZero() {
// Until is monotonic, it uses a monotonic clock in this case instead of the wall clock
@ -37,6 +40,7 @@ func (pgp *GopenPGP) getNow() time.Time {
return time.Now()
}
// getTimeGenerator Returns a time generator function
func (pgp *GopenPGP) getTimeGenerator() func() time.Time {
return func() time.Time {
return pgp.getNow()