package data import ( "bytes" rjson "encoding/json" "path" "reflect" "testing" "time" cjson "github.com/docker/go/canonical/json" "github.com/stretchr/testify/require" ) func validTargetsTemplate() *SignedTargets { return &SignedTargets{ Signed: Targets{ SignedCommon: SignedCommon{Type: "Targets", Version: 1, Expires: time.Now()}, Targets: Files{}, Delegations: Delegations{ Roles: []*Role{}, Keys: Keys{ "key1": NewPublicKey(RSAKey, []byte("key1")), "key2": NewPublicKey(RSAKey, []byte("key2")), }, }, }, Signatures: []Signature{ {KeyID: "key1", Method: "method1", Signature: []byte("hello")}, }, } } func TestTargetsToSignedMarshalsSignedPortionWithCanonicalJSON(t *testing.T) { tg := SignedTargets{Signed: Targets{SignedCommon: SignedCommon{Type: "Targets", Version: 1, Expires: time.Now()}}} signedCanonical, err := tg.ToSigned() require.NoError(t, err) canonicalSignedPortion, err := cjson.MarshalCanonical(tg.Signed) require.NoError(t, err) castedCanonical := rjson.RawMessage(canonicalSignedPortion) // don't bother testing regular JSON because it might not be different require.True(t, bytes.Equal(signedCanonical.Signed, castedCanonical), "expected %v == %v", signedCanonical.Signed, castedCanonical) } func TestTargetsToSignCopiesSignatures(t *testing.T) { tg := SignedTargets{ Signed: Targets{SignedCommon: SignedCommon{Type: "Targets", Version: 2, Expires: time.Now()}}, Signatures: []Signature{ {KeyID: "key1", Method: "method1", Signature: []byte("hello")}, }, } signed, err := tg.ToSigned() require.NoError(t, err) require.True(t, reflect.DeepEqual(tg.Signatures, signed.Signatures), "expected %v == %v", tg.Signatures, signed.Signatures) tg.Signatures[0].KeyID = "changed" require.False(t, reflect.DeepEqual(tg.Signatures, signed.Signatures), "expected %v != %v", tg.Signatures, signed.Signatures) } func TestTargetsToSignedMarshallingErrorsPropagated(t *testing.T) { setDefaultSerializer(errorSerializer{}) defer setDefaultSerializer(canonicalJSON{}) tg := SignedTargets{ Signed: Targets{SignedCommon: SignedCommon{Type: "Targets", Version: 2, Expires: time.Now()}}, } _, err := tg.ToSigned() require.EqualError(t, err, "bad") } func TestTargetsMarshalJSONMarshalsSignedWithRegularJSON(t *testing.T) { tg := SignedTargets{ Signed: Targets{SignedCommon: SignedCommon{Type: "Targets", Version: 1, Expires: time.Now()}}, Signatures: []Signature{ {KeyID: "key1", Method: "method1", Signature: []byte("hello")}, {KeyID: "key2", Method: "method2", Signature: []byte("there")}, }, } serialized, err := tg.MarshalJSON() require.NoError(t, err) signed, err := tg.ToSigned() require.NoError(t, err) // don't bother testing canonical JSON because it might not be different regular, err := rjson.Marshal(signed) require.NoError(t, err) require.True(t, bytes.Equal(serialized, regular), "expected %v != %v", serialized, regular) } func TestTargetsMarshalJSONMarshallingErrorsPropagated(t *testing.T) { setDefaultSerializer(errorSerializer{}) defer setDefaultSerializer(canonicalJSON{}) tg := SignedTargets{ Signed: Targets{SignedCommon: SignedCommon{Type: "Targets", Version: 2, Expires: time.Now()}}, } _, err := tg.MarshalJSON() require.EqualError(t, err, "bad") } func TestTargetsFromSignedUnmarshallingErrorsPropagated(t *testing.T) { signed, err := validTargetsTemplate().ToSigned() require.NoError(t, err) setDefaultSerializer(errorSerializer{}) defer setDefaultSerializer(canonicalJSON{}) _, err = TargetsFromSigned(signed, CanonicalTargetsRole) require.EqualError(t, err, "bad") } // TargetsFromSigned succeeds if the targets is valid, and copies the signatures // rather than assigns them func TestTargetsFromSignedCopiesSignatures(t *testing.T) { for _, roleName := range []string{CanonicalTargetsRole, path.Join(CanonicalTargetsRole, "a")} { signed, err := validTargetsTemplate().ToSigned() require.NoError(t, err) signedTargets, err := TargetsFromSigned(signed, roleName) require.NoError(t, err) signed.Signatures[0] = Signature{KeyID: "key3", Method: "method3", Signature: []byte("world")} require.Equal(t, "key3", signed.Signatures[0].KeyID) require.Equal(t, "key1", signedTargets.Signatures[0].KeyID) } } // If the targets metadata contains delegations which are invalid, the targets metadata // fails to validate and thus fails to convert into a SignedTargets func TestTargetsFromSignedValidatesDelegations(t *testing.T) { for _, roleName := range []string{CanonicalTargetsRole, path.Join(CanonicalTargetsRole, "a")} { targets := validTargetsTemplate() delgRole, err := NewRole(path.Join(roleName, "b"), 1, []string{"key1"}, nil) require.NoError(t, err) targets.Signed.Delegations.Roles = []*Role{delgRole} // delegation has invalid threshold delgRole.Threshold = 0 s, err := targets.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.Error(t, err) require.IsType(t, ErrInvalidMetadata{}, err) delgRole.Threshold = 1 // Keys that aren't in the list of keys delgRole.KeyIDs = []string{"keys11"} s, err = targets.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.Error(t, err) require.IsType(t, ErrInvalidMetadata{}, err) delgRole.KeyIDs = []string{"keys1"} // not delegation role delgRole.Name = CanonicalRootRole s, err = targets.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.Error(t, err) require.IsType(t, ErrInvalidMetadata{}, err) // more than one level deep delgRole.Name = path.Join(roleName, "x", "y") s, err = targets.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.Error(t, err) require.IsType(t, ErrInvalidMetadata{}, err) // not in delegation hierarchy if IsDelegation(roleName) { delgRole.Name = path.Join(CanonicalTargetsRole, "z") s, err := targets.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.Error(t, err) require.IsType(t, ErrInvalidMetadata{}, err) } } } // Type must be "Targets" func TestTargetsFromSignedValidatesRoleType(t *testing.T) { for _, roleName := range []string{CanonicalTargetsRole, path.Join(CanonicalTargetsRole, "a")} { tg := validTargetsTemplate() for _, invalid := range []string{" Targets", CanonicalTargetsRole, "TARGETS"} { tg.Signed.Type = invalid s, err := tg.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.IsType(t, ErrInvalidMetadata{}, err) } tg = validTargetsTemplate() tg.Signed.Type = "Targets" s, err := tg.ToSigned() require.NoError(t, err) sTargets, err := TargetsFromSigned(s, roleName) require.NoError(t, err) require.Equal(t, "Targets", sTargets.Signed.Type) } } // The rolename passed to TargetsFromSigned must be a valid targets role name func TestTargetsFromSignedValidatesRoleName(t *testing.T) { for _, roleName := range []string{"TARGETS", "root/a"} { tg := validTargetsTemplate() s, err := tg.ToSigned() require.NoError(t, err) _, err = TargetsFromSigned(s, roleName) require.IsType(t, ErrInvalidRole{}, err) } }