Files
skopeo/signature/signature_test.go
2016-03-24 11:32:23 +01:00

280 lines
8.9 KiB
Go

package signature
import (
"encoding/json"
"io/ioutil"
"testing"
"github.com/projectatomic/skopeo/signature/fixtures"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvalidSignatureError(t *testing.T) {
// A stupid test just to keep code coverage
s := "test"
err := InvalidSignatureError{msg: s}
assert.Equal(t, s, err.Error())
}
func TestMarshalJSON(t *testing.T) {
// Empty string values
s := privateSignature{Signature{DockerManifestDigest: "", DockerReference: "_"}}
_, err := s.MarshalJSON()
assert.Error(t, err)
s = privateSignature{Signature{DockerManifestDigest: "_", DockerReference: ""}}
_, err = s.MarshalJSON()
assert.Error(t, err)
// Success
s = privateSignature{Signature{DockerManifestDigest: "digest!@#", DockerReference: "reference#@!"}}
marshaled, err := s.marshalJSONWithVariables(0, "CREATOR")
require.NoError(t, err)
assert.Equal(t, []byte("{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{\"creator\":\"CREATOR\",\"timestamp\":0}}"),
marshaled)
// We can't test MarshalJSON directly because the timestamp will keep changing, so just test that
// it doesn't fail. And call it through the JSON package for a good measure.
_, err = json.Marshal(s)
assert.NoError(t, err)
}
type mSI map[string]interface{} // To minimize typing the long name
func TestValidateExactMapKeys(t *testing.T) {
// Empty map and keys
err := validateExactMapKeys(mSI{})
assert.NoError(t, err)
// Success
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "b", "a")
assert.NoError(t, err)
// Extra map keys
err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "a")
assert.Error(t, err)
// Extra expected keys
err = validateExactMapKeys(mSI{"a": 1}, "b", "a")
assert.Error(t, err)
// Unexpected key values
err = validateExactMapKeys(mSI{"a": 1}, "b")
assert.Error(t, err)
}
func TestMapField(t *testing.T) {
// Field not found
_, err := mapField(mSI{"a": mSI{}}, "b")
assert.Error(t, err)
// Field has a wrong type
_, err = mapField(mSI{"a": 1}, "a")
assert.Error(t, err)
// Success
// FIXME? We can't use mSI as the type of child, that type apparently can't be converted to the raw map type.
child := map[string]interface{}{"b": mSI{}}
m, err := mapField(mSI{"a": child, "b": nil}, "a")
require.NoError(t, err)
assert.Equal(t, child, m)
}
func TestStringField(t *testing.T) {
// Field not found
_, err := stringField(mSI{"a": "x"}, "b")
assert.Error(t, err)
// Field has a wrong type
_, err = stringField(mSI{"a": 1}, "a")
assert.Error(t, err)
// Success
s, err := stringField(mSI{"a": "x", "b": nil}, "a")
require.NoError(t, err)
assert.Equal(t, "x", s)
}
// A short-hand way to get a JSON object field value or panic. No error handling done, we know
// what we are working with, a panic in a test is good enough, and fitting test cases on a single line
// is a priority.
func x(m mSI, fields ...string) mSI {
for _, field := range fields {
// Not .(mSI) because type assertion of an unnamed type to a named type always fails (the types
// are not "identical"), but the assignment is fine because they are "assignable".
m = m[field].(map[string]interface{})
}
return m
}
// Return the result of modifying validJSON with fn and unmarshaling it into *sig
func tryUnmarshalModified(t *testing.T, sig *privateSignature, validJSON []byte, modifyFn func(mSI)) error {
var tmp mSI
err := json.Unmarshal(validJSON, &tmp)
require.NoError(t, err)
modifyFn(tmp)
testJSON, err := json.Marshal(tmp)
require.NoError(t, err)
return json.Unmarshal(testJSON, sig)
}
func TestUnmarshalJSON(t *testing.T) {
var s privateSignature
// 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)
err = s.UnmarshalJSON([]byte("&"))
assert.Error(t, err)
// Not an object
err = json.Unmarshal([]byte("1"), &s)
assert.Error(t, err)
// Start with a valid JSON.
validSig := privateSignature{
Signature{
DockerManifestDigest: "digest!@#",
DockerReference: "reference#@!",
},
}
validJSON, err := validSig.MarshalJSON()
require.NoError(t, err)
// Success
s = privateSignature{}
err = json.Unmarshal(validJSON, &s)
require.NoError(t, err)
assert.Equal(t, validSig, s)
// Various ways to corrupt the JSON
breakFns := []func(mSI){
// A top-level field is missing
func(v mSI) { delete(v, "critical") },
func(v mSI) { delete(v, "optional") },
// Extra top-level sub-object
func(v mSI) { v["unexpected"] = 1 },
// "critical" not an object
func(v mSI) { v["critical"] = 1 },
// "optional" not an object
func(v mSI) { v["optional"] = 1 },
// A field of "critical" is missing
func(v mSI) { delete(x(v, "critical"), "type") },
func(v mSI) { delete(x(v, "critical"), "image") },
func(v mSI) { delete(x(v, "critical"), "identity") },
// Extra field of "critical"
func(v mSI) { x(v, "critical")["unexpected"] = 1 },
// Invalid "type"
func(v mSI) { x(v, "critical")["type"] = 1 },
func(v mSI) { x(v, "critical")["type"] = "unexpected" },
// Invalid "image" object
func(v mSI) { x(v, "critical")["image"] = 1 },
func(v mSI) { delete(x(v, "critical", "image"), "docker-manifest-digest") },
func(v mSI) { x(v, "critical", "image")["unexpected"] = 1 },
// Invalid "docker-manifest-digest"
func(v mSI) { x(v, "critical", "image")["docker-manifest-digest"] = 1 },
// Invalid "identity" object
func(v mSI) { x(v, "critical")["identity"] = 1 },
func(v mSI) { delete(x(v, "critical", "identity"), "docker-reference") },
func(v mSI) { x(v, "critical", "identity")["unexpected"] = 1 },
// Invalid "docker-reference"
func(v mSI) { x(v, "critical", "identity")["docker-reference"] = 1 },
}
for _, fn := range breakFns {
err = tryUnmarshalModified(t, &s, validJSON, fn)
assert.Error(t, err)
}
// Modifications to "optional" are allowed and ignored
allowedModificationFns := []func(mSI){
// Add an optional field
func(v mSI) { x(v, "optional")["unexpected"] = 1 },
// Delete an optional field
func(v mSI) { delete(x(v, "optional"), "creator") },
}
for _, fn := range allowedModificationFns {
s = privateSignature{}
err = tryUnmarshalModified(t, &s, validJSON, fn)
require.NoError(t, err)
assert.Equal(t, validSig, s)
}
}
type savedEnvironment struct {
vars []string
}
func TestSign(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
sig := privateSignature{
Signature{
DockerManifestDigest: "digest!@#",
DockerReference: "reference#@!",
},
}
// Successful signing
signature, err := sig.sign(mech, fixtures.TestKeyFingerprint)
require.NoError(t, err)
verified, err := verifyAndExtractSignature(mech, signature, fixtures.TestKeyFingerprint, sig.DockerReference)
require.NoError(t, err)
assert.Equal(t, sig.Signature, *verified)
// Error creating blob to sign
_, err = privateSignature{}.sign(mech, fixtures.TestKeyFingerprint)
assert.Error(t, err)
// Error signing
_, err = sig.sign(mech, "this fingerprint doesn't exist")
assert.Error(t, err)
}
func TestVerifyAndExtractSignature(t *testing.T) {
mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory)
require.NoError(t, err)
signature, err := ioutil.ReadFile("./fixtures/image.signature")
require.NoError(t, err)
// Successful verification
sig, err := verifyAndExtractSignature(mech, signature, fixtures.TestKeyFingerprint, fixtures.TestImageSignatureReference)
require.NoError(t, err)
assert.Equal(t, fixtures.TestImageSignatureReference, sig.DockerReference)
assert.Equal(t, fixtures.TestImageManifestDigest, sig.DockerManifestDigest)
// For extra paranoia, test that we return a nil signature object on error.
// Completely invalid signature.
sig, err = verifyAndExtractSignature(mech, []byte{}, fixtures.TestKeyFingerprint, fixtures.TestImageSignatureReference)
assert.Error(t, err)
assert.Nil(t, sig)
sig, err = verifyAndExtractSignature(mech, []byte("invalid signature"), fixtures.TestKeyFingerprint, fixtures.TestImageSignatureReference)
assert.Error(t, err)
assert.Nil(t, sig)
// Valid signature of non-JSON
invalidBlobSignature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature")
require.NoError(t, err)
sig, err = verifyAndExtractSignature(mech, invalidBlobSignature, fixtures.TestKeyFingerprint, fixtures.TestImageSignatureReference)
assert.Error(t, err)
assert.Nil(t, sig)
// Valid signature with a wrong key
sig, err = verifyAndExtractSignature(mech, signature, "unexpected fingerprint", fixtures.TestImageSignatureReference)
assert.Error(t, err)
assert.Nil(t, sig)
// Valid signature with a wrong image reference
sig, err = verifyAndExtractSignature(mech, signature, fixtures.TestKeyFingerprint, "unexpected docker reference")
assert.Error(t, err)
assert.Nil(t, sig)
}