package storage import ( "encoding/hex" "fmt" "time" "github.com/docker/go/canonical/json" "github.com/docker/notary" "github.com/docker/notary/storage" "github.com/docker/notary/tuf/data" ) // TUFMetaStorage wraps a MetaStore in order to walk the TUF tree for GetCurrent in a consistent manner, // by always starting from a current timestamp and then looking up other data by hash type TUFMetaStorage struct { MetaStore // cached metadata by checksum cachedMeta map[string]*storedMeta } // NewTUFMetaStorage instantiates a TUFMetaStorage instance func NewTUFMetaStorage(m MetaStore) *TUFMetaStorage { return &TUFMetaStorage{ MetaStore: m, cachedMeta: make(map[string]*storedMeta), } } type storedMeta struct { data []byte createupdate *time.Time } // GetCurrent gets a specific TUF record, by walking from the current Timestamp to other metadata by checksum func (tms TUFMetaStorage) GetCurrent(gun, tufRole string) (*time.Time, []byte, error) { timestampTime, timestampJSON, err := tms.MetaStore.GetCurrent(gun, data.CanonicalTimestampRole) if err != nil { return nil, nil, err } // If we wanted data for the timestamp role, we're done here if tufRole == data.CanonicalTimestampRole { return timestampTime, timestampJSON, nil } // If we want to lookup another role, walk to it via current timestamp --> snapshot by checksum --> desired role timestampMeta := &data.SignedTimestamp{} if err := json.Unmarshal(timestampJSON, timestampMeta); err != nil { return nil, nil, fmt.Errorf("could not parse current timestamp") } snapshotChecksums, err := timestampMeta.GetSnapshot() if err != nil || snapshotChecksums == nil { return nil, nil, fmt.Errorf("could not retrieve latest snapshot checksum") } snapshotSha256Bytes, ok := snapshotChecksums.Hashes[notary.SHA256] if !ok { return nil, nil, fmt.Errorf("could not retrieve latest snapshot sha256") } snapshotSha256Hex := hex.EncodeToString(snapshotSha256Bytes[:]) // Check the cache if we have our snapshot data var snapshotTime *time.Time var snapshotJSON []byte if cachedSnapshotData, ok := tms.cachedMeta[snapshotSha256Hex]; ok { snapshotTime = cachedSnapshotData.createupdate snapshotJSON = cachedSnapshotData.data } else { // Get the snapshot from the underlying store by checksum if it isn't cached yet snapshotTime, snapshotJSON, err = tms.GetChecksum(gun, data.CanonicalSnapshotRole, snapshotSha256Hex) if err != nil { return nil, nil, err } // cache for subsequent lookups tms.cachedMeta[snapshotSha256Hex] = &storedMeta{data: snapshotJSON, createupdate: snapshotTime} } // If we wanted data for the snapshot role, we're done here if tufRole == data.CanonicalSnapshotRole { return snapshotTime, snapshotJSON, nil } // If it's a different role, we should have the checksum in snapshot metadata, and we can use it to GetChecksum() snapshotMeta := &data.SignedSnapshot{} if err := json.Unmarshal(snapshotJSON, snapshotMeta); err != nil { return nil, nil, fmt.Errorf("could not parse current snapshot") } roleMeta, err := snapshotMeta.GetMeta(tufRole) if err != nil { return nil, nil, err } roleSha256Bytes, ok := roleMeta.Hashes[notary.SHA256] if !ok { return nil, nil, fmt.Errorf("could not retrieve latest %s sha256", tufRole) } roleSha256Hex := hex.EncodeToString(roleSha256Bytes[:]) // check if we can retrieve this data from cache if cachedRoleData, ok := tms.cachedMeta[roleSha256Hex]; ok { return cachedRoleData.createupdate, cachedRoleData.data, nil } roleTime, roleJSON, err := tms.MetaStore.GetChecksum(gun, tufRole, roleSha256Hex) if err != nil { return nil, nil, err } // cache for subsequent lookups tms.cachedMeta[roleSha256Hex] = &storedMeta{data: roleJSON, createupdate: roleTime} return roleTime, roleJSON, nil } // GetChecksum gets a specific TUF record by checksum, also checking the internal cache func (tms TUFMetaStorage) GetChecksum(gun, tufRole, checksum string) (*time.Time, []byte, error) { if cachedRoleData, ok := tms.cachedMeta[checksum]; ok { return cachedRoleData.createupdate, cachedRoleData.data, nil } roleTime, roleJSON, err := tms.MetaStore.GetChecksum(gun, tufRole, checksum) if err != nil { return nil, nil, err } // cache for subsequent lookups tms.cachedMeta[checksum] = &storedMeta{data: roleJSON, createupdate: roleTime} return roleTime, roleJSON, nil } // Bootstrap the store with tables if possible func (tms TUFMetaStorage) Bootstrap() error { if s, ok := tms.MetaStore.(storage.Bootstrapper); ok { return s.Bootstrap() } return fmt.Errorf("store does not support bootstrapping") }