2017-04-17 18:08:24 -04:00
|
|
|
package data
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"path"
|
|
|
|
|
|
|
|
"github.com/docker/go/canonical/json"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SignedTargets is a fully unpacked targets.json, or target delegation
|
|
|
|
// json file
|
|
|
|
type SignedTargets struct {
|
|
|
|
Signatures []Signature
|
|
|
|
Signed Targets
|
|
|
|
Dirty bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Targets is the Signed components of a targets.json or delegation json file
|
|
|
|
type Targets struct {
|
|
|
|
SignedCommon
|
|
|
|
Targets Files `json:"targets"`
|
|
|
|
Delegations Delegations `json:"delegations,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
|
|
|
|
// is valid for targets metadata. This does not check signatures or expiry, just that
|
|
|
|
// the metadata content is valid.
|
2017-08-24 18:40:24 -04:00
|
|
|
func isValidTargetsStructure(t Targets, roleName RoleName) error {
|
2017-04-17 18:08:24 -04:00
|
|
|
if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
|
|
|
|
return ErrInvalidRole{Role: roleName}
|
|
|
|
}
|
|
|
|
|
|
|
|
// even if it's a delegated role, the metadata type is "Targets"
|
|
|
|
expectedType := TUFTypes[CanonicalTargetsRole]
|
|
|
|
if t.Type != expectedType {
|
|
|
|
return ErrInvalidMetadata{
|
|
|
|
role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.Version < 1 {
|
|
|
|
return ErrInvalidMetadata{role: roleName, msg: "version cannot be less than one"}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, roleObj := range t.Delegations.Roles {
|
2017-08-24 18:40:24 -04:00
|
|
|
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name.String()) != roleName.String() {
|
2017-04-17 18:08:24 -04:00
|
|
|
return ErrInvalidMetadata{
|
|
|
|
role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
|
|
|
|
}
|
|
|
|
if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTargets intiializes a new empty SignedTargets object
|
|
|
|
func NewTargets() *SignedTargets {
|
|
|
|
return &SignedTargets{
|
|
|
|
Signatures: make([]Signature, 0),
|
|
|
|
Signed: Targets{
|
|
|
|
SignedCommon: SignedCommon{
|
|
|
|
Type: TUFTypes["targets"],
|
|
|
|
Version: 0,
|
|
|
|
Expires: DefaultExpires("targets"),
|
|
|
|
},
|
|
|
|
Targets: make(Files),
|
|
|
|
Delegations: *NewDelegations(),
|
|
|
|
},
|
|
|
|
Dirty: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMeta attempts to find the targets entry for the path. It
|
|
|
|
// will return nil in the case of the target not being found.
|
|
|
|
func (t SignedTargets) GetMeta(path string) *FileMeta {
|
|
|
|
for p, meta := range t.Signed.Targets {
|
|
|
|
if p == path {
|
|
|
|
return &meta
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetValidDelegations filters the delegation roles specified in the signed targets, and
|
|
|
|
// only returns roles that are direct children and restricts their paths
|
|
|
|
func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
|
|
|
|
roles := t.buildDelegationRoles()
|
|
|
|
result := []DelegationRole{}
|
|
|
|
for _, r := range roles {
|
|
|
|
validRole, err := parent.Restrict(r)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result = append(result, validRole)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
|
|
|
|
// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated.
|
2017-08-24 18:40:24 -04:00
|
|
|
func (t *SignedTargets) BuildDelegationRole(roleName RoleName) (DelegationRole, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
for _, role := range t.Signed.Delegations.Roles {
|
|
|
|
if role.Name == roleName {
|
|
|
|
pubKeys := make(map[string]PublicKey)
|
|
|
|
for _, keyID := range role.KeyIDs {
|
|
|
|
pubKey, ok := t.Signed.Delegations.Keys[keyID]
|
|
|
|
if !ok {
|
|
|
|
// Couldn't retrieve all keys, so stop walking and return invalid role
|
|
|
|
return DelegationRole{}, ErrInvalidRole{
|
|
|
|
Role: roleName,
|
|
|
|
Reason: "role lists unknown key " + keyID + " as a signing key",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pubKeys[keyID] = pubKey
|
|
|
|
}
|
|
|
|
return DelegationRole{
|
|
|
|
BaseRole: BaseRole{
|
|
|
|
Name: role.Name,
|
|
|
|
Keys: pubKeys,
|
|
|
|
Threshold: role.Threshold,
|
|
|
|
},
|
|
|
|
Paths: role.Paths,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return DelegationRole{}, ErrNoSuchRole{Role: roleName}
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to create DelegationRole structures from all delegations in a SignedTargets,
|
|
|
|
// these delegations are read directly from the SignedTargets and not modified or validated
|
|
|
|
func (t SignedTargets) buildDelegationRoles() []DelegationRole {
|
|
|
|
var roles []DelegationRole
|
|
|
|
for _, roleData := range t.Signed.Delegations.Roles {
|
|
|
|
delgRole, err := t.BuildDelegationRole(roleData.Name)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
roles = append(roles, delgRole)
|
|
|
|
}
|
|
|
|
return roles
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddTarget adds or updates the meta for the given path
|
|
|
|
func (t *SignedTargets) AddTarget(path string, meta FileMeta) {
|
|
|
|
t.Signed.Targets[path] = meta
|
|
|
|
t.Dirty = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddDelegation will add a new delegated role with the given keys,
|
|
|
|
// ensuring the keys either already exist, or are added to the map
|
|
|
|
// of delegation keys
|
|
|
|
func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
|
|
|
|
return errors.New("Not Implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToSigned partially serializes a SignedTargets for further signing
|
|
|
|
func (t *SignedTargets) ToSigned() (*Signed, error) {
|
|
|
|
s, err := defaultSerializer.MarshalCanonical(t.Signed)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
signed := json.RawMessage{}
|
|
|
|
err = signed.UnmarshalJSON(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sigs := make([]Signature, len(t.Signatures))
|
|
|
|
copy(sigs, t.Signatures)
|
|
|
|
return &Signed{
|
|
|
|
Signatures: sigs,
|
|
|
|
Signed: &signed,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON returns the serialized form of SignedTargets as bytes
|
|
|
|
func (t *SignedTargets) MarshalJSON() ([]byte, error) {
|
|
|
|
signed, err := t.ToSigned()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return defaultSerializer.Marshal(signed)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
|
|
|
|
// a role name (so it can validate the SignedTargets object)
|
2017-08-24 18:40:24 -04:00
|
|
|
func TargetsFromSigned(s *Signed, roleName RoleName) (*SignedTargets, error) {
|
2017-04-17 18:08:24 -04:00
|
|
|
t := Targets{}
|
|
|
|
if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := isValidTargetsStructure(t, roleName); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sigs := make([]Signature, len(s.Signatures))
|
|
|
|
copy(sigs, s.Signatures)
|
|
|
|
return &SignedTargets{
|
|
|
|
Signatures: sigs,
|
|
|
|
Signed: t,
|
|
|
|
}, nil
|
|
|
|
}
|