123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.TrustedMetadataStore = void 0;
- const models_1 = require("@tufjs/models");
- const error_1 = require("./error");
- class TrustedMetadataStore {
- constructor(rootData) {
- this.trustedSet = {};
- // Client workflow 5.1: record fixed update start time
- this.referenceTime = new Date();
- // Client workflow 5.2: load trusted root metadata
- this.loadTrustedRoot(rootData);
- }
- get root() {
- if (!this.trustedSet.root) {
- throw new ReferenceError('No trusted root metadata');
- }
- return this.trustedSet.root;
- }
- get timestamp() {
- return this.trustedSet.timestamp;
- }
- get snapshot() {
- return this.trustedSet.snapshot;
- }
- get targets() {
- return this.trustedSet.targets;
- }
- getRole(name) {
- return this.trustedSet[name];
- }
- updateRoot(bytesBuffer) {
- const data = JSON.parse(bytesBuffer.toString('utf8'));
- const newRoot = models_1.Metadata.fromJSON(models_1.MetadataKind.Root, data);
- if (newRoot.signed.type != models_1.MetadataKind.Root) {
- throw new error_1.RepositoryError(`Expected 'root', got ${newRoot.signed.type}`);
- }
- // Client workflow 5.4: check for arbitrary software attack
- this.root.verifyDelegate(models_1.MetadataKind.Root, newRoot);
- // Client workflow 5.5: check for rollback attack
- if (newRoot.signed.version != this.root.signed.version + 1) {
- throw new error_1.BadVersionError(`Expected version ${this.root.signed.version + 1}, got ${newRoot.signed.version}`);
- }
- // Check that new root is signed by self
- newRoot.verifyDelegate(models_1.MetadataKind.Root, newRoot);
- // Client workflow 5.7: set new root as trusted root
- this.trustedSet.root = newRoot;
- return newRoot;
- }
- updateTimestamp(bytesBuffer) {
- if (this.snapshot) {
- throw new error_1.RuntimeError('Cannot update timestamp after snapshot');
- }
- if (this.root.signed.isExpired(this.referenceTime)) {
- throw new error_1.ExpiredMetadataError('Final root.json is expired');
- }
- const data = JSON.parse(bytesBuffer.toString('utf8'));
- const newTimestamp = models_1.Metadata.fromJSON(models_1.MetadataKind.Timestamp, data);
- if (newTimestamp.signed.type != models_1.MetadataKind.Timestamp) {
- throw new error_1.RepositoryError(`Expected 'timestamp', got ${newTimestamp.signed.type}`);
- }
- // Client workflow 5.4.2: check for arbitrary software attack
- this.root.verifyDelegate(models_1.MetadataKind.Timestamp, newTimestamp);
- if (this.timestamp) {
- // Prevent rolling back timestamp version
- // Client workflow 5.4.3.1: check for rollback attack
- if (newTimestamp.signed.version < this.timestamp.signed.version) {
- throw new error_1.BadVersionError(`New timestamp version ${newTimestamp.signed.version} is less than current version ${this.timestamp.signed.version}`);
- }
- // Keep using old timestamp if versions are equal.
- if (newTimestamp.signed.version === this.timestamp.signed.version) {
- throw new error_1.EqualVersionError(`New timestamp version ${newTimestamp.signed.version} is equal to current version ${this.timestamp.signed.version}`);
- }
- // Prevent rolling back snapshot version
- // Client workflow 5.4.3.2: check for rollback attack
- const snapshotMeta = this.timestamp.signed.snapshotMeta;
- const newSnapshotMeta = newTimestamp.signed.snapshotMeta;
- if (newSnapshotMeta.version < snapshotMeta.version) {
- throw new error_1.BadVersionError(`New snapshot version ${newSnapshotMeta.version} is less than current version ${snapshotMeta.version}`);
- }
- }
- // expiry not checked to allow old timestamp to be used for rollback
- // protection of new timestamp: expiry is checked in update_snapshot
- this.trustedSet.timestamp = newTimestamp;
- // Client workflow 5.4.4: check for freeze attack
- this.checkFinalTimestamp();
- return newTimestamp;
- }
- updateSnapshot(bytesBuffer, trusted = false) {
- if (!this.timestamp) {
- throw new error_1.RuntimeError('Cannot update snapshot before timestamp');
- }
- if (this.targets) {
- throw new error_1.RuntimeError('Cannot update snapshot after targets');
- }
- // Snapshot cannot be loaded if final timestamp is expired
- this.checkFinalTimestamp();
- const snapshotMeta = this.timestamp.signed.snapshotMeta;
- // Verify non-trusted data against the hashes in timestamp, if any.
- // Trusted snapshot data has already been verified once.
- // Client workflow 5.5.2: check against timestamp role's snaphsot hash
- if (!trusted) {
- snapshotMeta.verify(bytesBuffer);
- }
- const data = JSON.parse(bytesBuffer.toString('utf8'));
- const newSnapshot = models_1.Metadata.fromJSON(models_1.MetadataKind.Snapshot, data);
- if (newSnapshot.signed.type != models_1.MetadataKind.Snapshot) {
- throw new error_1.RepositoryError(`Expected 'snapshot', got ${newSnapshot.signed.type}`);
- }
- // Client workflow 5.5.3: check for arbitrary software attack
- this.root.verifyDelegate(models_1.MetadataKind.Snapshot, newSnapshot);
- // version check against meta version (5.5.4) is deferred to allow old
- // snapshot to be used in rollback protection
- // Client workflow 5.5.5: check for rollback attack
- if (this.snapshot) {
- Object.entries(this.snapshot.signed.meta).forEach(([fileName, fileInfo]) => {
- const newFileInfo = newSnapshot.signed.meta[fileName];
- if (!newFileInfo) {
- throw new error_1.RepositoryError(`Missing file ${fileName} in new snapshot`);
- }
- if (newFileInfo.version < fileInfo.version) {
- throw new error_1.BadVersionError(`New version ${newFileInfo.version} of ${fileName} is less than current version ${fileInfo.version}`);
- }
- });
- }
- this.trustedSet.snapshot = newSnapshot;
- // snapshot is loaded, but we raise if it's not valid _final_ snapshot
- // Client workflow 5.5.4 & 5.5.6
- this.checkFinalSnapsnot();
- return newSnapshot;
- }
- updateDelegatedTargets(bytesBuffer, roleName, delegatorName) {
- if (!this.snapshot) {
- throw new error_1.RuntimeError('Cannot update delegated targets before snapshot');
- }
- // Targets cannot be loaded if final snapshot is expired or its version
- // does not match meta version in timestamp.
- this.checkFinalSnapsnot();
- const delegator = this.trustedSet[delegatorName];
- if (!delegator) {
- throw new error_1.RuntimeError(`No trusted ${delegatorName} metadata`);
- }
- // Extract metadata for the delegated role from snapshot
- const meta = this.snapshot.signed.meta?.[`${roleName}.json`];
- if (!meta) {
- throw new error_1.RepositoryError(`Missing ${roleName}.json in snapshot`);
- }
- // Client workflow 5.6.2: check against snapshot role's targets hash
- meta.verify(bytesBuffer);
- const data = JSON.parse(bytesBuffer.toString('utf8'));
- const newDelegate = models_1.Metadata.fromJSON(models_1.MetadataKind.Targets, data);
- if (newDelegate.signed.type != models_1.MetadataKind.Targets) {
- throw new error_1.RepositoryError(`Expected 'targets', got ${newDelegate.signed.type}`);
- }
- // Client workflow 5.6.3: check for arbitrary software attack
- delegator.verifyDelegate(roleName, newDelegate);
- // Client workflow 5.6.4: Check against snapshot role’s targets version
- const version = newDelegate.signed.version;
- if (version != meta.version) {
- throw new error_1.BadVersionError(`Version ${version} of ${roleName} does not match snapshot version ${meta.version}`);
- }
- // Client workflow 5.6.5: check for a freeze attack
- if (newDelegate.signed.isExpired(this.referenceTime)) {
- throw new error_1.ExpiredMetadataError(`${roleName}.json is expired`);
- }
- this.trustedSet[roleName] = newDelegate;
- }
- // Verifies and loads data as trusted root metadata.
- // Note that an expired initial root is still considered valid.
- loadTrustedRoot(bytesBuffer) {
- const data = JSON.parse(bytesBuffer.toString('utf8'));
- const root = models_1.Metadata.fromJSON(models_1.MetadataKind.Root, data);
- if (root.signed.type != models_1.MetadataKind.Root) {
- throw new error_1.RepositoryError(`Expected 'root', got ${root.signed.type}`);
- }
- root.verifyDelegate(models_1.MetadataKind.Root, root);
- this.trustedSet['root'] = root;
- }
- checkFinalTimestamp() {
- // Timestamp MUST be loaded
- if (!this.timestamp) {
- throw new ReferenceError('No trusted timestamp metadata');
- }
- // Client workflow 5.4.4: check for freeze attack
- if (this.timestamp.signed.isExpired(this.referenceTime)) {
- throw new error_1.ExpiredMetadataError('Final timestamp.json is expired');
- }
- }
- checkFinalSnapsnot() {
- // Snapshot and timestamp MUST be loaded
- if (!this.snapshot) {
- throw new ReferenceError('No trusted snapshot metadata');
- }
- if (!this.timestamp) {
- throw new ReferenceError('No trusted timestamp metadata');
- }
- // Client workflow 5.5.6: check for freeze attack
- if (this.snapshot.signed.isExpired(this.referenceTime)) {
- throw new error_1.ExpiredMetadataError('snapshot.json is expired');
- }
- // Client workflow 5.5.4: check against timestamp role’s snapshot version
- const snapshotMeta = this.timestamp.signed.snapshotMeta;
- if (this.snapshot.signed.version !== snapshotMeta.version) {
- throw new error_1.BadVersionError("Snapshot version doesn't match timestamp");
- }
- }
- }
- exports.TrustedMetadataStore = TrustedMetadataStore;
|