From 7103bdf2a590f1d1677ff5864b72421876d62341 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 11 Feb 2022 14:21:06 +0100 Subject: [PATCH] Generic implementation of splitting messages --- crypto/attachment.go | 2 +- crypto/message.go | 121 +++++++------------------------------- crypto/message_test.go | 6 +- crypto/sessionkey_test.go | 2 +- 4 files changed, 25 insertions(+), 106 deletions(-) diff --git a/crypto/attachment.go b/crypto/attachment.go index a362d17..9533256 100644 --- a/crypto/attachment.go +++ b/crypto/attachment.go @@ -96,7 +96,7 @@ func (keyRing *KeyRing) newAttachmentProcessor( message := &PGPMessage{ Data: ciphertext, } - split, splitError := message.SeparateKeyAndData(estimatedSize, garbageCollector) + split, splitError := message.SeparateKeyAndData() if attachmentProc.err == nil { attachmentProc.err = splitError } diff --git a/crypto/message.go b/crypto/message.go index a3f45b7..1fd4f04 100644 --- a/crypto/message.go +++ b/crypto/message.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "regexp" - "runtime" "strings" "time" @@ -141,7 +140,7 @@ func NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error) { return nil, err } - return message.SeparateKeyAndData(len(encrypted), -1) + return message.SeparateKeyAndData() } // NewPGPSignature generates a new PGPSignature from the unarmored binary data. @@ -325,112 +324,32 @@ func (msg *PGPSplitMessage) GetPGPMessage() *PGPMessage { return NewPGPMessage(append(msg.KeyPacket, msg.DataPacket...)) } -// SeparateKeyAndData returns the first keypacket and the (hopefully unique) -// dataPacket (not verified). -// * estimatedLength is the estimate length of the message. -// * garbageCollector > 0 activates the garbage collector. -func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error) { - // For info on each, see: https://golang.org/pkg/runtime/#MemStats - packets := packet.NewReader(bytes.NewReader(msg.Data)) - outSplit = &PGPSplitMessage{} - - // Store encrypted key and symmetrically encrypted packet separately - var encryptedKey *packet.EncryptedKey +// SeparateKeyAndData splits the message into key and data packet(s). +// Parameters are for backwards compatibility and are unused. +func (msg *PGPMessage) SeparateKeyAndData(_ ...int) (*PGPSplitMessage, error) { + bytesReader := bytes.NewReader(msg.Data) + packets := packet.NewReader(bytesReader) + splitPoint := int64(0) +Loop: for { - var p packet.Packet - if p, err = packets.Next(); goerrors.Is(err, io.EOF) { - err = nil //nolint:wastedassign - break - } - switch p := p.(type) { - case *packet.EncryptedKey: - // TODO: add support for multiple keypackets - if encryptedKey != nil && encryptedKey.Key != nil { - break - } - encryptedKey = p - case *packet.SymmetricallyEncrypted: - outSplit.DataPacket, err = readPacketContents(p.Contents, estimatedLength, garbageCollector) - if err != nil { - return nil, err - } - case *packet.AEADEncrypted: - outSplit.DataPacket, err = readPacketContents(p.Contents, estimatedLength, garbageCollector) - if err != nil { - return nil, err - } - } - } - if encryptedKey == nil { - return nil, errors.New("gopenpgp: packets don't include an encrypted key packet") - } - - var buf bytes.Buffer - if err := encryptedKey.Serialize(&buf); err != nil { - return nil, errors.Wrap(err, "gopenpgp: cannot serialize encrypted key") - } - outSplit.KeyPacket = buf.Bytes() - - return outSplit, nil -} - -func readPacketContents(contents io.Reader, estimatedLength int, garbageCollector int) ([]byte, error) { - gcCounter := 0 - var b bytes.Buffer - // 2^16 is an estimation of the size difference between input and output, the size difference is most probably - // 16 bytes at a maximum though. - // We need to avoid triggering a grow from the system as this will allocate too much memory causing problems - // in low-memory environments - b.Grow(1<<16 + estimatedLength) - // empty encoded length + start byte - if _, err := b.Write(make([]byte, 6)); err != nil { - return nil, errors.Wrap(err, "gopenpgp: error in writing data packet header") - } - - if err := b.WriteByte(byte(1)); err != nil { - return nil, errors.Wrap(err, "gopenpgp: error in writing data packet header") - } - - actualLength := 1 - block := make([]byte, 128) - for { - n, err := contents.Read(block) + p, err := packets.Next() if goerrors.Is(err, io.EOF) { break } - if _, err := b.Write(block[:n]); err != nil { - return nil, errors.Wrap(err, "gopenpgp: error in writing data packet body") + if err != nil { + return nil, err } - actualLength += n - gcCounter += n - if gcCounter > garbageCollector && garbageCollector > 0 { - runtime.GC() - gcCounter = 0 + switch p.(type) { + case *packet.SymmetricKeyEncrypted, *packet.EncryptedKey: + splitPoint = bytesReader.Size() - int64(bytesReader.Len()) + case *packet.SymmetricallyEncrypted, *packet.AEADEncrypted: + break Loop } } - - // quick encoding - symEncryptedData := b.Bytes() - switch { - case actualLength < 192: - symEncryptedData[4] = byte(210) - symEncryptedData[5] = byte(actualLength) - symEncryptedData = symEncryptedData[4:] - case actualLength < 8384: - actualLength -= 192 - symEncryptedData[3] = byte(210) - symEncryptedData[4] = 192 + byte(actualLength>>8) - symEncryptedData[5] = byte(actualLength) - symEncryptedData = symEncryptedData[3:] - default: - symEncryptedData[0] = byte(210) - symEncryptedData[1] = byte(255) - symEncryptedData[2] = byte(actualLength >> 24) - symEncryptedData[3] = byte(actualLength >> 16) - symEncryptedData[4] = byte(actualLength >> 8) - symEncryptedData[5] = byte(actualLength) - } - return symEncryptedData, nil + return &PGPSplitMessage{ + KeyPacket: msg.Data[:splitPoint], + DataPacket: msg.Data[splitPoint:], + }, nil } // GetBinary returns the unarmored binary content of the signature as a []byte. diff --git a/crypto/message_test.go b/crypto/message_test.go index 0fa363c..be807a5 100644 --- a/crypto/message_test.go +++ b/crypto/message_test.go @@ -96,7 +96,7 @@ func TestTextMessageEncryption(t *testing.T) { t.Fatal("Expected no error when encrypting, got:", err) } - split, err := ciphertext.SeparateKeyAndData(1024, 0) + split, err := ciphertext.SeparateKeyAndData() if err != nil { t.Fatal("Expected no error when splitting, got:", err) } @@ -120,7 +120,7 @@ func TestTextMessageEncryptionWithCompression(t *testing.T) { t.Fatal("Expected no error when encrypting, got:", err) } - split, err := ciphertext.SeparateKeyAndData(1024, 0) + split, err := ciphertext.SeparateKeyAndData() if err != nil { t.Fatal("Expected no error when splitting, got:", err) } @@ -252,7 +252,7 @@ func TestDummy(t *testing.T) { t.Fatal("Expected no error when encrypting, got:", err) } - split, err := ciphertext.SeparateKeyAndData(1024, 0) + split, err := ciphertext.SeparateKeyAndData() if err != nil { t.Fatal("Expected no error when splitting, got:", err) } diff --git a/crypto/sessionkey_test.go b/crypto/sessionkey_test.go index 27525a2..ce3c8e8 100644 --- a/crypto/sessionkey_test.go +++ b/crypto/sessionkey_test.go @@ -253,7 +253,7 @@ func TestDataPacketDecryption(t *testing.T) { t.Fatal("Expected no error when unarmoring, got:", err) } - split, err := pgpMessage.SeparateKeyAndData(1024, 0) + split, err := pgpMessage.SeparateKeyAndData(1024, 0) // Test passing parameters for backwards compatibility if err != nil { t.Fatal("Expected no error when splitting, got:", err) }