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
20 changes: 16 additions & 4 deletions sdk/internal/archive/tdf3_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
)

type TDFReader struct {
archiveReader Reader
archiveReader Reader
manifestMaxSize int64
}

const (
Expand All @@ -14,22 +15,33 @@ const (
manifestMaxSize = 1024 * 1024 * 10 // 10 MB
)

type TDFReaderOptions func(*TDFReader)

func WithTDFManifestMaxSize(size int64) TDFReaderOptions {
return func(tdfReader *TDFReader) {
tdfReader.manifestMaxSize = size
}
}

// NewTDFReader Create tdf reader instance.
func NewTDFReader(readSeeker io.ReadSeeker) (TDFReader, error) {
func NewTDFReader(readSeeker io.ReadSeeker, opt ...TDFReaderOptions) (TDFReader, error) {
archiveReader, err := NewReader(readSeeker)
if err != nil {
return TDFReader{}, err
}

tdfArchiveReader := TDFReader{}
tdfArchiveReader := TDFReader{manifestMaxSize: manifestMaxSize}
tdfArchiveReader.archiveReader = archiveReader
for _, o := range opt {
o(&tdfArchiveReader)
}

return tdfArchiveReader, nil
}

// Manifest Return the manifest of the tdf.
func (tdfReader TDFReader) Manifest() (string, error) {
fileContent, err := tdfReader.archiveReader.ReadAllFileData(TDFManifestFileName, manifestMaxSize)
fileContent, err := tdfReader.archiveReader.ReadAllFileData(TDFManifestFileName, tdfReader.manifestMaxSize)
if err != nil {
return "", err
}
Expand Down
12 changes: 6 additions & 6 deletions sdk/tdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,13 @@ func allowListFromKASRegistry(ctx context.Context, logger *slog.Logger, kasRegis

// LoadTDF loads the tdf and prepare for reading the payload from TDF
func (s SDK) LoadTDF(reader io.ReadSeeker, opts ...TDFReaderOption) (*Reader, error) {
config, err := newTDFReaderConfig(opts...)
if err != nil {
return nil, fmt.Errorf("newTDFReaderConfig failed: %w", err)
}

// create tdf reader
tdfReader, err := archive.NewTDFReader(reader)
tdfReader, err := archive.NewTDFReader(reader, archive.WithTDFManifestMaxSize(config.maxManifestSize))
if err != nil {
return nil, fmt.Errorf("archive.NewTDFReader failed: %w", err)
}
Expand All @@ -768,11 +773,6 @@ func (s SDK) LoadTDF(reader io.ReadSeeker, opts ...TDFReaderOption) (*Reader, er
opts = append([]TDFReaderOption{withSessionKey(s.kasSessionKey)}, opts...)
}

config, err := newTDFReaderConfig(opts...)
if err != nil {
return nil, fmt.Errorf("newAssertionConfig failed: %w", err)
}

useGlobalFulfillableObligations := len(config.fulfillableObligationFQNs) == 0 && len(s.fulfillableObligationFQNs) > 0
if useGlobalFulfillableObligations {
config.fulfillableObligationFQNs = s.fulfillableObligationFQNs
Expand Down
34 changes: 26 additions & 8 deletions sdk/tdf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
)

const (
tdf3KeySize = 2048
defaultSegmentSize = 2 * 1024 * 1024 // 2mb
maxSegmentSize = defaultSegmentSize * 2
minSegmentSize = 16 * 1024
DefaultRSAKeySize = 2048
ECKeySize256 = 256
ECKeySize384 = 384
ECKeySize521 = 521
tdf3KeySize = 2048
defaultMaxManifestSize = 10 * 1024 * 1024 // 10 MB
defaultSegmentSize = 2 * 1024 * 1024 // 2mb
maxSegmentSize = defaultSegmentSize * 2
minSegmentSize = 16 * 1024
DefaultRSAKeySize = 2048
ECKeySize256 = 256
ECKeySize384 = 384
ECKeySize521 = 521
)

type TDFFormat = int
Expand Down Expand Up @@ -271,6 +272,7 @@ type TDFReaderConfig struct {
kasAllowlist AllowList // KAS URLs that are allowed to be used for reading TDFs
ignoreAllowList bool // If true, the kasAllowlist will be ignored, and all KAS URLs will be allowed
fulfillableObligationFQNs []string
maxManifestSize int64
}

type AllowList map[string]bool
Expand Down Expand Up @@ -344,6 +346,7 @@ func (a AllowList) Add(kasURL string) error {
func newTDFReaderConfig(opt ...TDFReaderOption) (*TDFReaderConfig, error) {
c := &TDFReaderConfig{
disableAssertionVerification: false,
maxManifestSize: defaultMaxManifestSize,
}

for _, o := range opt {
Expand All @@ -364,6 +367,21 @@ func newTDFReaderConfig(opt ...TDFReaderOption) (*TDFReaderConfig, error) {
return c, nil
}

// WithMaxManifestSize sets the maximum allowed manifest size for the TDF reader.
// By default, the maximum manifest size is 10 MB.
// The manifest size is proportional to the sum of the sizes of the policy and the number of segments in the payload.
// Setting this limit helps prevent denial of service attacks due to large policies or overly segmented files.
// Use this option to override the default limit; the size parameter specifies the maximum size in bytes.
func WithMaxManifestSize(size int64) TDFReaderOption {
return func(c *TDFReaderConfig) error {
if size <= 0 {
return errors.New("max manifest size must be greater than 0")
}
c.maxManifestSize = size
return nil
}
}

func WithAssertionVerificationKeys(keys AssertionVerificationKeys) TDFReaderOption {
return func(c *TDFReaderConfig) error {
c.verifiers = keys
Expand Down
52 changes: 52 additions & 0 deletions sdk/tdf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/opentdf/platform/protocol/go/policy/kasregistry/kasregistryconnect"
wellknownpb "github.com/opentdf/platform/protocol/go/wellknownconfiguration"
wellknownconnect "github.com/opentdf/platform/protocol/go/wellknownconfiguration/wellknownconfigurationconnect"
"github.com/opentdf/platform/sdk/internal/archive"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -3100,3 +3101,54 @@ func TestGetKasErrorToReturn(t *testing.T) {
require.Equal(t, defaultError, result)
})
}

func (s *TDFSuite) Test_LargeManifest_WithMaxManifest() {
const maxManifestSize = 1024 * 1024 // 1MB

// Helper to create a large manifest JSON string
createLargeManifest := func(size int) []byte {
manifest := map[string]interface{}{
"payload": map[string]interface{}{
"data": string(bytes.Repeat([]byte{'a'}, size)),
},
"tdf_spec_version": TDFSpecVersion,
}
b, err := json.Marshal(manifest)
s.Require().NoError(err)
return b
}

// Helper to create a TDF file in memory for testing
createTestTDF := func(manifest []byte, payload []byte) *bytes.Reader {
tdfBuffer := new(bytes.Buffer)
tdfWriter := archive.NewTDFWriter(tdfBuffer)

// Add payload
err := tdfWriter.SetPayloadSize(int64(len(payload)))
s.Require().NoError(err)
err = tdfWriter.AppendPayload(payload)
s.Require().NoError(err)

// Add manifest
err = tdfWriter.AppendManifest(string(manifest))
s.Require().NoError(err)

_, err = tdfWriter.Finish()
s.Require().NoError(err)

return bytes.NewReader(tdfBuffer.Bytes())
}

// Case 1: Manifest just below the limit
manifestBelow := createLargeManifest(maxManifestSize - 100)
tdfBelow := createTestTDF(manifestBelow, []byte("payload"))
_, err := s.sdk.LoadTDF(tdfBelow, WithMaxManifestSize(maxManifestSize))
s.Require().NoError(err, "Manifest below max size should load successfully")

// Case 2: Manifest just above the limit
manifestAbove := createLargeManifest(maxManifestSize + 100)
tdfAbove := createTestTDF(manifestAbove, []byte("payload"))
_, err = s.sdk.LoadTDF(tdfAbove, WithMaxManifestSize(maxManifestSize))
s.Require().Error(err, "Manifest above max size should fail to load")
s.Require().ErrorContains(err, "size too large")
}
Loading