Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions openpgp/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ func (dke ErrMalformedMessage) Error() string {
return "openpgp: malformed message " + string(dke)
}

type messageTooLargeError int

func (e messageTooLargeError) Error() string {
return "openpgp: decompressed message size exceeds provided limit"
}

// ErrMessageTooLarge is returned if the read data from
// a compressed packet exceeds the provided limit.
var ErrMessageTooLarge error = messageTooLargeError(0)

// ErrEncryptionKeySelection is returned if encryption key selection fails (v2 API).
type ErrEncryptionKeySelection struct {
PrimaryKeyId string
Expand Down
31 changes: 31 additions & 0 deletions openpgp/packet/compressed.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ func (c *Compressed) parse(r io.Reader) error {
return err
}

// LimitedBodyReader wraps the provided body reader with a limiter that restricts
// the number of bytes read to the specified limit.
// If limit is nil, the reader is unbounded.
func (c *Compressed) LimitedBodyReader(limit *int64) io.Reader {
if limit == nil {
return c.Body
}
return &LimitReader{R: c.Body, N: *limit}
}

// compressedWriterCloser represents the serialized compression stream
// header and the compressor. Its Close() method ensures that both the
// compressor and serialized stream header are closed. Its Write()
Expand Down Expand Up @@ -159,3 +169,24 @@ func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *Compression

return
}

// LimitReader is an io.Reader that fails with MessageToLarge if read bytes exceed N.
type LimitReader struct {
R io.Reader // underlying reader
N int64 // max bytes allowed
}

func (l *LimitReader) Read(p []byte) (int, error) {
if l.N <= 0 {
return 0, errors.ErrMessageTooLarge
}

n, err := l.R.Read(p)
l.N -= int64(n)

if err == nil && l.N <= 0 {
err = errors.ErrMessageTooLarge
}

return n, err
}
12 changes: 12 additions & 0 deletions openpgp/packet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ type Config struct {
// When set to true, a key without flags is treated as if all flags are enabled.
// This behavior is consistent with GPG.
InsecureAllowAllKeyFlagsWhenMissing bool

// MaxDecompressedMessageSize specifies the maximum number of bytes that can be
// read from a compressed packet. This serves as an upper limit to prevent
// excessively large decompressed messages.
MaxDecompressedMessageSize *int64
}

func (c *Config) Random() io.Reader {
Expand Down Expand Up @@ -415,6 +420,13 @@ func (c *Config) AllowAllKeyFlagsWhenMissing() bool {
return c.InsecureAllowAllKeyFlagsWhenMissing
}

func (c *Config) DecompressedMessageSizeLimit() *int64 {
if c == nil {
return nil
}
return c.MaxDecompressedMessageSize
}

// BoolPointer is a helper function to set a boolean pointer in the Config.
// e.g., config.CheckPacketSequence = BoolPointer(true)
func BoolPointer(value bool) *bool {
Expand Down
2 changes: 1 addition & 1 deletion openpgp/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ FindLiteralData:
}
switch p := p.(type) {
case *packet.Compressed:
if err := packets.Push(p.Body); err != nil {
if err := packets.Push(p.LimitedBodyReader(config.DecompressedMessageSizeLimit())); err != nil {
return nil, err
}
case *packet.OnePassSignature:
Expand Down
2 changes: 1 addition & 1 deletion openpgp/v2/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ FindLiteralData:
}
switch p := p.(type) {
case *packet.Compressed:
if err := packets.Push(p.Body); err != nil {
if err := packets.Push(p.LimitedBodyReader(config.DecompressedMessageSizeLimit())); err != nil {
return nil, err
}
case *packet.OnePassSignature:
Expand Down
47 changes: 46 additions & 1 deletion openpgp/v2/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/sha512"
"encoding/base64"
"encoding/hex"
goerrors "errors"
"io"
"io/ioutil"
"math/bits"
Expand Down Expand Up @@ -1056,8 +1057,52 @@ func TestReadMessageWithSignOnly(t *testing.T) {
t.Error(err)
return
}
md, err = ReadMessage(msgReader.Body, key, nil, nil)
_, err = ReadMessage(msgReader.Body, key, nil, nil)
if err == nil {
t.Fatal("Should not decrypt")
}
}

func TestReadMessageCompressionLimit(t *testing.T) {
// Limit 1KB
max := int64(1024)
config := packet.Config{
MaxDecompressedMessageSize: &max,
}

msgReader, err := armor.Decode(strings.NewReader(compressed2KB))
if err != nil {
t.Error(err)
return
}
md, err := ReadMessage(msgReader.Body, nil, nil, &config)
if err != nil {
t.Error(err)
return
}
// Should not be able to read all data
_, err = io.ReadAll(md.UnverifiedBody)
if !goerrors.Is(err, errors.ErrMessageTooLarge) {
t.Errorf("Wrong error")
}

// Limit 4KB
max = int64(4 * 1024)
config = packet.Config{
MaxDecompressedMessageSize: &max,
}
msgReader, err = armor.Decode(strings.NewReader(compressed2KB))
if err != nil {
t.Error(err)
return
}
md, err = ReadMessage(msgReader.Body, nil, nil, &config)
if err != nil {
t.Error(err)
return
}
// Should be able to read all data
if _, err = io.ReadAll(md.UnverifiedBody); err != nil {
t.Fatal(err)
}
}
54 changes: 54 additions & 0 deletions openpgp/v2/read_write_test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,3 +788,57 @@ U9I6AUkZWdcsueib9ghKDDy+HbUbf2kCJWUnuyeOCKqQifDb8bsLmdQY4Wb6
EBeLgD8oZHVsH3NLjPakPw==
=STqy
-----END PGP MESSAGE-----`

const compressed2KB = `-----BEGIN PGP MESSAGE-----

yOsCeAEABAn79sQNAwAIAaNNfhjCDDG7AcvHRmIAAAAAAFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVMBVVFRUVFRUVFRUVFRU
VFRUVFRUVFRUVFRUVFRUVFRUVFTCwCkEAAEIAF0Fgmgu3EMJEKNNfhjCDDG7NRQA
AAAAABwAEHNhbHRAbm90YXRpb25zLm9wZW5wZ3Bqcy5vcmdpoIeCV9r1q63ur3kQ
tmDhFiEEX7dLHQOx48sxvC+Ko01+GMIMMbsAAGXDA/4+XjyYlcW5AQlK4bP71f4J
dmmg+ijVY48OvALy35BE/W68t+vrWmCaSsXwg1NIPqaHQGNkL6I1qjSkQAxe8K90
Z9JTerD46t0XuA6/v+W0j4uG5frNxfZ2D2AoNBW0yu6wgo5MM4IA+PH0xhtj0xa3
e9Jwn5aNHhCQpFB3y/FDXAEAAP//nNYTdw==
=ETAD
-----END PGP MESSAGE-----`