forked from notaryproject/notary
/
timestamp.go
183 lines (159 loc) · 7.19 KB
/
timestamp.go
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
package timestamp
import (
"encoding/hex"
"time"
"github.com/docker/go/canonical/json"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/trustpinning"
"github.com/theupdateframework/notary/tuf"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/signed"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary/server/snapshot"
"github.com/theupdateframework/notary/server/storage"
)
// GetOrCreateTimestampKey returns the timestamp key for the gun. It uses the store to
// lookup an existing timestamp key and the crypto to generate a new one if none is
// found. It attempts to handle the race condition that may occur if 2 servers try to
// create the key at the same time by simply querying the store a second time if it
// receives a conflict when writing.
func GetOrCreateTimestampKey(gun data.GUN, store storage.MetaStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) {
_, rootJSON, err := store.GetCurrent(gun, data.CanonicalRootRole)
if err != nil {
// If the error indicates we couldn't find the root, create a new key
if _, ok := err.(storage.ErrNotFound); !ok {
logrus.Errorf("Error when retrieving root role for GUN %s: %v", gun, err)
return nil, err
}
return crypto.Create(data.CanonicalTimestampRole, gun, createAlgorithm)
}
// If we have a current root, parse out the public key for the timestamp role, and return it
repoSignedRoot := new(data.SignedRoot)
if err := json.Unmarshal(rootJSON, repoSignedRoot); err != nil {
logrus.Errorf("Failed to unmarshal existing root for GUN %s to retrieve timestamp key ID", gun)
return nil, err
}
timestampRole, err := repoSignedRoot.BuildBaseRole(data.CanonicalTimestampRole)
if err != nil {
logrus.Errorf("Failed to extract timestamp role from root for GUN %s", gun)
return nil, err
}
// We currently only support single keys for snapshot and timestamp, so we can return the first and only key in the map if the signer has it
for keyID := range timestampRole.Keys {
if pubKey := crypto.GetKey(keyID); pubKey != nil {
return pubKey, nil
}
}
logrus.Debugf("Failed to find any timestamp keys in cryptosigner from root for GUN %s, generating new key", gun)
return crypto.Create(data.CanonicalTimestampRole, gun, createAlgorithm)
}
// RotateTimestampKey attempts to rotate a timestamp key in the signer, but might be rate-limited by the signer
func RotateTimestampKey(gun data.GUN, store storage.MetaStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) {
// Always attempt to create a new key, but this might be rate-limited
key, err := crypto.Create(data.CanonicalTimestampRole, gun, createAlgorithm)
if err != nil {
return nil, err
}
logrus.Debug("Created new pending timestamp key ", key.ID(), "to rotate to for ", gun, ". With algo: ", key.Algorithm())
return key, nil
}
// GetOrCreateTimestamp returns the current timestamp for the gun. This may mean
// a new timestamp is generated either because none exists, or because the current
// one has expired. Once generated, the timestamp is saved in the store.
// Additionally, if we had to generate a new snapshot for this timestamp,
// it is also saved in the store
func GetOrCreateTimestamp(gun data.GUN, store storage.MetaStore, cryptoService signed.CryptoService) (
*time.Time, []byte, error) {
updates := []storage.MetaUpdate{}
lastModified, timestampJSON, err := store.GetCurrent(gun, data.CanonicalTimestampRole)
if err != nil {
logrus.Debug("error retrieving timestamp: ", err.Error())
return nil, nil, err
}
prev := &data.SignedTimestamp{}
if err := json.Unmarshal(timestampJSON, prev); err != nil {
logrus.Error("Failed to unmarshal existing timestamp")
return nil, nil, err
}
snapChecksums, err := prev.GetSnapshot()
if err != nil || snapChecksums == nil {
return nil, nil, err
}
snapshotSHA256Bytes, ok := snapChecksums.Hashes[notary.SHA256]
if !ok {
return nil, nil, data.ErrMissingMeta{Role: data.CanonicalSnapshotRole.String()}
}
snapshotSHA256Hex := hex.EncodeToString(snapshotSHA256Bytes[:])
snapshotTime, snapshot, err := snapshot.GetOrCreateSnapshot(gun, snapshotSHA256Hex, store, cryptoService)
if err != nil {
logrus.Debug("Previous timestamp, but no valid snapshot for GUN ", gun)
return nil, nil, err
}
snapshotRole := &data.SignedSnapshot{}
if err := json.Unmarshal(snapshot, snapshotRole); err != nil {
logrus.Error("Failed to unmarshal retrieved snapshot")
return nil, nil, err
}
// If the snapshot was generated, we should write it with the timestamp
if snapshotTime == nil {
updates = append(updates, storage.MetaUpdate{Role: data.CanonicalSnapshotRole, Version: snapshotRole.Signed.Version, Data: snapshot})
}
if !timestampExpired(prev) && !snapshotExpired(prev, snapshot) {
return lastModified, timestampJSON, nil
}
tsUpdate, err := createTimestamp(gun, prev, snapshot, store, cryptoService)
if err != nil {
logrus.Error("Failed to create a new timestamp")
return nil, nil, err
}
updates = append(updates, *tsUpdate)
c := time.Now()
// Write the timestamp, and potentially snapshot
if err = store.UpdateMany(gun, updates); err != nil {
return nil, nil, err
}
return &c, tsUpdate.Data, nil
}
// timestampExpired compares the current time to the expiry time of the timestamp
func timestampExpired(ts *data.SignedTimestamp) bool {
return signed.IsExpired(ts.Signed.Expires)
}
// snapshotExpired verifies the checksum(s) for the given snapshot using metadata from the timestamp
func snapshotExpired(ts *data.SignedTimestamp, snapshot []byte) bool {
// If this check failed, it means the current snapshot was not exactly what we expect
// via the timestamp. So we can consider it to be "expired."
return data.CheckHashes(snapshot, data.CanonicalSnapshotRole.String(),
ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes) != nil
}
// CreateTimestamp creates a new timestamp. If a prev timestamp is provided, it
// is assumed this is the immediately previous one, and the new one will have a
// version number one higher than prev. The store is used to lookup the current
// snapshot, this function does not save the newly generated timestamp.
func createTimestamp(gun data.GUN, prev *data.SignedTimestamp, snapshot []byte, store storage.MetaStore,
cryptoService signed.CryptoService) (*storage.MetaUpdate, error) {
builder := tuf.NewRepoBuilder(gun, cryptoService, trustpinning.TrustPinConfig{})
// load the current root to ensure we use the correct timestamp key.
_, root, err := store.GetCurrent(gun, data.CanonicalRootRole)
if err != nil {
logrus.Debug("Previous timestamp, but no root for GUN ", gun)
return nil, err
}
if err := builder.Load(data.CanonicalRootRole, root, 1, false); err != nil {
logrus.Debug("Could not load valid previous root for GUN ", gun)
return nil, err
}
// load snapshot so we can include it in timestamp
if err := builder.Load(data.CanonicalSnapshotRole, snapshot, 1, false); err != nil {
logrus.Debug("Could not load valid previous snapshot for GUN ", gun)
return nil, err
}
meta, ver, err := builder.GenerateTimestamp(prev)
if err != nil {
return nil, err
}
return &storage.MetaUpdate{
Role: data.CanonicalTimestampRole,
Version: ver,
Data: meta,
}, nil
}