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