jsonld-signatures
Advanced tools
Comparing version 1.0.2 to 1.1.0
{ | ||
"name": "jsonld-signatures", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"moduleType": [ | ||
@@ -17,3 +17,3 @@ "amd" | ||
"es6-promise": "~2.0.1", | ||
"jsonld": "~0.4.1", | ||
"jsonld": "~0.4.2", | ||
"forge": "~0.6.34" | ||
@@ -20,0 +20,0 @@ }, |
@@ -10,3 +10,3 @@ /** | ||
* BSD 3-Clause License | ||
* Copyright (c) 2014 Digital Bazaar, Inc. | ||
* Copyright (c) 2014-2015 Digital Bazaar, Inc. | ||
* All rights reserved. | ||
@@ -55,4 +55,4 @@ * | ||
* @param [options] the options to use: | ||
* [inject] the dependencies to inject, available global defaults will | ||
* be used otherwise. | ||
* [inject] *deprecated*, use `use` API instead; the dependencies to | ||
* inject, available global defaults will be used otherwise. | ||
* [async] async API. | ||
@@ -63,39 +63,8 @@ * [forge] forge API. | ||
* document loader is configured. | ||
* [_] underscore API. | ||
* [disableLocalFraming] true to disable framing of local | ||
* documents based on the given local base URI (default: false). | ||
* [localBaseUri] must be given if disabling local framing. | ||
*/ | ||
function wrap(api, options) { | ||
// handle dependency injection | ||
options = options || {}; | ||
var inject = options.inject || {}; | ||
var async = inject.async || global.async; | ||
var crypto = inject.crypto || global.crypto; | ||
var forge = inject.forge || global.forge; | ||
var jsonld = inject.jsonld || global.jsonldjs; | ||
var _ = inject._ || global._; | ||
var libs = {}; | ||
// if dependencies not loaded and using node, load them | ||
if(_nodejs) { | ||
if(!async) { | ||
async = require('async'); | ||
} | ||
if(!crypto) { | ||
crypto = require('crypto'); | ||
} | ||
if(!forge) { | ||
forge = require('node-forge'); | ||
} | ||
if(!jsonld) { | ||
// locally configure jsonld | ||
jsonld = require('jsonld')(); | ||
jsonld.useDocumentLoader('node', {secure: true, strictSSL: true}); | ||
} | ||
if(!_) { | ||
_ = require('underscore'); | ||
} | ||
} | ||
/* API Constants */ | ||
@@ -108,2 +77,39 @@ | ||
/** | ||
* Allows injectables to be set or retrieved. | ||
* | ||
* @param name the name of the injectable to use ( | ||
* eg: `jsonld`, `jsonld-signatures`). | ||
* @param [injectable] the api to set for the injectable, only present for setter, | ||
* omit for getter. | ||
* | ||
* @return the API for `name` if not using this method as a setter, otherwise | ||
* undefined. | ||
*/ | ||
api.use = function(name, injectable) { | ||
// setter mode | ||
if(injectable) { | ||
libs[name] = injectable; | ||
return; | ||
} | ||
// getter mode: | ||
// api not set yet, load default | ||
if(!libs[name]) { | ||
// alias `forge` to `node-forge`, `jsonld` to `jsonldjs` | ||
var requireName = (name === 'forge' ? 'node-forge' : name); | ||
var globalName = (name === 'jsonld' ? 'jsonldjs' : name); | ||
libs[name] = global[globalName] || (_nodejs && require(requireName)); | ||
if(name === 'jsonld') { | ||
if(_nodejs) { | ||
// locally configure jsonld | ||
libs[name] = libs[name](); | ||
libs[name].useDocumentLoader('node', {secure: true, strictSSL: true}); | ||
} | ||
} | ||
} | ||
return libs[name]; | ||
}; | ||
/** | ||
* Signs a JSON-LD document using a digital signature. | ||
@@ -118,2 +124,4 @@ * | ||
* [nonce] an optional nonce to include in the signature. | ||
* [algorithm] the algorithm to use, eg: 'GraphSignature2012', | ||
* 'LinkedDataSignature2015' (default: 'GraphSignature2012'). | ||
* @param callback(err, signedDocument) called once the operation completes. | ||
@@ -131,2 +139,3 @@ */ | ||
var nonce = options.nonce || null; | ||
var algorithm = options.algorithm || 'GraphSignature2012'; | ||
@@ -149,2 +158,8 @@ if(typeof privateKeyPem !== 'string') { | ||
} | ||
if(['GraphSignature2012', 'LinkedDataSignature2015'].indexOf( | ||
algorithm) === -1) { | ||
return callback(new Error( | ||
'[jsig.sign] options.algorithm must be ' + | ||
'`GraphSignature2012` or `LinkedDataSignature2015`.')); | ||
} | ||
@@ -156,5 +171,15 @@ // create W3C-formatted date | ||
var jsonld = api.use('jsonld'); | ||
var async = api.use('async'); | ||
async.auto({ | ||
normalize: function(callback) { | ||
jsonld.normalize(input, {format: 'application/nquads'}, callback); | ||
var normalizeAlgorithm; | ||
if(algorithm === 'GraphSignature2012') { | ||
normalizeAlgorithm = 'URGNA2012'; | ||
} else if(algorithm === 'LinkedDataSignature2015') { | ||
normalizeAlgorithm = 'URDNA2015'; | ||
} | ||
jsonld.normalize( | ||
input, {algorithm: normalizeAlgorithm, format: 'application/nquads'}, | ||
callback); | ||
}, | ||
@@ -165,3 +190,2 @@ sign: ['normalize', function(callback, results) { | ||
var inputJson = ''; | ||
try { | ||
@@ -172,3 +196,2 @@ inputJson = JSON.stringify(input, null, 2); | ||
} | ||
return callback(new Error('[jsig.sign] ' + | ||
@@ -182,2 +205,3 @@ 'The data to sign is empty. This error may be caused because ' + | ||
_createSignature(normalized, { | ||
algorithm: algorithm, | ||
privateKeyPem: privateKeyPem, | ||
@@ -193,3 +217,3 @@ date: date, | ||
'@context': api.SECURITY_CONTEXT_URL, | ||
type: 'GraphSignature2012', | ||
type: algorithm, | ||
creator: creator, | ||
@@ -256,3 +280,205 @@ created: date, | ||
options = options || {}; | ||
var jsonld = api.use('jsonld'); | ||
// TODO: frame before getting signature, not just compact? considerations: | ||
// should the assumption be that the signature is on the top-level object | ||
// and thus framing is unnecessary? | ||
// compact to get signature and types | ||
jsonld.compact(input, api.SECURITY_CONTEXT_URL, function(err, compacted) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
var signature = jsonld.getValues(compacted, 'signature')[0] || null; | ||
if(!signature) { | ||
return callback(new Error('[jsigs.verify] No signature found.')); | ||
} | ||
var algorithm = jsonld.getValues(signature, 'type')[0] || ''; | ||
if(['GraphSignature2012', 'LinkedDataSignature2015'].indexOf( | ||
algorithm) === -1) { | ||
return callback(new Error('[jsigs.verify] Unsupported signature type.')); | ||
} | ||
if(algorithm === 'GraphSignature2012') { | ||
return _verifyGraphSignature2012(input, options, callback); | ||
} | ||
if(algorithm === 'LinkedDataSignature2015') { | ||
return _verifyLinkedDataSignature2015(input, options, callback); | ||
} | ||
}); | ||
}; | ||
/* Helper functions */ | ||
/** | ||
* Gets a remote public key. | ||
* | ||
* @param id the ID for the public key. | ||
* @param [options] the options to use: | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, key) called once the operation completes. | ||
*/ | ||
api.getPublicKey = function(id, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
api.getJsonLd(id, options, function(err, key) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
// FIXME: improve validation | ||
if(!('publicKeyPem' in key)) { | ||
return callback(new Error('[jsigs.getPublicKey] ' + | ||
'Could not get public key. Unknown format.')); | ||
} | ||
callback(null, key); | ||
}); | ||
}; | ||
/** | ||
* Checks to see if the given key is trusted. | ||
* | ||
* @param key the public key to check. | ||
* @param [options] the options to use: | ||
* [publicKeyOwner] the JSON-LD document describing the public key | ||
* owner. | ||
* [checkKeyOwner(owner, key)] a custom method to return whether | ||
* or not the key owner is trusted. | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, trusted) called once the operation completes. | ||
*/ | ||
api.checkKey = function(key, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
var jsonld = api.use('jsonld'); | ||
var async = api.use('async'); | ||
async.auto({ | ||
getOwner: function(callback) { | ||
if(options.publicKeyOwner) { | ||
return callback(null, options.publicKeyOwner); | ||
} | ||
api.getJsonLd(key.owner, options, callback); | ||
}, | ||
frameKey: function(callback) { | ||
var frame = { | ||
'@context': api.SECURITY_CONTEXT_URL, | ||
type: 'CryptographicKey' | ||
}; | ||
jsonld.frame(key, frame, function(err, framed) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
if(!framed['@graph'][0]) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The public key is not a CryptographicKey.')); | ||
} | ||
callback(null, framed['@graph'][0]); | ||
}); | ||
}, | ||
frameOwner: ['getOwner', function(callback, results) { | ||
var frame = { | ||
'@context': api.SECURITY_CONTEXT_URL, | ||
publicKey: {'@embed': '@never'} | ||
}; | ||
jsonld.frame(results.getOwner, frame, function(err, framed) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
callback(null, framed['@graph']); | ||
}); | ||
}], | ||
checkOwner: ['frameOwner', 'frameKey', function(callback, results) { | ||
// find specific owner of key | ||
var owner; | ||
var owners = results.frameOwner; | ||
var framedKey = results.frameKey; | ||
for(var i = 0; i < owners.length; ++i) { | ||
if(jsonld.hasValue(owners[i], 'publicKey', framedKey.id)) { | ||
owner = owners[i]; | ||
break; | ||
} | ||
} | ||
if(!owner) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The public key is not owned by its declared owner.')); | ||
} | ||
if(!options.checkKeyOwner) { | ||
return callback(); | ||
} | ||
options.checkKeyOwner(owner, key, options, function(err, trusted) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
if(!trusted) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The owner of the public key is not trusted.')); | ||
} | ||
return callback(); | ||
}); | ||
}] | ||
}, function(err) { | ||
callback(err, !err && true); | ||
}); | ||
}; | ||
/** | ||
* Retrieves a JSON-LD object over HTTP. To implement caching, override | ||
* this method. | ||
* | ||
* @param url the URL to HTTP GET. | ||
* @param [options] the options to use. | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, result) called once the operation completes. | ||
*/ | ||
api.getJsonLd = function(url, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
var jsonld = api.use('jsonld'); | ||
var documentLoader = options.documentLoader || jsonld.documentLoader; | ||
documentLoader(url, function(err, result) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
// ensure result is parsed | ||
if(typeof result.document === 'string') { | ||
try { | ||
result.document = JSON.parse(result.document); | ||
} catch(e) { | ||
return callback(e); | ||
} | ||
} | ||
if(!result.document) { | ||
return callback(new Error( | ||
'[jsigs.getJsonLd] No JSON-LD found at "' + url + '".')); | ||
} | ||
// compact w/context URL from link header | ||
if(result.contextUrl) { | ||
return jsonld.compact( | ||
result.document, result.contextUrl, {expandContext: result.contextUrl}, | ||
callback); | ||
} | ||
callback(null, result.document); | ||
}); | ||
}; | ||
// handle dependency injection | ||
(function() { | ||
var inject = options.inject || {}; | ||
for(var name in inject) { | ||
api.use(name, inject[name]); | ||
} | ||
})(); | ||
function _verifyGraphSignature2012(input, options, callback) { | ||
var checkTimestamp = ( | ||
@@ -262,3 +488,4 @@ 'checkTimestamp' in options ? options.checkTimestamp : false); | ||
'maxTimestampDelta' in options ? options.maxTimestampDelta : (15 * 60)); | ||
var jsonld = api.use('jsonld'); | ||
var async = api.use('async'); | ||
async.auto({ | ||
@@ -268,5 +495,2 @@ // FIXME: add support for multiple signatures | ||
// : for signed sigs, need to recurse? | ||
// FIXME: add support for different signature types | ||
// : frame with signatures to get types, then reframe to get | ||
// : correct structure for each type. | ||
frame: function(callback) { | ||
@@ -403,3 +627,4 @@ // frame message to retrieve signature | ||
jsonld.normalize( | ||
result, {format: 'application/nquads'}, function(err, normalized) { | ||
result, {algorithm: 'URGNA2012', format: 'application/nquads'}, | ||
function(err, normalized) { | ||
if(err) { | ||
@@ -414,7 +639,7 @@ return callback(err); | ||
var signature = results.normalize.signature; | ||
_verifySignature(results.normalize.data, signature.signatureValue, { | ||
algorithm: 'GraphSignature2012', | ||
publicKeyPem: key.publicKeyPem, | ||
nonce: signature.nonce, | ||
created: signature.created, | ||
date: signature.created, | ||
domain: signature.domain | ||
@@ -426,108 +651,111 @@ }, callback); | ||
}); | ||
}; | ||
} | ||
/* Helper functions */ | ||
/** | ||
* Gets a remote public key. | ||
* | ||
* @param id the ID for the public key. | ||
* @param [options] the options to use: | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, key) called once the operation completes. | ||
*/ | ||
api.getPublicKey = function(id, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
api.getJsonLd(id, options, function(err, key) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
// FIXME: improve validation | ||
if(!('publicKeyPem' in key)) { | ||
return callback(new Error('[jsigs.getPublicKey] ' + | ||
'Could not get public key. Unknown format.')); | ||
} | ||
callback(null, key); | ||
}); | ||
}; | ||
/** | ||
* Checks to see if the given key is trusted. | ||
* | ||
* @param key the public key to check. | ||
* @param [options] the options to use: | ||
* [publicKeyOwner] the JSON-LD document describing the public key | ||
* owner. | ||
* [checkKeyOwner(owner, key)] a custom method to return whether | ||
* or not the key owner is trusted. | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, trusted) called once the operation completes. | ||
*/ | ||
api.checkKey = function(key, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
function _verifyLinkedDataSignature2015(input, options, callback) { | ||
var checkTimestamp = ( | ||
'checkTimestamp' in options ? options.checkTimestamp : false); | ||
var maxTimestampDelta = ( | ||
'maxTimestampDelta' in options ? options.maxTimestampDelta : (15 * 60)); | ||
var jsonld = api.use('jsonld'); | ||
var async = api.use('async'); | ||
// TODO: better merge this code with GraphSignature2012 (make more DRY) | ||
async.auto({ | ||
getOwner: function(callback) { | ||
if(options.publicKeyOwner) { | ||
return callback(null, options.publicKeyOwner); | ||
} | ||
api.getJsonLd(key.owner, options, callback); | ||
}, | ||
frameKey: function(callback) { | ||
var frame = { | ||
// FIXME: add support for multiple signatures | ||
// : for many signers of an object, can just check all sigs | ||
// FIXME: frame to get signature, compacting for now because framing | ||
// requires @graph framing support in jsonld.js | ||
frame: function(callback) { | ||
/* var frame = { | ||
'@context': api.SECURITY_CONTEXT_URL, | ||
type: 'CryptographicKey' | ||
signature: { | ||
type: 'LinkedDataSignature2015', | ||
created: {}, | ||
creator: {}, | ||
signatureValue: {} | ||
} | ||
}; | ||
jsonld.frame(key, frame, function(err, framed) { | ||
if(options.id) { | ||
frame.id = options.id; | ||
}*/ | ||
jsonld.compact(input, api.SECURITY_CONTEXT_URL, function(err, framed) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
if(!framed['@graph'][0]) { | ||
var signatures = jsonld.getValues(framed, 'signature'); | ||
if(signatures.length > 1) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The public key is not a CryptographicKey.')); | ||
'More than one signed graph found.')); | ||
} | ||
callback(null, framed['@graph'][0]); | ||
callback(null, framed); | ||
}); | ||
}, | ||
frameOwner: ['getOwner', function(callback, results) { | ||
var frame = { | ||
'@context': api.SECURITY_CONTEXT_URL, | ||
publicKey: {'@embed': '@never'} | ||
checkNonce: ['frame', function(callback, results) { | ||
var signature = results.frame.signature; | ||
var cb = function(err, valid) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
if(!valid) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The message nonce is invalid.')); | ||
} | ||
callback(); | ||
}; | ||
jsonld.frame(results.getOwner, frame, function(err, framed) { | ||
if(!options.checkNonce) { | ||
return cb( | ||
null, (signature.nonce === null || signature.nonce === undefined)); | ||
} | ||
options.checkNonce(signature.nonce, options, cb); | ||
}], | ||
checkDomain: ['frame', function(callback, results) { | ||
var signature = results.frame.signature; | ||
var cb = function(err, valid) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
callback(null, framed['@graph']); | ||
}); | ||
if(!valid) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The message domain is invalid.')); | ||
} | ||
callback(); | ||
}; | ||
if(!options.checkDomain) { | ||
return cb( | ||
null, (signature.domain === null || signature.domain === undefined)); | ||
} | ||
options.checkDomain(signature.domain, options, cb); | ||
}], | ||
checkOwner: ['frameOwner', 'frameKey', function(callback, results) { | ||
// find specific owner of key | ||
var owner; | ||
var owners = results.frameOwner; | ||
var framedKey = results.frameKey; | ||
for(var i = 0; i < owners.length; ++i) { | ||
if(jsonld.hasValue(owners[i], 'publicKey', framedKey.id)) { | ||
owner = owners[i]; | ||
break; | ||
checkDate: ['frame', function(callback, results) { | ||
if(!checkTimestamp) { | ||
return callback(); | ||
} | ||
// ensure signature timestamp within a valid range | ||
var now = new Date().getTime(); | ||
var delta = maxTimestampDelta * 1000; | ||
try { | ||
var signature = results.frame.signature; | ||
var created = Date.parse(signature.created); | ||
if(created < (now - delta) || created > (now + delta)) { | ||
throw new Error('[jsigs.verify] ' + | ||
'The message digital signature timestamp is out of range.'); | ||
} | ||
} catch(ex) { | ||
return callback(ex); | ||
} | ||
if(!owner) { | ||
callback(); | ||
}], | ||
getPublicKey: ['frame', function(callback, results) { | ||
var signature = results.frame.signature; | ||
if(options.publicKey) { | ||
return callback(null, options.publicKey); | ||
} | ||
api.getPublicKey(signature.creator, options, callback); | ||
}], | ||
checkKey: ['getPublicKey', function(callback, results) { | ||
if('revoked' in results.getPublicKey) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The public key is not owned by its declared owner.')); | ||
'The message was signed with a key that has been revoked.')); | ||
} | ||
if(!options.checkKeyOwner) { | ||
return callback(); | ||
} | ||
options.checkKeyOwner(owner, key, options, function(err, trusted) { | ||
var cb = function(err, trusted) { | ||
if(err) { | ||
@@ -537,57 +765,45 @@ return callback(err); | ||
if(!trusted) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The owner of the public key is not trusted.')); | ||
throw new Error('[jsigs.verify] ' + | ||
'The message was not signed with a trusted key.'); | ||
} | ||
return callback(); | ||
callback(); | ||
}; | ||
if(options.checkKey) { | ||
return options.checkKey(results.getPublicKey, options, cb); | ||
} | ||
api.checkKey(results.getPublicKey, options, cb); | ||
}], | ||
normalize: [ | ||
'checkNonce', 'checkDate', 'checkKey', function(callback, results) { | ||
// remove signature property from object | ||
var result = results.frame; | ||
var signature = result.signature; | ||
delete result.signature; | ||
jsonld.normalize( | ||
result, {algorithm: 'URDNA2015', format: 'application/nquads'}, | ||
function(err, normalized) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
callback(null, {data: normalized, signature: signature}); | ||
}); | ||
}], | ||
verifySignature: ['normalize', function(callback, results) { | ||
var key = results.getPublicKey; | ||
var signature = results.normalize.signature; | ||
_verifySignature(results.normalize.data, signature.signatureValue, { | ||
algorithm: 'LinkedDataSignature2015', | ||
publicKeyPem: key.publicKeyPem, | ||
nonce: signature.nonce, | ||
date: signature.created, | ||
domain: signature.domain | ||
}, callback); | ||
}] | ||
}, function(err) { | ||
callback(err, !err && true); | ||
}, function(err, results) { | ||
callback(err, results.verifySignature); | ||
}); | ||
}; | ||
} | ||
/** | ||
* Retrieves a JSON-LD object over HTTP. To implement caching, override | ||
* this method. | ||
* | ||
* @param url the URL to HTTP GET. | ||
* @param [options] the options to use. | ||
* [documentLoader(url, callback(err, remoteDoc))] the document loader. | ||
* @param callback(err, result) called once the operation completes. | ||
*/ | ||
api.getJsonLd = function(url, options, callback) { | ||
if(typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
options = options || {}; | ||
var documentLoader = options.documentLoader || jsonld.documentLoader; | ||
documentLoader(url, function(err, result) { | ||
if(err) { | ||
return callback(err); | ||
} | ||
// ensure result is parsed | ||
if(typeof result.document === 'string') { | ||
try { | ||
result.document = JSON.parse(result.document); | ||
} catch(e) { | ||
return callback(e); | ||
} | ||
} | ||
if(!result.document) { | ||
return callback(new Error( | ||
'[jsigs.getJsonLd] No JSON-LD found at "' + url + '".')); | ||
} | ||
// compact w/context URL from link header | ||
if(result.contextUrl) { | ||
return jsonld.compact( | ||
result.document, result.contextUrl, {expandContext: result.contextUrl}, | ||
callback); | ||
} | ||
callback(null, result.document); | ||
}); | ||
}; | ||
/** | ||
* Implements the node.js/browser-specific code for creating a digital | ||
@@ -597,3 +813,4 @@ * signature. | ||
* @param input the data to sign. | ||
* @param [options] options to use: | ||
* @param options options to use: | ||
* algorithm 'GraphSignature2012' or 'LinkedDataSignature2015'. | ||
* privateKeyPem A PEM-encoded private key. | ||
@@ -605,40 +822,23 @@ * [date] an optional date to override the signature date with. | ||
*/ | ||
var _createSignature = null; | ||
if(_nodejs) { | ||
_createSignature = function(input, options, callback) { | ||
// generate base64-encoded signature | ||
var signer = crypto.createSign('RSA-SHA256'); | ||
if(options.nonce !== null) { | ||
signer.update(options.nonce); | ||
} | ||
signer.update(options.date); | ||
var _createSignature = (function() { | ||
if(_nodejs) { | ||
return function(input, options, callback) { | ||
var crypto = api.use('crypto'); | ||
var signer = crypto.createSign('RSA-SHA256'); | ||
signer.update(_getDataToHash(input, options), 'utf8'); | ||
var signature = signer.sign(options.privateKeyPem, 'base64'); | ||
callback(null, signature); | ||
}; | ||
} else if(_browser) { | ||
return function(input, options, callback) { | ||
var forge = api.use('forge'); | ||
var privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem); | ||
var md = forge.md.sha256.create(); | ||
md.update(_getDataToHash(input, options), 'utf8'); | ||
var signature = forge.util.encode64(privateKey.sign(md)); | ||
callback(null, signature); | ||
}; | ||
} | ||
})(); | ||
signer.update(input); | ||
if(options.domain !== null) { | ||
signer.update('@' + options.domain); | ||
} | ||
var signature = signer.sign(options.privateKeyPem, 'base64'); | ||
callback(null, signature); | ||
}; | ||
} else if(_browser) { | ||
_createSignature = function(input, options, callback) { | ||
// convert a PEM-formatted private key to a Forge private key | ||
var privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem); | ||
var md = forge.md.sha256.create(); | ||
if(options.nonce !== null) { | ||
md.update(options.nonce); | ||
} | ||
md.update(options.date); | ||
md.update(input); | ||
if(options.domain !== null) { | ||
md.update('@' + options.domain); | ||
} | ||
var signature = forge.util.encode64(privateKey.sign(md)); | ||
callback(null, signature); | ||
}; | ||
} | ||
/** | ||
@@ -650,3 +850,4 @@ * Implements the node.js/browser-specific code for creating a digital | ||
* @param signature the base-64 encoded signature on the data. | ||
* @param [options] options to use: | ||
* @param options options to use: | ||
* algorithm 'GraphSignature2012' or 'LinkedDataSignature2015'. | ||
* publicKeyPem A PEM-encoded public key. | ||
@@ -658,46 +859,59 @@ * [date] an optional date to override the signature date with. | ||
*/ | ||
var _verifySignature = null; | ||
if(_nodejs) { | ||
_verifySignature = function(input, signature, options, callback) { | ||
var verifier = crypto.createVerify('RSA-SHA256'); | ||
if(options.nonce) { | ||
verifier.update(options.nonce); | ||
} | ||
verifier.update(options.created); | ||
verifier.update(input); | ||
if(options.domain) { | ||
verifier.update('@' + options.domain); | ||
} | ||
var _verifySignature = (function() { | ||
if(_nodejs) { | ||
return function(input, signature, options, callback) { | ||
var crypto = api.use('crypto'); | ||
var verifier = crypto.createVerify('RSA-SHA256'); | ||
verifier.update(_getDataToHash(input, options), 'utf8'); | ||
var verified = verifier.verify(options.publicKeyPem, signature, 'base64'); | ||
if(!verified) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The digital signature on the message is invalid.')); | ||
} | ||
callback(null, verified); | ||
}; | ||
} else if(_browser) { | ||
return function(input, signature, options, callback) { | ||
var forge = api.use('forge'); | ||
var publicKey = forge.pki.publicKeyFromPem(options.publicKeyPem); | ||
var md = forge.md.sha256.create(); | ||
md.update(_getDataToHash(input, options), 'utf8'); | ||
var verified = publicKey.verify( | ||
md.digest().bytes(), forge.util.decode64(signature)); | ||
if(!verified) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The digital signature on the message is invalid.')); | ||
} | ||
callback(null, verified); | ||
}; | ||
} | ||
})(); | ||
var verified = verifier.verify(options.publicKeyPem, signature, 'base64'); | ||
if(!verified) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The digital signature on the message is invalid.')); | ||
function _getDataToHash(input, options) { | ||
var toHash = ''; | ||
if(options.algorithm === 'GraphSignature2012') { | ||
if(options.nonce !== null && options.nonce !== undefined) { | ||
toHash += options.nonce; | ||
} | ||
callback(null, verified); | ||
}; | ||
} else if(_browser) { | ||
_verifySignature = function(input, signature, options, callback) { | ||
var publicKey = forge.pki.publicKeyFromPem(options.publicKeyPem); | ||
var md = forge.md.sha256.create(); | ||
if(options.nonce !== undefined) { | ||
md.update(options.nonce); | ||
toHash += options.date; | ||
toHash += input; | ||
if(options.domain !== null && options.domain !== undefined) { | ||
toHash += '@' + options.domain; | ||
} | ||
md.update(options.created); | ||
md.update(input); | ||
if(options.domain !== undefined) { | ||
md.update('@' + options.domain); | ||
} else if(options.algorithm === 'LinkedDataSignature2015') { | ||
// headers are lexicographical order | ||
var headers = { | ||
'http://purl.org/dc/elements/1.1/created': options.date, | ||
'https://w3id.org/security#domain': options.domain, | ||
'https://w3id.org/security#nonce': options.nonce | ||
}; | ||
for(var key in headers) { | ||
var value = headers[key]; | ||
if(value !== null && value !== undefined) { | ||
toHash = key + ':' + value + '\n'; | ||
} | ||
} | ||
var verified = publicKey.verify( | ||
md.digest().bytes(), forge.util.decode64(signature)); | ||
if(!verified) { | ||
return callback(new Error('[jsigs.verify] ' + | ||
'The digital signature on the message is invalid.')); | ||
} | ||
callback(null, verified); | ||
}; | ||
toHash += input; | ||
} | ||
return toHash; | ||
} | ||
@@ -773,2 +987,3 @@ | ||
var slice = Array.prototype.slice; | ||
var jsonld = api.use('jsonld'); | ||
var promisify = jsonld.promisify; | ||
@@ -812,8 +1027,5 @@ | ||
// used to generate a new verifier API instance | ||
var factory = function(inject) { | ||
return wrap(function() {return factory();}, inject); | ||
var factory = function(options) { | ||
return wrap(function() {return factory();}, options); | ||
}; | ||
// TODO: remove automatic wrapping as it forces certain hard-coded dependencies | ||
// that may not actually be required (as they are instead provided via | ||
// dependency injection)... this may require a version bump | ||
wrap(factory); | ||
@@ -820,0 +1032,0 @@ |
{ | ||
"name": "jsonld-signatures", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "An implementation of the Linked Data Signatures specification for JSON-LD in node.js.", | ||
@@ -33,8 +33,3 @@ "homepage": "http://github.com/digitalbazaar/jsonld-signatures", | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "BSD", | ||
"url": "https://github.com/digitalbazaar/jsonld-signatures/raw/master/LICENSE" | ||
} | ||
], | ||
"license": "BSD-3-Clause", | ||
"main": "lib/jsonld-signatures.js", | ||
@@ -45,3 +40,3 @@ "dependencies": { | ||
"es6-promise": "~2.0.1", | ||
"jsonld": "~0.4.1", | ||
"jsonld": "~0.4.2", | ||
"node-forge": "~0.6.18", | ||
@@ -48,0 +43,0 @@ "underscore": "~1.7.0" |
@@ -28,2 +28,3 @@ /** | ||
var system = require('system'); | ||
require('./bind'); | ||
require('./setImmediate'); | ||
@@ -113,3 +114,3 @@ var _jsdir = system.env.JSDIR || 'lib'; | ||
describe('signing and verify w/o security context', function() { | ||
describe('signing and verify Graph2012 w/o security context', function() { | ||
// the test document that will be signed | ||
@@ -131,2 +132,3 @@ var testDocument = { | ||
jsigs.sign(testDocument, { | ||
algorithm: 'GraphSignature2012', | ||
privateKeyPem: testPrivateKeyPem, | ||
@@ -161,4 +163,51 @@ creator: testPublicKeyUrl | ||
describe('signing and verify w/security context', function() { | ||
describe('signing and verify Graph2015 w/o security context', function() { | ||
// the test document that will be signed | ||
var testDocument = { | ||
'@context': { | ||
schema: 'http://schema.org/', | ||
name: 'schema:name', | ||
homepage: 'schema:url', | ||
image: 'schema:image' | ||
}, | ||
name: 'Manu Sporny', | ||
homepage: 'https://manu.sporny.org/', | ||
image: 'https://manu.sporny.org/images/manu.png' | ||
}; | ||
var testDocumentSigned = {}; | ||
it('should successfully sign a local document', function(done) { | ||
jsigs.sign(testDocument, { | ||
algorithm: 'LinkedDataSignature2015', | ||
privateKeyPem: testPrivateKeyPem, | ||
creator: testPublicKeyUrl | ||
}, function(err, signedDocument) { | ||
assert.ifError(err); | ||
assert.notEqual( | ||
signedDocument['https://w3id.org/security#signature'], undefined, | ||
'signature was not created'); | ||
assert.equal( | ||
signedDocument['https://w3id.org/security#signature'] | ||
['http://purl.org/dc/terms/creator']['@id'], testPublicKeyUrl, | ||
'creator key for signature is wrong'); | ||
testDocumentSigned = signedDocument; | ||
done(); | ||
}); | ||
}); | ||
it('should successfully verify a local signed document', function(done) { | ||
jsigs.verify(testDocumentSigned, { | ||
publicKey: testPublicKey, | ||
publicKeyOwner: testPublicKeyOwner | ||
}, function(err, verified) { | ||
assert.ifError(err); | ||
assert.equal(verified, true, 'signature verification failed'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('signing and verify GraphSignature2012 w/security context', function() { | ||
// the test document that will be signed | ||
@@ -180,2 +229,3 @@ var testDocument = { | ||
jsigs.sign(testDocument, { | ||
algorithm: 'GraphSignature2012', | ||
privateKeyPem: testPrivateKeyPem, | ||
@@ -206,2 +256,47 @@ creator: testPublicKeyUrl | ||
}); | ||
describe('signing and verify LinkedDataSignature2015 w/security context', function() { | ||
// the test document that will be signed | ||
var testDocument = { | ||
'@context': [{ | ||
schema: 'http://schema.org/', | ||
name: 'schema:name', | ||
homepage: 'schema:url', | ||
image: 'schema:image' | ||
}, jsigs.SECURITY_CONTEXT_URL], | ||
name: 'Manu Sporny', | ||
homepage: 'https://manu.sporny.org/', | ||
image: 'https://manu.sporny.org/images/manu.png' | ||
}; | ||
var testDocumentSigned = {}; | ||
it('should successfully sign a local document', function(done) { | ||
jsigs.sign(testDocument, { | ||
algorithm: 'LinkedDataSignature2015', | ||
privateKeyPem: testPrivateKeyPem, | ||
creator: testPublicKeyUrl | ||
}, function(err, signedDocument) { | ||
assert.ifError(err); | ||
assert.notEqual(signedDocument.signature, undefined, | ||
'signature was not created'); | ||
assert.equal(signedDocument.signature.creator, testPublicKeyUrl, | ||
'creator key for signature is wrong'); | ||
testDocumentSigned = signedDocument; | ||
done(); | ||
}); | ||
}); | ||
it('should successfully verify a local signed document', function(done) { | ||
jsigs.verify(testDocumentSigned, { | ||
publicKey: testPublicKey, | ||
publicKeyOwner: testPublicKeyOwner | ||
}, function(err, verified) { | ||
assert.ifError(err); | ||
assert.equal(verified, true, 'signature verification failed'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -227,2 +322,3 @@ | ||
"GraphSignature2012": "sec:GraphSignature2012", | ||
"LinkedDataSignature2015": "sec:LinkedDataSignature2015", | ||
"CryptographicKey": "sec:Key", | ||
@@ -229,0 +325,0 @@ |
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
68927
13
1619
0
4
Updatedjsonld@~0.4.2