Skip to content
Open
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
56 changes: 56 additions & 0 deletions sdk/chunked_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1381,3 +1381,59 @@ func TestChunkedGetManifestDoesNotBlockWriteSegment(t *testing.T) {
require.NoError(t, err)
assert.Len(t, later.Segments, 2)
}

// partialSplitter names two KAS URLs on one split but resolves a public
// key for only the first, the shape a splitter produces when a KAS
// lookup fails and the failure is swallowed upstream.
type partialSplitter struct {
known *policy.SimpleKasKey
// missingURL is listed on the split but absent from KASPublicKeys.
missingURL string
}

func (s partialSplitter) Split(_ context.Context, _ []*policy.Value, dek []byte, _ *policy.SimpleKasKey) (*SplitResult, error) {
url := s.known.GetKasUri()
share := make([]byte, len(dek))
copy(share, dek)
return &SplitResult{
KASPublicKeys: map[string]KASPublicKey{
url: {
Algorithm: "rsa:2048",
KID: s.known.GetPublicKey().GetKid(),
PEM: s.known.GetPublicKey().GetPem(),
URL: url,
},
},
Splits: []Split{{
Data: share,
KASURLs: []string{url, s.missingURL},
}},
}, nil
}

// TestChunkedFinalizeRejectsUnresolvedKAS checks that a split naming a
// KAS with no resolved public key fails Finalize. Skipping it would
// emit a TDF whose KAO set silently omits that KAS -- and if every URL
// on a split were missing, the share would be unrecoverable.
func TestChunkedFinalizeRejectsUnresolvedKAS(t *testing.T) {
ctx := context.Background()
kasBundle := newChunkedFakeKAS(t)
defer kasBundle.server.Close()

w, err := NewChunkedWriter(ctx,
WithChunkedDefaultKAS(kasBundle.simpleKey()),
WithChunkedKeySplitter(partialSplitter{
known: kasBundle.simpleKey(),
missingURL: "https://unresolved.example.com",
}),
)
require.NoError(t, err)

_, err = w.WriteSegment(ctx, 0, []byte("payload"))
require.NoError(t, err)

_, err = w.Finalize(ctx)
require.Error(t, err)
assert.Contains(t, err.Error(), "https://unresolved.example.com")
assert.Contains(t, err.Error(), "kas public key is missing")
}
11 changes: 7 additions & 4 deletions sdk/chunked_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,14 @@ func buildChunkedKeyAccessObjects(splits *SplitResult, policyBytes []byte, metad
encMeta = m
}
for _, url := range split.KASURLs {
// A KAS named by a split but absent from KASPublicKeys is an
// error, not something to skip. Dropping it silently removes
// the only KAO that would have let that KAS unwrap this
// share; if every URL on the split is missing, the share
// becomes unrecoverable and the TDF undecryptable, with
// nothing in the output to say why.
pk, ok := splits.KASPublicKeys[url]
if !ok {
continue
}
if pk.PEM == "" {
if !ok || pk.PEM == "" {
return nil, fmt.Errorf("splitID:[%s], kas:[%s]: %w", split.ID, url, errKasPubKeyMissing)
}
kao, err := createKeyAccess(pk.toKASInfo(), split.Data, policyBinding, encMeta, split.ID)
Expand Down
Loading