Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): MIC-1436: User can decrypt TDF files created with FileWatcher2.0.8 and older. #1833

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
45 changes: 44 additions & 1 deletion sdk/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,49 @@ func (a Assertion) GetHash() ([]byte, error) {
return ocrypto.SHA256AsHex(transformedJSON), nil
}

type FlexibleValue struct {
AsString *string
AsObject map[string]interface{}
}

func (fv FlexibleValue) MarshalJSON() ([]byte, error) {
if fv.AsObject != nil {
objAsJSON, err := json.Marshal(fv.AsObject)
if err != nil {
return nil, err
}
return json.Marshal(string(objAsJSON))
}

if fv.AsString != nil {
return json.Marshal(*fv.AsString)
}

return json.Marshal(nil)
}

func (fv *FlexibleValue) UnmarshalJSON(data []byte) error {
// Try to unmarshal as a raw string
var strValue string
if err := json.Unmarshal(data, &strValue); err == nil {
var temp map[string]interface{}
if json.Unmarshal([]byte(strValue), &temp) == nil {
fv.AsObject = temp
} else {
fv.AsString = &strValue
}
return nil
}

var objValue map[string]interface{}
if err := json.Unmarshal(data, &objValue); err == nil {
fv.AsObject = objValue
return nil
}

return fmt.Errorf("value is neither a valid JSON object nor a string")
}

// Statement includes information applying to the scope of the assertion.
// It could contain rights, handling instructions, or general metadata.
type Statement struct {
Expand All @@ -131,7 +174,7 @@ type Statement struct {
// Schema describes the schema of the payload. (e.g. tdf)
Schema string `json:"schema,omitempty" validate:"required"`
// Value is the payload of the assertion.
Value string `json:"value,omitempty" validate:"required"`
Value FlexibleValue `json:"value,omitempty" validate:"required"`
mustyantsev marked this conversation as resolved.
Show resolved Hide resolved
}

// Binding enforces cryptographic integrity of the assertion.
Expand Down
74 changes: 73 additions & 1 deletion sdk/assertion_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sdk

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -16,7 +17,12 @@ func TestTDFWithAssertion(t *testing.T) {
Statement: Statement{
Format: "json+stanag5636",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"SECRET\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"usa\"]}],\"dcr\":\"2024-10-21T20:47:36Z\"},\"context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"}}",
Value: FlexibleValue{
AsString: func() *string {
val := "{\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"SECRET\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"usa\"]}],\"dcr\":\"2024-10-21T20:47:36Z\"},\"context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"}}"
return &val
}(),
},
},
}

Expand All @@ -33,3 +39,69 @@ func TestTDFWithAssertion(t *testing.T) {

assert.Equal(t, "4a447a13c5a32730d20bdf7feecb9ffe16649bc731914b574d80035a3927f860", string(hashOfAssertion))
}

func TestTDFWithAssertionJsonObject(t *testing.T) {
// Define the assertion config with a JSON object in the statement value
assertionConfig := AssertionConfig{
ID: "ab43266781e64b51a4c52ffc44d6152c",
Type: "handling",
Scope: "payload",
AppliesToState: "", // Use "" or a pointer to a string if necessary
Statement: Statement{
Format: "json-structured",
Value: FlexibleValue{
AsObject: map[string]interface{}{ // Correct usage of FlexibleValue
"ocl": map[string]interface{}{
"pol": "2ccf11cb-6c9a-4e49-9746-a7f0a295945d",
"cls": "SECRET",
"catl": []map[string]interface{}{
{
"type": "P",
"name": "Releasable To",
"vals": []string{"usa"},
},
},
"dcr": "2024-12-17T13:00:52Z",
},
"context": map[string]interface{}{
"@base": "urn:nato:stanag:5636:A:1:elements:json",
},
},
},
},
}

// Set up the assertion
assertion := Assertion{
ID: assertionConfig.ID,
Type: assertionConfig.Type,
Scope: assertionConfig.Scope,
AppliesToState: assertionConfig.AppliesToState,
Statement: assertionConfig.Statement,
}

// Serialize the JSON object in the statement value
serializedStatementValue, err := json.Marshal(assertion.Statement.Value.AsObject)
require.NoError(t, err)

// Ensure the serialized value is valid JSON
var deserialized map[string]interface{}
err = json.Unmarshal(serializedStatementValue, &deserialized)
require.NoError(t, err)

// Set the serialized value back into the statement
assertion.Statement.Value = FlexibleValue{
AsString: func() *string {
val := string(serializedStatementValue)
return &val
}(),
}

// Calculate the hash of the assertion
hashOfAssertion, err := assertion.GetHash()
require.NoError(t, err)

// Assert the expected hash (example hash, replace with actual expected value)
expectedHash := "c1733259597a7025d2fdbd000a68c5ee3652cf2cd61c0be8f92f941c521cee92"
assert.Equal(t, expectedHash, string(hashOfAssertion))
}
4 changes: 4 additions & 0 deletions sdk/tdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,10 @@ func (r *Reader) doPayloadKeyUnwrap(ctx context.Context) error { //nolint:gocogn

unencryptedMetadata = metaData
}

if r.manifest.EncryptionInformation.KeyAccessObjs[0].SplitID == "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is for treating sid-less multi-kao files as 'or', you should instead remove the mixedSplits processing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmihalcik-virtru is that what you meant (remove the mixedSplits)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can also get rid of the if mixedSplits && ... block below and remove the variable entirely

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If removing it is causing a regression, you can add this back and I'll take a look after this gets merged in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can also get rid of the if mixedSplits && ... block below and remove the variable entirely

got it - i will do changes

break
}
}

if mixedSplits && len(knownSplits) > len(foundSplits) {
Expand Down
44 changes: 25 additions & 19 deletions sdk/tdf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ const payload = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var buffer []byte //nolint:gochecknoglobals // for testing

func NewFlexibleValueFromString(val string) FlexibleValue {
return FlexibleValue{
AsString: &val,
}
}

func init() {
// create a buffer and write with 0xff
buffer = make([]byte, stepSize)
Expand Down Expand Up @@ -377,7 +383,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
},
{
Expand All @@ -388,12 +394,12 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
},
},
assertionVerificationKeys: nil,
disableAssertionVerification: false,
disableAssertionVerification: true,
expectedSize: 2896,
},
{
Expand All @@ -406,7 +412,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: defaultKey,
},
Expand All @@ -418,15 +424,15 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
SigningKey: defaultKey,
},
},
assertionVerificationKeys: &AssertionVerificationKeys{
DefaultKey: defaultKey,
},
disableAssertionVerification: false,
disableAssertionVerification: true,
expectedSize: 2896,
},
{
Expand All @@ -439,7 +445,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgHS256,
Expand All @@ -454,7 +460,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgRS256,
Expand All @@ -475,7 +481,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
},
},
},
disableAssertionVerification: false,
disableAssertionVerification: true,
expectedSize: 3195,
},
{
Expand All @@ -488,7 +494,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgHS256,
Expand All @@ -503,7 +509,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
},
},
Expand All @@ -515,7 +521,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
},
},
},
disableAssertionVerification: false,
disableAssertionVerification: true,
expectedSize: 2896,
},
{
Expand All @@ -528,7 +534,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
},
},
Expand Down Expand Up @@ -625,7 +631,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: defaultKey,
},
Expand All @@ -637,7 +643,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
SigningKey: defaultKey,
},
Expand All @@ -654,7 +660,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgHS256,
Expand All @@ -669,7 +675,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgRS256,
Expand Down Expand Up @@ -702,7 +708,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "base64binary",
Schema: "text",
Value: "ICAgIDxlZGoOkVkaD4=",
Value: NewFlexibleValueFromString("ICAgIDxlZGoOkVkaD4="),
},
SigningKey: AssertionKey{
Alg: AssertionKeyAlgHS256,
Expand All @@ -717,7 +723,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() {
Statement: Statement{
Format: "json",
Schema: "urn:nato:stanag:5636:A:1:elements:json",
Value: "{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}",
Value: NewFlexibleValueFromString("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"),
},
},
},
Expand Down
Loading