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
66 changes: 66 additions & 0 deletions docs/atomic-signature-embedded-json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"title": "JSON embedded in an atomic container signature",
"description": "This schema is a supplement to atomic-signature.md in this directory.\n\nConsumers of the JSON MUST use the processing rules documented in atomic-signature.md, especially the requirements for the 'critical' subjobject.\n\nWhenever this schema and atomic-signature.md, or the github.com/containers/image/signature implementation, differ,\nit is the atomic-signature.md document, or the github.com/containers/image/signature implementation, which governs.\n\nUsers are STRONGLY RECOMMENDED to use the github.com/containeres/image/signature implementation instead of writing\ntheir own, ESPECIALLY when consuming signatures, so that the policy.json format can be shared by all image consumers.\n",
"type": "object",
"required": [
"critical",
"optional"
],
"additionalProperties": false,
"properties": {
"critical": {
"type": "object",
"required": [
"type",
"image",
"identity"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"atomic container signature"
]
},
"image": {
"type": "object",
"required": [
"docker-manifest-digest"
],
"additionalProperties": false,
"properties": {
"docker-manifest-digest": {
"type": "string"
}
}
},
"identity": {
"type": "object",
"required": [
"docker-reference"
],
"additionalProperties": false,
"properties": {
"docker-reference": {
"type": "string"
}
}
}
}
},
"optional": {
"type": "object",
"description": "All members are optional, but if they are included, they must be valid.",
"additionalProperties": true,
"properties": {
"creator": {
"type": "string"
},
"timestamp": {
"type": "integer"
}
}
}
}
}
2 changes: 1 addition & 1 deletion signature/signature.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Note: Consider the API unstable until the code supports at least three different image formats or transports.

// NOTE: Keep this in sync with docs/atomic-signature.md!
// NOTE: Keep this in sync with docs/atomic-signature.md and docs/atomic-signature-embedded.json!

package signature

Expand Down
68 changes: 48 additions & 20 deletions signature/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package signature
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"testing"
"time"

Expand All @@ -11,6 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xeipuuv/gojsonschema"
)

func TestInvalidSignatureError(t *testing.T) {
Expand Down Expand Up @@ -78,43 +80,71 @@ func TestMarshalJSON(t *testing.T) {
}
}

// Return the result of modifying validJSON with fn and unmarshaling it into *sig
func tryUnmarshalModifiedSignature(t *testing.T, sig *untrustedSignature, validJSON []byte, modifyFn func(mSI)) error {
// Return the result of modifying validJSON with fn
func modifiedUntrustedSignatureJSON(t *testing.T, validJSON []byte, modifyFn func(mSI)) []byte {
var tmp mSI
err := json.Unmarshal(validJSON, &tmp)
require.NoError(t, err)

modifyFn(tmp)

testJSON, err := json.Marshal(tmp)
modifiedJSON, err := json.Marshal(tmp)
require.NoError(t, err)
return modifiedJSON
}

// Verify that input can be unmarshaled as an untrustedSignature, and that it passes JSON schema validation, and return the unmarshaled untrustedSignature.
func succesfullyUnmarshalUntrustedSignature(t *testing.T, schemaLoader gojsonschema.JSONLoader, input []byte) untrustedSignature {
inputString := string(input)

var s untrustedSignature
err := json.Unmarshal(input, &s)
require.NoError(t, err, inputString)

res, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewStringLoader(inputString))
assert.True(t, err == nil, inputString)
assert.True(t, res.Valid(), inputString)

*sig = untrustedSignature{}
return json.Unmarshal(testJSON, sig)
return s
}

func TestUnmarshalJSON(t *testing.T) {
// Verify that input can't be unmashaled as an untrusted signature, and that it fails JSON schema validation.
func assertUnmarshalUntrustedSignatureFails(t *testing.T, schemaLoader gojsonschema.JSONLoader, input []byte) {
inputString := string(input)

var s untrustedSignature
err := json.Unmarshal(input, &s)
assert.Error(t, err, inputString)

res, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewStringLoader(inputString))
assert.True(t, err != nil || !res.Valid(), inputString)
}

func TestUnmarshalJSON(t *testing.T) {
// NOTE: The schema at schemaPath is NOT authoritative; docs/atomic-signature.json and the code is, rather!
// The schemaPath references are not testing that the code follows the behavior declared by the schema,
// they are testing that the schema follows the behavior of the code!
schemaPath, err := filepath.Abs("../docs/atomic-signature-embedded-json.json")
require.NoError(t, err)
schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)

// Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our
// UnmarshalJSON implementation; so test that first, then test our error handling for completeness.
err := json.Unmarshal([]byte("&"), &s)
assert.Error(t, err)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, []byte("&"))
var s untrustedSignature
err = s.UnmarshalJSON([]byte("&"))
assert.Error(t, err)

// Not an object
err = json.Unmarshal([]byte("1"), &s)
assert.Error(t, err)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, []byte("1"))

// Start with a valid JSON.
validSig := newUntrustedSignature("digest!@#", "reference#@!")
validJSON, err := validSig.MarshalJSON()
require.NoError(t, err)

// Success
s = untrustedSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
s = succesfullyUnmarshalUntrustedSignature(t, schemaLoader, validJSON)
assert.Equal(t, validSig, s)

// Various ways to corrupt the JSON
Expand Down Expand Up @@ -156,8 +186,8 @@ func TestUnmarshalJSON(t *testing.T) {
func(v mSI) { x(v, "optional")["timestamp"] = 0.5 }, // Fractional input
}
for _, fn := range breakFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
assert.Error(t, err)
testJSON := modifiedUntrustedSignatureJSON(t, validJSON, fn)
assertUnmarshalUntrustedSignatureFails(t, schemaLoader, testJSON)
}

// Modifications to unrecognized fields in "optional" are allowed and ignored
Expand All @@ -166,8 +196,8 @@ func TestUnmarshalJSON(t *testing.T) {
func(v mSI) { x(v, "optional")["unexpected"] = 1 },
}
for _, fn := range allowedModificationFns {
err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn)
require.NoError(t, err)
testJSON := modifiedUntrustedSignatureJSON(t, validJSON, fn)
s := succesfullyUnmarshalUntrustedSignature(t, schemaLoader, testJSON)
assert.Equal(t, validSig, s)
}

Expand All @@ -180,9 +210,7 @@ func TestUnmarshalJSON(t *testing.T) {
}
validJSON, err = validSig.MarshalJSON()
require.NoError(t, err)
s = untrustedSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
s = succesfullyUnmarshalUntrustedSignature(t, schemaLoader, validJSON)
assert.Equal(t, validSig, s)
}

Expand Down
2 changes: 2 additions & 0 deletions vendor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ gopkg.in/cheggaaa/pb.v1 d7e6ca3010b6f084d8056847f55d7f572f180678
gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a
k8s.io/client-go bcde30fb7eaed76fd98a36b4120321b94995ffb6
github.com/xeipuuv/gojsonschema master
github.com/xeipuuv/gojsonreference master
github.com/xeipuuv/gojsonpointer master