Skip to content

Commit

Permalink
fix(hashes): IntegrityMetadata -> Hash
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `.isIntegrityMetadata` is now `.isHash`. Also, any references to `IntegrityMetadata` now refer to `Hash`.
  • Loading branch information
zkat committed Apr 3, 2017
1 parent 1926ac8 commit d04aa1f
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 59 deletions.
54 changes: 26 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ ssri.checkData(fs.readFileSync('./my-file'), integrity) // => 'sha512'
* Strict standard compliance.
* `?foo` metadata option support.
* Multiple entries for the same algorithm.
* Object-based integrity metadata manipulation.
* Object-based integrity hash manipulation.
* Small footprint: no dependencies, concise implementation.
* Full test coverage.
* Customizable algorithm picker.
Expand All @@ -80,9 +80,9 @@ jump in if you'd like to, or even ask us questions if something isn't clear.
#### <a name="parse"></a> `> ssri.parse(sri, [opts]) -> Integrity`

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:

```javascript
{
Expand All @@ -94,9 +94,9 @@ will be an `Integrity` instance that has this shape:
}
```

If `opts.single` is truthy, a single `IntegrityMetadata` 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.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
it strictly follows the Subresource Integrity spec, throwing away any entries
Expand All @@ -116,7 +116,7 @@ ssri.parse('sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/

This function is identical to [`Integrity#toString()`](#integrity-to-string),
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.

The `opts.sep` option defines the string to use when joining multiple entries
together. To be spec-compliant, this _must_ be whitespace. The default is a
Expand All @@ -132,7 +132,7 @@ parsing rules. See [`ssri.parse`](#parse).
ssri.stringify('\n\rsha512-foo\n\t\tsha384-bar')
// -> 'sha512-foo sha384-bar'

// IntegrityMetadata-like: only a single entry.
// Hash-like: only a single entry.
ssri.stringify({
algorithm: 'sha512',
digest:'9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==',
Expand All @@ -157,8 +157,8 @@ ssri.stringify({

#### <a name="integrity-concat"></a> `> Integrity#concat(otherIntegrity, [opts]) -> Integrity`

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

This is functionally equivalent to concatenating the string format of both
integrity arguments, and calling [`ssri.parse`](#ssri-parse) on the new string.
Expand All @@ -183,7 +183,7 @@ desktopIntegrity.concat(mobileIntegrity)

#### <a name="integrity-to-string"></a> `> Integrity#toString([opts]) -> String`

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 `' '`.

If you want to serialize an object that didn't from from an `ssri` function,
Expand Down Expand Up @@ -219,9 +219,9 @@ ssri.parse('sha1-WEakDigEST sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0

#### <a name="integrity-hex-digest"></a> `> Integrity#hexDigest() -> String`

`Integrity` is assumed to be either a single-hash `Integrity` instance, or an
`IntegrityMetadata` instance. Returns its `digest`, converted to a hex
representation of the base64 data.
`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

Expand All @@ -240,15 +240,15 @@ 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 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
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 `IntegrityMetadata` object will be returned.
If `opts.single` is true, a single `Hash` object will be returned.

##### Example

Expand All @@ -261,13 +261,13 @@ ssri.fromHex('75e69d6de79f', 'sha1').toString() // 'sha1-deadbeef'
Creates an `Integrity` object from either string or `Buffer` data, calculating
all the requested hashes and adding any specified options to the object.

`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
`opts.algorithms` is `['sha512']`. All algorithm strings must be hashes listed
in `crypto.getHashes()` for the host Node.js platform.

`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
specified semantics besides being `?`-separated. Use at your own risk, and
probably avoid if your integrity strings are meant to be used with browsers.
Expand Down Expand Up @@ -312,7 +312,7 @@ ssri.fromStream(fs.createReadStream('index.js'), {
}) // succeeds
```

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

Verifies `data` integrity against an `sri` argument. `data` may be either a
`String` or a `Buffer`, and `sri` can be any subresource integrity
Expand All @@ -334,14 +334,14 @@ ssri.checkData(data, 'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0')
ssri.checkData(data, 'sha1-BaDDigEST') // -> false
```

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

Verifies the contents of `stream` against an `sri` argument. `stream` will be
consumed in its entirety by this process. `sri` can be any subresource integrity
representation that [`ssri.parse`](#parse) can handle.

`checkStream` will return a Promise that either resolves to the
`IntegrityMetadata` that succeeded verification, or, if the verification fails
`Hash` that succeeded verification, or, if the verification fails
or an error happens with `stream`, the Promise will be rejected.

If the Promise is rejected because verification failed, the returned error will
Expand Down Expand Up @@ -373,7 +373,7 @@ ssri.checkStream(
ssri.checkStream(
fs.createReadStream('index.js'),
'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0'
) // -> Promise<IntegrityMetadata>
) // -> Promise<Hash>

ssri.checkStream(
fs.createReadStream('index.js'),
Expand All @@ -392,15 +392,13 @@ data, respectively.
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 `IntegrityMetadata` instance will be
returned.
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 `IntegrityMetadata` object that is the one that succeeded
verification. If verification fails, the stream will error with an
`EBADCHECKSUM` error code.
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
Expand Down
42 changes: 21 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ const SRI_REGEX = /^([^-]+)-([^?]+)([?\S*]*)$/
const STRICT_SRI_REGEX = /^([^-]+)-([A-Za-z0-9+/]+(?:=?=?))([?\x21-\x7E]*)$/
const VCHAR_REGEX = /^[\x21-\x7E]+$/

class IntegrityMetadata {
get isIntegrityMetadata () { return true }
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
const match = this.source.match(
strict
Expand Down Expand Up @@ -71,8 +71,8 @@ class Integrity {
sep = sep.replace(/\S+/g, ' ')
}
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)
}).filter(x => x.length).join(sep)
}
Expand Down Expand Up @@ -111,14 +111,14 @@ function _parse (integrity, opts) {
// 3.4.3. Parse metadata
// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
if (opts.single) {
return new IntegrityMetadata(integrity, opts)
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)
}
return acc
}, new Integrity())
Expand All @@ -127,7 +127,7 @@ function _parse (integrity, opts) {
module.exports.stringify = stringify
function stringify (obj, opts) {
if (obj.algorithm && obj.digest) {
return IntegrityMetadata.prototype.toString.call(obj, opts)
return Hash.prototype.toString.call(obj, opts)
} else if (typeof obj === 'string') {
return stringify(parse(obj, opts), opts)
} else {
Expand Down Expand Up @@ -156,14 +156,14 @@ function fromData (data, opts) {
: ''
return algorithms.reduce((acc, algo) => {
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)
}
return acc
}, new Integrity())
Expand Down Expand Up @@ -192,7 +192,7 @@ function checkData (data, sri, opts) {
const algorithm = sri.pickAlgorithm(opts)
const digests = sri[algorithm]
const digest = crypto.createHash(algorithm).update(data).digest('base64')
return digests.find(meta => meta.digest === digest) || false
return digests.find(hash => hash.digest === digest) || false
}

module.exports.checkStream = checkStream
Expand Down Expand Up @@ -243,9 +243,9 @@ function integrityStream (opts) {
const match = (
// Integrity verification mode
opts.integrity &&
digests.find(meta => {
return newSri[algorithm].find(newmeta => {
return meta.digest === newmeta.digest
digests.find(hash => {
return newSri[algorithm].find(newhash => {
return hash.digest === newhash.digest
})
})
)
Expand Down
4 changes: 2 additions & 2 deletions test/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ test('checkData', t => {
digest: hash(TEST_DATA, 'sha512')
}),
meta,
'Accepts IntegrityMetadata-like SRI'
'Accepts Hash-like SRI'
)
t.deepEqual(
ssri.checkData(TEST_DATA.toString('utf8'), sri),
Expand Down Expand Up @@ -110,7 +110,7 @@ test('checkStream', t => {
digest: hash(TEST_DATA, 'sha512')
})
}).then(res => {
t.deepEqual(res, meta, 'Accepts IntegrityMetadata-like SRI')
t.deepEqual(res, meta, 'Accepts Hash-like SRI')
return ssri.checkStream(
fileStream(),
`sha512-nope sha512-${hash(TEST_DATA, 'sha512')}`
Expand Down
8 changes: 4 additions & 4 deletions test/integrity.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('concat()', t => {
t.equal(
sri.concat({digest: 'bar', algorithm: 'sha384'}).toString(),
'sha512-foo sha384-bar',
'concatenates with an IntegrityMetadata-like'
'concatenates with an Hash-like'
)
t.equal(
sri.concat({
Expand Down Expand Up @@ -97,12 +97,12 @@ test('hexDigest()', t => {
t.done()
})

test('isIntegrity and isIntegrityMetadata', t => {
test('isIntegrity and isHash', t => {
const sri = ssri.parse('sha512-bar')
t.ok(sri.isIntegrity, 'full sri has !!.isIntegrity')
t.ok(
sri['sha512'][0].isIntegrityMetadata,
'sri hash has !!.isIntegrityMetadata'
sri['sha512'][0].isHash,
'sri hash has !!.isHash'
)
t.done()
})
Expand Down
6 changes: 3 additions & 3 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ test('parses single-entry integrity string', t => {
t.done()
})

test('can parse single-entry string directly into IntegrityMetadata', t => {
test('can parse single-entry string directly into Hash', t => {
const sha = hash(TEST_DATA, 'sha512')
const integrity = `sha512-${sha}`
t.deepEqual(ssri.parse(integrity, {single: true}), {
source: integrity,
digest: sha,
algorithm: 'sha512',
options: []
}, 'single entry parsed into single IntegrityMetadata instance')
}, 'single entry parsed into single Hash instance')
t.done()
})

test('accepts IntegrityMetadata-likes as input', t => {
test('accepts Hash-likes as input', t => {
const algorithm = 'sha512'
const digest = hash(TEST_DATA, 'sha512')
const sriLike = {
Expand Down
2 changes: 1 addition & 1 deletion test/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test('serializes Integrity-likes', t => {
t.done()
})

test('serializes IntegrityMetadata-likes', t => {
test('serializes Hash-likes', t => {
const sriLike = {
digest: 'foo',
algorithm: 'sha512'
Expand Down

0 comments on commit d04aa1f

Please sign in to comment.