• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.TrustedMetadataStore = void 0;
4const models_1 = require("@tufjs/models");
5const error_1 = require("./error");
6class TrustedMetadataStore {
7    constructor(rootData) {
8        this.trustedSet = {};
9        // Client workflow 5.1: record fixed update start time
10        this.referenceTime = new Date();
11        // Client workflow 5.2: load trusted root metadata
12        this.loadTrustedRoot(rootData);
13    }
14    get root() {
15        if (!this.trustedSet.root) {
16            throw new ReferenceError('No trusted root metadata');
17        }
18        return this.trustedSet.root;
19    }
20    get timestamp() {
21        return this.trustedSet.timestamp;
22    }
23    get snapshot() {
24        return this.trustedSet.snapshot;
25    }
26    get targets() {
27        return this.trustedSet.targets;
28    }
29    getRole(name) {
30        return this.trustedSet[name];
31    }
32    updateRoot(bytesBuffer) {
33        const data = JSON.parse(bytesBuffer.toString('utf8'));
34        const newRoot = models_1.Metadata.fromJSON(models_1.MetadataKind.Root, data);
35        if (newRoot.signed.type != models_1.MetadataKind.Root) {
36            throw new error_1.RepositoryError(`Expected 'root', got ${newRoot.signed.type}`);
37        }
38        // Client workflow 5.4: check for arbitrary software attack
39        this.root.verifyDelegate(models_1.MetadataKind.Root, newRoot);
40        // Client workflow 5.5: check for rollback attack
41        if (newRoot.signed.version != this.root.signed.version + 1) {
42            throw new error_1.BadVersionError(`Expected version ${this.root.signed.version + 1}, got ${newRoot.signed.version}`);
43        }
44        // Check that new root is signed by self
45        newRoot.verifyDelegate(models_1.MetadataKind.Root, newRoot);
46        // Client workflow 5.7: set new root as trusted root
47        this.trustedSet.root = newRoot;
48        return newRoot;
49    }
50    updateTimestamp(bytesBuffer) {
51        if (this.snapshot) {
52            throw new error_1.RuntimeError('Cannot update timestamp after snapshot');
53        }
54        if (this.root.signed.isExpired(this.referenceTime)) {
55            throw new error_1.ExpiredMetadataError('Final root.json is expired');
56        }
57        const data = JSON.parse(bytesBuffer.toString('utf8'));
58        const newTimestamp = models_1.Metadata.fromJSON(models_1.MetadataKind.Timestamp, data);
59        if (newTimestamp.signed.type != models_1.MetadataKind.Timestamp) {
60            throw new error_1.RepositoryError(`Expected 'timestamp', got ${newTimestamp.signed.type}`);
61        }
62        // Client workflow 5.4.2: check for arbitrary software attack
63        this.root.verifyDelegate(models_1.MetadataKind.Timestamp, newTimestamp);
64        if (this.timestamp) {
65            // Prevent rolling back timestamp version
66            // Client workflow 5.4.3.1: check for rollback attack
67            if (newTimestamp.signed.version < this.timestamp.signed.version) {
68                throw new error_1.BadVersionError(`New timestamp version ${newTimestamp.signed.version} is less than current version ${this.timestamp.signed.version}`);
69            }
70            //  Keep using old timestamp if versions are equal.
71            if (newTimestamp.signed.version === this.timestamp.signed.version) {
72                throw new error_1.EqualVersionError(`New timestamp version ${newTimestamp.signed.version} is equal to current version ${this.timestamp.signed.version}`);
73            }
74            // Prevent rolling back snapshot version
75            // Client workflow 5.4.3.2: check for rollback attack
76            const snapshotMeta = this.timestamp.signed.snapshotMeta;
77            const newSnapshotMeta = newTimestamp.signed.snapshotMeta;
78            if (newSnapshotMeta.version < snapshotMeta.version) {
79                throw new error_1.BadVersionError(`New snapshot version ${newSnapshotMeta.version} is less than current version ${snapshotMeta.version}`);
80            }
81        }
82        // expiry not checked to allow old timestamp to be used for rollback
83        // protection of new timestamp: expiry is checked in update_snapshot
84        this.trustedSet.timestamp = newTimestamp;
85        // Client workflow 5.4.4: check for freeze attack
86        this.checkFinalTimestamp();
87        return newTimestamp;
88    }
89    updateSnapshot(bytesBuffer, trusted = false) {
90        if (!this.timestamp) {
91            throw new error_1.RuntimeError('Cannot update snapshot before timestamp');
92        }
93        if (this.targets) {
94            throw new error_1.RuntimeError('Cannot update snapshot after targets');
95        }
96        // Snapshot cannot be loaded if final timestamp is expired
97        this.checkFinalTimestamp();
98        const snapshotMeta = this.timestamp.signed.snapshotMeta;
99        // Verify non-trusted data against the hashes in timestamp, if any.
100        // Trusted snapshot data has already been verified once.
101        // Client workflow 5.5.2: check against timestamp role's snaphsot hash
102        if (!trusted) {
103            snapshotMeta.verify(bytesBuffer);
104        }
105        const data = JSON.parse(bytesBuffer.toString('utf8'));
106        const newSnapshot = models_1.Metadata.fromJSON(models_1.MetadataKind.Snapshot, data);
107        if (newSnapshot.signed.type != models_1.MetadataKind.Snapshot) {
108            throw new error_1.RepositoryError(`Expected 'snapshot', got ${newSnapshot.signed.type}`);
109        }
110        // Client workflow 5.5.3: check for arbitrary software attack
111        this.root.verifyDelegate(models_1.MetadataKind.Snapshot, newSnapshot);
112        // version check against meta version (5.5.4) is deferred to allow old
113        // snapshot to be used in rollback protection
114        // Client workflow 5.5.5: check for rollback attack
115        if (this.snapshot) {
116            Object.entries(this.snapshot.signed.meta).forEach(([fileName, fileInfo]) => {
117                const newFileInfo = newSnapshot.signed.meta[fileName];
118                if (!newFileInfo) {
119                    throw new error_1.RepositoryError(`Missing file ${fileName} in new snapshot`);
120                }
121                if (newFileInfo.version < fileInfo.version) {
122                    throw new error_1.BadVersionError(`New version ${newFileInfo.version} of ${fileName} is less than current version ${fileInfo.version}`);
123                }
124            });
125        }
126        this.trustedSet.snapshot = newSnapshot;
127        // snapshot is loaded, but we raise if it's not valid _final_ snapshot
128        // Client workflow 5.5.4 & 5.5.6
129        this.checkFinalSnapsnot();
130        return newSnapshot;
131    }
132    updateDelegatedTargets(bytesBuffer, roleName, delegatorName) {
133        if (!this.snapshot) {
134            throw new error_1.RuntimeError('Cannot update delegated targets before snapshot');
135        }
136        // Targets cannot be loaded if final snapshot is expired or its version
137        // does not match meta version in timestamp.
138        this.checkFinalSnapsnot();
139        const delegator = this.trustedSet[delegatorName];
140        if (!delegator) {
141            throw new error_1.RuntimeError(`No trusted ${delegatorName} metadata`);
142        }
143        // Extract metadata for the delegated role from snapshot
144        const meta = this.snapshot.signed.meta?.[`${roleName}.json`];
145        if (!meta) {
146            throw new error_1.RepositoryError(`Missing ${roleName}.json in snapshot`);
147        }
148        // Client workflow 5.6.2: check against snapshot role's targets hash
149        meta.verify(bytesBuffer);
150        const data = JSON.parse(bytesBuffer.toString('utf8'));
151        const newDelegate = models_1.Metadata.fromJSON(models_1.MetadataKind.Targets, data);
152        if (newDelegate.signed.type != models_1.MetadataKind.Targets) {
153            throw new error_1.RepositoryError(`Expected 'targets', got ${newDelegate.signed.type}`);
154        }
155        // Client workflow 5.6.3: check for arbitrary software attack
156        delegator.verifyDelegate(roleName, newDelegate);
157        // Client workflow 5.6.4: Check against snapshot role’s targets version
158        const version = newDelegate.signed.version;
159        if (version != meta.version) {
160            throw new error_1.BadVersionError(`Version ${version} of ${roleName} does not match snapshot version ${meta.version}`);
161        }
162        // Client workflow 5.6.5: check for a freeze attack
163        if (newDelegate.signed.isExpired(this.referenceTime)) {
164            throw new error_1.ExpiredMetadataError(`${roleName}.json is expired`);
165        }
166        this.trustedSet[roleName] = newDelegate;
167    }
168    // Verifies and loads data as trusted root metadata.
169    // Note that an expired initial root is still considered valid.
170    loadTrustedRoot(bytesBuffer) {
171        const data = JSON.parse(bytesBuffer.toString('utf8'));
172        const root = models_1.Metadata.fromJSON(models_1.MetadataKind.Root, data);
173        if (root.signed.type != models_1.MetadataKind.Root) {
174            throw new error_1.RepositoryError(`Expected 'root', got ${root.signed.type}`);
175        }
176        root.verifyDelegate(models_1.MetadataKind.Root, root);
177        this.trustedSet['root'] = root;
178    }
179    checkFinalTimestamp() {
180        // Timestamp MUST be loaded
181        if (!this.timestamp) {
182            throw new ReferenceError('No trusted timestamp metadata');
183        }
184        // Client workflow 5.4.4: check for freeze attack
185        if (this.timestamp.signed.isExpired(this.referenceTime)) {
186            throw new error_1.ExpiredMetadataError('Final timestamp.json is expired');
187        }
188    }
189    checkFinalSnapsnot() {
190        // Snapshot and timestamp MUST be loaded
191        if (!this.snapshot) {
192            throw new ReferenceError('No trusted snapshot metadata');
193        }
194        if (!this.timestamp) {
195            throw new ReferenceError('No trusted timestamp metadata');
196        }
197        // Client workflow 5.5.6: check for freeze attack
198        if (this.snapshot.signed.isExpired(this.referenceTime)) {
199            throw new error_1.ExpiredMetadataError('snapshot.json is expired');
200        }
201        // Client workflow 5.5.4: check against timestamp role’s snapshot version
202        const snapshotMeta = this.timestamp.signed.snapshotMeta;
203        if (this.snapshot.signed.version !== snapshotMeta.version) {
204            throw new error_1.BadVersionError("Snapshot version doesn't match timestamp");
205        }
206    }
207}
208exports.TrustedMetadataStore = TrustedMetadataStore;
209