Skip to content

Commit 96f52ad

Browse files
soldairzkat
authored andcommitted
feat: adding ssri.create for a crypto style interface (#2)
* 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
1 parent cc54b31 commit 96f52ad

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes.
2020
* [`stringify`](#stringify)
2121
* [`Integrity#concat`](#integrity-concat)
2222
* [`Integrity#toString`](#integrity-to-string)
23+
* [`Integrity#toJSON`](#integrity-to-json)
2324
* [`Integrity#pickAlgorithm`](#integrity-pick-algorithm)
2425
* [`Integrity#hexDigest`](#integrity-hex-digest)
2526
* Integrity Generation
2627
* [`fromHex`](#from-hex)
2728
* [`fromData`](#from-data)
2829
* [`fromStream`](#from-stream)
30+
* [`create`](#create)
2931
* Integrity Verification
3032
* [`checkData`](#check-data)
3133
* [`checkStream`](#check-stream)
@@ -200,6 +202,22 @@ const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xp
200202
ssri.parse(integrity).toString() === integrity
201203
```
202204

205+
#### <a name="integrity-to-json"></a> `> Integrity#toJSON() -> String`
206+
207+
Returns the string representation of an `Integrity` object. All hash entries
208+
will be concatenated in the string by `' '`.
209+
210+
This is a convenience method so you can pass an `Integrity` object directly to `JSON.stringify`.
211+
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).
212+
213+
##### Example
214+
215+
```javascript
216+
const integrity = '"sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo"'
217+
218+
JSON.stringify(ssri.parse(integrity)) === integrity
219+
```
220+
203221
#### <a name="integrity-pick-algorithm"></a> `> Integrity#pickAlgorithm([opts]) -> String`
204222

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

333+
#### <a name="create"></a> `> ssri.create([opts]) -> <Hash>`
334+
335+
Returns a Hash object with `update(<Buffer or string>[,enc])` and `digest()` methods.
336+
337+
338+
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).
339+
`digest()` accepts no arguments and returns an Integrity object calculated by reading data from
340+
calls to update.
341+
342+
It accepts both `opts.algorithms` and `opts.options`, which are documented as
343+
part of [`ssri.fromData`](#from-data).
344+
345+
If `opts.strict` is true, the integrity object will be created using strict
346+
parsing rules. See [`ssri.parse`](#parse).
347+
348+
##### Example
349+
350+
```javascript
351+
const integrity = ssri.create().update('foobarbaz').digest()
352+
integrity.toString()
353+
// ->
354+
// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
355+
```
356+
315357
#### <a name="check-data"></a> `> ssri.checkData(data, sri, [opts]) -> Hash|false`
316358

317359
Verifies `data` integrity against an `sri` argument. `data` may be either a

index.js

+41
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class Hash {
3333
hexDigest () {
3434
return this.digest && bufFrom(this.digest, 'base64').toString('hex')
3535
}
36+
toJSON () {
37+
return this.toString()
38+
}
3639
toString (opts) {
3740
if (opts && opts.strict) {
3841
// Strict mode enforces the standard as close to the foot of the
@@ -63,6 +66,9 @@ class Hash {
6366

6467
class Integrity {
6568
get isIntegrity () { return true }
69+
toJSON () {
70+
return this.toString()
71+
}
6672
toString (opts) {
6773
opts = opts || {}
6874
let sep = opts.sep || ' '
@@ -275,6 +281,41 @@ function integrityStream (opts) {
275281
return stream
276282
}
277283

284+
module.exports.create = createIntegrity
285+
function createIntegrity (opts) {
286+
opts = opts || {}
287+
const algorithms = opts.algorithms || ['sha512']
288+
const optString = opts.options && opts.options.length
289+
? `?${opts.options.join('?')}`
290+
: ''
291+
292+
const hashes = algorithms.map(crypto.createHash)
293+
294+
return {
295+
update: function (chunk, enc) {
296+
hashes.forEach(h => h.update(chunk, enc))
297+
return this
298+
},
299+
digest: function (enc) {
300+
const integrity = algorithms.reduce((acc, algo) => {
301+
const digest = hashes.shift().digest('base64')
302+
const hash = new Hash(
303+
`${algo}-${digest}${optString}`,
304+
opts
305+
)
306+
if (hash.algorithm && hash.digest) {
307+
const algo = hash.algorithm
308+
if (!acc[algo]) { acc[algo] = [] }
309+
acc[algo].push(hash)
310+
}
311+
return acc
312+
}, new Integrity())
313+
314+
return integrity
315+
}
316+
}
317+
}
318+
278319
// This is a Best Effort™ at a reasonable priority for hash algos
279320
const DEFAULT_PRIORITY = [
280321
'md5', 'whirlpool', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'

test/create.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const test = require('tap').test
2+
3+
const ssri = require('..')
4+
5+
test('works just like from', function (t) {
6+
const integrity = ssri.fromData('hi')
7+
const integrityCreate = ssri.create().update('hi').digest()
8+
9+
t.ok(integrityCreate instanceof integrity.constructor, 'should be same Integrity that fromData returns')
10+
t.equals(integrity + '', integrityCreate + '', 'should be the sam as fromData')
11+
t.end()
12+
})
13+
14+
test('can pass options', function (t) {
15+
const integrity = ssri.create({algorithms: ['sha256', 'sha384']}).update('hi').digest()
16+
17+
t.equals(
18+
integrity + '',
19+
'sha256-j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ= ' +
20+
'sha384-B5EAbfgShHckT1PQ/c4hDbgfVXV1EOJqzuNcGKa86qKNzbv9bcBBubTcextU439S',
21+
'should be expected value'
22+
)
23+
t.end()
24+
})

test/integrity.js

+15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ test('toString()', t => {
2424
t.done()
2525
})
2626

27+
test('toJSON()', t => {
28+
const sri = ssri.parse('sha512-foo sha256-bar!')
29+
t.equal(
30+
sri.toJSON(),
31+
'sha512-foo sha256-bar!',
32+
'integrity objects from ssri.parse() can use toJSON()'
33+
)
34+
t.equal(
35+
sri.sha512[0].toJSON(),
36+
'sha512-foo',
37+
'hash objects should toJSON also'
38+
)
39+
t.done()
40+
})
41+
2742
test('concat()', t => {
2843
const sri = ssri.parse('sha512-foo')
2944
t.equal(

0 commit comments

Comments
 (0)