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