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" "github.com/theupdateframework/go-tuf/pkg/keys" ) type signedMeta struct { Type string `json:"_type"` Expires time.Time `json:"expires"` Version int64 `json:"version"` } // VerifySignature takes a signed JSON message, a signature, and a // verifier and verifies the given signature on the JSON message // using the verifier. It returns an error if verification fails. func VerifySignature(signed json.RawMessage, sig data.HexBytes, verifier keys.Verifier) error { var decoded map[string]interface{} if err := json.Unmarshal(signed, &decoded); err != nil { return err } msg, err := cjson.EncodeCanonical(decoded) if err != nil { return err } return verifier.Verify(msg, sig) } 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} } // 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 := VerifySignature(s.Signed, sig.Signature, verifier); err != nil { // If a signature fails verification, don't count it towards the // threshold but also return early and error out immediately. // Note: Because of this, it is impossible to distinguish between // an error of an invalid signature and a threshold not achieved. // Invalid signatures lead to not achieving the threshold. continue } // 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) }