notary/server/timestamp/timestamp.go

184 lines
7.2 KiB
Go

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
}