From 806e8c851c59cbc4d7d11dd46ee85b8fe4554fe4 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 24 Oct 2019 13:30:45 -0700 Subject: [PATCH 1/5] fix: Do not blow up if the opts object is mutated Pacote has a use case where the integrity value may not be known at the outset, but is later established, either via the dist.integrity in a packument, or by the x-local-hash header value when make-fetch-happen loads a response from the cache. In these cases, we have already started an integrity stream at the beginning of the request, and don't get the expected integrity until _after_ the integrity stream is created, resulting in a spurious EINTEGRITY error. This patch makes ssri responsive to (and resilient against) updates to the integrity and size options after the stream has started. --- index.js | 60 +++++++++++++++++++-------------- test/mutable-opts-resilience.js | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 test/mutable-opts-resilience.js diff --git a/index.js b/index.js index 028a498..dce61af 100644 --- a/index.js +++ b/index.js @@ -23,26 +23,40 @@ const SsriOpts = figgyPudding({ strict: { default: false } }) +const getOptString = options => !options || !options.length ? '' + : `?${options.join('?')}` + +const _onEnd = Symbol('_onEnd') +const _getOptions = Symbol('_getOptions') class IntegrityStream extends MiniPass { constructor (opts) { super() this.size = 0 this.opts = opts - // For verification - this.sri = opts.integrity && parse(opts.integrity, opts) - this.goodSri = this.sri && Object.keys(this.sri).length - this.algorithm = this.goodSri && this.sri.pickAlgorithm(opts) - this.digests = this.goodSri && this.sri[this.algorithm] - // Calculating stream + + // may be overridden later, but set now for class consistency + this[_getOptions]() + + // options used for calculating stream. can't be changed. this.algorithms = Array.from( new Set(opts.algorithms.concat(this.algorithm ? [this.algorithm] : [])) ) this.hashes = this.algorithms.map(crypto.createHash) - this.onEnd = this.onEnd.bind(this) + } + + [_getOptions] () { + const opts = this.opts + // For verification + this.sri = opts.integrity && parse(opts.integrity, opts) + this.expectedSize = opts.size + this.goodSri = this.sri ? Object.keys(this.sri).length || null : null + this.algorithm = this.goodSri ? this.sri.pickAlgorithm(opts) : null + this.digests = this.goodSri ? this.sri[this.algorithm] : null + this.optString = getOptString(opts.options) } emit (ev, data) { - if (ev === 'end') this.onEnd() + if (ev === 'end') this[_onEnd]() return super.emit(ev, data) } @@ -52,23 +66,23 @@ class IntegrityStream extends MiniPass { return super.write(data) } - onEnd () { - const optString = (this.opts.options && this.opts.options.length) - ? `?${this.opts.options.join('?')}` - : '' + [_onEnd] () { + if (!this.goodSri) { + this[_getOptions]() + } const newSri = parse(this.hashes.map((h, i) => { - return `${this.algorithms[i]}-${h.digest('base64')}${optString}` + return `${this.algorithms[i]}-${h.digest('base64')}${this.optString}` }).join(' '), this.opts) // Integrity verification mode const match = this.goodSri && newSri.match(this.sri, this.opts) - if (typeof this.opts.size === 'number' && this.size !== this.opts.size) { - const err = new Error(`stream size mismatch when checking ${this.sri}.\n Wanted: ${this.opts.size}\n Found: ${this.size}`) + if (typeof this.expectedSize === 'number' && this.size !== this.expectedSize) { + const err = new Error(`stream size mismatch when checking ${this.sri}.\n Wanted: ${this.expectedSize}\n Found: ${this.size}`) err.code = 'EBADSIZE' err.found = this.size - err.expected = this.opts.size + err.expected = this.expectedSize err.sri = this.sri this.emit('error', err) - } else if (this.opts.integrity && !match) { + } else if (this.sri && !match) { const err = new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${newSri}. (${this.size} bytes)`) err.code = 'EINTEGRITY' err.found = newSri @@ -260,9 +274,7 @@ function stringify (obj, opts) { module.exports.fromHex = fromHex function fromHex (hexDigest, algorithm, opts) { opts = SsriOpts(opts) - const optString = opts.options && opts.options.length - ? `?${opts.options.join('?')}` - : '' + const optString = getOptString(opts.options) return parse( `${algorithm}-${ Buffer.from(hexDigest, 'hex').toString('base64') @@ -274,9 +286,7 @@ module.exports.fromData = fromData function fromData (data, opts) { opts = SsriOpts(opts) const algorithms = opts.algorithms - const optString = opts.options && opts.options.length - ? `?${opts.options.join('?')}` - : '' + const optString = getOptString(opts.options) return algorithms.reduce((acc, algo) => { const digest = crypto.createHash(algo).update(data).digest('base64') const hash = new Hash( @@ -375,9 +385,7 @@ module.exports.create = createIntegrity function createIntegrity (opts) { opts = SsriOpts(opts) const algorithms = opts.algorithms - const optString = opts.options.length - ? `?${opts.options.join('?')}` - : '' + const optString = getOptString(opts.options) const hashes = algorithms.map(crypto.createHash) diff --git a/test/mutable-opts-resilience.js b/test/mutable-opts-resilience.js new file mode 100644 index 0000000..d695dcd --- /dev/null +++ b/test/mutable-opts-resilience.js @@ -0,0 +1,60 @@ +const ssri = require('../') +const t = require('tap') + +const data = 'hello world' +const expectIntegrity = ssri.fromData(data, { algorithms: ['sha512'] }) +const expectSize = data.length + +t.test('support adding bad integrity later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.integrity = ssri.parse('sha512-deepbeets') + return t.rejects(stream.end(data).collect(), { + code: 'EINTEGRITY' + }) +}) + +t.test('support adding bad integrity string later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.integrity = 'sha512-deepbeets' + return t.rejects(stream.end(data).collect(), { + code: 'EINTEGRITY' + }) +}) + +t.test('support adding bad size later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.size = 2 + return t.rejects(stream.end(data).collect(), { + code: 'EBADSIZE' + }) +}) + +t.test('support adding good integrity later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.integrity = expectIntegrity + return stream.end(data).on('verified', match => { + t.same(match, expectIntegrity.sha512[0]) + }).collect() +}) + +t.test('support adding good integrity string later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.integrity = String(expectIntegrity) + return stream.end(data).on('verified', match => { + t.same(match, expectIntegrity.sha512[0]) + }).collect() +}) + +t.test('support adding good size later', t => { + const opts = {} + const stream = ssri.integrityStream(opts) + opts.size = expectSize + return stream.end(data).on('size', size => { + t.same(size, expectSize) + }).collect() +}) From 6545b4b5126ec7458c442838dd01715fa1c21efc Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 24 Oct 2019 14:36:58 -0700 Subject: [PATCH 2/5] deps: minipass@3.1.1 --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3274fa..b148c3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3286,9 +3286,9 @@ } }, "minipass": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.0.0.tgz", - "integrity": "sha512-FKNU4XrAPDX0+ynwns7njVu4RolyG1mUKSlT6n6GwGXLtYSYh2Znc0S83Rl6zEr1zgFfXvAzIBabnmItm+n19g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", "requires": { "yallist": "^4.0.0" }, diff --git a/package.json b/package.json index fb219bb..7bb5f63 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "license": "ISC", "dependencies": { "figgy-pudding": "^3.5.1", - "minipass": "^3.0.0" + "minipass": "^3.1.1" }, "devDependencies": { "standard": "^14.3.0", From 3084efd8782eec2a925de93b55e5238eba8b5ee7 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 24 Oct 2019 14:37:20 -0700 Subject: [PATCH 3/5] deps: tap@14.8.2 --- package-lock.json | 425 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 217 insertions(+), 210 deletions(-) diff --git a/package-lock.json b/package-lock.json index b148c3f..252036b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ } }, "@babel/generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", - "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", "dev": true, "requires": { - "@babel/types": "^7.6.0", + "@babel/types": "^7.6.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -111,15 +111,15 @@ } }, "@babel/parser": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", - "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", "dev": true }, "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", + "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", "dev": true, "requires": { "regenerator-runtime": "^0.13.2" @@ -137,17 +137,17 @@ } }, "@babel/traverse": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", - "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.2", + "@babel/generator": "^7.6.3", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.2", - "@babel/types": "^7.6.0", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -171,9 +171,9 @@ } }, "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -237,9 +237,9 @@ } }, "anymatch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.0.tgz", - "integrity": "sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -326,9 +326,9 @@ "dev": true }, "async-hook-domain": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-1.1.1.tgz", - "integrity": "sha512-nHfgkoCbzXqCPEFshW5/LROfUqKcZdOW4mfvR66V7bdSuvvvt+s2CuHVBmJxHfOVFhCJ29xlRqEJxyNQKQsqBg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-1.1.3.tgz", + "integrity": "sha512-ZovMxSbADV3+biB7oR1GL5lGyptI24alp0LWHlmz1OFc5oL47pz3EiIF6nXOkDW7yLqih4NtsiYduzdDW0i+Wg==", "dev": true, "requires": { "source-map-support": "^0.5.11" @@ -464,12 +464,6 @@ "quick-lru": "^1.0.0" } }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -494,19 +488,30 @@ "dev": true }, "chokidar": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.1.1.tgz", - "integrity": "sha512-df4o16uZmMHzVQwECZRHwfguOt5ixpuQVaZHjYMvYisgKhE+JXwcj/Tcr3+3bu/XeOJQ9ycYmzu7Mv8XrGxJDQ==", - "dev": true, - "requires": { - "anymatch": "^3.1.0", - "braces": "^3.0.2", - "fsevents": "^2.0.6", - "glob-parent": "^5.0.0", - "is-binary-path": "^2.1.0", - "is-glob": "^4.0.1", - "normalize-path": "^3.0.0", - "readdirp": "^3.1.1" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.2.2.tgz", + "integrity": "sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } } }, "cli-cursor": { @@ -964,9 +969,9 @@ "dev": true }, "coveralls": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.6.tgz", - "integrity": "sha512-Pgh4v3gCI4T/9VijVrm8Ym5v0OgjvGLKj3zTUwkvsCiwqae/p6VLzpsFNjQS2i6ewV7ef+DjFJ5TSKxYt/mCrA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.7.tgz", + "integrity": "sha512-mUuH2MFOYB2oBaA4D4Ykqi9LaEYpMMlsiOMJOrv358yAjP6enPIk55fod2fNJ8AvwoYXStWQls37rA+s5e7boA==", "dev": true, "requires": { "growl": "~> 1.10.0", @@ -1913,18 +1918,18 @@ "dev": true }, "flow-parser": { - "version": "0.108.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.108.0.tgz", - "integrity": "sha512-Ug8VuwlyDIZq5Xgrf+T7XLpKydhqYyNd8lmFtf7PZbu90T5LL+FeHjWzxyrBn35RCCZMw7pXrjCrHOSs+2zXyg==", + "version": "0.110.1", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.110.1.tgz", + "integrity": "sha512-k0QoogCKfE7cr3WSgHYTt8QhZxxoi0tun4uVMSqx68L0E9ao1ltxlhJAKibTGlwR1V6lnGfKXU1zU9JB1q2ZRg==", "dev": true }, "flow-remove-types": { - "version": "2.108.0", - "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.108.0.tgz", - "integrity": "sha512-cbYe0AijNVlc6V1Xx99fNqQtRMJ+xbQwG5rQtcheFQiBPO6b6VwvhMs/OelJvpO+YUTz49IhFKzoZGj5xm74PA==", + "version": "2.110.1", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.110.1.tgz", + "integrity": "sha512-xOhoJcgC5z55RDdKyMgoZFuXL+jGacGWivR8xhEjpzwLlIzqouVIY67jWqmGPS92qCEtKx7wSaIBw6CZ9I1csQ==", "dev": true, "requires": { - "flow-parser": "^0.108.0", + "flow-parser": "^0.110.1", "pirates": "^3.0.2", "vlq": "^0.2.1" } @@ -1978,9 +1983,9 @@ "dev": true }, "fsevents": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", - "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", + "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", "dev": true, "optional": true }, @@ -3448,9 +3453,9 @@ "dev": true }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3770,9 +3775,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true } } @@ -4141,9 +4146,9 @@ } }, "readdirp": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.2.tgz", - "integrity": "sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { "picomatch": "^2.0.4" @@ -4272,9 +4277,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4901,15 +4906,14 @@ } }, "tap": { - "version": "14.6.9", - "resolved": "https://registry.npmjs.org/tap/-/tap-14.6.9.tgz", - "integrity": "sha512-impxvxJo49knWdQNctdY+hAyQGbLjGR9foNkX4B9NnotgV5tfmJNqLrFu+YluHSNhIjfqF6Ea+Pj08xPAHxfSQ==", + "version": "14.8.2", + "resolved": "https://registry.npmjs.org/tap/-/tap-14.8.2.tgz", + "integrity": "sha512-md0cw6fH1SbT9HBdzHJgMsVqp7daZ6MMAwuVIHNTtQusFMaLOEAQEbqxPc4rnjNezgKy7wKS0aEc17gtwrVtUQ==", "dev": true, "requires": { - "async-hook-domain": "^1.1.1", + "async-hook-domain": "^1.1.2", "bind-obj-methods": "^2.0.0", "browser-process-hrtime": "^1.0.0", - "capture-stack-trace": "^1.0.0", "chokidar": "^3.0.2", "color-support": "^1.1.0", "coveralls": "^3.0.6", @@ -4935,24 +4939,24 @@ "react": "^16.9.0", "rimraf": "^2.7.1", "signal-exit": "^3.0.0", - "source-map-support": "^0.5.13", + "source-map-support": "github:tapjs/node-source-map-support#node-header-length-change", "stack-utils": "^1.0.2", "tap-mocha-reporter": "^5.0.0", - "tap-parser": "^10.0.0", + "tap-parser": "^10.0.1", "tap-yaml": "^1.0.0", "tcompare": "^2.3.0", - "treport": "^0.4.1", + "treport": "^0.4.2", "trivial-deferred": "^1.0.1", "ts-node": "^8.3.0", "typescript": "^3.6.3", - "which": "^1.3.1", + "which": "^2.0.1", "write-file-atomic": "^3.0.0", "yaml": "^1.6.0", "yapool": "^1.0.0" }, "dependencies": { "@babel/runtime": { - "version": "7.4.5", + "version": "7.6.3", "bundled": true, "dev": true, "requires": { @@ -4960,19 +4964,19 @@ }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", + "version": "0.13.3", "bundled": true, "dev": true } } }, "@types/prop-types": { - "version": "15.7.1", + "version": "15.7.3", "bundled": true, "dev": true }, "@types/react": { - "version": "16.8.22", + "version": "16.9.5", "bundled": true, "dev": true, "requires": { @@ -4981,9 +4985,12 @@ } }, "ansi-escapes": { - "version": "3.2.0", + "version": "4.2.1", "bundled": true, - "dev": true + "dev": true, + "requires": { + "type-fest": "^0.5.2" + } }, "ansi-regex": { "version": "2.1.1", @@ -5011,7 +5018,7 @@ "dev": true }, "auto-bind": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "dev": true, "requires": { @@ -5243,6 +5250,11 @@ "concat-map": "0.0.1" } }, + "buffer-from": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, "caller-callsite": { "version": "2.0.0", "bundled": true, @@ -5331,15 +5343,22 @@ "dev": true, "requires": { "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + } } }, "core-js": { - "version": "2.6.5", + "version": "2.6.10", "bundled": true, "dev": true }, "csstype": { - "version": "2.6.5", + "version": "2.6.7", "bundled": true, "dev": true }, @@ -5375,7 +5394,7 @@ "dev": true }, "esutils": { - "version": "2.0.2", + "version": "2.0.3", "bundled": true, "dev": true }, @@ -5385,9 +5404,9 @@ "dev": true }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5439,11 +5458,12 @@ } }, "ink": { - "version": "2.3.0", + "version": "2.5.0", "bundled": true, "dev": true, "requires": { "@types/react": "^16.8.6", + "ansi-escapes": "^4.2.1", "arrify": "^1.0.1", "auto-bind": "^2.0.0", "chalk": "^2.4.1", @@ -5453,8 +5473,8 @@ "lodash.throttle": "^4.1.1", "log-update": "^3.0.0", "prop-types": "^15.6.2", - "react-reconciler": "^0.20.0", - "scheduler": "^0.13.2", + "react-reconciler": "^0.21.0", + "scheduler": "^0.15.0", "signal-exit": "^3.0.2", "slice-ansi": "^1.0.0", "string-length": "^2.0.0", @@ -5463,11 +5483,6 @@ "yoga-layout-prebuilt": "^1.9.3" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, "ansi-styles": { "version": "3.2.1", "bundled": true, @@ -5486,24 +5501,6 @@ "supports-color": "^5.3.0" } }, - "string-width": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, "supports-color": { "version": "5.5.0", "bundled": true, @@ -5511,16 +5508,6 @@ "requires": { "has-flag": "^3.0.0" } - }, - "wrap-ansi": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } } } }, @@ -5569,7 +5556,7 @@ "dev": true }, "lodash": { - "version": "4.17.14", + "version": "4.17.15", "bundled": true, "dev": true }, @@ -5579,7 +5566,7 @@ "dev": true }, "log-update": { - "version": "3.2.0", + "version": "3.3.0", "bundled": true, "dev": true, "requires": { @@ -5588,46 +5575,10 @@ "wrap-ansi": "^5.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", + "ansi-escapes": { + "version": "3.2.0", "bundled": true, "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "bundled": true, - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "string-width": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } } } }, @@ -5639,6 +5590,11 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -5648,7 +5604,7 @@ } }, "minipass": { - "version": "3.0.0", + "version": "3.0.1", "bundled": true, "dev": true, "requires": { @@ -5698,13 +5654,6 @@ "dev": true, "requires": { "mimic-fn": "^1.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "1.2.0", - "bundled": true, - "dev": true - } } }, "os-homedir": { @@ -5743,7 +5692,7 @@ "dev": true }, "react": { - "version": "16.9.0", + "version": "16.10.2", "bundled": true, "dev": true, "requires": { @@ -5753,19 +5702,19 @@ } }, "react-is": { - "version": "16.8.6", + "version": "16.10.2", "bundled": true, "dev": true }, "react-reconciler": { - "version": "0.20.4", + "version": "0.21.0", "bundled": true, "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "scheduler": "^0.15.0" } }, "redeyed": { @@ -5803,13 +5752,8 @@ "signal-exit": "^3.0.2" } }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, "scheduler": { - "version": "0.13.6", + "version": "0.15.0", "bundled": true, "dev": true, "requires": { @@ -5835,6 +5779,21 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "from": "github:tapjs/node-source-map-support#node-header-length-change", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "string-length": { "version": "2.0.0", "bundled": true, @@ -5897,7 +5856,7 @@ "dev": true }, "tap-parser": { - "version": "10.0.0", + "version": "10.0.1", "bundled": true, "dev": true, "requires": { @@ -5920,7 +5879,7 @@ "dev": true }, "treport": { - "version": "0.4.1", + "version": "0.4.2", "bundled": true, "dev": true, "requires": { @@ -5931,7 +5890,7 @@ "ms": "^2.1.1", "react": "^16.8.6", "string-length": "^2.0.0", - "tap-parser": "^10.0.0", + "tap-parser": "^10.0.1", "unicode-length": "^2.0.1" }, "dependencies": { @@ -5982,10 +5941,15 @@ "bundled": true, "dev": true }, + "type-fest": { + "version": "0.5.2", + "bundled": true, + "dev": true + }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -5999,12 +5963,55 @@ "string-width": "^2.1.1" } }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "yaml": { - "version": "1.6.0", + "version": "1.7.1", "bundled": true, "dev": true, "requires": { - "@babel/runtime": "^7.4.5" + "@babel/runtime": "^7.5.5" } }, "yoga-layout-prebuilt": { @@ -6040,9 +6047,9 @@ } }, "tap-parser": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-10.0.0.tgz", - "integrity": "sha512-kzeUPvVoSyovAlYvN8m8eajjh1LAJpGn8C3hVIbq7TDW6FDzuH09egdJZMczG4bDdc7+uQSqOlin+XKRLtHbeA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-10.0.1.tgz", + "integrity": "sha512-qdT15H0DoJIi7zOqVXDn9X0gSM68JjNy1w3VemwTJlDnETjbi6SutnqmBfjDJAwkFS79NJ97gZKqie00ZCGmzg==", "dev": true, "requires": { "events-to-array": "^1.0.1", @@ -6087,9 +6094,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6330,9 +6337,9 @@ } }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "dev": true }, "uglify-js": { @@ -6490,9 +6497,9 @@ } }, "write-file-atomic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.0.tgz", - "integrity": "sha512-EIgkf60l2oWsffja2Sf2AL384dx328c0B+cIYPTQq5q2rOYuDV00/iPFBOUiDKKwKMOhkymH8AidPaRvzfxY+Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", "dev": true, "requires": { "imurmurhash": "^0.1.4", @@ -6520,12 +6527,12 @@ "dev": true }, "yaml": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.0.tgz", - "integrity": "sha512-BEXCJKbbJmDzjuG4At0R4nHJKlP81hxoLQqUCaxzqZ8HHgjAlOXbiOHVCQ4YuQqO/rLR8HoQ6kxGkYJ3tlKIsg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", "dev": true, "requires": { - "@babel/runtime": "^7.5.5" + "@babel/runtime": "^7.6.3" } }, "yapool": { diff --git a/package.json b/package.json index 7bb5f63..6ca7657 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "devDependencies": { "standard": "^14.3.0", "standard-version": "^7.0.0", - "tap": "^14.6.9", + "tap": "^14.8.2", "weallbehave": "^1.2.0", "weallcontribute": "^1.0.8" }, From 065a9247393adb2de4fc209d183508261f8bc52d Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 24 Oct 2019 15:41:43 -0700 Subject: [PATCH 4/5] feat: Add Integrity#merge method This is going to be used in Pacote to upgrade the Fetcher.integrity value with the data that comes out of cacache, but in such a way that we don't accidentally suppress integrity errors. --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ index.js | 18 ++++++++++++++++++ test/update.js | 13 +++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 test/update.js diff --git a/README.md b/README.md index e0e2bc1..0cd41be 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes. * [`parse`](#parse) * [`stringify`](#stringify) * [`Integrity#concat`](#integrity-concat) + * [`Integrity#merge`](#integrity-merge) * [`Integrity#toString`](#integrity-to-string) * [`Integrity#toJSON`](#integrity-to-json) * [`Integrity#match`](#integrity-match) @@ -184,6 +185,45 @@ const mobileIntegrity = ssri.fromData(fs.readFileSync('./index.mobile.js')) desktopIntegrity.concat(mobileIntegrity) ``` +#### `> Integrity#merge(otherIntegrity, [opts])` + +Safely merges another IntegrityLike or integrity string into an `Integrity` +object. + +If the other integrity value has any algorithms in common with the current +object, then the hash digests must match, or an error is thrown. + +Any new hashes will be added to the current object's set. + +This is useful when an integrity value may be upgraded with a stronger +algorithm, you wish to prevent accidentally supressing integrity errors by +overwriting the expected integrity value. + +##### Example + +```javascript +const data = fs.readFileSync('data.txt') + +// integrity.txt contains 'sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4=' +// because we were young, and didn't realize sha1 would not last +const expectedIntegrity = ssri.parse(fs.readFileSync('integrity.txt', 'utf8')) +const match = ssri.checkData(data, expectedIntegrity, { + algorithms: ['sha512', 'sha1'] +}) +if (!match) { + throw new Error('data corrupted or something!') +} + +// get a stronger algo! +if (match && match.algorithm !== 'sha512') { + const updatedIntegrity = ssri.fromData(data, { algorithms: ['sha512'] }) + expectedIntegrity.merge(updatedIntegrity) + fs.writeFileSync('integrity.txt', expectedIntegrity.toString()) + // file now contains + // 'sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4= sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==' +} +``` + #### `> Integrity#toString([opts]) -> String` Returns the string representation of an `Integrity` object. All hash entries diff --git a/index.js b/index.js index dce61af..ce74753 100644 --- a/index.js +++ b/index.js @@ -198,6 +198,24 @@ class Integrity { return parse(this, { single: true }).hexDigest() } + // add additional hashes to an integrity value, but prevent + // *changing* an existing integrity hash. + merge (integrity, opts) { + opts = SsriOpts(opts) + const other = parse(integrity, opts) + for (const algo in other) { + if (this[algo]) { + if (!this[algo].find(hash => + other[algo].find(otherhash => + hash.digest === otherhash.digest))) { + throw new Error('hashes do not match, cannot update integrity') + } + } else { + this[algo] = other[algo] + } + } + } + match (integrity, opts) { opts = SsriOpts(opts) const other = parse(integrity, opts) diff --git a/test/update.js b/test/update.js new file mode 100644 index 0000000..aa16de5 --- /dev/null +++ b/test/update.js @@ -0,0 +1,13 @@ +const ssri = require('../') +const t = require('tap') + +const i = ssri.parse('sha1-foo') +const o = ssri.parse('sha512-bar') +i.merge(o) +t.equal(i.toString(), 'sha1-foo sha512-bar', 'added second algo') +t.throws(() => i.merge(ssri.parse('sha1-baz')), { + message: 'hashes do not match, cannot update integrity' +}) +i.merge(o) +i.merge(ssri.parse('sha1-foo')) +t.equal(i.toString(), 'sha1-foo sha512-bar', 'did not duplicate') From 1065074f01884b432a32379b4d7716f451f7d507 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 24 Oct 2019 16:41:42 -0700 Subject: [PATCH 5/5] chore: minor code cleanup nits Via @mikemimik --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index ce74753..daee85d 100644 --- a/index.js +++ b/index.js @@ -47,9 +47,9 @@ class IntegrityStream extends MiniPass { [_getOptions] () { const opts = this.opts // For verification - this.sri = opts.integrity && parse(opts.integrity, opts) + this.sri = opts.integrity ? parse(opts.integrity, opts) : null this.expectedSize = opts.size - this.goodSri = this.sri ? Object.keys(this.sri).length || null : null + this.goodSri = this.sri ? !!Object.keys(this.sri).length : false this.algorithm = this.goodSri ? this.sri.pickAlgorithm(opts) : null this.digests = this.goodSri ? this.sri[this.algorithm] : null this.optString = getOptString(opts.options)