1package data
2
3import (
4	"errors"
5	"fmt"
6	"path"
7
8	"github.com/docker/go/canonical/json"
9)
10
11// SignedTargets is a fully unpacked targets.json, or target delegation
12// json file
13type SignedTargets struct {
14	Signatures []Signature
15	Signed     Targets
16	Dirty      bool
17}
18
19// Targets is the Signed components of a targets.json or delegation json file
20type Targets struct {
21	SignedCommon
22	Targets     Files       `json:"targets"`
23	Delegations Delegations `json:"delegations,omitempty"`
24}
25
26// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
27// is valid for targets metadata.  This does not check signatures or expiry, just that
28// the metadata content is valid.
29func isValidTargetsStructure(t Targets, roleName RoleName) error {
30	if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
31		return ErrInvalidRole{Role: roleName}
32	}
33
34	// even if it's a delegated role, the metadata type is "Targets"
35	expectedType := TUFTypes[CanonicalTargetsRole]
36	if t.Type != expectedType {
37		return ErrInvalidMetadata{
38			role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
39	}
40
41	if t.Version < 1 {
42		return ErrInvalidMetadata{role: roleName, msg: "version cannot be less than one"}
43	}
44
45	for _, roleObj := range t.Delegations.Roles {
46		if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name.String()) != roleName.String() {
47			return ErrInvalidMetadata{
48				role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
49		}
50		if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
51			return err
52		}
53	}
54	return nil
55}
56
57// NewTargets intiializes a new empty SignedTargets object
58func NewTargets() *SignedTargets {
59	return &SignedTargets{
60		Signatures: make([]Signature, 0),
61		Signed: Targets{
62			SignedCommon: SignedCommon{
63				Type:    TUFTypes["targets"],
64				Version: 0,
65				Expires: DefaultExpires("targets"),
66			},
67			Targets:     make(Files),
68			Delegations: *NewDelegations(),
69		},
70		Dirty: true,
71	}
72}
73
74// GetMeta attempts to find the targets entry for the path. It
75// will return nil in the case of the target not being found.
76func (t SignedTargets) GetMeta(path string) *FileMeta {
77	for p, meta := range t.Signed.Targets {
78		if p == path {
79			return &meta
80		}
81	}
82	return nil
83}
84
85// GetValidDelegations filters the delegation roles specified in the signed targets, and
86// only returns roles that are direct children and restricts their paths
87func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
88	roles := t.buildDelegationRoles()
89	result := []DelegationRole{}
90	for _, r := range roles {
91		validRole, err := parent.Restrict(r)
92		if err != nil {
93			continue
94		}
95		result = append(result, validRole)
96	}
97	return result
98}
99
100// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
101// Will error for invalid role name or key metadata within this SignedTargets.  Path data is not validated.
102func (t *SignedTargets) BuildDelegationRole(roleName RoleName) (DelegationRole, error) {
103	for _, role := range t.Signed.Delegations.Roles {
104		if role.Name == roleName {
105			pubKeys := make(map[string]PublicKey)
106			for _, keyID := range role.KeyIDs {
107				pubKey, ok := t.Signed.Delegations.Keys[keyID]
108				if !ok {
109					// Couldn't retrieve all keys, so stop walking and return invalid role
110					return DelegationRole{}, ErrInvalidRole{
111						Role:   roleName,
112						Reason: "role lists unknown key " + keyID + " as a signing key",
113					}
114				}
115				pubKeys[keyID] = pubKey
116			}
117			return DelegationRole{
118				BaseRole: BaseRole{
119					Name:      role.Name,
120					Keys:      pubKeys,
121					Threshold: role.Threshold,
122				},
123				Paths: role.Paths,
124			}, nil
125		}
126	}
127	return DelegationRole{}, ErrNoSuchRole{Role: roleName}
128}
129
130// helper function to create DelegationRole structures from all delegations in a SignedTargets,
131// these delegations are read directly from the SignedTargets and not modified or validated
132func (t SignedTargets) buildDelegationRoles() []DelegationRole {
133	var roles []DelegationRole
134	for _, roleData := range t.Signed.Delegations.Roles {
135		delgRole, err := t.BuildDelegationRole(roleData.Name)
136		if err != nil {
137			continue
138		}
139		roles = append(roles, delgRole)
140	}
141	return roles
142}
143
144// AddTarget adds or updates the meta for the given path
145func (t *SignedTargets) AddTarget(path string, meta FileMeta) {
146	t.Signed.Targets[path] = meta
147	t.Dirty = true
148}
149
150// AddDelegation will add a new delegated role with the given keys,
151// ensuring the keys either already exist, or are added to the map
152// of delegation keys
153func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
154	return errors.New("Not Implemented")
155}
156
157// ToSigned partially serializes a SignedTargets for further signing
158func (t *SignedTargets) ToSigned() (*Signed, error) {
159	s, err := defaultSerializer.MarshalCanonical(t.Signed)
160	if err != nil {
161		return nil, err
162	}
163	signed := json.RawMessage{}
164	err = signed.UnmarshalJSON(s)
165	if err != nil {
166		return nil, err
167	}
168	sigs := make([]Signature, len(t.Signatures))
169	copy(sigs, t.Signatures)
170	return &Signed{
171		Signatures: sigs,
172		Signed:     &signed,
173	}, nil
174}
175
176// MarshalJSON returns the serialized form of SignedTargets as bytes
177func (t *SignedTargets) MarshalJSON() ([]byte, error) {
178	signed, err := t.ToSigned()
179	if err != nil {
180		return nil, err
181	}
182	return defaultSerializer.Marshal(signed)
183}
184
185// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
186// a role name (so it can validate the SignedTargets object)
187func TargetsFromSigned(s *Signed, roleName RoleName) (*SignedTargets, error) {
188	t := Targets{}
189	if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
190		return nil, err
191	}
192	if err := isValidTargetsStructure(t, roleName); err != nil {
193		return nil, err
194	}
195	sigs := make([]Signature, len(s.Signatures))
196	copy(sigs, s.Signatures)
197	return &SignedTargets{
198		Signatures: sigs,
199		Signed:     t,
200	}, nil
201}
202