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