diff --git a/ProposalChanges.md b/ProposalChanges.md index 4349d76..acb66ed 100644 --- a/ProposalChanges.md +++ b/ProposalChanges.md @@ -332,11 +332,7 @@ No change. No change. ### UnmarshalJSON -Renamed. -``` -(kr *KeyRing) UnmarshalJSON(b []byte) (err error): -* (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error) -``` +No change. ### Identities No change diff --git a/README.md b/README.md index b4255e5..ebbccf4 100644 --- a/README.md +++ b/README.md @@ -309,3 +309,22 @@ var verifyTime = pgp.GetUnixTime() verifiedPlainText, err := VerifyCleartextMessageArmored(publicKey, armored, verifyTime) ``` + +### Encrypting and decrypting session Keys +```go +// Keys initialization as before (omitted) + +symmetricKey := &SymmetricKey{ + Key: "RandomTokenabcdef", + Algo: constants.AES256, +} + +keyPacket, err := publicKey.EncryptSessionKey(symmetricKey) +``` +`KeyPacket` is a `[]byte` containing the session key encrypted with the private key. + + +```go +outputSymmetricKey, err := privateKey.DecryptSessionKey(keyPacket) +``` +`outputSymmetricKey` is an object of type `*SymmetricKey` that can be used to decrypt the correspondig message. diff --git a/crypto/attachment.go b/crypto/attachment.go index 7cc487e..e37b185 100644 --- a/crypto/attachment.go +++ b/crypto/attachment.go @@ -88,7 +88,8 @@ func (keyRing *KeyRing) newAttachmentProcessor( } // EncryptAttachment encrypts a file given a PlainMessage and a fileName. -// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data +// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data. +// Specifically designed for attachments rather than text messages. func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error) { ap, err := keyRing.newAttachmentProcessor(len(message.GetBinary()), fileName, -1) if err != nil { @@ -114,6 +115,7 @@ func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor( // DecryptAttachment takes a PGPSplitMessage, containing a session key packet and symmetrically encrypted data // and returns a decrypted PlainMessage +// Specifically designed for attachments rather than text messages. func (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error) { privKeyEntries := keyRing.entities diff --git a/crypto/attachment_test.go b/crypto/attachment_test.go index 382c4b1..104622e 100644 --- a/crypto/attachment_test.go +++ b/crypto/attachment_test.go @@ -64,7 +64,7 @@ func TestAttachmentEncrypt(t *testing.T) { t.Fatal("Expected no error while encrypting attachment, got:", err) } - pgpMessage := NewPGPMessage(append(encSplit.GetBinaryKeyPacket(), encSplit.GetBinaryDataPacket()...)) + pgpMessage := NewPGPMessage(encSplit.GetBinary()) redecData, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0) if err != nil { diff --git a/crypto/keyring.go b/crypto/keyring.go index c61ee3b..b110408 100644 --- a/crypto/keyring.go +++ b/crypto/keyring.go @@ -286,8 +286,8 @@ func (pgp *GopenPGP) BuildKeyRingArmored(key string) (keyRing *KeyRing, err erro return &KeyRing{entities: keyEntries}, err } -// ReadFromJSON reads multiple keys from a json array and fills the keyring -func (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error) { +// UnmarshalJSON reads multiple keys from a json array and fills the keyring +func (keyRing *KeyRing) UnmarshalJSON(jsonData []byte) (err error) { keyObjs, err := unmarshalJSON(jsonData) if err != nil { return err @@ -301,6 +301,10 @@ func (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error) { // 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) + if err != nil { + return nil, err + } + newKeyRing = &KeyRing{} err = newKeyRing.newKeyRingFromPGPKeyObject(keyObjs) if err != nil { @@ -356,17 +360,13 @@ func (keyRing *KeyRing) newKeyRingFromPGPKeyObject(keyObjs []pgpKeyObject) error return nil } -// unmarshalJSON implements encoding/json.Unmarshaler. +// unmarshalJSON decodes key json from the API 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 } @@ -447,3 +447,12 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err return filteredKeys, nil } + +// FirstKey returns a KeyRing with only the first key of the original one +func (keyRing *KeyRing) FirstKey() *KeyRing { + newKeyRing := &KeyRing{} + newKeyRing.FirstKeyID = keyRing.FirstKeyID + newKeyRing.entities = keyRing.entities[:1] + + return newKeyRing; +} diff --git a/crypto/keyring_message.go b/crypto/keyring_message.go index aa076ed..490cb82 100644 --- a/crypto/keyring_message.go +++ b/crypto/keyring_message.go @@ -12,8 +12,8 @@ import ( // 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 +// * 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 { @@ -24,9 +24,11 @@ func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PG } // 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) +// * 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) +// +// When verifyKey is not provided, then verifyTime should be zero, and signature verification will be ignored func (keyRing *KeyRing) Decrypt( message *PGPMessage, verifyKey *KeyRing, verifyTime int64, ) (*PlainMessage, error) { diff --git a/crypto/keyring_test.go b/crypto/keyring_test.go index 4a9aa2e..e9a0194 100644 --- a/crypto/keyring_test.go +++ b/crypto/keyring_test.go @@ -43,12 +43,12 @@ var testIdentity = &Identity{ func init() { var err error - testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false))) + testPrivateKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) if err != nil { panic(err) } - testPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false))) + testPublicKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_publicKey", false)) if err != nil { panic(err) } @@ -92,7 +92,7 @@ func TestKeyRing_ArmoredPublicKeyString(t *testing.T) { } func TestCheckPassphrase(t *testing.T) { - encryptedKeyRing, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false))) + encryptedKeyRing, _ := pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) isCorrect := encryptedKeyRing.CheckPassphrase("Wrong password") assert.Exactly(t, false, isCorrect) @@ -107,7 +107,7 @@ func TestIdentities(t *testing.T) { } func TestFilterExpiredKeys(t *testing.T) { - expiredKey, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("key_expiredKey", false))) + expiredKey, _ := pgp.BuildKeyRingArmored(readTestFile("key_expiredKey", false)) keys := []*KeyRing{testPrivateKeyRing, expiredKey} unexpired, err := FilterExpiredKeys(keys) @@ -149,9 +149,9 @@ func TestKeyIds(t *testing.T) { assert.Exactly(t, assertKeyIDs, keyIDs) } -func TestReadFromJson(t *testing.T) { +func TestUnmarshalJSON(t *testing.T) { decodedKeyRing := &KeyRing{} - err = decodedKeyRing.ReadFromJSON([]byte(readTestFile("keyring_jsonKeys", false))) + err = decodedKeyRing.UnmarshalJSON([]byte(readTestFile("keyring_jsonKeys", false))) if err != nil { t.Fatal("Expected no error while reading JSON, got:", err) } @@ -165,14 +165,14 @@ func TestReadFromJson(t *testing.T) { } func TestUnlockJson(t *testing.T) { - userKeyRing, err := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_userKey", false))) + userKeyRing, err := pgp.BuildKeyRingArmored(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) + t.Fatal("Expected no error while decrypting keyring, got:", err) } addressKeyRing, err := userKeyRing.UnlockJSONKeyRing([]byte(readTestFile("keyring_newJSONKeys", false))) @@ -183,4 +183,38 @@ func TestUnlockJson(t *testing.T) { for _, e := range addressKeyRing.entities { assert.Exactly(t, false, e.PrivateKey.Encrypted) } + + addressKeyRing, err = userKeyRing.UnlockJSONKeyRing([]byte(readTestFile("keyring_jsonKeys", false))) + if err != nil { + t.Fatal("Expected no error while reading and decrypting JSON, got:", err) + } + + for _, e := range addressKeyRing.entities { + assert.Exactly(t, true, e.PrivateKey.Encrypted) + } +} + +func TestMutlipleKeyRing(t *testing.T) { + testPublicKeyRing, _ = pgp.BuildKeyRingArmored(readTestFile("keyring_publicKey", false)) + assert.Exactly(t, 1, len(testPublicKeyRing.entities)) + + ids := testPublicKeyRing.KeyIds() + assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0]) + + err = testPublicKeyRing.readFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true) + if err != nil { + t.Fatal("Expected no error while adding a key to the keyring, got:", err) + } + + assert.Exactly(t, 2, len(testPublicKeyRing.entities)) + + ids = testPublicKeyRing.KeyIds() + assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0]) + assert.Exactly(t, uint64(0x374130b32ee1e5ea), ids[1]) + + singleKey := testPublicKeyRing.FirstKey() + assert.Exactly(t, 1, len(singleKey.entities)) + + ids = singleKey.KeyIds() + assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0]) } diff --git a/crypto/message.go b/crypto/message.go index 8362834..0a45410 100644 --- a/crypto/message.go +++ b/crypto/message.go @@ -20,7 +20,7 @@ import ( // ---- MODELS ----- -// PlainMessage stores an unencrypted message. +// PlainMessage stores a plain text / unencrypted message. type PlainMessage struct { // The content of the message Data []byte @@ -47,7 +47,9 @@ type PGPSplitMessage struct { KeyPacket []byte } -// ClearTextMessage, split signed clear text message container +// ClearTextMessage, split signed clear text message container. +// A Cleartext message is a signed PGP message, that is not encrypted, +// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE----- type ClearTextMessage struct { Data []byte Signature []byte @@ -153,7 +155,7 @@ func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage { func NewClearTextMessageFromArmored(signedMessage string) (*ClearTextMessage, error) { modulusBlock, rest := clearsign.Decode([]byte(signedMessage)) if len(rest) != 0 { - return nil, errors.New("pmapi: extra data after modulus") + return nil, errors.New("gopenpgp: extra data after modulus") } signature, err := ioutil.ReadAll(modulusBlock.ArmoredSignature.Body) @@ -221,7 +223,19 @@ func (msg *PGPSplitMessage) GetBinaryKeyPacket() []byte { return msg.KeyPacket } +// GetBinary returns the unarmored binary joined packets as a []byte +func (msg *PGPSplitMessage) GetBinary() []byte { + return append(msg.KeyPacket , msg.DataPacket...) +} + +// GetArmored returns the armored message as a string, with joined data and key packets +func (msg *PGPSplitMessage) GetArmored() (string, error) { + return armor.ArmorWithType(msg.GetBinary(), constants.PGPMessageHeader) +} + // SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified) +// * estimatedLength is the estimate length of the message +// * garbageCollector > 0 activates the garbage collector 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)) diff --git a/crypto/message_test.go b/crypto/message_test.go index 4b437a0..b8bf7cd 100644 --- a/crypto/message_test.go +++ b/crypto/message_test.go @@ -1,11 +1,14 @@ package crypto import ( + "bytes" "encoding/base64" + "io" "strings" "testing" "github.com/stretchr/testify/assert" + "golang.org/x/crypto/openpgp/packet" ) func TestTextMessageEncryptionWithSymmetricKey(t *testing.T) { @@ -52,8 +55,8 @@ func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) { 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))) + testPublicKeyRing, _ = pgp.BuildKeyRingArmored(readTestFile("keyring_publicKey", false)) + testPrivateKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) // Password defined in keyring_test err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword) @@ -77,8 +80,8 @@ 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))) + testPublicKeyRing, _ = pgp.BuildKeyRingArmored(readTestFile("keyring_publicKey", false)) + testPrivateKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) // Password defined in keyring_test err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword) @@ -96,6 +99,13 @@ func TestBinaryMessageEncryption(t *testing.T) { t.Fatal("Expected no error when decrypting, got:", err) } assert.Exactly(t, message.GetBinary(), decrypted.GetBinary()) + + // Decrypt without verifying + decrypted, err = testPrivateKeyRing.Decrypt(ciphertext, nil, 0) + if err != nil { + t.Fatal("Expected no error when decrypting, got:", err) + } + assert.Exactly(t, message.GetString(), decrypted.GetString()) } func TestIssue11(t *testing.T) { @@ -128,3 +138,70 @@ func TestIssue11(t *testing.T) { assert.Exactly(t, "message from sender", plainMessage.GetString()) } + +func TestSignedMessageDecryption(t *testing.T) { + testPrivateKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) + + // Password defined in keyring_test + err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword) + if err != nil { + t.Fatal("Expected no error unlocking privateKey, got:", err) + } + + pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false)) + if err != nil { + t.Fatal("Expected no error when unarmoring, got:", err) + } + + decrypted, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0) + if err != nil { + t.Fatal("Expected no error when decrypting, got:", err) + } + assert.Exactly(t, readTestFile("message_plaintext", true), decrypted.GetString()) +} + +func TestMultipleKeyMessageEncryption(t *testing.T) { + var message = NewPlainMessageFromString("plain text") + + testPublicKeyRing, _ = pgp.BuildKeyRingArmored(readTestFile("keyring_publicKey", false)) + err = testPublicKeyRing.readFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true) + if err != nil { + t.Fatal("Expected no error adding second public key, got:", err) + } + + assert.Exactly(t, 2, len(testPublicKeyRing.entities)) + + testPrivateKeyRing, err = pgp.BuildKeyRingArmored(readTestFile("keyring_privateKey", false)) + + // Password defined in keyring_test + err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword) + if err != nil { + t.Fatal("Expected no error unlocking privateKey, got:", err) + } + + ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing) + if err != nil { + t.Fatal("Expected no error when encrypting, got:", err) + } + + numKeyPackets := 0 + packets := packet.NewReader(bytes.NewReader(ciphertext.Data)) + for { + var p packet.Packet + if p, err = packets.Next(); err == io.EOF { + err = nil + break + } + switch p.(type) { + case *packet.EncryptedKey: + numKeyPackets++ + } + } + assert.Exactly(t, 2, numKeyPackets) + + decrypted, 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()) +} diff --git a/crypto/symmetrickey.go b/crypto/symmetrickey.go index 1b872d7..27d32e1 100644 --- a/crypto/symmetrickey.go +++ b/crypto/symmetrickey.go @@ -74,8 +74,8 @@ func newSymmetricKeyFromEncrypted(ek *packet.EncryptedKey) (*SymmetricKey, error } // Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey -// message : The plain data as a PlainMessage -// output : The encrypted data as PGPMessage +// * 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 { @@ -86,8 +86,8 @@ func (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, e } // Decrypt decrypts password protected pgp binary messages -// encrypted: PGPMessage -// output: PlainMessage +// * encrypted: PGPMessage +// * output: PlainMessage func (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error) { decrypted, err := symmetricDecrypt(message.NewReader(), symmetricKey) if err != nil { diff --git a/crypto/testdata/keyring_jsonKeys b/crypto/testdata/keyring_jsonKeys index 50bc12d..f403bd2 100644 --- a/crypto/testdata/keyring_jsonKeys +++ b/crypto/testdata/keyring_jsonKeys @@ -6,7 +6,8 @@ "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 + "Token": null, + "Signature": null }, { "ID": "88xeHAObMEZp8b15R3643qJFbRoAXPm-FNuHxWO_VBVEqm294wuKCbIdoytQwHq2uwTxAXB-c4Jh0R47F34qig==", @@ -15,6 +16,7 @@ "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 + "Token": null, + "Signature": null } ] diff --git a/crypto/testdata/message_plaintext b/crypto/testdata/message_plaintext new file mode 100644 index 0000000..d0976d7 --- /dev/null +++ b/crypto/testdata/message_plaintext @@ -0,0 +1 @@ +