blob: f5675a250e6fec8615e5becd6790d5d919cfdeb3 [file] [log] [blame]
package verify
import (
"encoding/json"
"strings"
"time"
"github.com/secure-systems-lab/go-securesystemslib/cjson"
"github.com/theupdateframework/go-tuf/data"
"github.com/theupdateframework/go-tuf/internal/roles"
)
type signedMeta struct {
Type string `json:"_type"`
Expires time.Time `json:"expires"`
Version int64 `json:"version"`
}
func (db *DB) VerifyIgnoreExpiredCheck(s *data.Signed, role string, minVersion int64) error {
if err := db.VerifySignatures(s, role); err != nil {
return err
}
sm := &signedMeta{}
if err := json.Unmarshal(s.Signed, sm); err != nil {
return err
}
if roles.IsTopLevelRole(role) {
// Top-level roles can only sign metadata of the same type (e.g. snapshot
// metadata must be signed by the snapshot role).
if !strings.EqualFold(sm.Type, role) {
return ErrWrongMetaType
}
} else {
// Delegated (non-top-level) roles may only sign targets metadata.
if strings.ToLower(sm.Type) != "targets" {
return ErrWrongMetaType
}
}
if sm.Version < minVersion {
return ErrLowVersion{sm.Version, minVersion}
}
return nil
}
func (db *DB) Verify(s *data.Signed, role string, minVersion int64) error {
// Verify signatures and versions
err := db.VerifyIgnoreExpiredCheck(s, role, minVersion)
if err != nil {
return err
}
sm := &signedMeta{}
if err := json.Unmarshal(s.Signed, sm); err != nil {
return err
}
// Verify expiration
if IsExpired(sm.Expires) {
return ErrExpired{sm.Expires}
}
return nil
}
var IsExpired = func(t time.Time) bool {
return time.Until(t) <= 0
}
func (db *DB) VerifySignatures(s *data.Signed, role string) error {
if len(s.Signatures) == 0 {
return ErrNoSignatures
}
roleData := db.GetRole(role)
if roleData == nil {
return ErrUnknownRole{role}
}
var decoded map[string]interface{}
if err := json.Unmarshal(s.Signed, &decoded); err != nil {
return err
}
msg, err := cjson.EncodeCanonical(decoded)
if err != nil {
return err
}
// Verify that a threshold of keys signed the data. Since keys can have
// multiple key ids, we need to protect against multiple attached
// signatures that just differ on the key id.
verifiedKeyIDs := make(map[string]struct{})
numVerifiedKeys := 0
for _, sig := range s.Signatures {
if !roleData.ValidKey(sig.KeyID) {
continue
}
verifier, err := db.GetVerifier(sig.KeyID)
if err != nil {
continue
}
if err := verifier.Verify(msg, sig.Signature); err != nil {
// FIXME: don't err out on the 1st bad signature.
return ErrInvalid
}
// Only consider this key valid if we haven't seen any of it's
// key ids before.
// Careful: we must not rely on the key IDs _declared in the file_,
// instead we get to decide what key IDs this key correspond to.
// XXX dangerous; better stop supporting multiple key IDs altogether.
keyIDs := verifier.MarshalPublicKey().IDs()
wasKeySeen := false
for _, keyID := range keyIDs {
if _, present := verifiedKeyIDs[keyID]; present {
wasKeySeen = true
}
}
if !wasKeySeen {
for _, id := range keyIDs {
verifiedKeyIDs[id] = struct{}{}
}
numVerifiedKeys++
}
}
if numVerifiedKeys < roleData.Threshold {
return ErrRoleThreshold{roleData.Threshold, numVerifiedKeys}
}
return nil
}
func (db *DB) Unmarshal(b []byte, v interface{}, role string, minVersion int64) error {
s := &data.Signed{}
if err := json.Unmarshal(b, s); err != nil {
return err
}
if err := db.Verify(s, role, minVersion); err != nil {
return err
}
return json.Unmarshal(s.Signed, v)
}
// UnmarshalExpired is exactly like Unmarshal except ignores expired timestamp error.
func (db *DB) UnmarshalIgnoreExpired(b []byte, v interface{}, role string, minVersion int64) error {
s := &data.Signed{}
if err := json.Unmarshal(b, s); err != nil {
return err
}
// Note: If verification fails, then we wont attempt to unmarshal
// unless when verification error is errExpired.
verifyErr := db.Verify(s, role, minVersion)
if verifyErr != nil {
if _, ok := verifyErr.(ErrExpired); !ok {
return verifyErr
}
}
return json.Unmarshal(s.Signed, v)
}
func (db *DB) UnmarshalTrusted(b []byte, v interface{}, role string) error {
s := &data.Signed{}
if err := json.Unmarshal(b, s); err != nil {
return err
}
if err := db.VerifySignatures(s, role); err != nil {
return err
}
return json.Unmarshal(s.Signed, v)
}