summaryrefslogtreecommitdiffstats
path: root/verify/verify.go
blob: e62042ee1625e748226970038c6111246c0ac8e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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)
}