From 8453f3697bc86fddbf48fc14dfabcb3d86bb06d2 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 24 Feb 2025 19:15:39 +0100 Subject: [PATCH 1/3] feat: add custom json marshaler for sdk.Statement --- sdk/assertion.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sdk/assertion.go b/sdk/assertion.go index 6c1efde4cf..2ab0c1236a 100644 --- a/sdk/assertion.go +++ b/sdk/assertion.go @@ -129,6 +129,32 @@ func (a Assertion) GetHash() ([]byte, error) { return ocrypto.SHA256AsHex(transformedJSON), nil } +func (s *Statement) MarshalJSON() ([]byte, error) { + // Define a custom struct for serialization + type Alias Statement + aux := &struct { + Value interface{} `json:"value,omitempty"` + *Alias + }{ + Alias: (*Alias)(s), + } + + if s.Format == "json-structured" { + raw := json.RawMessage(s.Value) + var tmp interface{} + // Attempt to decode Value to validate it's actual json + if err := json.Unmarshal(raw, &tmp); err == nil { + aux.Value = raw + } else { + aux.Value = s.Value + } + } else { + aux.Value = s.Value + } + + return json.Marshal(aux) +} + func (s *Statement) UnmarshalJSON(data []byte) error { // Define a custom struct for deserialization type Alias Statement From 75be560d3ac2909fc165bb8ffc6dcd9e5391bb41 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Tue, 29 Apr 2025 17:47:39 +0200 Subject: [PATCH 2/3] fix: GetHash for Statement object values --- sdk/assertion.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sdk/assertion.go b/sdk/assertion.go index 2ab0c1236a..844321595a 100644 --- a/sdk/assertion.go +++ b/sdk/assertion.go @@ -114,6 +114,16 @@ func (a Assertion) GetHash() ([]byte, error) { // Remove the binding key delete(jsonObject, "binding") + // Deep patch: fix "value" inside "statement" if it is a string + if statement, ok := jsonObject["statement"].(map[string]interface{}); ok { + if valueStr, ok := statement["value"].(string); ok { + var valueObj interface{} + if err := json.Unmarshal([]byte(valueStr), &valueObj); err == nil { + statement["value"] = valueObj // replace the string with parsed object + } + } + } + // Marshal the map back to JSON assertionJSON, err = json.Marshal(jsonObject) if err != nil { From 19f2093150bab2efe41756dca098465ed4dd6f15 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Fri, 1 Aug 2025 09:19:23 +0200 Subject: [PATCH 3/3] refactor: make Statement.Value a json.RawMessage to get rid of custom (un)marshallers --- sdk/assertion.go | 75 ++----------------------------------------- sdk/assertion_test.go | 12 +++---- sdk/tdf_test.go | 34 ++++++++++---------- 3 files changed, 25 insertions(+), 96 deletions(-) diff --git a/sdk/assertion.go b/sdk/assertion.go index 844321595a..30b73c5efd 100644 --- a/sdk/assertion.go +++ b/sdk/assertion.go @@ -114,16 +114,6 @@ func (a Assertion) GetHash() ([]byte, error) { // Remove the binding key delete(jsonObject, "binding") - // Deep patch: fix "value" inside "statement" if it is a string - if statement, ok := jsonObject["statement"].(map[string]interface{}); ok { - if valueStr, ok := statement["value"].(string); ok { - var valueObj interface{} - if err := json.Unmarshal([]byte(valueStr), &valueObj); err == nil { - statement["value"] = valueObj // replace the string with parsed object - } - } - } - // Marshal the map back to JSON assertionJSON, err = json.Marshal(jsonObject) if err != nil { @@ -139,67 +129,6 @@ func (a Assertion) GetHash() ([]byte, error) { return ocrypto.SHA256AsHex(transformedJSON), nil } -func (s *Statement) MarshalJSON() ([]byte, error) { - // Define a custom struct for serialization - type Alias Statement - aux := &struct { - Value interface{} `json:"value,omitempty"` - *Alias - }{ - Alias: (*Alias)(s), - } - - if s.Format == "json-structured" { - raw := json.RawMessage(s.Value) - var tmp interface{} - // Attempt to decode Value to validate it's actual json - if err := json.Unmarshal(raw, &tmp); err == nil { - aux.Value = raw - } else { - aux.Value = s.Value - } - } else { - aux.Value = s.Value - } - - return json.Marshal(aux) -} - -func (s *Statement) UnmarshalJSON(data []byte) error { - // Define a custom struct for deserialization - type Alias Statement - aux := &struct { - Value json.RawMessage `json:"value,omitempty"` - *Alias - }{ - Alias: (*Alias)(s), - } - - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - // Attempt to decode Value as an object - var temp map[string]interface{} - if json.Unmarshal(aux.Value, &temp) == nil { - // Re-encode the object as a string and assign to Value - objAsString, err := json.Marshal(temp) - if err != nil { - return err - } - s.Value = string(objAsString) - } else { - // Assign raw string to Value - var str string - if err := json.Unmarshal(aux.Value, &str); err != nil { - return fmt.Errorf("value is neither a valid JSON object nor a string: %s", string(aux.Value)) - } - s.Value = str - } - - return nil -} - // Statement includes information applying to the scope of the assertion. // It could contain rights, handling instructions, or general metadata. type Statement struct { @@ -208,7 +137,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 json.RawMessage `json:"value,omitempty" validate:"required"` } // Binding enforces cryptographic integrity of the assertion. @@ -363,7 +292,7 @@ func GetSystemMetadataAssertionConfig() (AssertionConfig, error) { Statement: Statement{ Format: "json", Schema: SystemMetadataSchemaV1, - Value: string(metadataJSON), + Value: metadataJSON, }, }, nil } diff --git a/sdk/assertion_test.go b/sdk/assertion_test.go index 3b5c93a85c..9d4c068f73 100644 --- a/sdk/assertion_test.go +++ b/sdk/assertion_test.go @@ -18,7 +18,7 @@ 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: []byte("{\"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\"}}"), }, } @@ -33,12 +33,12 @@ func TestTDFWithAssertion(t *testing.T) { hashOfAssertion, err := assertion.GetHash() require.NoError(t, err) - assert.Equal(t, "4a447a13c5a32730d20bdf7feecb9ffe16649bc731914b574d80035a3927f860", string(hashOfAssertion)) + assert.Equal(t, "cf73d5df901bc81fc697594c4af0e528b859674ffcd34df4ba385f13a3579650", string(hashOfAssertion)) } func TestTDFWithAssertionJsonObject(t *testing.T) { // Define the assertion config with a JSON object in the statement value - value := `{ + value := []byte(`{ "ocl": { "pol": "2ccf11cb-6c9a-4e49-9746-a7f0a295945d", "cls": "SECRET", @@ -54,7 +54,7 @@ func TestTDFWithAssertionJsonObject(t *testing.T) { "context": { "@base": "urn:nato:stanag:5636:A:1:elements:json" } - }` + }`) assertionConfig := AssertionConfig{ ID: "ab43266781e64b51a4c52ffc44d6152c", Type: "handling", @@ -92,7 +92,7 @@ func TestTDFWithAssertionJsonObject(t *testing.T) { hashOfAssertion, err := assertion.GetHash() require.NoError(t, err) - expectedHash := "722dd40a90a0f7ec718fb156207a647e64daa43c0ae1f033033473a172c72aee" + expectedHash := "eef5daaa17ec25312f2f254d5471fbc1866fabbf52fbc07d4941cb1bc1f1f373" assert.Equal(t, expectedHash, string(hashOfAssertion)) } @@ -180,5 +180,5 @@ func TestDeserializingAssertionWithStringInStatementValue(t *testing.T) { err := json.Unmarshal([]byte(assertionVal), &assertion) require.NoError(t, err, "Error deserializing the assertion with a JSON object in the statement value") - assert.Equal(t, "this is a value", assertion.Statement.Value) + assert.Equal(t, json.RawMessage(`"this is a value"`), assertion.Statement.Value) } diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index b56db85e4d..f51d65c75b 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -717,7 +717,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, }, { @@ -728,7 +728,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, }, }, @@ -746,7 +746,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, }, { @@ -757,7 +757,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, }, }, @@ -776,7 +776,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: defaultKey, }, @@ -788,7 +788,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, SigningKey: defaultKey, }, @@ -809,7 +809,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgHS256, @@ -824,7 +824,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgRS256, @@ -858,7 +858,7 @@ func (s *TDFSuite) Test_TDFWithAssertion() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgHS256, @@ -873,7 +873,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, }, }, @@ -898,7 +898,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, }, }, @@ -1123,7 +1123,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: defaultKey, }, @@ -1135,7 +1135,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, SigningKey: defaultKey, }, @@ -1152,7 +1152,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgHS256, @@ -1167,7 +1167,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: []byte(`"{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"`), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgRS256, @@ -1200,7 +1200,7 @@ func (s *TDFSuite) Test_TDFWithAssertionNegativeTests() { Statement: Statement{ Format: "base64binary", Schema: "text", - Value: "ICAgIDxlZGoOkVkaD4=", + Value: []byte(`"ICAgIDxlZGoOkVkaD4="`), }, SigningKey: AssertionKey{ Alg: AssertionKeyAlgHS256, @@ -1215,7 +1215,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: []byte("{\"uuid\":\"f74efb60-4a9a-11ef-a6f1-8ee1a61c148a\",\"body\":{\"dataAttributes\":null,\"dissem\":null}}"), }, }, },