blob: e4a717b9daf5aa5ba6980475cbf008a631c050f0 [file] [log] [blame]
package data
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"sync"
"time"
cjson "github.com/tent/canonical-json-go"
)
const (
KeyIDLength = sha256.Size * 2
KeyTypeEd25519 = "ed25519"
KeyTypeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256"
KeySchemeEd25519 = "ed25519"
KeySchemeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256"
)
var (
KeyAlgorithms = []string{"sha256"}
)
type Signed struct {
Signed json.RawMessage `json:"signed"`
Signatures []Signature `json:"signatures"`
}
type Signature struct {
KeyID string `json:"keyid"`
// FIXME(TUF-0.9) removed in TUF 1.0, keeping it around for backwards
// compatibility with TUF 0.9.
Method string `json:"method"`
Signature HexBytes `json:"sig"`
}
type Key struct {
Type string `json:"keytype"`
Scheme string `json:"scheme,omitempty"`
Algorithms []string `json:"keyid_hash_algorithms,omitempty"`
Value KeyValue `json:"keyval"`
ids []string
idOnce sync.Once
}
func (k *Key) IDs() []string {
k.idOnce.Do(func() {
data, _ := cjson.Marshal(k)
digest := sha256.Sum256(data)
k.ids = []string{hex.EncodeToString(digest[:])}
// FIXME(TUF-0.9) If we receive TUF-1.0 compatible metadata,
// the key id we just calculated won't be compatible with
// TUF-0.9. So we also need to calculate the TUF-0.9 key id to
// be backwards compatible.
if k.Scheme != "" || len(k.Algorithms) != 0 {
data, _ = cjson.Marshal(Key{
Type: k.Type,
Value: k.Value,
})
digest = sha256.Sum256(data)
k.ids = append(k.ids, hex.EncodeToString(digest[:]))
}
})
return k.ids
}
func (k *Key) ContainsID(id string) bool {
for _, keyid := range k.IDs() {
if id == keyid {
return true
}
}
return false
}
type KeyValue struct {
Public HexBytes `json:"public"`
}
func DefaultExpires(role string) time.Time {
var t time.Time
switch role {
case "root":
t = time.Now().AddDate(1, 0, 0)
case "targets":
t = time.Now().AddDate(0, 3, 0)
case "snapshot":
t = time.Now().AddDate(0, 0, 7)
case "timestamp":
t = time.Now().AddDate(0, 0, 1)
}
return t.UTC().Round(time.Second)
}
type Root struct {
Type string `json:"_type"`
SpecVersion string `json:"spec_version"`
Version int `json:"version"`
Expires time.Time `json:"expires"`
Keys map[string]*Key `json:"keys"`
Roles map[string]*Role `json:"roles"`
ConsistentSnapshot bool `json:"consistent_snapshot"`
}
func NewRoot() *Root {
return &Root{
Type: "root",
SpecVersion: "1.0",
Expires: DefaultExpires("root"),
Keys: make(map[string]*Key),
Roles: make(map[string]*Role),
ConsistentSnapshot: true,
}
}
func (r *Root) AddKey(key *Key) bool {
changed := false
for _, id := range key.IDs() {
if _, ok := r.Keys[id]; !ok {
changed = true
r.Keys[id] = key
}
}
return changed
}
// UniqueKeys returns the unique keys for each associated role.
// We might have multiple key IDs that correspond to the same key.
func (r Root) UniqueKeys() map[string][]*Key {
keysByRole := make(map[string][]*Key)
for name, role := range r.Roles {
seen := make(map[string]struct{})
keys := []*Key{}
for _, id := range role.KeyIDs {
// Double-check that there is actually a key with that ID.
if key, ok := r.Keys[id]; ok {
val := key.Value.Public.String()
if _, ok := seen[val]; ok {
continue
}
seen[val] = struct{}{}
keys = append(keys, key)
}
}
keysByRole[name] = keys
}
return keysByRole
}
type Role struct {
KeyIDs []string `json:"keyids"`
Threshold int `json:"threshold"`
}
func (r *Role) AddKeyIDs(ids []string) bool {
roleIDs := make(map[string]struct{})
for _, id := range r.KeyIDs {
roleIDs[id] = struct{}{}
}
changed := false
for _, id := range ids {
if _, ok := roleIDs[id]; !ok {
changed = true
r.KeyIDs = append(r.KeyIDs, id)
}
}
return changed
}
type Files map[string]FileMeta
type FileMeta struct {
Length int64 `json:"length",omitempty`
Hashes Hashes `json:"hashes",ompitempty`
Custom *json.RawMessage `json:"custom,omitempty"`
}
type Hashes map[string]HexBytes
func (f FileMeta) HashAlgorithms() []string {
funcs := make([]string, 0, len(f.Hashes))
for name := range f.Hashes {
funcs = append(funcs, name)
}
return funcs
}
type SnapshotFileMeta struct {
FileMeta
Version int `json:"version"`
}
type SnapshotFiles map[string]SnapshotFileMeta
type Snapshot struct {
Type string `json:"_type"`
SpecVersion string `json:"spec_version"`
Version int `json:"version"`
Expires time.Time `json:"expires"`
Meta SnapshotFiles `json:"meta"`
}
func NewSnapshot() *Snapshot {
return &Snapshot{
Type: "snapshot",
SpecVersion: "1.0",
Expires: DefaultExpires("snapshot"),
Meta: make(SnapshotFiles),
}
}
type TargetFiles map[string]TargetFileMeta
type TargetFileMeta struct {
FileMeta
}
func (f TargetFileMeta) HashAlgorithms() []string {
return f.FileMeta.HashAlgorithms()
}
type Targets struct {
Type string `json:"_type"`
SpecVersion string `json:"spec_version"`
Version int `json:"version"`
Expires time.Time `json:"expires"`
Targets TargetFiles `json:"targets"`
}
func NewTargets() *Targets {
return &Targets{
Type: "targets",
SpecVersion: "1.0",
Expires: DefaultExpires("targets"),
Targets: make(TargetFiles),
}
}
type TimestampFileMeta struct {
FileMeta
Version int `json:"version"`
}
type TimestampFiles map[string]TimestampFileMeta
type Timestamp struct {
Type string `json:"_type"`
SpecVersion string `json:"spec_version"`
Version int `json:"version"`
Expires time.Time `json:"expires"`
Meta TimestampFiles `json:"meta"`
}
func NewTimestamp() *Timestamp {
return &Timestamp{
Type: "timestamp",
SpecVersion: "1.0",
Expires: DefaultExpires("timestamp"),
Meta: make(TimestampFiles),
}
}