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