diff --git a/CHANGELOG.md b/CHANGELOG.md index 632737b1..f420c707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # jsonld-signatures ChangeLog +## 11.0.0 - 2022-08-xx + +### Changed +- **BREAKING**: Use `jsonld@7` to make use of `safe` mode. The `safe` mode + is a more robust replacement for `expansionMap`. + +### Removed +- **BREAKING**: Remove `expansionMap` option. Now `safe` mode is used internally + instead. + ## 10.0.0 - 2022-06-06 ### Changed diff --git a/lib/ProofSet.js b/lib/ProofSet.js index 95d4b2b8..28ab234f 100644 --- a/lib/ProofSet.js +++ b/lib/ProofSet.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2018-2022 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; @@ -7,7 +7,6 @@ const constants = require('./constants'); const jsonld = require('jsonld'); const {extendContextLoader, strictDocumentLoader} = require('./documentLoader'); const {serializeError} = require('serialize-error'); -const strictExpansionMap = require('./expansionMap'); module.exports = class ProofSet { /** @@ -35,16 +34,11 @@ module.exports = class ProofSet { * * @param [documentLoader] {function} a custom document loader, * `Promise documentLoader(url)`. - * @param [expansionMap] {function} A custom expansion map that is - * passed to the JSON-LD processor; by default a function that will throw - * an error when unmapped properties are detected in the input, use `false` - * to turn this off and allow unmapped properties to be dropped or use a - * custom function. * * @return {Promise} resolves with the signed document, with * the signature in the top-level `proof` property. */ - async add(document, {suite, purpose, documentLoader, expansionMap} = {}) { + async add(document, {suite, purpose, documentLoader} = {}) { if(!suite) { throw new TypeError('"options.suite" is required.'); } @@ -57,11 +51,6 @@ module.exports = class ProofSet { } else { documentLoader = strictDocumentLoader; } - if(expansionMap === undefined) { - expansionMap = strictExpansionMap; - } else if(expansionMap === false) { - expansionMap = undefined; - } // preprocess document to prepare to remove existing proofs // let input; @@ -73,7 +62,7 @@ module.exports = class ProofSet { // create the new proof (suites MUST output a proof using the security-v2 // `@context`) const proof = await suite.createProof({ - document: input, purpose, documentLoader, expansionMap + document: input, purpose, documentLoader }); jsonld.addValue(document, 'proof', proof); @@ -103,11 +92,6 @@ module.exports = class ProofSet { * * @param {function} [documentLoader] a custom document loader, * `Promise documentLoader(url)`. - * @param {function} [expansionMap] - A custom expansion map that is - * passed to the JSON-LD processor; by default a function that will throw - * an error when unmapped properties are detected in the input, use `false` - * to turn this off and allow unmapped properties to be dropped or use a - * custom function. * * @return {Promise<{verified: boolean, results: Array, error: *}>} resolves * with an object with a `verified`boolean property that is `true` if at @@ -115,7 +99,7 @@ module.exports = class ProofSet { * otherwise; a `results` property with an array of detailed results; * if `false` an `error` property will be present. */ - async verify(document, {suite, purpose, documentLoader, expansionMap} = {}) { + async verify(document, {suite, purpose, documentLoader} = {}) { if(!suite) { throw new TypeError('"options.suite" is required.'); } @@ -132,26 +116,19 @@ module.exports = class ProofSet { } else { documentLoader = strictDocumentLoader; } - if(expansionMap === undefined) { - expansionMap = strictExpansionMap; - } else if(expansionMap === false) { - expansionMap = undefined; - } try { // shallow copy to allow for removal of proof set prior to canonize document = {...document}; // get proofs from document - const {proofSet, document: doc} = await _getProofs({ - document, documentLoader, expansionMap - }); + const {proofSet, document: doc} = await _getProofs( + {document, documentLoader}); document = doc; // verify proofs - const results = await _verify({ - document, suites, proofSet, purpose, documentLoader, expansionMap - }); + const results = await _verify( + {document, suites, proofSet, purpose, documentLoader}); if(results.length === 0) { const error = new Error( 'Did not verify any proofs; insufficient proofs matched the ' + @@ -201,7 +178,7 @@ async function _getProofs({document}) { } async function _verify({ - document, suites, proofSet, purpose, documentLoader, expansionMap + document, suites, proofSet, purpose, documentLoader }) { // map each purpose to at least one proof to verify const purposes = Array.isArray(purpose) ? purpose : [purpose]; @@ -210,7 +187,7 @@ async function _verify({ const suiteMatchQueue = new Map(); await Promise.all(purposes.map(purpose => _matchProofSet({ purposeToProofs, proofToSuite, purpose, proofSet, suites, - suiteMatchQueue, document, documentLoader, expansionMap + suiteMatchQueue, document, documentLoader }))); // every purpose must have at least one matching proof or verify will fail @@ -233,9 +210,8 @@ async function _verify({ return {valid: true}; } }; - const {verified, verificationMethod, error} = await suite.verifyProof({ - proof, document, purpose, documentLoader, expansionMap - }); + const {verified, verificationMethod, error} = await suite.verifyProof( + {proof, document, purpose, documentLoader}); if(!vm) { vm = verificationMethod; } @@ -268,7 +244,7 @@ async function _verify({ let purposeResult; try { purposeResult = await purpose.validate(proof, { - document, suite, verificationMethod, documentLoader, expansionMap + document, suite, verificationMethod, documentLoader }); } catch(error) { purposeResult = {valid: false, error}; @@ -316,11 +292,11 @@ function _makeSerializable(error) { async function _matchProofSet({ purposeToProofs, proofToSuite, purpose, proofSet, suites, - suiteMatchQueue, document, documentLoader, expansionMap + suiteMatchQueue, document, documentLoader }) { for(const proof of proofSet) { // first check if the proof matches the purpose; if it doesn't continue - if(!await purpose.match(proof, {document, documentLoader, expansionMap})) { + if(!await purpose.match(proof, {document, documentLoader})) { continue; } @@ -339,7 +315,7 @@ async function _matchProofSet({ } let promise = matchingProofs.get(proof); if(!promise) { - promise = s.matchProof({proof, document, documentLoader, expansionMap}); + promise = s.matchProof({proof, document, documentLoader}); matchingProofs.set(proof, promise); } if(await promise) { diff --git a/lib/jsonld-signatures.js b/lib/jsonld-signatures.js index 428e6200..de006d98 100644 --- a/lib/jsonld-signatures.js +++ b/lib/jsonld-signatures.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2010-2018 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2010-2022 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; @@ -37,11 +37,7 @@ const VerificationError = require('./VerificationError'); * * Advanced optional parameters and overrides: * - * @param {function} [options.expansionMap] - A custom expansion map that is - * passed to the JSON-LD processor; by default a function that will throw - * an error when unmapped properties are detected in the input, use `false` - * to turn this off and allow unmapped properties to be dropped or use a - * custom function. + * @param {function} [options.expansionMap] - NOT SUPPORTED; do not use. * @param {boolean} [options.addSuiteContext=true] - Toggles the default * behavior of each signature suite enforcing the presence of its own * `@context` (if it is not present, it's added to the context list). @@ -51,6 +47,9 @@ const VerificationError = require('./VerificationError'); api.sign = async function sign(document, { suite, purpose, documentLoader, expansionMap, addSuiteContext = true } = {}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } if(typeof document !== 'object') { throw new TypeError('The "document" parameter must be an object.'); } @@ -60,8 +59,7 @@ api.sign = async function sign(document, { suite.ensureSuiteContext({document, addSuiteContext}); try { - return await new ProofSet().add( - document, {suite, purpose, documentLoader, expansionMap}); + return await new ProofSet().add(document, {suite, purpose, documentLoader}); } catch(e) { if(!documentLoader && e.name === 'jsonld.InvalidUrl') { const {details: {url}} = e; @@ -81,7 +79,8 @@ api.sign = async function sign(document, { * @param {object} document - The JSON-LD document with one or more proofs to be * verified. * - * @param {LinkedDataSignature|LinkedDataSignature[]} suite - + * @param {object} options - The options to use. + * @param {LinkedDataSignature|LinkedDataSignature[]} options.suite - * Acceptable signature suite instances for verifying the proof(s). * * @param {ProofPurpose} purpose - A proof purpose instance that will @@ -90,13 +89,9 @@ api.sign = async function sign(document, { * * Advanced optional parameters and overrides: * - * @param {function} [documentLoader] - A custom document loader, + * @param {function} [options.documentLoader] - A custom document loader, * `Promise documentLoader(url)`. - * @param {function} [expansionMap] - A custom expansion map that is - * passed to the JSON-LD processor; by default a function that will throw - * an error when unmapped properties are detected in the input, use `false` - * to turn this off and allow unmapped properties to be dropped or use a - * custom function. + * @param {function} [options.expansionMap] - NOT SUPPORTED; do not use. * * @return {Promise<{verified: boolean, results: Array, * error: VerificationError}>} @@ -107,12 +102,16 @@ api.sign = async function sign(document, { * containing all of the errors that occurred during the verification process. */ api.verify = async function verify(document, { - suite, purpose, documentLoader, expansionMap} = {}) { + suite, purpose, documentLoader, expansionMap +} = {}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } if(typeof document !== 'object') { throw new TypeError('The "document" parameter must be an object.'); } const result = await new ProofSet().verify( - document, {suite, purpose, documentLoader, expansionMap}); + document, {suite, purpose, documentLoader}); const {error} = result; if(error) { if(!documentLoader && error.name === 'jsonld.InvalidUrl') { @@ -136,4 +135,3 @@ api.purposes = require('./purposes').purposes; // expose document loader helpers Object.assign(api, require('./documentLoader')); - diff --git a/lib/purposes/ControllerProofPurpose.js b/lib/purposes/ControllerProofPurpose.js index e72f68a9..67226806 100644 --- a/lib/purposes/ControllerProofPurpose.js +++ b/lib/purposes/ControllerProofPurpose.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2018-2022 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; @@ -108,7 +108,7 @@ module.exports = class ControllerProofPurpose extends ProofPurpose { '@embed': '@never', id: verificationId } - }, {documentLoader, compactToRelative: false}); + }, {documentLoader, compactToRelative: false, safe: true}); } result.controller = document; } diff --git a/lib/purposes/ProofPurpose.js b/lib/purposes/ProofPurpose.js index 7b8b1de8..670262e2 100644 --- a/lib/purposes/ProofPurpose.js +++ b/lib/purposes/ProofPurpose.js @@ -43,7 +43,11 @@ module.exports = class ProofPurpose { */ async validate( proof, {/*document, suite, verificationMethod, - documentLoader, expansionMap*/}) { + documentLoader,*/ expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } + try { // check expiration if(this.maxTimestampDelta !== Infinity) { @@ -73,7 +77,10 @@ module.exports = class ProofPurpose { * @return {Promise} resolves to the proof instance (in the * `constants.SECURITY_CONTEXT_URL`. */ - async update(proof, {/*document, suite, documentLoader, expansionMap */}) { + async update(proof, {/*document, suite, documentLoader,*/ expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } proof.proofPurpose = this.term; return proof; } @@ -87,7 +94,10 @@ module.exports = class ProofPurpose { * * @return {Promise} `true` if there's a match, `false` if not. */ - async match(proof, {/* document, documentLoader, expansionMap */}) { + async match(proof, {/* document, documentLoader,*/ expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } return proof.proofPurpose === this.term; } }; diff --git a/lib/suites/LinkedDataProof.js b/lib/suites/LinkedDataProof.js index 8fbe797f..d1a4004d 100644 --- a/lib/suites/LinkedDataProof.js +++ b/lib/suites/LinkedDataProof.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2018-2022 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; @@ -12,10 +12,11 @@ module.exports = class LinkedDataProof { } /** - * @param document {object} to be signed. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.document - The document to be signed. + * @param {ProofPurpose} options.purpose - The proof purpose instance. + * @param {function} options.documentLoader - The document loader to use. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise} Resolves with the created proof object. */ @@ -26,11 +27,12 @@ module.exports = class LinkedDataProof { } /** - * @param proof {object} the proof to be verified. - * @param document {object} the document the proof applies to. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.proof - The proof to be verified. + * @param {object} options.document - The document the proof applies to. + * @param {ProofPurpose} options.purpose - The proof purpose instance. + * @param {function} options.documentLoader - The document loader to use. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise<{object}>} Resolves with the verification result. */ @@ -43,8 +45,8 @@ module.exports = class LinkedDataProof { /** * Checks whether a given proof exists in the document. * - * @param {object} options - Options hashmap. - * @param {object} options.proof + * @param {object} options - The options to use. + * @param {object} options.proof - The proof to match. * * @returns {Promise} Whether a match for the proof was found. */ diff --git a/lib/suites/LinkedDataSignature.js b/lib/suites/LinkedDataSignature.js index e01c661f..3faf1677 100644 --- a/lib/suites/LinkedDataSignature.js +++ b/lib/suites/LinkedDataSignature.js @@ -83,14 +83,19 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { } /** - * @param document {object} to be signed. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.document - The document to be signed. + * @param {ProofPurpose} options.purpose - The proof purpose instance. + * @param {function} options.documentLoader - The document loader to use. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise} Resolves with the created proof object. */ async createProof({document, purpose, documentLoader, expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } + // build proof (currently known as `signature options` in spec) let proof; if(this.proof) { @@ -123,50 +128,54 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { proof.verificationMethod = this.verificationMethod; // add any extensions to proof (mostly for legacy support) - proof = await this.updateProof({ - document, proof, purpose, documentLoader, expansionMap - }); + proof = await this.updateProof({document, proof, purpose, documentLoader}); // allow purpose to update the proof; the `proof` is in the // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must // ensure any added fields are also represented in that same `@context` proof = await purpose.update( - proof, {document, suite: this, documentLoader, expansionMap}); + proof, {document, suite: this, documentLoader}); // create data to sign - const verifyData = await this.createVerifyData({ - document, proof, documentLoader, expansionMap - }); + const verifyData = await this.createVerifyData( + {document, proof, documentLoader}); // sign data - proof = await this.sign( - {verifyData, document, proof, documentLoader, expansionMap}); + proof = await this.sign({verifyData, document, proof, documentLoader}); return proof; } /** - * @param document {object} to be signed. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.proof - The proof to be updated. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise} Resolves with the created proof object. */ - async updateProof({proof}) { + async updateProof({proof, expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } // extending classes may do more return proof; } /** - * @param proof {object} the proof to be verified. - * @param document {object} the document the proof applies to. - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.proof - The proof to be verified. + * @param {object} options.document - The document the proof applies to. + * @param {ProofPurpose} options.purpose - The proof purpose instance. + * @param {function} options.documentLoader - The document loader to use. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise<{object}>} Resolves with the verification result. */ async verifyProof({proof, document, documentLoader, expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } + try { // create data to verify const verifyData = await this.createVerifyData( @@ -191,17 +200,23 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { } async canonize(input, {documentLoader, expansionMap, skipExpansion}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } return jsonld.canonize(input, { algorithm: 'URDNA2015', format: 'application/n-quads', documentLoader, - expansionMap, + safe: true, skipExpansion, useNative: this.useNativeCanonize }); } async canonizeProof(proof, {document, documentLoader, expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } // `jws`,`signatureValue`,`proofValue` must not be included in the proof // options proof = { @@ -214,19 +229,24 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { return this.canonize(proof, { documentLoader, expansionMap, + safe: true, skipExpansion: false }); } /** - * @param document {object} to be signed/verified. - * @param proof {object} - * @param documentLoader {function} - * @param expansionMap {function} + * @param {object} options - The options to use. + * @param {object} options.document - The document to be signed/verified. + * @param {object} options.proof - The proof to be verified. + * @param {function} options.documentLoader - The document loader to use. + * @param {function} options.expansionMap - NOT SUPPORTED; do not use. * * @returns {Promise<{Uint8Array}>}. */ async createVerifyData({document, proof, documentLoader, expansionMap}) { + if(expansionMap) { + throw new Error('"expansionMap" not supported.'); + } // get cached document hash let cachedDocHash; const {_hashCache} = this; @@ -277,7 +297,7 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { '@context': constants.SECURITY_CONTEXT_URL, '@embed': '@always', id: verificationMethod - }, {documentLoader, compactToRelative: false}); + }, {documentLoader, compactToRelative: false, safe: true}); if(!framed) { throw new Error(`Verification method ${verificationMethod} not found.`); } diff --git a/package.json b/package.json index 9ff0bceb..7da39748 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ "lib/**/*.js" ], "dependencies": { - "jsonld": "^6.0.0", + "jsonld": "^7.0.0", "security-context": "^4.0.0", - "serialize-error": "^8.0.1" + "serialize-error": "^8.1.0" }, "devDependencies": { "chai": "^4.3.6",