Skip to content

Commit

Permalink
feat: adding ssri.create for a crypto style interface (#2)
Browse files Browse the repository at this point in the history
* feat: adding ssri.create for a crypto style interface

adding crypto.create
adding toJSON to Integrity

* chore: standard

* fix: update nits and fix documentation for create.

create was misdocumented as returning Integrity but it return a Hash
  • Loading branch information
soldair authored and zkat committed Apr 7, 2017
1 parent cc54b31 commit 96f52ad
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes.
* [`stringify`](#stringify)
* [`Integrity#concat`](#integrity-concat)
* [`Integrity#toString`](#integrity-to-string)
* [`Integrity#toJSON`](#integrity-to-json)
* [`Integrity#pickAlgorithm`](#integrity-pick-algorithm)
* [`Integrity#hexDigest`](#integrity-hex-digest)
* Integrity Generation
* [`fromHex`](#from-hex)
* [`fromData`](#from-data)
* [`fromStream`](#from-stream)
* [`create`](#create)
* Integrity Verification
* [`checkData`](#check-data)
* [`checkStream`](#check-stream)
Expand Down Expand Up @@ -200,6 +202,22 @@ const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xp
ssri.parse(integrity).toString() === integrity
```

#### <a name="integrity-to-json"></a> `> Integrity#toJSON() -> String`

Returns the string representation of an `Integrity` object. All hash entries
will be concatenated in the string by `' '`.

This is a convenience method so you can pass an `Integrity` object directly to `JSON.stringify`.
For 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).

##### Example

```javascript
const integrity = '"sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo"'

JSON.stringify(ssri.parse(integrity)) === integrity
```

#### <a name="integrity-pick-algorithm"></a> `> Integrity#pickAlgorithm([opts]) -> String`

Returns the "best" algorithm from those available in the integrity object.
Expand Down Expand Up @@ -312,6 +330,30 @@ ssri.fromStream(fs.createReadStream('index.js'), {
}) // succeeds
```

#### <a name="create"></a> `> ssri.create([opts]) -> <Hash>`

Returns a Hash object with `update(<Buffer or string>[,enc])` and `digest()` methods.


The Hash object provides the same methods as [crypto class Hash](https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html#crypto_class_hash).
`digest()` accepts no arguments and returns an Integrity object calculated by reading data from
calls to update.

It accepts both `opts.algorithms` and `opts.options`, which are documented as
part of [`ssri.fromData`](#from-data).

If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).

##### Example

```javascript
const integrity = ssri.create().update('foobarbaz').digest()
integrity.toString()
// ->
// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
```

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

Verifies `data` integrity against an `sri` argument. `data` may be either a
Expand Down
41 changes: 41 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class Hash {
hexDigest () {
return this.digest && bufFrom(this.digest, 'base64').toString('hex')
}
toJSON () {
return this.toString()
}
toString (opts) {
if (opts && opts.strict) {
// Strict mode enforces the standard as close to the foot of the
Expand Down Expand Up @@ -63,6 +66,9 @@ class Hash {

class Integrity {
get isIntegrity () { return true }
toJSON () {
return this.toString()
}
toString (opts) {
opts = opts || {}
let sep = opts.sep || ' '
Expand Down Expand Up @@ -275,6 +281,41 @@ function integrityStream (opts) {
return stream
}

module.exports.create = createIntegrity
function createIntegrity (opts) {
opts = opts || {}
const algorithms = opts.algorithms || ['sha512']
const optString = opts.options && opts.options.length
? `?${opts.options.join('?')}`
: ''

const hashes = algorithms.map(crypto.createHash)

return {
update: function (chunk, enc) {
hashes.forEach(h => h.update(chunk, enc))
return this
},
digest: function (enc) {
const integrity = algorithms.reduce((acc, algo) => {
const digest = hashes.shift().digest('base64')
const hash = new Hash(
`${algo}-${digest}${optString}`,
opts
)
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(hash)
}
return acc
}, new Integrity())

return integrity
}
}
}

// This is a Best Effort™ at a reasonable priority for hash algos
const DEFAULT_PRIORITY = [
'md5', 'whirlpool', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'
Expand Down
24 changes: 24 additions & 0 deletions test/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const test = require('tap').test

const ssri = require('..')

test('works just like from', function (t) {
const integrity = ssri.fromData('hi')
const integrityCreate = ssri.create().update('hi').digest()

t.ok(integrityCreate instanceof integrity.constructor, 'should be same Integrity that fromData returns')
t.equals(integrity + '', integrityCreate + '', 'should be the sam as fromData')
t.end()
})

test('can pass options', function (t) {
const integrity = ssri.create({algorithms: ['sha256', 'sha384']}).update('hi').digest()

t.equals(
integrity + '',
'sha256-j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ= ' +
'sha384-B5EAbfgShHckT1PQ/c4hDbgfVXV1EOJqzuNcGKa86qKNzbv9bcBBubTcextU439S',
'should be expected value'
)
t.end()
})
15 changes: 15 additions & 0 deletions test/integrity.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ test('toString()', t => {
t.done()
})

test('toJSON()', t => {
const sri = ssri.parse('sha512-foo sha256-bar!')
t.equal(
sri.toJSON(),
'sha512-foo sha256-bar!',
'integrity objects from ssri.parse() can use toJSON()'
)
t.equal(
sri.sha512[0].toJSON(),
'sha512-foo',
'hash objects should toJSON also'
)
t.done()
})

test('concat()', t => {
const sri = ssri.parse('sha512-foo')
t.equal(
Expand Down

0 comments on commit 96f52ad

Please sign in to comment.