From 97323a4c2b768c83481ad8f457f8d91e493c7579 Mon Sep 17 00:00:00 2001 From: "M. Thiercelin" Date: Thu, 6 Apr 2023 11:38:15 +0200 Subject: [PATCH] Add signature context for embedded signatures --- CHANGELOG.md | 1 + crypto/keyring_message.go | 97 ++++++++--- crypto/keyring_message_test.go | 42 +++++ crypto/keyring_streaming.go | 243 ++++++++++++++++++++------ crypto/keyring_streaming_test.go | 254 ++++++++++++++++++++++++++++ crypto/sessionkey.go | 175 +++++++++++++------ crypto/sessionkey_streaming.go | 136 ++++++++++----- crypto/sessionkey_streaming_test.go | 98 +++++++++++ crypto/sessionkey_test.go | 24 +++ crypto/signature.go | 9 +- crypto/signature_test.go | 21 +++ 11 files changed, 931 insertions(+), 169 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a99f952..0c533fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ status `constants.SIGNATURE_BAD_CONTEXT` instead of `constants.SIGNATURE_FAILED` ## Added - Add api for signature context on streams `SignDetachedStreamWithContext`. +- Add API for signature context on embedded signatures. ## [2.6.1] 2023-03-22 diff --git a/crypto/keyring_message.go b/crypto/keyring_message.go index e3809c3..f0fc16e 100644 --- a/crypto/keyring_message.go +++ b/crypto/keyring_message.go @@ -17,13 +17,16 @@ import ( // * 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) { - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()} - encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config) - if err != nil { - return nil, err - } + return asymmetricEncrypt(message, keyRing, privateKey, false, nil) +} - return NewPGPMessage(encrypted), nil +// EncryptWithContext 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. +// * signingContext : (optional) the context for the signature. +func (keyRing *KeyRing) EncryptWithContext(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) { + return asymmetricEncrypt(message, keyRing, privateKey, false, signingContext) } // EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage using public/private keys. @@ -31,32 +34,47 @@ func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PG // * privateKey : (optional) an unlocked private keyring to include signature in the message. // * output : The encrypted data as PGPMessage. func (keyRing *KeyRing) EncryptWithCompression(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) { - config := &packet.Config{ - DefaultCipher: packet.CipherAES256, - Time: getTimeGenerator(), - DefaultCompressionAlgo: constants.DefaultCompression, - CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}, - } + return asymmetricEncrypt(message, keyRing, privateKey, true, nil) +} - encrypted, err := asymmetricEncrypt(message, keyRing, privateKey, config) - if err != nil { - return nil, err - } - - return NewPGPMessage(encrypted), nil +// EncryptWithContextAndCompression encrypts with compression support a PlainMessage to PGPMessage using public/private keys. +// * message : The plain data as a PlainMessage. +// * privateKey : (optional) an unlocked private keyring to include signature in the message. +// * signingContext : (optional) the context for the signature. +// * output : The encrypted data as PGPMessage. +func (keyRing *KeyRing) EncryptWithContextAndCompression(message *PlainMessage, privateKey *KeyRing, signingContext *SigningContext) (*PGPMessage, error) { + return asymmetricEncrypt(message, keyRing, privateKey, true, signingContext) } // 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) +// * verificationContext : (optional) the context for the signature verification. // // 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) { - return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime) + return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, nil) +} + +// DecryptWithContext 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) +// * verificationContext : (optional) the context for the signature verification. +// +// When verifyKey is not provided, then verifyTime should be zero, and +// signature verification will be ignored. +func (keyRing *KeyRing) DecryptWithContext( + message *PGPMessage, + verifyKey *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (*PlainMessage, error) { + return asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime, verificationContext) } // SignDetached generates and returns a PGPSignature for a given PlainMessage. @@ -181,8 +199,9 @@ func (keyRing *KeyRing) GetVerifiedSignatureTimestampWithContext( func asymmetricEncrypt( plainMessage *PlainMessage, publicKey, privateKey *KeyRing, - config *packet.Config, -) ([]byte, error) { + compress bool, + signingContext *SigningContext, +) (*PGPMessage, error) { var outBuf bytes.Buffer var encryptWriter io.WriteCloser var err error @@ -193,7 +212,7 @@ func asymmetricEncrypt( ModTime: plainMessage.getFormattedTime(), } - encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, config) + encryptWriter, err = asymmetricEncryptStream(hints, &outBuf, &outBuf, publicKey, privateKey, compress, signingContext) if err != nil { return nil, err } @@ -208,7 +227,7 @@ func asymmetricEncrypt( return nil, errors.Wrap(err, "gopenpgp: error in closing message") } - return outBuf.Bytes(), nil + return &PGPMessage{outBuf.Bytes()}, nil } // Core for encryption+signature (all) functions. @@ -217,10 +236,24 @@ func asymmetricEncryptStream( keyPacketWriter io.Writer, dataPacketWriter io.Writer, publicKey, privateKey *KeyRing, - config *packet.Config, + compress bool, + signingContext *SigningContext, ) (encryptWriter io.WriteCloser, err error) { - var signEntity *openpgp.Entity + config := &packet.Config{ + DefaultCipher: packet.CipherAES256, + Time: getTimeGenerator(), + } + if compress { + config.DefaultCompressionAlgo = constants.DefaultCompression + config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel} + } + + if signingContext != nil { + config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation()) + } + + var signEntity *openpgp.Entity if privateKey != nil && len(privateKey.entities) > 0 { var err error signEntity, err = privateKey.getSigningEntity() @@ -242,13 +275,18 @@ func asymmetricEncryptStream( // Core for decryption+verification (non streaming) functions. func asymmetricDecrypt( - encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64, + encryptedIO io.Reader, + privateKey *KeyRing, + verifyKey *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, ) (message *PlainMessage, err error) { messageDetails, err := asymmetricDecryptStream( encryptedIO, privateKey, verifyKey, verifyTime, + verificationContext, ) if err != nil { return nil, err @@ -261,7 +299,7 @@ func asymmetricDecrypt( if verifyKey != nil { processSignatureExpiration(messageDetails, verifyTime) - err = verifyDetailsSignature(messageDetails, verifyKey) + err = verifyDetailsSignature(messageDetails, verifyKey, verificationContext) } return &PlainMessage{ @@ -278,6 +316,7 @@ func asymmetricDecryptStream( privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64, + verificationContext *VerificationContext, ) (messageDetails *openpgp.MessageDetails, err error) { privKeyEntries := privateKey.entities var additionalEntries openpgp.EntityList @@ -304,6 +343,10 @@ func asymmetricDecryptStream( }, } + if verificationContext != nil { + config.KnownNotations = map[string]bool{constants.SignatureContextName: true} + } + messageDetails, err = openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config) if err != nil { return nil, errors.Wrap(err, "gopenpgp: error in reading message") diff --git a/crypto/keyring_message_test.go b/crypto/keyring_message_test.go index c577339..de1f8ee 100644 --- a/crypto/keyring_message_test.go +++ b/crypto/keyring_message_test.go @@ -36,3 +36,45 @@ func TestAEADKeyRingDecryption(t *testing.T) { assert.Exactly(t, "hello world\n", decrypted.GetString()) } + +func TestTextMessageEncryptionWithSignatureAndContext(t *testing.T) { + var message = NewPlainMessageFromString("plain text") + var testContext = "test-context" + + ciphertext, err := keyRingTestPublic.EncryptWithContext(message, keyRingTestPrivate, NewSigningContext(testContext, true)) + if err != nil { + t.Fatal("Expected no error when encrypting, got:", err) + } + + decrypted, err := keyRingTestPrivate.DecryptWithContext( + ciphertext, + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error when decrypting, got:", err) + } + assert.Exactly(t, message.GetString(), decrypted.GetString()) +} + +func TestTextMessageEncryptionWithSignatureAndContextAndCompression(t *testing.T) { + var message = NewPlainMessageFromString("plain text") + var testContext = "test-context" + + ciphertext, err := keyRingTestPublic.EncryptWithContextAndCompression(message, keyRingTestPrivate, NewSigningContext(testContext, true)) + if err != nil { + t.Fatal("Expected no error when encrypting, got:", err) + } + + decrypted, err := keyRingTestPrivate.DecryptWithContext( + ciphertext, + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error when decrypting, got:", err) + } + assert.Exactly(t, message.GetString(), decrypted.GetString()) +} diff --git a/crypto/keyring_streaming.go b/crypto/keyring_streaming.go index b0ba511..6d99402 100644 --- a/crypto/keyring_streaming.go +++ b/crypto/keyring_streaming.go @@ -6,8 +6,6 @@ import ( "time" "github.com/ProtonMail/go-crypto/openpgp" - "github.com/ProtonMail/go-crypto/openpgp/packet" - "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/pkg/errors" ) @@ -42,14 +40,35 @@ func (keyRing *KeyRing) EncryptStream( plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (plainMessageWriter WriteCloser, err error) { - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()} - - return keyRing.encryptStreamWithConfig( - config, + return encryptStream( + keyRing, pgpMessageWriter, pgpMessageWriter, plainMessageMetadata, signKeyRing, + false, + nil, + ) +} + +// EncryptStreamWithContext is used to encrypt data as a Writer. +// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) a context for the embedded signature. +func (keyRing *KeyRing) EncryptStreamWithContext( + pgpMessageWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + signingContext *SigningContext, +) (plainMessageWriter WriteCloser, err error) { + return encryptStream( + keyRing, + pgpMessageWriter, + pgpMessageWriter, + plainMessageMetadata, + signKeyRing, + false, + signingContext, ) } @@ -62,28 +81,47 @@ func (keyRing *KeyRing) EncryptStreamWithCompression( plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (plainMessageWriter WriteCloser, err error) { - config := &packet.Config{ - DefaultCipher: packet.CipherAES256, - Time: getTimeGenerator(), - DefaultCompressionAlgo: constants.DefaultCompression, - CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}, - } - - return keyRing.encryptStreamWithConfig( - config, + return encryptStream( + keyRing, pgpMessageWriter, pgpMessageWriter, plainMessageMetadata, signKeyRing, + true, + nil, ) } -func (keyRing *KeyRing) encryptStreamWithConfig( - config *packet.Config, +// EncryptStreamWithContextAndCompression is used to encrypt data as a Writer. +// The plaintext data is compressed before being encrypted. +// It takes a writer for the encrypted data and returns a WriteCloser for the plaintext data +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) a context for the embedded signature. +func (keyRing *KeyRing) EncryptStreamWithContextAndCompression( + pgpMessageWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + signingContext *SigningContext, +) (plainMessageWriter WriteCloser, err error) { + return encryptStream( + keyRing, + pgpMessageWriter, + pgpMessageWriter, + plainMessageMetadata, + signKeyRing, + true, + signingContext, + ) +} + +func encryptStream( + encryptionKeyRing *KeyRing, keyPacketWriter Writer, dataPacketWriter Writer, plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, + compress bool, + signingContext *SigningContext, ) (plainMessageWriter WriteCloser, err error) { if plainMessageMetadata == nil { // Use sensible default metadata @@ -100,7 +138,7 @@ func (keyRing *KeyRing) encryptStreamWithConfig( ModTime: time.Unix(plainMessageMetadata.ModTime, 0), } - plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, keyRing, signKeyRing, config) + plainMessageWriter, err = asymmetricEncryptStream(hints, keyPacketWriter, dataPacketWriter, encryptionKeyRing, signKeyRing, compress, signingContext) if err != nil { return nil, err } @@ -148,25 +186,36 @@ func (keyRing *KeyRing) EncryptSplitStream( plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (*EncryptSplitResult, error) { - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: getTimeGenerator()} - - var keyPacketBuf bytes.Buffer - - plainMessageWriter, err := keyRing.encryptStreamWithConfig( - config, - &keyPacketBuf, + return encryptSplitStream( + keyRing, dataPacketWriter, plainMessageMetadata, signKeyRing, + false, + nil, ) - if err != nil { - return nil, err - } +} - return &EncryptSplitResult{ - keyPacketBuf: &keyPacketBuf, - plainMessageWriter: plainMessageWriter, - }, nil +// EncryptSplitStreamWithContext is used to encrypt data as a stream. +// It takes a writer for the Symmetrically Encrypted Data Packet +// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) +// and returns a writer for the plaintext data and the key packet. +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) a context for the embedded signature. +func (keyRing *KeyRing) EncryptSplitStreamWithContext( + dataPacketWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + signingContext *SigningContext, +) (*EncryptSplitResult, error) { + return encryptSplitStream( + keyRing, + dataPacketWriter, + plainMessageMetadata, + signKeyRing, + false, + signingContext, + ) } // EncryptSplitStreamWithCompression is used to encrypt data as a stream. @@ -179,21 +228,55 @@ func (keyRing *KeyRing) EncryptSplitStreamWithCompression( plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (*EncryptSplitResult, error) { - config := &packet.Config{ - DefaultCipher: packet.CipherAES256, - Time: getTimeGenerator(), - DefaultCompressionAlgo: constants.DefaultCompression, - CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}, - } + return encryptSplitStream( + keyRing, + dataPacketWriter, + plainMessageMetadata, + signKeyRing, + true, + nil, + ) +} +// EncryptSplitStreamWithContextAndCompression is used to encrypt data as a stream. +// It takes a writer for the Symmetrically Encrypted Data Packet +// (https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) +// and returns a writer for the plaintext data and the key packet. +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) a context for the embedded signature. +func (keyRing *KeyRing) EncryptSplitStreamWithContextAndCompression( + dataPacketWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + signingContext *SigningContext, +) (*EncryptSplitResult, error) { + return encryptSplitStream( + keyRing, + dataPacketWriter, + plainMessageMetadata, + signKeyRing, + true, + signingContext, + ) +} + +func encryptSplitStream( + encryptionKeyRing *KeyRing, + dataPacketWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + compress bool, + signingContext *SigningContext, +) (*EncryptSplitResult, error) { var keyPacketBuf bytes.Buffer - - plainMessageWriter, err := keyRing.encryptStreamWithConfig( - config, + plainMessageWriter, err := encryptStream( + encryptionKeyRing, &keyPacketBuf, dataPacketWriter, plainMessageMetadata, signKeyRing, + compress, + signingContext, ) if err != nil { return nil, err @@ -208,10 +291,11 @@ func (keyRing *KeyRing) EncryptSplitStreamWithCompression( // PlainMessageReader is used to wrap the data of the decrypted plain message. // It can be used to read the decrypted data and verify the embedded signature. type PlainMessageReader struct { - details *openpgp.MessageDetails - verifyKeyRing *KeyRing - verifyTime int64 - readAll bool + details *openpgp.MessageDetails + verifyKeyRing *KeyRing + verifyTime int64 + readAll bool + verificationContext *VerificationContext } // GetMetadata returns the metadata of the decrypted message. @@ -243,7 +327,7 @@ func (msg *PlainMessageReader) VerifySignature() (err error) { } if msg.verifyKeyRing != nil { processSignatureExpiration(msg.details, msg.verifyTime) - err = verifyDetailsSignature(msg.details, msg.verifyKeyRing) + err = verifyDetailsSignature(msg.details, msg.verifyKeyRing, msg.verificationContext) } else { err = errors.New("gopenpgp: no verify keyring was provided before decryption") } @@ -260,11 +344,49 @@ func (keyRing *KeyRing) DecryptStream( verifyKeyRing *KeyRing, verifyTime int64, ) (plainMessage *PlainMessageReader, err error) { - messageDetails, err := asymmetricDecryptStream( - message, + return decryptStream( keyRing, + message, verifyKeyRing, verifyTime, + nil, + ) +} + +// DecryptStreamWithContext is used to decrypt a pgp message as a Reader. +// It takes a reader for the message data +// and returns a PlainMessageReader for the plaintext data. +// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will +// verify the embedded signature with the given key ring and verification time. +// * verificationContext (optional): context for the signature verification. +func (keyRing *KeyRing) DecryptStreamWithContext( + message Reader, + verifyKeyRing *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (plainMessage *PlainMessageReader, err error) { + return decryptStream( + keyRing, + message, + verifyKeyRing, + verifyTime, + verificationContext, + ) +} + +func decryptStream( + decryptionKeyRing *KeyRing, + message Reader, + verifyKeyRing *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (plainMessage *PlainMessageReader, err error) { + messageDetails, err := asymmetricDecryptStream( + message, + decryptionKeyRing, + verifyKeyRing, + verifyTime, + verificationContext, ) if err != nil { return nil, err @@ -275,6 +397,7 @@ func (keyRing *KeyRing) DecryptStream( verifyKeyRing, verifyTime, false, + verificationContext, }, err } @@ -299,6 +422,30 @@ func (keyRing *KeyRing) DecryptSplitStream( ) } +// DecryptSplitStreamWithContext is used to decrypt a split pgp message as a Reader. +// It takes a key packet and a reader for the data packet +// and returns a PlainMessageReader for the plaintext data. +// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will +// verify the embedded signature with the given key ring and verification time. +// * verificationContext (optional): context for the signature verification. +func (keyRing *KeyRing) DecryptSplitStreamWithContext( + keypacket []byte, + dataPacketReader Reader, + verifyKeyRing *KeyRing, verifyTime int64, + verificationContext *VerificationContext, +) (plainMessage *PlainMessageReader, err error) { + messageReader := io.MultiReader( + bytes.NewReader(keypacket), + dataPacketReader, + ) + return keyRing.DecryptStreamWithContext( + messageReader, + verifyKeyRing, + verifyTime, + verificationContext, + ) +} + // SignDetachedStream generates and returns a PGPSignature for a given message Reader. func (keyRing *KeyRing) SignDetachedStream(message Reader) (*PGPSignature, error) { return keyRing.SignDetachedStreamWithContext(message, nil) diff --git a/crypto/keyring_streaming_test.go b/crypto/keyring_streaming_test.go index 77083f9..1cd6e44 100644 --- a/crypto/keyring_streaming_test.go +++ b/crypto/keyring_streaming_test.go @@ -10,6 +10,8 @@ import ( "github.com/pkg/errors" ) +const testContext = "test-context" + var testMeta = &PlainMessageMetadata{ IsBinary: true, Filename: "filename.txt", @@ -106,6 +108,190 @@ func TestKeyRing_EncryptDecryptStream(t *testing.T) { } } +func TestKeyRing_EncryptDecryptStreamWithContext(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + var ciphertextBuf bytes.Buffer + messageWriter, err := keyRingTestPublic.EncryptStreamWithContext( + &ciphertextBuf, + testMeta, + keyRingTestPrivate, + NewSigningContext(testContext, true), + ) + if err != nil { + t.Fatal("Expected no error while encrypting stream with key ring, got:", err) + } + reachedEnd := false + bufferSize := 2 + buffer := make([]byte, bufferSize) + for !reachedEnd { + n, err := messageReader.Read(buffer) + if err != nil { + if errors.Is(err, io.EOF) { + reachedEnd = true + } else { + t.Fatal("Expected no error while reading data, got:", err) + } + } + writtenTotal := 0 + for writtenTotal < n { + written, err := messageWriter.Write(buffer[writtenTotal:n]) + if err != nil { + t.Fatal("Expected no error while writing data, got:", err) + } + writtenTotal += written + } + } + err = messageWriter.Close() + if err != nil { + t.Fatal("Expected no error while closing plaintext writer, got:", err) + } + ciphertextBytes := ciphertextBuf.Bytes() + decryptedReader, err := keyRingTestPrivate.DecryptStreamWithContext( + bytes.NewReader(ciphertextBytes), + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error while calling decrypting stream with key ring, got:", err) + } + err = decryptedReader.VerifySignature() + if err == nil { + t.Fatal("Expected an error while verifying the signature before reading the data, got nil") + } + decryptedBytes, err := ioutil.ReadAll(decryptedReader) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + err = decryptedReader.VerifySignature() + if err != nil { + t.Fatal("Expected no error while verifying the signature, got:", err) + } + decryptedMeta := decryptedReader.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } + decryptedReaderNoVerify, err := keyRingTestPrivate.DecryptStream( + bytes.NewReader(ciphertextBytes), + nil, + 0, + ) + if err != nil { + t.Fatal("Expected no error while calling decrypting stream with key ring, got:", err) + } + decryptedBytes, err = ioutil.ReadAll(decryptedReaderNoVerify) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + decryptedMeta = decryptedReaderNoVerify.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } + err = decryptedReaderNoVerify.VerifySignature() + if err == nil { + t.Fatal("Expected an error while verifying the signature with no keyring, got nil") + } +} + +func TestKeyRing_EncryptDecryptStreamWithContextAndCompression(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + var ciphertextBuf bytes.Buffer + messageWriter, err := keyRingTestPublic.EncryptStreamWithContextAndCompression( + &ciphertextBuf, + testMeta, + keyRingTestPrivate, + NewSigningContext(testContext, true), + ) + if err != nil { + t.Fatal("Expected no error while encrypting stream with key ring, got:", err) + } + reachedEnd := false + bufferSize := 2 + buffer := make([]byte, bufferSize) + for !reachedEnd { + n, err := messageReader.Read(buffer) + if err != nil { + if errors.Is(err, io.EOF) { + reachedEnd = true + } else { + t.Fatal("Expected no error while reading data, got:", err) + } + } + writtenTotal := 0 + for writtenTotal < n { + written, err := messageWriter.Write(buffer[writtenTotal:n]) + if err != nil { + t.Fatal("Expected no error while writing data, got:", err) + } + writtenTotal += written + } + } + err = messageWriter.Close() + if err != nil { + t.Fatal("Expected no error while closing plaintext writer, got:", err) + } + ciphertextBytes := ciphertextBuf.Bytes() + decryptedReader, err := keyRingTestPrivate.DecryptStreamWithContext( + bytes.NewReader(ciphertextBytes), + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error while calling decrypting stream with key ring, got:", err) + } + err = decryptedReader.VerifySignature() + if err == nil { + t.Fatal("Expected an error while verifying the signature before reading the data, got nil") + } + decryptedBytes, err := ioutil.ReadAll(decryptedReader) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + err = decryptedReader.VerifySignature() + if err != nil { + t.Fatal("Expected no error while verifying the signature, got:", err) + } + decryptedMeta := decryptedReader.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } + decryptedReaderNoVerify, err := keyRingTestPrivate.DecryptStream( + bytes.NewReader(ciphertextBytes), + nil, + 0, + ) + if err != nil { + t.Fatal("Expected no error while calling decrypting stream with key ring, got:", err) + } + decryptedBytes, err = ioutil.ReadAll(decryptedReaderNoVerify) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + decryptedMeta = decryptedReaderNoVerify.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } + err = decryptedReaderNoVerify.VerifySignature() + if err == nil { + t.Fatal("Expected an error while verifying the signature with no keyring, got nil") + } +} + func TestKeyRing_EncryptStreamCompatible(t *testing.T) { enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) { return keyRingTestPublic.EncryptStream( @@ -299,6 +485,57 @@ func TestKeyRing_EncryptDecryptSplitStream(t *testing.T) { } } +func TestKeyRing_EncryptDecryptSplitStreamWithCont(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + + var dataPacketBuf bytes.Buffer + encryptionResult, err := keyRingTestPublic.EncryptSplitStreamWithContext( + &dataPacketBuf, + testMeta, + keyRingTestPrivate, + NewSigningContext(testContext, true), + ) + if err != nil { + t.Fatal("Expected no error while calling encrypting split stream with key ring, got:", err) + } + messageWriter := encryptionResult + _, err = io.Copy(messageWriter, messageReader) + if err != nil { + t.Fatal("Expected no error while copying plaintext writer, got:", err) + } + err = messageWriter.Close() + if err != nil { + t.Fatal("Expected no error while closing plaintext writer, got:", err) + } + keyPacket, err := encryptionResult.GetKeyPacket() + if err != nil { + t.Fatal("Expected no error while accessing key packet, got:", err) + } + dataPacket := dataPacketBuf.Bytes() + decryptedReader, err := keyRingTestPrivate.DecryptSplitStreamWithContext( + keyPacket, + bytes.NewReader(dataPacket), + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error while decrypting split stream with key ring, got:", err) + } + decryptedBytes, err := ioutil.ReadAll(decryptedReader) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + err = decryptedReader.VerifySignature() + if err != nil { + t.Fatal("Expected no error while verifying the signature, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } +} + func TestKeyRing_EncryptSplitStreamCompatible(t *testing.T) { enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (*EncryptSplitResult, error) { return keyRingTestPublic.EncryptSplitStream( @@ -462,6 +699,23 @@ func TestKeyRing_SignVerifyDetachedStream(t *testing.T) { } } +func TestKeyRing_SignVerifyDetachedStreamWithContext(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + signature, err := keyRingTestPrivate.SignDetachedStreamWithContext(messageReader, NewSigningContext(testContext, true)) + if err != nil { + t.Fatal("Expected no error while signing the message, got:", err) + } + _, err = messageReader.Seek(0, 0) + if err != nil { + t.Fatal("Expected no error while rewinding the message reader, got:", err) + } + err = keyRingTestPublic.VerifyDetachedStreamWithContext(messageReader, signature, GetUnixTime(), NewVerificationContext(testContext, true, 0)) + if err != nil { + t.Fatal("Expected no error while verifying the detached signature, got:", err) + } +} + func TestKeyRing_SignDetachedStreamCompatible(t *testing.T) { messageBytes := []byte("Hello World!") messageReader := bytes.NewReader(messageBytes) diff --git a/crypto/sessionkey.go b/crypto/sessionkey.go index c8c4dcf..1e48209 100644 --- a/crypto/sessionkey.go +++ b/crypto/sessionkey.go @@ -138,17 +138,7 @@ func newSessionKeyFromEncrypted(ek *packet.EncryptedKey) (*SessionKey, error) { // * message : The plain data as a PlainMessage. // * output : The encrypted data as PGPMessage. func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) { - dc, err := sk.GetCipherFunc() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key") - } - - config := &packet.Config{ - Time: getTimeGenerator(), - DefaultCipher: dc, - } - - return encryptWithSessionKey(message, sk, nil, config) + return encryptWithSessionKey(message, sk, nil, false, nil) } // EncryptAndSign encrypts a PlainMessage to PGPMessage with a SessionKey and signs it with a Private key. @@ -156,59 +146,50 @@ func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) { // * signKeyRing: The KeyRing to sign the message // * output : The encrypted data as PGPMessage. func (sk *SessionKey) EncryptAndSign(message *PlainMessage, signKeyRing *KeyRing) ([]byte, error) { - dc, err := sk.GetCipherFunc() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key") - } + return encryptWithSessionKey(message, sk, signKeyRing, false, nil) +} - config := &packet.Config{ - Time: getTimeGenerator(), - DefaultCipher: dc, - } - - signEntity, err := signKeyRing.getSigningEntity() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to sign") - } - - return encryptWithSessionKey(message, sk, signEntity, config) +// EncryptAndSignWithContext encrypts a PlainMessage to PGPMessage with a SessionKey and signs it with a Private key. +// * message : The plain data as a PlainMessage. +// * signKeyRing: The KeyRing to sign the message +// * output : The encrypted data as PGPMessage. +// * signingContext : (optional) the context for the signature. +func (sk *SessionKey) EncryptAndSignWithContext(message *PlainMessage, signKeyRing *KeyRing, signingContext *SigningContext) ([]byte, error) { + return encryptWithSessionKey(message, sk, signKeyRing, false, signingContext) } // EncryptWithCompression encrypts with compression support a PlainMessage to PGPMessage with a SessionKey. // * message : The plain data as a PlainMessage. // * output : The encrypted data as PGPMessage. func (sk *SessionKey) EncryptWithCompression(message *PlainMessage) ([]byte, error) { - dc, err := sk.GetCipherFunc() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key") - } - - config := &packet.Config{ - Time: getTimeGenerator(), - DefaultCipher: dc, - DefaultCompressionAlgo: constants.DefaultCompression, - CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}, - } - - return encryptWithSessionKey(message, sk, nil, config) + return encryptWithSessionKey(message, sk, nil, true, nil) } -func encryptWithSessionKey(message *PlainMessage, sk *SessionKey, signEntity *openpgp.Entity, config *packet.Config) ([]byte, error) { +func encryptWithSessionKey( + message *PlainMessage, + sk *SessionKey, + signKeyRing *KeyRing, + compress bool, + signingContext *SigningContext, +) ([]byte, error) { var encBuf = new(bytes.Buffer) encryptWriter, signWriter, err := encryptStreamWithSessionKey( - message.IsBinary(), - message.Filename, - message.Time, + NewPlainMessageMetadata( + message.IsBinary(), + message.Filename, + int64(message.Time), + ), encBuf, sk, - signEntity, - config, + signKeyRing, + compress, + signingContext, ) if err != nil { return nil, err } - if signEntity != nil { + if signKeyRing != nil { _, err = signWriter.Write(message.GetBinary()) if err != nil { return nil, errors.Wrap(err, "gopenpgp: error in writing signed message") @@ -231,6 +212,61 @@ func encryptWithSessionKey(message *PlainMessage, sk *SessionKey, signEntity *op } func encryptStreamWithSessionKey( + plainMessageMetadata *PlainMessageMetadata, + dataPacketWriter io.Writer, + sk *SessionKey, + signKeyRing *KeyRing, + compress bool, + signingContext *SigningContext, +) (encryptWriter, signWriter io.WriteCloser, err error) { + dc, err := sk.GetCipherFunc() + if err != nil { + return nil, nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key") + } + + config := &packet.Config{ + Time: getTimeGenerator(), + DefaultCipher: dc, + } + + var signEntity *openpgp.Entity + if signKeyRing != nil { + signEntity, err = signKeyRing.getSigningEntity() + if err != nil { + return nil, nil, errors.Wrap(err, "gopenpgp: unable to sign") + } + } + + if compress { + config.DefaultCompressionAlgo = constants.DefaultCompression + config.CompressionConfig = &packet.CompressionConfig{Level: constants.DefaultCompressionLevel} + } + + if signingContext != nil { + config.SignatureNotations = append(config.SignatureNotations, signingContext.getNotation()) + } + + if plainMessageMetadata == nil { + // Use sensible default metadata + plainMessageMetadata = &PlainMessageMetadata{ + IsBinary: true, + Filename: "", + ModTime: GetUnixTime(), + } + } + + return encryptStreamWithSessionKeyAndConfig( + plainMessageMetadata.IsBinary, + plainMessageMetadata.Filename, + uint32(plainMessageMetadata.ModTime), + dataPacketWriter, + sk, + signEntity, + config, + ) +} + +func encryptStreamWithSessionKeyAndConfig( isBinary bool, filename string, modTime uint32, @@ -297,9 +333,41 @@ func (sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error) { // * verifyTime: when should the signature be valid, as timestamp. If 0 time verification is disabled. // * output: PlainMessage. func (sk *SessionKey) DecryptAndVerify(dataPacket []byte, verifyKeyRing *KeyRing, verifyTime int64) (*PlainMessage, error) { + return decryptWithSessionKeyAndContext( + sk, + dataPacket, + verifyKeyRing, + verifyTime, + nil, + ) +} + +// DecryptAndVerifyWithContext decrypts pgp data packets using directly a session key and verifies embedded signatures. +// * encrypted: PGPMessage. +// * verifyKeyRing: KeyRing with verification public keys +// * verifyTime: when should the signature be valid, as timestamp. If 0 time verification is disabled. +// * output: PlainMessage. +// * verificationContext (optional): context for the signature verification. +func (sk *SessionKey) DecryptAndVerifyWithContext(dataPacket []byte, verifyKeyRing *KeyRing, verifyTime int64, verificationContext *VerificationContext) (*PlainMessage, error) { + return decryptWithSessionKeyAndContext( + sk, + dataPacket, + verifyKeyRing, + verifyTime, + verificationContext, + ) +} + +func decryptWithSessionKeyAndContext( + sk *SessionKey, + dataPacket []byte, + verifyKeyRing *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (*PlainMessage, error) { var messageReader = bytes.NewReader(dataPacket) - md, err := decryptStreamWithSessionKey(sk, messageReader, verifyKeyRing) + md, err := decryptStreamWithSessionKey(sk, messageReader, verifyKeyRing, verificationContext) if err != nil { return nil, err } @@ -311,7 +379,7 @@ func (sk *SessionKey) DecryptAndVerify(dataPacket []byte, verifyKeyRing *KeyRing if verifyKeyRing != nil { processSignatureExpiration(md, verifyTime) - err = verifyDetailsSignature(md, verifyKeyRing) + err = verifyDetailsSignature(md, verifyKeyRing, verificationContext) } return &PlainMessage{ @@ -322,7 +390,12 @@ func (sk *SessionKey) DecryptAndVerify(dataPacket []byte, verifyKeyRing *KeyRing }, err } -func decryptStreamWithSessionKey(sk *SessionKey, messageReader io.Reader, verifyKeyRing *KeyRing) (*openpgp.MessageDetails, error) { +func decryptStreamWithSessionKey( + sk *SessionKey, + messageReader io.Reader, + verifyKeyRing *KeyRing, + verificationContext *VerificationContext, +) (*openpgp.MessageDetails, error) { var decrypted io.ReadCloser var keyring openpgp.EntityList @@ -356,6 +429,10 @@ func decryptStreamWithSessionKey(sk *SessionKey, messageReader io.Reader, verify Time: getTimeGenerator(), } + if verificationContext != nil { + config.KnownNotations = map[string]bool{constants.SignatureContextName: true} + } + // Push decrypted packet as literal packet and use openpgp's reader if verifyKeyRing != nil { keyring = verifyKeyRing.entities diff --git a/crypto/sessionkey_streaming.go b/crypto/sessionkey_streaming.go index 8ed3685..200ca63 100644 --- a/crypto/sessionkey_streaming.go +++ b/crypto/sessionkey_streaming.go @@ -1,9 +1,6 @@ package crypto import ( - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/ProtonMail/go-crypto/openpgp/packet" - "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/pkg/errors" ) @@ -31,14 +28,31 @@ func (sk *SessionKey) EncryptStream( plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (plainMessageWriter WriteCloser, err error) { - config := &packet.Config{ - Time: getTimeGenerator(), - } - return sk.encryptStreamWithConfig( - config, + return sk.encryptStream( dataPacketWriter, plainMessageMetadata, signKeyRing, + false, + nil, + ) +} + +// EncryptStreamWithContext is used to encrypt data as a Writer. +// It takes a writer for the encrypted data packet and returns a writer for the plaintext data. +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) the context for the signature. +func (sk *SessionKey) EncryptStreamWithContext( + dataPacketWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + signingContext *SigningContext, +) (plainMessageWriter WriteCloser, err error) { + return sk.encryptStream( + dataPacketWriter, + plainMessageMetadata, + signKeyRing, + false, + signingContext, ) } @@ -46,60 +60,55 @@ func (sk *SessionKey) EncryptStream( // The plaintext data is compressed before being encrypted. // It takes a writer for the encrypted data packet and returns a writer for the plaintext data. // If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) the context for the signature. func (sk *SessionKey) EncryptStreamWithCompression( dataPacketWriter Writer, plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, ) (plainMessageWriter WriteCloser, err error) { - config := &packet.Config{ - Time: getTimeGenerator(), - DefaultCompressionAlgo: constants.DefaultCompression, - CompressionConfig: &packet.CompressionConfig{Level: constants.DefaultCompressionLevel}, - } - return sk.encryptStreamWithConfig( - config, + return sk.encryptStream( dataPacketWriter, plainMessageMetadata, signKeyRing, + true, + nil, ) } -func (sk *SessionKey) encryptStreamWithConfig( - config *packet.Config, +// EncryptStreamWithContextAndCompression is used to encrypt data as a Writer. +// The plaintext data is compressed before being encrypted. +// It takes a writer for the encrypted data packet and returns a writer for the plaintext data. +// If signKeyRing is not nil, it is used to do an embedded signature. +// * signingContext : (optional) the context for the signature. +func (sk *SessionKey) EncryptStreamWithContextAndCompression( dataPacketWriter Writer, plainMessageMetadata *PlainMessageMetadata, signKeyRing *KeyRing, + signingContext *SigningContext, ) (plainMessageWriter WriteCloser, err error) { - dc, err := sk.GetCipherFunc() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key") - } - config.DefaultCipher = dc - var signEntity *openpgp.Entity - if signKeyRing != nil { - signEntity, err = signKeyRing.getSigningEntity() - if err != nil { - return nil, errors.Wrap(err, "gopenpgp: unable to sign") - } - } - - if plainMessageMetadata == nil { - // Use sensible default metadata - plainMessageMetadata = &PlainMessageMetadata{ - IsBinary: true, - Filename: "", - ModTime: GetUnixTime(), - } - } + return sk.encryptStream( + dataPacketWriter, + plainMessageMetadata, + signKeyRing, + true, + signingContext, + ) +} +func (sk *SessionKey) encryptStream( + dataPacketWriter Writer, + plainMessageMetadata *PlainMessageMetadata, + signKeyRing *KeyRing, + compress bool, + signingContext *SigningContext, +) (plainMessageWriter WriteCloser, err error) { encryptWriter, signWriter, err := encryptStreamWithSessionKey( - plainMessageMetadata.IsBinary, - plainMessageMetadata.Filename, - uint32(plainMessageMetadata.ModTime), + plainMessageMetadata, dataPacketWriter, sk, - signEntity, - config, + signKeyRing, + compress, + signingContext, ) if err != nil { @@ -123,10 +132,48 @@ func (sk *SessionKey) DecryptStream( verifyKeyRing *KeyRing, verifyTime int64, ) (plainMessage *PlainMessageReader, err error) { - messageDetails, err := decryptStreamWithSessionKey( + return decryptStreamWithSessionKeyAndContext( sk, dataPacketReader, verifyKeyRing, + verifyTime, + nil, + ) +} + +// DecryptStreamWithContext is used to decrypt a data packet as a Reader. +// It takes a reader for the data packet +// and returns a PlainMessageReader for the plaintext data. +// If verifyKeyRing is not nil, PlainMessageReader.VerifySignature() will +// verify the embedded signature with the given key ring and verification time. +// * verificationContext (optional): context for the signature verification. +func (sk *SessionKey) DecryptStreamWithContext( + dataPacketReader Reader, + verifyKeyRing *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (plainMessage *PlainMessageReader, err error) { + return decryptStreamWithSessionKeyAndContext( + sk, + dataPacketReader, + verifyKeyRing, + verifyTime, + verificationContext, + ) +} + +func decryptStreamWithSessionKeyAndContext( + sessionKey *SessionKey, + dataPacketReader Reader, + verifyKeyRing *KeyRing, + verifyTime int64, + verificationContext *VerificationContext, +) (plainMessage *PlainMessageReader, err error) { + messageDetails, err := decryptStreamWithSessionKey( + sessionKey, + dataPacketReader, + verifyKeyRing, + verificationContext, ) if err != nil { return nil, errors.Wrap(err, "gopenpgp: error in reading message") @@ -137,5 +184,6 @@ func (sk *SessionKey) DecryptStream( verifyKeyRing, verifyTime, false, + verificationContext, }, err } diff --git a/crypto/sessionkey_streaming_test.go b/crypto/sessionkey_streaming_test.go index a6f5a62..0ea9f09 100644 --- a/crypto/sessionkey_streaming_test.go +++ b/crypto/sessionkey_streaming_test.go @@ -73,6 +73,104 @@ func TestSessionKey_EncryptDecryptStream(t *testing.T) { } } +func TestSessionKey_EncryptDecryptStreamWithContext(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + var dataPacketBuf bytes.Buffer + testContext := "test-context" + messageWriter, err := testSessionKey.EncryptStreamWithContext( + &dataPacketBuf, + testMeta, + keyRingTestPrivate, + NewSigningContext(testContext, true), + ) + if err != nil { + t.Fatal("Expected no error while encrypting, got:", err) + } + _, err = io.Copy(messageWriter, messageReader) + if err != nil { + t.Fatal("Expected no error while copying plaintext, got:", err) + } + err = messageWriter.Close() + if err != nil { + t.Fatal("Expected no error while closing plaintext writer, got:", err) + } + dataPacket := dataPacketBuf.Bytes() + decryptedReader, err := testSessionKey.DecryptStreamWithContext( + bytes.NewReader(dataPacket), + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error while calling DecryptStream, got:", err) + } + decryptedBytes, err := ioutil.ReadAll(decryptedReader) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + err = decryptedReader.VerifySignature() + if err != nil { + t.Fatal("Expected no error while verifying the signature, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + decryptedMeta := decryptedReader.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } +} + +func TestSessionKey_EncryptDecryptStreamWithContextAndCompression(t *testing.T) { + messageBytes := []byte("Hello World!") + messageReader := bytes.NewReader(messageBytes) + var dataPacketBuf bytes.Buffer + testContext := "test-context" + messageWriter, err := testSessionKey.EncryptStreamWithContextAndCompression( + &dataPacketBuf, + testMeta, + keyRingTestPrivate, + NewSigningContext(testContext, true), + ) + if err != nil { + t.Fatal("Expected no error while encrypting, got:", err) + } + _, err = io.Copy(messageWriter, messageReader) + if err != nil { + t.Fatal("Expected no error while copying plaintext, got:", err) + } + err = messageWriter.Close() + if err != nil { + t.Fatal("Expected no error while closing plaintext writer, got:", err) + } + dataPacket := dataPacketBuf.Bytes() + decryptedReader, err := testSessionKey.DecryptStreamWithContext( + bytes.NewReader(dataPacket), + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error while calling DecryptStream, got:", err) + } + decryptedBytes, err := ioutil.ReadAll(decryptedReader) + if err != nil { + t.Fatal("Expected no error while reading the decrypted data, got:", err) + } + err = decryptedReader.VerifySignature() + if err != nil { + t.Fatal("Expected no error while verifying the signature, got:", err) + } + if !bytes.Equal(decryptedBytes, messageBytes) { + t.Fatalf("Expected the decrypted data to be %s got %s", string(decryptedBytes), string(messageBytes)) + } + decryptedMeta := decryptedReader.GetMetadata() + if !reflect.DeepEqual(testMeta, decryptedMeta) { + t.Fatalf("Expected the decrypted metadata to be %v got %v", testMeta, decryptedMeta) + } +} + func TestSessionKey_EncryptStreamCompatible(t *testing.T) { enc := func(w io.Writer, meta *PlainMessageMetadata, kr *KeyRing) (io.WriteCloser, error) { return testSessionKey.EncryptStream(w, meta, kr) diff --git a/crypto/sessionkey_test.go b/crypto/sessionkey_test.go index 905bc22..09b2823 100644 --- a/crypto/sessionkey_test.go +++ b/crypto/sessionkey_test.go @@ -249,6 +249,30 @@ func TestDataPacketEncryptionAndSignature(t *testing.T) { assert.Exactly(t, message.GetString(), finalMessage.GetString()) } +func TestDataPacketEncryptionAndSignatureWithContext(t *testing.T) { + var message = NewPlainMessageFromString( + "The secret code is... 1, 2, 3, 4, 5. I repeat: the secret code is... 1, 2, 3, 4, 5", + ) + var testContext = "test-context" + // Encrypt data with session key + dataPacket, err := testSessionKey.EncryptAndSignWithContext(message, keyRingTestPrivate, NewSigningContext(testContext, true)) + if err != nil { + t.Fatal("Expected no error when encrypting and signing, got:", err) + } + + // Decrypt & verify data with the good session key and keyring + decrypted, err := testSessionKey.DecryptAndVerifyWithContext( + dataPacket, + keyRingTestPublic, + GetUnixTime(), + NewVerificationContext(testContext, true, 0), + ) + if err != nil { + t.Fatal("Expected no error when decrypting & verifying, got:", err) + } + assert.Exactly(t, message.GetString(), decrypted.GetString()) +} + func TestDataPacketDecryption(t *testing.T) { pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false)) if err != nil { diff --git a/crypto/signature.go b/crypto/signature.go index d80900f..0d3942b 100644 --- a/crypto/signature.go +++ b/crypto/signature.go @@ -116,7 +116,7 @@ func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) { } // verifyDetailsSignature verifies signature from message details. -func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) error { +func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing, verificationContext *VerificationContext) error { if !md.IsSigned { return newSignatureNotSigned() } @@ -133,6 +133,13 @@ func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) er md.Signature.Hash > allowedHashes[len(allowedHashes)-1] { return newSignatureInsecure() } + if verificationContext != nil { + err := verificationContext.verifyContext(md.Signature) + if err != nil { + return newSignatureBadContext(err) + } + } + return nil } diff --git a/crypto/signature_test.go b/crypto/signature_test.go index fa0c649..1fab68f 100644 --- a/crypto/signature_test.go +++ b/crypto/signature_test.go @@ -154,6 +154,27 @@ func Test_KeyRing_GetVerifiedSignatureTimestampSuccess(t *testing.T) { } } +func Test_KeyRing_GetVerifiedSignatureTimestampWithContext(t *testing.T) { + message := NewPlainMessageFromString(testMessage) + var time int64 = 1600000000 + pgp.latestServerTime = time + defer func() { + pgp.latestServerTime = testTime + }() + var testContext = "test-context" + signature, err := keyRingTestPrivate.SignDetachedWithContext(message, NewSigningContext(testContext, true)) + if err != nil { + t.Errorf("Got an error while generating the signature: %v", err) + } + actualTime, err := keyRingTestPublic.GetVerifiedSignatureTimestampWithContext(message, signature, 0, NewVerificationContext(testContext, true, 0)) + if err != nil { + t.Errorf("Got an error while parsing the signature creation time: %v", err) + } + if time != actualTime { + t.Errorf("Expected creation time to be %d, got %d", time, actualTime) + } +} + func Test_KeyRing_GetVerifiedSignatureWithTwoKeysTimestampSuccess(t *testing.T) { publicKey1Armored, err := ioutil.ReadFile("testdata/signature/publicKey1") if err != nil {