1package data
2
3import (
4	"bytes"
5	"fmt"
6
7	"github.com/docker/go/canonical/json"
8	"github.com/sirupsen/logrus"
9	"github.com/theupdateframework/notary"
10)
11
12// SignedSnapshot is a fully unpacked snapshot.json
13type SignedSnapshot struct {
14	Signatures []Signature
15	Signed     Snapshot
16	Dirty      bool
17}
18
19// Snapshot is the Signed component of a snapshot.json
20type Snapshot struct {
21	SignedCommon
22	Meta Files `json:"meta"`
23}
24
25// IsValidSnapshotStructure returns an error, or nil, depending on whether the content of the
26// struct is valid for snapshot metadata.  This does not check signatures or expiry, just that
27// the metadata content is valid.
28func IsValidSnapshotStructure(s Snapshot) error {
29	expectedType := TUFTypes[CanonicalSnapshotRole]
30	if s.Type != expectedType {
31		return ErrInvalidMetadata{
32			role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
33	}
34
35	if s.Version < 1 {
36		return ErrInvalidMetadata{
37			role: CanonicalSnapshotRole, msg: "version cannot be less than one"}
38	}
39
40	for _, file := range []RoleName{CanonicalRootRole, CanonicalTargetsRole} {
41		// Meta is a map of FileMeta, so if the role isn't in the map it returns
42		// an empty FileMeta, which has an empty map, and you can check on keys
43		// from an empty map.
44		//
45		// For now sha256 is required and sha512 is not.
46		if _, ok := s.Meta[file.String()].Hashes[notary.SHA256]; !ok {
47			return ErrInvalidMetadata{
48				role: CanonicalSnapshotRole,
49				msg:  fmt.Sprintf("missing %s sha256 checksum information", file.String()),
50			}
51		}
52		if err := CheckValidHashStructures(s.Meta[file.String()].Hashes); err != nil {
53			return ErrInvalidMetadata{
54				role: CanonicalSnapshotRole,
55				msg:  fmt.Sprintf("invalid %s checksum information, %v", file.String(), err),
56			}
57		}
58	}
59	return nil
60}
61
62// NewSnapshot initilizes a SignedSnapshot with a given top level root
63// and targets objects
64func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
65	logrus.Debug("generating new snapshot...")
66	targetsJSON, err := json.Marshal(targets)
67	if err != nil {
68		logrus.Debug("Error Marshalling Targets")
69		return nil, err
70	}
71	rootJSON, err := json.Marshal(root)
72	if err != nil {
73		logrus.Debug("Error Marshalling Root")
74		return nil, err
75	}
76	rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), NotaryDefaultHashes...)
77	if err != nil {
78		return nil, err
79	}
80	targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), NotaryDefaultHashes...)
81	if err != nil {
82		return nil, err
83	}
84	return &SignedSnapshot{
85		Signatures: make([]Signature, 0),
86		Signed: Snapshot{
87			SignedCommon: SignedCommon{
88				Type:    TUFTypes[CanonicalSnapshotRole],
89				Version: 0,
90				Expires: DefaultExpires(CanonicalSnapshotRole),
91			},
92			Meta: Files{
93				CanonicalRootRole.String():    rootMeta,
94				CanonicalTargetsRole.String(): targetsMeta,
95			},
96		},
97	}, nil
98}
99
100// ToSigned partially serializes a SignedSnapshot for further signing
101func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
102	s, err := defaultSerializer.MarshalCanonical(sp.Signed)
103	if err != nil {
104		return nil, err
105	}
106	signed := json.RawMessage{}
107	err = signed.UnmarshalJSON(s)
108	if err != nil {
109		return nil, err
110	}
111	sigs := make([]Signature, len(sp.Signatures))
112	copy(sigs, sp.Signatures)
113	return &Signed{
114		Signatures: sigs,
115		Signed:     &signed,
116	}, nil
117}
118
119// AddMeta updates a role in the snapshot with new meta
120func (sp *SignedSnapshot) AddMeta(role RoleName, meta FileMeta) {
121	sp.Signed.Meta[role.String()] = meta
122	sp.Dirty = true
123}
124
125// GetMeta gets the metadata for a particular role, returning an error if it's
126// not found
127func (sp *SignedSnapshot) GetMeta(role RoleName) (*FileMeta, error) {
128	if meta, ok := sp.Signed.Meta[role.String()]; ok {
129		if _, ok := meta.Hashes["sha256"]; ok {
130			return &meta, nil
131		}
132	}
133	return nil, ErrMissingMeta{Role: role.String()}
134}
135
136// DeleteMeta removes a role from the snapshot. If the role doesn't
137// exist in the snapshot, it's a noop.
138func (sp *SignedSnapshot) DeleteMeta(role RoleName) {
139	if _, ok := sp.Signed.Meta[role.String()]; ok {
140		delete(sp.Signed.Meta, role.String())
141		sp.Dirty = true
142	}
143}
144
145// MarshalJSON returns the serialized form of SignedSnapshot as bytes
146func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
147	signed, err := sp.ToSigned()
148	if err != nil {
149		return nil, err
150	}
151	return defaultSerializer.Marshal(signed)
152}
153
154// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
155func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
156	sp := Snapshot{}
157	if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
158		return nil, err
159	}
160	if err := IsValidSnapshotStructure(sp); err != nil {
161		return nil, err
162	}
163	sigs := make([]Signature, len(s.Signatures))
164	copy(sigs, s.Signatures)
165	return &SignedSnapshot{
166		Signatures: sigs,
167		Signed:     sp,
168	}, nil
169}
170