UNPKG

20.4 kBMarkdownView Raw
1# ssri [![npm version](https://img.shields.io/npm/v/ssri.svg)](https://npm.im/ssri) [![license](https://img.shields.io/npm/l/ssri.svg)](https://npm.im/ssri) [![Travis](https://img.shields.io/travis/npm/ssri.svg)](https://travis-ci.org/npm/ssri) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/npm/ssri?svg=true)](https://ci.appveyor.com/project/npm/ssri) [![Coverage Status](https://coveralls.io/repos/github/npm/ssri/badge.svg?branch=latest)](https://coveralls.io/github/npm/ssri?branch=latest)
2
3[`ssri`](https://github.com/npm/ssri), short for Standard Subresource
4Integrity, is a Node.js utility for parsing, manipulating, serializing,
5generating, and verifying [Subresource
6Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes.
7
8## Install
9
10`$ npm install --save ssri`
11
12## Table of Contents
13
14* [Example](#example)
15* [Features](#features)
16* [Contributing](#contributing)
17* [API](#api)
18 * Parsing & Serializing
19 * [`parse`](#parse)
20 * [`stringify`](#stringify)
21 * [`Integrity#concat`](#integrity-concat)
22 * [`Integrity#merge`](#integrity-merge)
23 * [`Integrity#toString`](#integrity-to-string)
24 * [`Integrity#toJSON`](#integrity-to-json)
25 * [`Integrity#match`](#integrity-match)
26 * [`Integrity#pickAlgorithm`](#integrity-pick-algorithm)
27 * [`Integrity#hexDigest`](#integrity-hex-digest)
28 * Integrity Generation
29 * [`fromHex`](#from-hex)
30 * [`fromData`](#from-data)
31 * [`fromStream`](#from-stream)
32 * [`create`](#create)
33 * Integrity Verification
34 * [`checkData`](#check-data)
35 * [`checkStream`](#check-stream)
36 * [`integrityStream`](#integrity-stream)
37
38### Example
39
40```javascript
41const ssri = require('ssri')
42
43const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
44
45// Parsing and serializing
46const parsed = ssri.parse(integrity)
47ssri.stringify(parsed) // === integrity (works on non-Integrity objects)
48parsed.toString() // === integrity
49
50// Async stream functions
51ssri.checkStream(fs.createReadStream('./my-file'), integrity).then(...)
52ssri.fromStream(fs.createReadStream('./my-file')).then(sri => {
53 sri.toString() === integrity
54})
55fs.createReadStream('./my-file').pipe(ssri.createCheckerStream(sri))
56
57// Sync data functions
58ssri.fromData(fs.readFileSync('./my-file')) // === parsed
59ssri.checkData(fs.readFileSync('./my-file'), integrity) // => 'sha512'
60```
61
62### Features
63
64* Parses and stringifies SRI strings.
65* Generates SRI strings from raw data or Streams.
66* Strict standard compliance.
67* `?foo` metadata option support.
68* Multiple entries for the same algorithm.
69* Object-based integrity hash manipulation.
70* Small footprint: no dependencies, concise implementation.
71* Full test coverage.
72* Customizable algorithm picker.
73
74### Contributing
75
76The ssri team enthusiastically welcomes contributions and project participation!
77There's a bunch of things you can do if you want to contribute! The [Contributor
78Guide](CONTRIBUTING.md) has all the information you need for everything from
79reporting bugs to contributing entire new features. Please don't hesitate to
80jump in if you'd like to, or even ask us questions if something isn't clear.
81
82### API
83
84#### <a name="parse"></a> `> ssri.parse(sri, [opts]) -> Integrity`
85
86Parses `sri` into an `Integrity` data structure. `sri` can be an integrity
87string, an `Hash`-like with `digest` and `algorithm` fields and an optional
88`options` field, or an `Integrity`-like object. The resulting object will be an
89`Integrity` instance that has this shape:
90
91```javascript
92{
93 'sha1': [{algorithm: 'sha1', digest: 'deadbeef', options: []}],
94 'sha512': [
95 {algorithm: 'sha512', digest: 'c0ffee', options: []},
96 {algorithm: 'sha512', digest: 'bad1dea', options: ['foo']}
97 ],
98}
99```
100
101If `opts.single` is truthy, a single `Hash` object will be returned. That is, a
102single object that looks like `{algorithm, digest, options}`, as opposed to a
103larger object with multiple of these.
104
105If `opts.strict` is truthy, the resulting object will be filtered such that
106it strictly follows the Subresource Integrity spec, throwing away any entries
107with any invalid components. This also means a restricted set of algorithms
108will be used -- the spec limits them to `sha256`, `sha384`, and `sha512`.
109
110Strict mode is recommended if the integrity strings are intended for use in
111browsers, or in other situations where strict adherence to the spec is needed.
112
113##### Example
114
115```javascript
116ssri.parse('sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo') // -> Integrity object
117```
118
119#### <a name="stringify"></a> `> ssri.stringify(sri, [opts]) -> String`
120
121This function is identical to [`Integrity#toString()`](#integrity-to-string),
122except it can be used on _any_ object that [`parse`](#parse) can handle -- that
123is, a string, an `Hash`-like, or an `Integrity`-like.
124
125The `opts.sep` option defines the string to use when joining multiple entries
126together. To be spec-compliant, this _must_ be whitespace. The default is a
127single space (`' '`).
128
129If `opts.strict` is true, the integrity string will be created using strict
130parsing rules. See [`ssri.parse`](#parse).
131
132##### Example
133
134```javascript
135// Useful for cleaning up input SRI strings:
136ssri.stringify('\n\rsha512-foo\n\t\tsha384-bar')
137// -> 'sha512-foo sha384-bar'
138
139// Hash-like: only a single entry.
140ssri.stringify({
141 algorithm: 'sha512',
142 digest:'9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==',
143 options: ['foo']
144})
145// ->
146// 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
147
148// Integrity-like: full multi-entry syntax. Similar to output of `ssri.parse`
149ssri.stringify({
150 'sha512': [
151 {
152 algorithm: 'sha512',
153 digest:'9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==',
154 options: ['foo']
155 }
156 ]
157})
158// ->
159// 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
160```
161
162#### <a name="integrity-concat"></a> `> Integrity#concat(otherIntegrity, [opts]) -> Integrity`
163
164Concatenates an `Integrity` object with another IntegrityLike, or an integrity
165string.
166
167This is functionally equivalent to concatenating the string format of both
168integrity arguments, and calling [`ssri.parse`](#ssri-parse) on the new string.
169
170If `opts.strict` is true, the new `Integrity` will be created using strict
171parsing rules. See [`ssri.parse`](#parse).
172
173##### Example
174
175```javascript
176// This will combine the integrity checks for two different versions of
177// your index.js file so you can use a single integrity string and serve
178// either of these to clients, from a single `<script>` tag.
179const desktopIntegrity = ssri.fromData(fs.readFileSync('./index.desktop.js'))
180const mobileIntegrity = ssri.fromData(fs.readFileSync('./index.mobile.js'))
181
182// Note that browsers (and ssri) will succeed as long as ONE of the entries
183// for the *prioritized* algorithm succeeds. That is, in order for this fallback
184// to work, both desktop and mobile *must* use the same `algorithm` values.
185desktopIntegrity.concat(mobileIntegrity)
186```
187
188#### <a name="integrity-merge"></a> `> Integrity#merge(otherIntegrity, [opts])`
189
190Safely merges another IntegrityLike or integrity string into an `Integrity`
191object.
192
193If the other integrity value has any algorithms in common with the current
194object, then the hash digests must match, or an error is thrown.
195
196Any new hashes will be added to the current object's set.
197
198This is useful when an integrity value may be upgraded with a stronger
199algorithm, you wish to prevent accidentally supressing integrity errors by
200overwriting the expected integrity value.
201
202##### Example
203
204```javascript
205const 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
209const expectedIntegrity = ssri.parse(fs.readFileSync('integrity.txt', 'utf8'))
210const match = ssri.checkData(data, expectedIntegrity, {
211 algorithms: ['sha512', 'sha1']
212})
213if (!match) {
214 throw new Error('data corrupted or something!')
215}
216
217// get a stronger algo!
218if (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
227#### <a name="integrity-to-string"></a> `> Integrity#toString([opts]) -> String`
228
229Returns the string representation of an `Integrity` object. All hash entries
230will be concatenated in the string by `opts.sep`, which defaults to `' '`.
231
232If you want to serialize an object that didn't come from an `ssri` function,
233use [`ssri.stringify()`](#stringify).
234
235If `opts.strict` is true, the integrity string will be created using strict
236parsing rules. See [`ssri.parse`](#parse).
237
238##### Example
239
240```javascript
241const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
242
243ssri.parse(integrity).toString() === integrity
244```
245
246#### <a name="integrity-to-json"></a> `> Integrity#toJSON() -> String`
247
248Returns the string representation of an `Integrity` object. All hash entries
249will be concatenated in the string by `' '`.
250
251This is a convenience method so you can pass an `Integrity` object directly to `JSON.stringify`.
252For more info check out [toJSON() behavior on mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior).
253
254##### Example
255
256```javascript
257const integrity = '"sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo"'
258
259JSON.stringify(ssri.parse(integrity)) === integrity
260```
261
262#### <a name="integrity-match"></a> `> Integrity#match(sri, [opts]) -> Hash | false`
263
264Returns the matching (truthy) hash if `Integrity` matches the argument passed as
265`sri`, which can be anything that [`parse`](#parse) will accept. `opts` will be
266passed through to `parse` and [`pickAlgorithm()`](#integrity-pick-algorithm).
267
268##### Example
269
270```javascript
271const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A=='
272
273ssri.parse(integrity).match(integrity)
274// Hash {
275// digest: '9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A=='
276// algorithm: 'sha512'
277// }
278
279ssri.parse(integrity).match('sha1-deadbeef')
280// false
281```
282
283#### <a name="integrity-pick-algorithm"></a> `> Integrity#pickAlgorithm([opts]) -> String`
284
285Returns the "best" algorithm from those available in the integrity object.
286
287If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
288arguments. ssri will prioritize whichever of the two algorithms is returned by
289this function. Note that the function may be called multiple times, and it
290**must** return one of the two algorithms provided. By default, ssri will make
291a best-effort to pick the strongest/most reliable of the given algorithms. It
292may intentionally deprioritize algorithms with known vulnerabilities.
293
294##### Example
295
296```javascript
297ssri.parse('sha1-WEakDigEST sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1').pickAlgorithm() // sha512
298```
299
300#### <a name="integrity-hex-digest"></a> `> Integrity#hexDigest() -> String`
301
302`Integrity` is assumed to be either a single-hash `Integrity` instance, or a
303`Hash` instance. Returns its `digest`, converted to a hex representation of the
304base64 data.
305
306##### Example
307
308```javascript
309ssri.parse('sha1-deadbeef').hexDigest() // '75e69d6de79f'
310```
311
312#### <a name="from-hex"></a> `> ssri.fromHex(hexDigest, algorithm, [opts]) -> Integrity`
313
314Creates an `Integrity` object with a single entry, based on a hex-formatted
315hash. This is a utility function to help convert existing shasums to the
316Integrity format, and is roughly equivalent to something like:
317
318```javascript
319algorithm + '-' + Buffer.from(hexDigest, 'hex').toString('base64')
320```
321
322`opts.options` may optionally be passed in: it must be an array of option
323strings that will be added to all generated integrity hashes generated by
324`fromData`. This is a loosely-specified feature of SRIs, and currently has no
325specified semantics besides being `?`-separated. Use at your own risk, and
326probably avoid if your integrity strings are meant to be used with browsers.
327
328If `opts.strict` is true, the integrity object will be created using strict
329parsing rules. See [`ssri.parse`](#parse).
330
331If `opts.single` is true, a single `Hash` object will be returned.
332
333##### Example
334
335```javascript
336ssri.fromHex('75e69d6de79f', 'sha1').toString() // 'sha1-deadbeef'
337```
338
339#### <a name="from-data"></a> `> ssri.fromData(data, [opts]) -> Integrity`
340
341Creates an `Integrity` object from either string or `Buffer` data, calculating
342all the requested hashes and adding any specified options to the object.
343
344`opts.algorithms` determines which algorithms to generate hashes for. All
345results will be included in a single `Integrity` object. The default value for
346`opts.algorithms` is `['sha512']`. All algorithm strings must be hashes listed
347in `crypto.getHashes()` for the host Node.js platform.
348
349`opts.options` may optionally be passed in: it must be an array of option
350strings that will be added to all generated integrity hashes generated by
351`fromData`. This is a loosely-specified feature of SRIs, and currently has no
352specified semantics besides being `?`-separated. Use at your own risk, and
353probably avoid if your integrity strings are meant to be used with browsers.
354
355If `opts.strict` is true, the integrity object will be created using strict
356parsing rules. See [`ssri.parse`](#parse).
357
358##### Example
359
360```javascript
361const integrityObj = ssri.fromData('foobarbaz', {
362 algorithms: ['sha256', 'sha384', 'sha512']
363})
364integrity.toString('\n')
365// ->
366// sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0=
367// sha384-irnCxQ0CfQhYGlVAUdwTPC9bF3+YWLxlaDGM4xbYminxpbXEq+D+2GCEBTxcjES9
368// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
369```
370
371#### <a name="from-stream"></a> `> ssri.fromStream(stream, [opts]) -> Promise<Integrity>`
372
373Returns a Promise of an Integrity object calculated by reading data from
374a given `stream`.
375
376It accepts both `opts.algorithms` and `opts.options`, which are documented as
377part of [`ssri.fromData`](#from-data).
378
379Additionally, `opts.Promise` may be passed in to inject a Promise library of
380choice. By default, ssri will use Node's built-in Promises.
381
382If `opts.strict` is true, the integrity object will be created using strict
383parsing rules. See [`ssri.parse`](#parse).
384
385##### Example
386
387```javascript
388ssri.fromStream(fs.createReadStream('index.js'), {
389 algorithms: ['sha1', 'sha512']
390}).then(integrity => {
391 return ssri.checkStream(fs.createReadStream('index.js'), integrity)
392}) // succeeds
393```
394
395#### <a name="create"></a> `> ssri.create([opts]) -> <Hash>`
396
397Returns a Hash object with `update(<Buffer or string>[,enc])` and `digest()` methods.
398
399
400The Hash object provides the same methods as [crypto class Hash](https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html#crypto_class_hash).
401`digest()` accepts no arguments and returns an Integrity object calculated by reading data from
402calls to update.
403
404It accepts both `opts.algorithms` and `opts.options`, which are documented as
405part of [`ssri.fromData`](#from-data).
406
407If `opts.strict` is true, the integrity object will be created using strict
408parsing rules. See [`ssri.parse`](#parse).
409
410##### Example
411
412```javascript
413const integrity = ssri.create().update('foobarbaz').digest()
414integrity.toString()
415// ->
416// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
417```
418
419#### <a name="check-data"></a> `> ssri.checkData(data, sri, [opts]) -> Hash|false`
420
421Verifies `data` integrity against an `sri` argument. `data` may be either a
422`String` or a `Buffer`, and `sri` can be any subresource integrity
423representation that [`ssri.parse`](#parse) can handle.
424
425If verification succeeds, `checkData` will return the name of the algorithm that
426was used for verification (a truthy value). Otherwise, it will return `false`.
427
428If `opts.pickAlgorithm` is provided, it will be used by
429[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
430the available digests to match against.
431
432If `opts.error` is true, and verification fails, `checkData` will throw either
433an `EBADSIZE` or an `EINTEGRITY` error, instead of just returning false.
434
435##### Example
436
437```javascript
438const data = fs.readFileSync('index.js')
439ssri.checkData(data, ssri.fromData(data)) // -> 'sha512'
440ssri.checkData(data, 'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0')
441ssri.checkData(data, 'sha1-BaDDigEST') // -> false
442ssri.checkData(data, 'sha1-BaDDigEST', {error: true}) // -> Error! EINTEGRITY
443```
444
445#### <a name="check-stream"></a> `> ssri.checkStream(stream, sri, [opts]) -> Promise<Hash>`
446
447Verifies the contents of `stream` against an `sri` argument. `stream` will be
448consumed in its entirety by this process. `sri` can be any subresource integrity
449representation that [`ssri.parse`](#parse) can handle.
450
451`checkStream` will return a Promise that either resolves to the
452`Hash` that succeeded verification, or, if the verification fails
453or an error happens with `stream`, the Promise will be rejected.
454
455If the Promise is rejected because verification failed, the returned error will
456have `err.code` as `EINTEGRITY`.
457
458If `opts.size` is given, it will be matched against the stream size. An error
459with `err.code` `EBADSIZE` will be returned by a rejection if the expected size
460and actual size fail to match.
461
462If `opts.pickAlgorithm` is provided, it will be used by
463[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
464the available digests to match against.
465
466##### Example
467
468```javascript
469const integrity = ssri.fromData(fs.readFileSync('index.js'))
470
471ssri.checkStream(
472 fs.createReadStream('index.js'),
473 integrity
474)
475// ->
476// Promise<{
477// algorithm: 'sha512',
478// digest: 'sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1'
479// }>
480
481ssri.checkStream(
482 fs.createReadStream('index.js'),
483 'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0'
484) // -> Promise<Hash>
485
486ssri.checkStream(
487 fs.createReadStream('index.js'),
488 'sha1-BaDDigEST'
489) // -> Promise<Error<{code: 'EINTEGRITY'}>>
490```
491
492#### <a name="integrity-stream"></a> `> integrityStream([opts]) -> IntegrityStream`
493
494Returns a `Transform` stream that data can be piped through in order to generate
495and optionally check data integrity for piped data. When the stream completes
496successfully, it emits `size` and `integrity` events, containing the total
497number of bytes processed and a calculated `Integrity` instance based on stream
498data, respectively.
499
500If `opts.algorithms` is passed in, the listed algorithms will be calculated when
501generating the final `Integrity` instance. The default is `['sha512']`.
502
503If `opts.single` is passed in, a single `Hash` instance will be returned.
504
505If `opts.integrity` is passed in, it should be an `integrity` value understood
506by [`parse`](#parse) that the stream will check the data against. If
507verification succeeds, the integrity stream will emit a `verified` event whose
508value is a single `Hash` object that is the one that succeeded verification. If
509verification fails, the stream will error with an `EINTEGRITY` error code.
510
511If `opts.size` is given, it will be matched against the stream size. An error
512with `err.code` `EBADSIZE` will be emitted by the stream if the expected size
513and actual size fail to match.
514
515If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
516arguments. ssri will prioritize whichever of the two algorithms is returned by
517this function. Note that the function may be called multiple times, and it
518**must** return one of the two algorithms provided. By default, ssri will make
519a best-effort to pick the strongest/most reliable of the given algorithms. It
520may intentionally deprioritize algorithms with known vulnerabilities.
521
522##### Example
523
524```javascript
525const integrity = ssri.fromData(fs.readFileSync('index.js'))
526fs.createReadStream('index.js')
527.pipe(ssri.integrityStream({integrity}))
528```