store.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.TrustedMetadataStore = void 0;
  4. const models_1 = require("@tufjs/models");
  5. const error_1 = require("./error");
  6. class 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. }
  208. exports.TrustedMetadataStore = TrustedMetadataStore;