Socket
Socket
Sign inDemoInstall

ssri

Package Overview
Dependencies
0
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.0 to 3.0.0

32

CHANGELOG.md

@@ -5,2 +5,34 @@ # Change Log

<a name="3.0.0"></a>
# [3.0.0](https://github.com/zkat/ssri/compare/v2.0.0...v3.0.0) (2017-04-03)
### Bug Fixes
* **hashes:** IntegrityMetadata -> Hash ([d04aa1f](https://github.com/zkat/ssri/commit/d04aa1f))
### Features
* **check:** return IntegrityMetadata on check success ([2301e74](https://github.com/zkat/ssri/commit/2301e74))
* **fromHex:** ssri.fromHex to make it easier to generate them from hex valus ([049b89e](https://github.com/zkat/ssri/commit/049b89e))
* **hex:** utility function for getting hex version of digest ([a9f021c](https://github.com/zkat/ssri/commit/a9f021c))
* **hexDigest:** added hexDigest method to Integrity objects too ([85208ba](https://github.com/zkat/ssri/commit/85208ba))
* **integrity:** add .isIntegrity and .isIntegrityMetadata ([1b29e6f](https://github.com/zkat/ssri/commit/1b29e6f))
* **integrityStream:** new stream that can both generate and check streamed data ([fd23e1b](https://github.com/zkat/ssri/commit/fd23e1b))
* **parse:** allow parsing straight into a single IntegrityMetadata object ([c8ddf48](https://github.com/zkat/ssri/commit/c8ddf48))
* **pickAlgorithm:** Intergrity#pickAlgorithm() added ([b97a796](https://github.com/zkat/ssri/commit/b97a796))
* **size:** calculate and update stream sizes ([02ed1ad](https://github.com/zkat/ssri/commit/02ed1ad))
### BREAKING CHANGES
* **hashes:** `.isIntegrityMetadata` is now `.isHash`. Also, any references to `IntegrityMetadata` now refer to `Hash`.
* **integrityStream:** createCheckerStream has been removed and replaced with a general-purpose integrityStream.
To convert existing createCheckerStream code, move the `sri` argument into `opts.integrity` in integrityStream. All other options should be the same.
* **check:** `checkData`, `checkStream`, and `createCheckerStream` now yield a whole IntegrityMetadata instance representing the first successful hash match.
<a name="2.0.0"></a>

@@ -7,0 +39,0 @@ # [2.0.0](https://github.com/zkat/ssri/compare/v1.0.0...v2.0.0) (2017-03-24)

183

index.js

@@ -13,7 +13,8 @@ 'use strict'

class IntegrityMetadata {
constructor (metadata, opts) {
class Hash {
get isHash () { return true }
constructor (hash, opts) {
const strict = !!(opts && opts.strict)
this.source = metadata.trim()
// 3.1. Integrity metadata
this.source = hash.trim()
// 3.1. Integrity metadata (called "Hash" by ssri)
// https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description

@@ -33,2 +34,5 @@ const match = this.source.match(

}
hexDigest () {
return this.digest && bufFrom(this.digest, 'base64').toString('hex')
}
toString (opts) {

@@ -63,2 +67,3 @@ if (opts && opts.strict) {

class Integrity {
get isIntegrity () { return true }
toString (opts) {

@@ -72,4 +77,4 @@ opts = opts || {}

return Object.keys(this).map(k => {
return this[k].map(meta => {
return IntegrityMetadata.prototype.toString.call(meta, opts)
return this[k].map(hash => {
return Hash.prototype.toString.call(hash, opts)
}).filter(x => x.length).join(sep)

@@ -84,2 +89,11 @@ }).filter(x => x.length).join(sep)

}
hexDigest () {
return parse(this, {single: true}).hexDigest()
}
pickAlgorithm (opts) {
const pickAlgorithm = (opts && opts.pickAlgorithm) || getPrioritizedHash
return Object.keys(this).reduce((acc, algo) => {
return pickAlgorithm(acc, algo) || acc
})
}
}

@@ -104,8 +118,11 @@

// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
if (opts.single) {
return new Hash(integrity, opts)
}
return integrity.trim().split(/\s+/).reduce((acc, string) => {
const metadata = new IntegrityMetadata(string, opts)
if (metadata.algorithm && metadata.digest) {
const algo = metadata.algorithm
const hash = new Hash(string, opts)
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(metadata)
acc[algo].push(hash)
}

@@ -119,3 +136,3 @@ return acc

if (obj.algorithm && obj.digest) {
return IntegrityMetadata.prototype.toString.call(obj, opts)
return Hash.prototype.toString.call(obj, opts)
} else if (typeof obj === 'string') {

@@ -128,2 +145,14 @@ return stringify(parse(obj, opts), opts)

module.exports.fromHex = fromHex
function fromHex (hexDigest, algorithm, opts) {
const optString = (opts && opts.options && opts.options.length)
? `?${opts.options.join('?')}`
: ''
return parse(
`${algorithm}-${
bufFrom(hexDigest, 'hex').toString('base64')
}${optString}`, opts
)
}
module.exports.fromData = fromData

@@ -138,10 +167,10 @@ function fromData (data, opts) {

const digest = crypto.createHash(algo).update(data).digest('base64')
const meta = new IntegrityMetadata(
const hash = new Hash(
`${algo}-${digest}${optString}`,
opts
)
if (meta.algorithm && meta.digest) {
const algo = meta.algorithm
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(meta)
acc[algo].push(hash)
}

@@ -155,27 +184,12 @@ return acc

opts = opts || {}
const algorithms = opts.algorithms || ['sha512']
const optString = opts.options && opts.options.length
? `?${opts.options.join('?')}`
: ''
const P = opts.promise || Promise
const P = opts.Promise || Promise
const istream = integrityStream(opts)
return new P((resolve, reject) => {
const hashes = algorithms.map(algo => crypto.createHash(algo))
stream.on('data', d => hashes.forEach(hash => hash.update(d)))
stream.pipe(istream)
stream.on('error', reject)
stream.on('end', () => {
resolve(algorithms.reduce((acc, algo, i) => {
const hash = hashes[i]
const digest = hash.digest('base64')
const meta = new IntegrityMetadata(
`${algo}-${digest}${optString}`,
opts
)
if (meta.algorithm && meta.digest) {
const algo = meta.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(meta)
}
return acc
}, new Integrity()))
})
istream.on('error', reject)
let sri
istream.on('integrity', s => { sri = s })
istream.on('end', () => resolve(sri))
istream.on('data', () => {})
})

@@ -188,9 +202,6 @@ }

sri = parse(sri, opts)
const pickAlgorithm = opts.pickAlgorithm || getPrioritizedHash
const algorithm = Object.keys(sri).reduce((acc, algo) => {
return pickAlgorithm(acc, algo) || acc
})
const digests = sri[algorithm].map(m => m.digest)
const algorithm = sri.pickAlgorithm(opts)
const digests = sri[algorithm]
const digest = crypto.createHash(algorithm).update(data).digest('base64')
return digests.some(d => d === digest) && algorithm
return digests.find(hash => hash.digest === digest) || false
}

@@ -202,3 +213,8 @@

const P = opts.Promise || Promise
const checker = createCheckerStream(sri, opts)
const checker = integrityStream({
integrity: sri,
size: opts.size,
strict: opts.strict,
pickAlgorithm: opts.pickAlgorithm
})
return new P((resolve, reject) => {

@@ -208,36 +224,63 @@ stream.pipe(checker)

checker.on('error', reject)
checker.on('verified', algo => {
resolve(algo)
})
let sri
checker.on('verified', s => { sri = s })
checker.on('end', () => resolve(sri))
checker.on('data', () => {})
})
}
module.exports.createCheckerStream = createCheckerStream
function createCheckerStream (sri, opts) {
module.exports.integrityStream = integrityStream
function integrityStream (opts) {
opts = opts || {}
sri = parse(sri, opts)
const pickAlgorithm = opts.pickAlgorithm || getPrioritizedHash
const algorithm = Object.keys(sri).reduce((acc, algo) => {
return pickAlgorithm(acc, algo) || acc
})
const digests = sri[algorithm].map(m => m.digest)
const hash = crypto.createHash(algorithm)
// For verification
const sri = opts.integrity && parse(opts.integrity, opts)
const algorithm = sri && sri.pickAlgorithm(opts)
const digests = sri && sri[algorithm]
// Calculating stream
const algorithms = opts.algorithms || [algorithm || 'sha512']
const hashes = algorithms.map(crypto.createHash)
let streamSize = 0
const stream = new Transform({
transform: function (chunk, enc, cb) {
hash.update(chunk, enc)
transform (chunk, enc, cb) {
streamSize += chunk.length
hashes.forEach(h => h.update(chunk, enc))
cb(null, chunk, enc)
},
flush: function (cb) {
const digest = hash.digest('base64')
if (digests.some(d => d === digest)) {
stream.emit('verified', algorithm)
return cb()
} else {
const err = new Error(`${algorithm} integrity checksum failed`)
flush (done) {
const optString = (opts.options && opts.options.length)
? `?${opts.options.join('?')}`
: ''
const newSri = parse(hashes.map((h, i) => {
return `${algorithms[i]}-${h.digest('base64')}${optString}`
}).join(' '), opts)
const match = (
// Integrity verification mode
opts.integrity &&
digests.find(hash => {
return newSri[algorithm].find(newhash => {
return hash.digest === newhash.digest
})
})
)
if (typeof opts.size === 'number' && streamSize !== opts.size) {
const err = new Error(`stream size mismatch when checking ${sri}.\n Wanted: ${opts.size}\n Found: ${streamSize}`)
err.code = 'EBADSIZE'
err.found = streamSize
err.expected = opts.size
err.sri = sri
stream.emit('error', err)
} else if (opts.integrity && !match) {
const err = new Error(`${sri} integrity checksum failed when using ${algorithm}`)
err.code = 'EBADCHECKSUM'
err.found = digest
err.found = newSri
err.expected = digests
err.algorithm = algorithm
return cb(err)
err.sri = sri
stream.emit('error', err)
} else {
stream.emit('size', streamSize)
stream.emit('integrity', newSri)
match && stream.emit('verified', match)
}
done()
}

@@ -257,1 +300,5 @@ })

}
function bufFrom (data, enc) {
return Buffer.from ? Buffer.from(data, enc) : new Buffer(data, enc)
}
{
"name": "ssri",
"version": "2.0.0",
"version": "3.0.0",
"description": "Standard Subresource Integrity library -- parses, serializes, generates, and verifies integrity metadata according to the SRI spec.",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -23,3 +23,6 @@ # 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)

* [`Integrity#toString`](#integrity-to-string)
* [`Integrity#pickAlgorithm`](#integrity-pick-algorithm)
* [`Integrity#hexDigest`](#integrity-hex-digest)
* Integrity Generation
* [`fromHex`](#from-hex)
* [`fromData`](#from-data)

@@ -30,3 +33,3 @@ * [`fromStream`](#from-stream)

* [`checkStream`](#check-stream)
* [`createCheckerStream`](#create-checker-stream)
* [`integrityStream`](#integrity-stream)

@@ -64,3 +67,3 @@ ### Example

* Multiple entries for the same algorithm.
* Object-based integrity metadata manipulation.
* Object-based integrity hash manipulation.
* Small footprint: no dependencies, concise implementation.

@@ -83,5 +86,5 @@ * Full test coverage.

Parses `sri` into an `Integrity` data structure. `sri` can be an integrity
string, an `IntegrityMetadata`-like with `digest` and `algorithm` fields and an
optional `options` field, or an `Integrity`-like object. The resulting object
will be an `Integrity` instance that has this shape:
string, an `Hash`-like with `digest` and `algorithm` fields and an optional
`options` field, or an `Integrity`-like object. The resulting object will be an
`Integrity` instance that has this shape:

@@ -98,2 +101,6 @@ ```javascript

If `opts.single` is truthy, a single `Hash` object will be returned. That is, a
single object that looks like `{algorithm, digest, options}`, as opposed to a
larger object with multiple of these.
If `opts.strict` is truthy, the resulting object will be filtered such that

@@ -117,3 +124,3 @@ it strictly follows the Subresource Integrity spec, throwing away any entries

except it can be used on _any_ object that [`parse`](#parse) can handle -- that
is, a string, an `IntegrityMetadata`-like, or an `Integrity`-like.
is, a string, an `Hash`-like, or an `Integrity`-like.

@@ -134,3 +141,3 @@ The `opts.sep` option defines the string to use when joining multiple entries

// IntegrityMetadata-like: only a single entry.
// Hash-like: only a single entry.
ssri.stringify({

@@ -160,4 +167,4 @@ algorithm: 'sha512',

Concatenates an `Integrity` object with another IntegrityLike, or a string
representing integrity metadata.
Concatenates an `Integrity` object with another IntegrityLike, or an integrity
string.

@@ -187,3 +194,3 @@ This is functionally equivalent to concatenating the string format of both

Returns the string representation of an `Integrity` object. All metadata entries
Returns the string representation of an `Integrity` object. All hash entries
will be concatenated in the string by `opts.sep`, which defaults to `' '`.

@@ -205,2 +212,58 @@

#### <a name="integrity-pick-algorithm"></a> `> Integrity#pickAlgorithm([opts]) -> String`
Returns the "best" algorithm from those available in the integrity object.
If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
arguments. ssri will prioritize whichever of the two algorithms is returned by
this function. Note that the function may be called multiple times, and it
**must** return one of the two algorithms provided. By default, ssri will make
a best-effort to pick the strongest/most reliable of the given algorithms. It
may intentionally deprioritize algorithms with known vulnerabilities.
##### Example
```javascript
ssri.parse('sha1-WEakDigEST sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1').pickAlgorithm() // sha512
```
#### <a name="integrity-hex-digest"></a> `> Integrity#hexDigest() -> String`
`Integrity` is assumed to be either a single-hash `Integrity` instance, or a
`Hash` instance. Returns its `digest`, converted to a hex representation of the
base64 data.
##### Example
```javascript
ssri.parse('sha1-deadbeef').hexDigest() // '75e69d6de79f'
```
#### <a name="from-hex"></a> `> ssri.fromHex(hexDigest, algorithm, [opts]) -> Integrity`
Creates an `Integrity` object with a single entry, based on a hex-formatted
hash. This is a utility function to help convert existing shasums to the
Integrity format, and is roughly equivalent to something like:
```javascript
algorithm + '-' + Buffer.from(hexDigest, 'hex').toString('base64')
```
`opts.options` may optionally be passed in: it must be an array of option
strings that will be added to all generated integrity hashes generated by
`fromData`. This is a loosely-specified feature of SRIs, and currently has no
specified semantics besides being `?`-separated. Use at your own risk, and
probably avoid if your integrity strings are meant to be used with browsers.
If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).
If `opts.single` is true, a single `Hash` object will be returned.
##### Example
```javascript
ssri.fromHex('75e69d6de79f', 'sha1').toString() // 'sha1-deadbeef'
```
#### <a name="from-data"></a> `> ssri.fromData(data, [opts]) -> Integrity`

@@ -211,3 +274,3 @@

`opts.algorithms` determines which algorithms to generate metadata for. All
`opts.algorithms` determines which algorithms to generate hashes for. All
results will be included in a single `Integrity` object. The default value for

@@ -218,3 +281,3 @@ `opts.algorithms` is `['sha512']`. All algorithm strings must be hashes listed

`opts.options` may optionally be passed in: it must be an array of option
strings that will be added to all generated integrity metadata generated by
strings that will be added to all generated integrity hashes generated by
`fromData`. This is a loosely-specified feature of SRIs, and currently has no

@@ -264,3 +327,3 @@ specified semantics besides being `?`-separated. Use at your own risk, and

#### <a name="check-data"></a> `> ssri.checkData(data, sri, [opts]) -> Algorithm|false`
#### <a name="check-data"></a> `> ssri.checkData(data, sri, [opts]) -> Hash|false`

@@ -274,8 +337,5 @@ Verifies `data` integrity against an `sri` argument. `data` may be either a

If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
arguments. ssri will prioritize whichever of the two algorithms is returned by
this function. Note that the function may be called multiple times, and it
**must** return one of the two algorithms provided. By default, ssri will make
a best-effort to pick the strongest/most reliable of the given algorithms. It
may intentionally deprioritize algorithms with known vulnerabilities.
If `opts.pickAlgorithm` is provided, it will be used by
[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
the available digests to match against.

@@ -291,3 +351,3 @@ ##### Example

#### <a name="check-stream"></a> `> ssri.checkStream(stream, sri, [opts]) -> Promise<Algorithm>`
#### <a name="check-stream"></a> `> ssri.checkStream(stream, sri, [opts]) -> Promise<Hash>`

@@ -298,5 +358,5 @@ Verifies the contents of `stream` against an `sri` argument. `stream` will be

`checkStream` will return a Promise that either resolves to the string name of
the algorithm that verification was done with, or, if the verification fails or
an error happens with `stream`, the Promise will be rejected.
`checkStream` will return a Promise that either resolves to the
`Hash` that succeeded verification, or, if the verification fails
or an error happens with `stream`, the Promise will be rejected.

@@ -306,9 +366,10 @@ If the Promise is rejected because verification failed, the returned error will

If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
arguments. ssri will prioritize whichever of the two algorithms is returned by
this function. Note that the function may be called multiple times, and it
**must** return one of the two algorithms provided. By default, ssri will make
a best-effort to pick the strongest/most reliable of the given algorithms. It
may intentionally deprioritize algorithms with known vulnerabilities.
If `opts.size` is given, it will be matched against the stream size. An error
with `err.code` `EBADSIZE` will be returned by a rejection if the expected size
and actual size fail to match.
If `opts.pickAlgorithm` is provided, it will be used by
[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
the available digests to match against.
##### Example

@@ -322,3 +383,8 @@

integrity
) // -> Promise<'sha512'>
)
// ->
// Promise<{
// algorithm: 'sha512',
// digest: 'sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1'
// }>

@@ -328,3 +394,3 @@ ssri.checkStream(

'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0'
) // -> Promise<'sha256'>
) // -> Promise<Hash>

@@ -334,14 +400,28 @@ ssri.checkStream(

'sha1-BaDDigEST'
) // -> Promise<Error<EBADCHECKSUM>>
) // -> Promise<Error<{code: 'EBADCHECKSUM'}>>
```
#### <a name="create-checker-stream"></a> `> createCheckerStream(sri, [opts]) -> CheckerStream`
#### <a name="integrity-stream"></a> `> integrityStream(sri, [opts]) -> IntegrityStream`
Returns a `Through` stream that data can be piped through in order to check it
against `sri`. `sri` can be any subresource integrity representation that
[`ssri.parse`](#parse) can handle.
Returns a `Transform` stream that data can be piped through in order to generate
and optionally check data integrity for piped data. When the stream completes
successfully, it emits `size` and `integrity` events, containing the total
number of bytes processed and a calculated `Integrity` instance based on stream
data, respectively.
If verification fails, the returned stream will error with an `EBADCHECKSUM`
error code.
If `opts.algorithms` is passed in, the listed algorithms will be calculated when
generating the final `Integrity` instance. The default is `['sha512']`.
If `opts.single` is passed in, a single `Hash` instance will be returned.
If `opts.integrity` is passed in, it should be an `integrity` value understood
by [`parse`](#parse) that the stream will check the data against. If
verification succeeds, the integrity stream will emit a `verified` event whose
value is a single `Hash` object that is the one that succeeded verification. If
verification fails, the stream will error with an `EBADCHECKSUM` error code.
If `opts.size` is given, it will be matched against the stream size. An error
with `err.code` `EBADSIZE` will be emitted by the stream if the expected size
and actual size fail to match.
If `opts.pickAlgorithm` is provided, it will be passed two algorithms as

@@ -348,0 +428,0 @@ arguments. ssri will prioritize whichever of the two algorithms is returned by

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc