Merge pull request #230 from ProtonMail/feat/embedded-signature-context

Add API for signature context on embedded signatures
This commit is contained in:
marinthiercelin 2023-04-11 09:20:29 +02:00 committed by GitHub
commit 27783985cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 931 additions and 169 deletions

View file

@ -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

View file

@ -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")

View file

@ -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())
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {