Skip to content

Commit 0572c1d

Browse files
committedOct 24, 2019
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. PR-URL: #4 Credit: @isaacs Close: #4 Reviewed-by: @isaacs
1 parent 3084efd commit 0572c1d

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed
 

‎README.md

+40
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes.
1919
* [`parse`](#parse)
2020
* [`stringify`](#stringify)
2121
* [`Integrity#concat`](#integrity-concat)
22+
* [`Integrity#merge`](#integrity-merge)
2223
* [`Integrity#toString`](#integrity-to-string)
2324
* [`Integrity#toJSON`](#integrity-to-json)
2425
* [`Integrity#match`](#integrity-match)
@@ -184,6 +185,45 @@ const mobileIntegrity = ssri.fromData(fs.readFileSync('./index.mobile.js'))
184185
desktopIntegrity.concat(mobileIntegrity)
185186
```
186187

188+
#### <a name="integrity-merge"></a> `> Integrity#merge(otherIntegrity, [opts])`
189+
190+
Safely merges another IntegrityLike or integrity string into an `Integrity`
191+
object.
192+
193+
If the other integrity value has any algorithms in common with the current
194+
object, then the hash digests must match, or an error is thrown.
195+
196+
Any new hashes will be added to the current object's set.
197+
198+
This is useful when an integrity value may be upgraded with a stronger
199+
algorithm, you wish to prevent accidentally supressing integrity errors by
200+
overwriting the expected integrity value.
201+
202+
##### Example
203+
204+
```javascript
205+
const data = fs.readFileSync('data.txt')
206+
207+
// integrity.txt contains 'sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4='
208+
// because we were young, and didn't realize sha1 would not last
209+
const expectedIntegrity = ssri.parse(fs.readFileSync('integrity.txt', 'utf8'))
210+
const match = ssri.checkData(data, expectedIntegrity, {
211+
algorithms: ['sha512', 'sha1']
212+
})
213+
if (!match) {
214+
throw new Error('data corrupted or something!')
215+
}
216+
217+
// get a stronger algo!
218+
if (match && match.algorithm !== 'sha512') {
219+
const updatedIntegrity = ssri.fromData(data, { algorithms: ['sha512'] })
220+
expectedIntegrity.merge(updatedIntegrity)
221+
fs.writeFileSync('integrity.txt', expectedIntegrity.toString())
222+
// file now contains
223+
// 'sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4= sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg=='
224+
}
225+
```
226+
187227
#### <a name="integrity-to-string"></a> `> Integrity#toString([opts]) -> String`
188228

189229
Returns the string representation of an `Integrity` object. All hash entries

‎index.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ class IntegrityStream extends MiniPass {
4747
[_getOptions] () {
4848
const opts = this.opts
4949
// For verification
50-
this.sri = opts.integrity && parse(opts.integrity, opts)
50+
this.sri = opts.integrity ? parse(opts.integrity, opts) : null
5151
this.expectedSize = opts.size
52-
this.goodSri = this.sri ? Object.keys(this.sri).length || null : null
52+
this.goodSri = this.sri ? !!Object.keys(this.sri).length : false
5353
this.algorithm = this.goodSri ? this.sri.pickAlgorithm(opts) : null
5454
this.digests = this.goodSri ? this.sri[this.algorithm] : null
5555
this.optString = getOptString(opts.options)
@@ -198,6 +198,24 @@ class Integrity {
198198
return parse(this, { single: true }).hexDigest()
199199
}
200200

201+
// add additional hashes to an integrity value, but prevent
202+
// *changing* an existing integrity hash.
203+
merge (integrity, opts) {
204+
opts = SsriOpts(opts)
205+
const other = parse(integrity, opts)
206+
for (const algo in other) {
207+
if (this[algo]) {
208+
if (!this[algo].find(hash =>
209+
other[algo].find(otherhash =>
210+
hash.digest === otherhash.digest))) {
211+
throw new Error('hashes do not match, cannot update integrity')
212+
}
213+
} else {
214+
this[algo] = other[algo]
215+
}
216+
}
217+
}
218+
201219
match (integrity, opts) {
202220
opts = SsriOpts(opts)
203221
const other = parse(integrity, opts)

‎test/update.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const ssri = require('../')
2+
const t = require('tap')
3+
4+
const i = ssri.parse('sha1-foo')
5+
const o = ssri.parse('sha512-bar')
6+
i.merge(o)
7+
t.equal(i.toString(), 'sha1-foo sha512-bar', 'added second algo')
8+
t.throws(() => i.merge(ssri.parse('sha1-baz')), {
9+
message: 'hashes do not match, cannot update integrity'
10+
})
11+
i.merge(o)
12+
i.merge(ssri.parse('sha1-foo'))
13+
t.equal(i.toString(), 'sha1-foo sha512-bar', 'did not duplicate')

0 commit comments

Comments
 (0)
Please sign in to comment.