582 lines
14 KiB
Go
582 lines
14 KiB
Go
// Copyright 2024 The Update Framework Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package metadata
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// The following marshal/unmarshal methods override the default behavior for for each TUF type
|
|
// in order to support unrecognized fields
|
|
|
|
func (signed RootType) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
dict["_type"] = signed.Type
|
|
dict["spec_version"] = signed.SpecVersion
|
|
dict["consistent_snapshot"] = signed.ConsistentSnapshot
|
|
dict["version"] = signed.Version
|
|
dict["expires"] = signed.Expires
|
|
dict["keys"] = signed.Keys
|
|
dict["roles"] = signed.Roles
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *RootType) UnmarshalJSON(data []byte) error {
|
|
type Alias RootType
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*signed = RootType(s)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "_type")
|
|
delete(dict, "spec_version")
|
|
delete(dict, "consistent_snapshot")
|
|
delete(dict, "version")
|
|
delete(dict, "expires")
|
|
delete(dict, "keys")
|
|
delete(dict, "roles")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (signed SnapshotType) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
dict["_type"] = signed.Type
|
|
dict["spec_version"] = signed.SpecVersion
|
|
dict["version"] = signed.Version
|
|
dict["expires"] = signed.Expires
|
|
dict["meta"] = signed.Meta
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *SnapshotType) UnmarshalJSON(data []byte) error {
|
|
type Alias SnapshotType
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*signed = SnapshotType(s)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "_type")
|
|
delete(dict, "spec_version")
|
|
delete(dict, "version")
|
|
delete(dict, "expires")
|
|
delete(dict, "meta")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (signed TimestampType) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
dict["_type"] = signed.Type
|
|
dict["spec_version"] = signed.SpecVersion
|
|
dict["version"] = signed.Version
|
|
dict["expires"] = signed.Expires
|
|
dict["meta"] = signed.Meta
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *TimestampType) UnmarshalJSON(data []byte) error {
|
|
type Alias TimestampType
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*signed = TimestampType(s)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "_type")
|
|
delete(dict, "spec_version")
|
|
delete(dict, "version")
|
|
delete(dict, "expires")
|
|
delete(dict, "meta")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (signed TargetsType) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
dict["_type"] = signed.Type
|
|
dict["spec_version"] = signed.SpecVersion
|
|
dict["version"] = signed.Version
|
|
dict["expires"] = signed.Expires
|
|
dict["targets"] = signed.Targets
|
|
if signed.Delegations != nil {
|
|
dict["delegations"] = signed.Delegations
|
|
}
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *TargetsType) UnmarshalJSON(data []byte) error {
|
|
type Alias TargetsType
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*signed = TargetsType(s)
|
|
|
|
// populate the path field for each target
|
|
for name, targetFile := range signed.Targets {
|
|
targetFile.Path = name
|
|
}
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "_type")
|
|
delete(dict, "spec_version")
|
|
delete(dict, "version")
|
|
delete(dict, "expires")
|
|
delete(dict, "targets")
|
|
delete(dict, "delegations")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (signed MetaFiles) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
// length and hashes are optional
|
|
if signed.Length != 0 {
|
|
dict["length"] = signed.Length
|
|
}
|
|
if len(signed.Hashes) != 0 {
|
|
dict["hashes"] = signed.Hashes
|
|
}
|
|
dict["version"] = signed.Version
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *MetaFiles) UnmarshalJSON(data []byte) error {
|
|
type Alias MetaFiles
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
*signed = MetaFiles(s)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "length")
|
|
delete(dict, "hashes")
|
|
delete(dict, "version")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (signed TargetFiles) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(signed.UnrecognizedFields) != 0 {
|
|
copyMapValues(signed.UnrecognizedFields, dict)
|
|
}
|
|
dict["length"] = signed.Length
|
|
dict["hashes"] = signed.Hashes
|
|
if signed.Custom != nil {
|
|
dict["custom"] = signed.Custom
|
|
}
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (signed *TargetFiles) UnmarshalJSON(data []byte) error {
|
|
type Alias TargetFiles
|
|
var s Alias
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
// Per TUF spec, hashes are mandatory for target files
|
|
if len(s.Hashes) == 0 {
|
|
return fmt.Errorf("hashes must not be empty for target files")
|
|
}
|
|
*signed = TargetFiles(s)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "length")
|
|
delete(dict, "hashes")
|
|
delete(dict, "custom")
|
|
signed.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (key Key) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(key.UnrecognizedFields) != 0 {
|
|
copyMapValues(key.UnrecognizedFields, dict)
|
|
}
|
|
dict["keytype"] = key.Type
|
|
dict["scheme"] = key.Scheme
|
|
dict["keyval"] = key.Value
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (key *Key) UnmarshalJSON(data []byte) error {
|
|
type Alias Key
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
// nolint
|
|
*key = Key(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "keytype")
|
|
delete(dict, "scheme")
|
|
delete(dict, "keyval")
|
|
key.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (meta Metadata[T]) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(meta.UnrecognizedFields) != 0 {
|
|
copyMapValues(meta.UnrecognizedFields, dict)
|
|
}
|
|
dict["signed"] = meta.Signed
|
|
dict["signatures"] = meta.Signatures
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (meta *Metadata[T]) UnmarshalJSON(data []byte) error {
|
|
tmp := any(new(T))
|
|
var m map[string]any
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return err
|
|
}
|
|
switch tmp.(type) {
|
|
case *RootType:
|
|
dict := struct {
|
|
Signed RootType `json:"signed"`
|
|
Signatures []Signature `json:"signatures"`
|
|
}{}
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
var i interface{} = dict.Signed
|
|
meta.Signed = i.(T)
|
|
meta.Signatures = dict.Signatures
|
|
case *SnapshotType:
|
|
dict := struct {
|
|
Signed SnapshotType `json:"signed"`
|
|
Signatures []Signature `json:"signatures"`
|
|
}{}
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
var i interface{} = dict.Signed
|
|
meta.Signed = i.(T)
|
|
meta.Signatures = dict.Signatures
|
|
case *TimestampType:
|
|
dict := struct {
|
|
Signed TimestampType `json:"signed"`
|
|
Signatures []Signature `json:"signatures"`
|
|
}{}
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
var i interface{} = dict.Signed
|
|
meta.Signed = i.(T)
|
|
meta.Signatures = dict.Signatures
|
|
case *TargetsType:
|
|
dict := struct {
|
|
Signed TargetsType `json:"signed"`
|
|
Signatures []Signature `json:"signatures"`
|
|
}{}
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
var i interface{} = dict.Signed
|
|
meta.Signed = i.(T)
|
|
meta.Signatures = dict.Signatures
|
|
default:
|
|
return &ErrValue{Msg: "unrecognized metadata type"}
|
|
}
|
|
delete(m, "signed")
|
|
delete(m, "signatures")
|
|
meta.UnrecognizedFields = m
|
|
return nil
|
|
}
|
|
|
|
func (s Signature) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(s.UnrecognizedFields) != 0 {
|
|
copyMapValues(s.UnrecognizedFields, dict)
|
|
}
|
|
dict["keyid"] = s.KeyID
|
|
dict["sig"] = s.Signature
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (s *Signature) UnmarshalJSON(data []byte) error {
|
|
type Alias Signature
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*s = Signature(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "keyid")
|
|
delete(dict, "sig")
|
|
s.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (kv KeyVal) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(kv.UnrecognizedFields) != 0 {
|
|
copyMapValues(kv.UnrecognizedFields, dict)
|
|
}
|
|
dict["public"] = kv.PublicKey
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (kv *KeyVal) UnmarshalJSON(data []byte) error {
|
|
type Alias KeyVal
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*kv = KeyVal(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "public")
|
|
kv.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (role Role) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(role.UnrecognizedFields) != 0 {
|
|
copyMapValues(role.UnrecognizedFields, dict)
|
|
}
|
|
dict["keyids"] = role.KeyIDs
|
|
dict["threshold"] = role.Threshold
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (role *Role) UnmarshalJSON(data []byte) error {
|
|
type Alias Role
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*role = Role(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "keyids")
|
|
delete(dict, "threshold")
|
|
role.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (d Delegations) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(d.UnrecognizedFields) != 0 {
|
|
copyMapValues(d.UnrecognizedFields, dict)
|
|
}
|
|
// only one is allowed
|
|
dict["keys"] = d.Keys
|
|
if d.Roles != nil {
|
|
dict["roles"] = d.Roles
|
|
} else if d.SuccinctRoles != nil {
|
|
dict["succinct_roles"] = d.SuccinctRoles
|
|
}
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (d *Delegations) UnmarshalJSON(data []byte) error {
|
|
type Alias Delegations
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*d = Delegations(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "keys")
|
|
delete(dict, "roles")
|
|
delete(dict, "succinct_roles")
|
|
d.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (role DelegatedRole) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(role.UnrecognizedFields) != 0 {
|
|
copyMapValues(role.UnrecognizedFields, dict)
|
|
}
|
|
dict["name"] = role.Name
|
|
dict["keyids"] = role.KeyIDs
|
|
dict["threshold"] = role.Threshold
|
|
dict["terminating"] = role.Terminating
|
|
// make sure we have only one of the two (per spec)
|
|
if role.Paths != nil && role.PathHashPrefixes != nil {
|
|
return nil, &ErrValue{Msg: "failed to marshal: not allowed to have both \"paths\" and \"path_hash_prefixes\" present"}
|
|
}
|
|
if role.Paths != nil {
|
|
dict["paths"] = role.Paths
|
|
} else if role.PathHashPrefixes != nil {
|
|
dict["path_hash_prefixes"] = role.PathHashPrefixes
|
|
}
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (role *DelegatedRole) UnmarshalJSON(data []byte) error {
|
|
type Alias DelegatedRole
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*role = DelegatedRole(a)
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "name")
|
|
delete(dict, "keyids")
|
|
delete(dict, "threshold")
|
|
delete(dict, "terminating")
|
|
delete(dict, "paths")
|
|
delete(dict, "path_hash_prefixes")
|
|
role.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (role SuccinctRoles) MarshalJSON() ([]byte, error) {
|
|
dict := map[string]any{}
|
|
if len(role.UnrecognizedFields) != 0 {
|
|
copyMapValues(role.UnrecognizedFields, dict)
|
|
}
|
|
dict["keyids"] = role.KeyIDs
|
|
dict["threshold"] = role.Threshold
|
|
dict["bit_length"] = role.BitLength
|
|
dict["name_prefix"] = role.NamePrefix
|
|
return json.Marshal(dict)
|
|
}
|
|
|
|
func (role *SuccinctRoles) UnmarshalJSON(data []byte) error {
|
|
type Alias SuccinctRoles
|
|
var a Alias
|
|
if err := json.Unmarshal(data, &a); err != nil {
|
|
return err
|
|
}
|
|
*role = SuccinctRoles(a)
|
|
|
|
// Validate BitLength: must be between 1 and 32 inclusive.
|
|
// - BitLength determines the number of bins as 2^BitLength
|
|
// - We use the leftmost BitLength bits of a SHA-256 hash (32 bits max from 4 bytes)
|
|
// - BitLength < 1 would result in 0 or fractional bins
|
|
// - BitLength > 32 would cause a negative shift value in GetRolesForTarget
|
|
if role.BitLength < 1 || role.BitLength > 32 {
|
|
return fmt.Errorf("invalid bit_length: %d, must be between 1 and 32", role.BitLength)
|
|
}
|
|
|
|
var dict map[string]any
|
|
if err := json.Unmarshal(data, &dict); err != nil {
|
|
return err
|
|
}
|
|
delete(dict, "keyids")
|
|
delete(dict, "threshold")
|
|
delete(dict, "bit_length")
|
|
delete(dict, "name_prefix")
|
|
role.UnrecognizedFields = dict
|
|
return nil
|
|
}
|
|
|
|
func (b *HexBytes) UnmarshalJSON(data []byte) error {
|
|
if len(data) < 2 || len(data)%2 != 0 || data[0] != '"' || data[len(data)-1] != '"' {
|
|
return errors.New("tuf: invalid JSON hex bytes")
|
|
}
|
|
res := make([]byte, hex.DecodedLen(len(data)-2))
|
|
_, err := hex.Decode(res, data[1:len(data)-1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*b = res
|
|
return nil
|
|
}
|
|
|
|
func (b HexBytes) MarshalJSON() ([]byte, error) {
|
|
res := make([]byte, hex.EncodedLen(len(b))+2)
|
|
res[0] = '"'
|
|
res[len(res)-1] = '"'
|
|
hex.Encode(res[1:], b)
|
|
return res, nil
|
|
}
|
|
|
|
func (b HexBytes) String() string {
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// copyMapValues copies the values of the src map to dst
|
|
func copyMapValues(src, dst map[string]any) {
|
|
for k, v := range src {
|
|
dst[k] = v
|
|
}
|
|
}
|