diff --git a/.eslintrc.js b/.eslintrc.js index b5b2418f..f8f8c9ce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,22 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ module.exports = { root: true, + extends: [ + 'eslint-config-digitalbazaar', + // 'eslint-config-digitalbazaar/jsdoc' + ], env: { - commonjs: true, node: true }, - extends: ['eslint-config-digitalbazaar'] + parserOptions: { + // this is required for dynamic import() + ecmaVersion: 2020 + }, + ignorePatterns: ['node_modules', 'dist'], + rules: { + 'jsdoc/check-examples': 0, + 'jsdoc/require-description-complete-sentence': 0 + } }; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 431fe6b1..39e45004 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,75 +3,71 @@ name: Node.js CI on: [push] jobs: - test-node: + lint: runs-on: ubuntu-latest timeout-minutes: 10 strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [14.x] steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Run test with Node.js ${{ matrix.node-version }} - run: npm run test-node - test-karma: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Run eslint + run: npm run lint + test-node: runs-on: ubuntu-latest + needs: [lint] timeout-minutes: 10 strategy: matrix: - node-version: [14.x] - bundler: [webpack, browserify] + node-version: [12.x, 14.x] steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Run karma tests - env: - BUNDLER: ${{ matrix.bundler }} - run: npm run test-karma - lint: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Run test with Node.js ${{ matrix.node-version }} + run: npm run test-node + test-karma: runs-on: ubuntu-latest + needs: [lint] timeout-minutes: 10 strategy: matrix: node-version: [14.x] steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - name: Run eslint - run: npm run lint + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Run karma tests + run: npm run test-karma coverage: needs: [test-node, test-karma] - runs-on: ubuntu-latest timeout-minutes: 10 + runs-on: ubuntu-latest strategy: matrix: node-version: [14.x] steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Install - run: npm install - - name: Generate coverage report - run: npm run coverage-ci - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - file: ./coverage/lcov.info - fail_ci_if_error: true + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Generate coverage report + run: npm run coverage-ci + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + file: ./coverage/lcov.info + fail_ci_if_error: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 4703e4a3..4955e6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # jsonld-signatures ChangeLog +## 8.0.0 - + +### Changed +- **BREAKING**: Remove `PublicKeyProofPurpose`. +- **BREAKING**: Remove `GraphSignature2012` suite. +- **BREAKING**: Remove `LinkedDataSignature2015` suite. +- **BREAKING**: Drop support for deprecated `owner` proof property. +- **BREAKING**: Drop support for deprecated `creator` proof property. +- **BREAKING**: Remove bundled signature suites; all moved to external repos: + - `JwsLinkedDataSignature` suite moved to https://github.com/digitalbazaar/jws-linked-data-signature + - `RsaSignature2018` suite moved to https://github.com/digitalbazaar/rsa-signature-2018 + - `Ed25519Signature2018` suite moved to https://github.com/digitalbazaar/ed25519-signature-2018 +- **BREAKING**: Drop support for Node.js v10 (it's leaving LTS). +- **BREAKING**: Remove `compactProof` parameter when signing and verifying. This + means that going forward, documents are required to use the appropriate + contexts for the proof that they're using (error will be thrown otherwise). + ## 7.0.0 - 2021-02-11 ### Changed diff --git a/README.md b/README.md index fd41013c..696090c7 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,31 @@ -jsonld-signatures -================= +# JSON-LD Signatures _(jsonld-signatures)_ [![Build status](https://img.shields.io/github/workflow/status/digitalbazaar/jsonld-signatures/Node.js%20CI)](https://github.com/digitalbazaar/jsonld-signatures/actions?query=workflow%3A%22Node.js+CI%22) [![Coverage status](https://img.shields.io/codecov/c/github/digitalbazaar/jsonld-signatures)](https://codecov.io/gh/digitalbazaar/jsonld-signatures) [![Dependency Status](https://img.shields.io/david/digitalbazaar/jsonld-signatures.svg)](https://david-dm.org/digitalbazaar/jsonld-signatures) +[![NPM Version](https://img.shields.io/npm/v/jsonld-signatures.svg)](https://npm.im/jsonld-signatures) -An implementation of the Linked Data Signatures specification for JSON-LD. -This software works in all modern browsers as well as node.js via [npm](https://www.npmjs.com/package/jsonld-signatures). +> An implementation of the Linked Data Signatures specification for JSON-LD, for Node.js and browsers. -Introduction ------------- +## Table of Contents + +- [Background](#background) +- [Security](#security) +- [Install](#install) +- [Usage](#usage) +- [Contribute](#contribute) +- [Commercial Support](#commercial-support) +- [License](#license) + +## Background A Linked Data Signature proof is created (or verified) by specifying a signature suite and a proof purpose. The signature suite performs the cryptographic operation required to sign (or verify) a digital signature and includes information in a proof such as the -`verificationMethod` identifier (aka `creator`) and the date the proof was -created (aka `created`). +`verificationMethod` identifier, the proof's `controller`, and the date the +proof was created. The proof purpose indicates why the proof was created and what its intended use is. This information can also be used to make sure that the @@ -29,12 +37,27 @@ signing documents for reasons they did not intend. This library provides base classes for signature suites and proof purposes so that custom extensions can be written. It also provides some commonly -used signature suites and proof purposes. +used proof purposes. + +### Relationship to Verifiable Credentials + +`jsonld-signatures` is a low-level library that is meant to sign _any_ JSON-LD +document. + +One common use case for creating these signatures is for use with +[Verifiable Credentials](https://w3c.github.io/vc-data-model) (VCs). If you're +working with those, you should use a higher-level library that's specifically +made for that purpose, such as [`vc-js`](https://github.com/digitalbazaar/vc-js). +(Incidentally, `vc-js` uses this library, `jsonld-signatures`, under the hood.) + +## Security + +As with most security- and cryptography-related tools, the overall security of +your system will largely depend on your design decisions (which key types you +will use, where you'll store the private keys, what you put into your +credentials, and so on). -This library also supports legacy signature suites such as `GraphSignature2012`, -and `LinkedDataSignature2015`. These signature -suites must be used with a `PublicKeyProofPurpose` instance as the proof -purpose as they were created before extensible proof purposes were possible. +### Document Loader During verification, the key and key controller information must be discovered. This library allows for the key and key controller information to be looked up @@ -60,217 +83,67 @@ increase security by using a custom `documentLoader` that is similarly strict and will only load a subset of documents that is constrained by some technical, security, or business rules. -Install with npm: +## Install -``` -npm install jsonld-signatures -``` +- Node.js 12+ is required. -In Node.js, include the library like this: -```js -const jsigs = require('jsonld-signatures'); -``` +To install locally (for development): -In a browser environment, include `jsonld`, `forge`, and -`dist/jsonld-signatures.min.js` via script tag or other mechanism. - -Examples --------- - -Signing and verifying a simple assertion: - -```js -// to generate the next two lines, run the following command: -// -// openssl genrsa -out key.pem; cat key.pem; -// openssl rsa -in key.pem -pubout -out pubkey.pem; -// cat pubkey.pem; rm key.pem pubkey.pem -// -// for an example of how to specify these keys, look at [key-example]: -const publicKeyPem = "-----BEGIN PUBLIC KEY-----\r\n..."; -const privateKeyPem = "-----BEGIN PRIVATE KEY-----\r\n..."; - -// specify the public key object -const publicKey = { - '@context': jsigs.SECURITY_CONTEXT_URL, - type: 'RsaVerificationKey2018', - id: 'https://example.com/i/alice/keys/1', - controller: 'https://example.com/i/alice', - publicKeyPem -}; - -// specify the public key controller object -const controller = { - '@context': jsigs.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/alice', - publicKey: [publicKey], - // this authorizes this key to be used for making assertions - assertionMethod: [publicKey.id] -}; - -// create the JSON-LD document that should be signed -const doc = { - '@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' -}; - -// sign the document as a simple assertion -const {RsaSignature2018} = jsigs.suites; -const {AssertionProofPurpose} = jsigs.purposes; -const {RSAKeyPair} = require('crypto-ld'); -const {documentLoaders} = require('jsonld'); - -const key = new RSAKeyPair({...publicKey, privateKeyPem}); -const signed = await jsigs.sign(doc, { - suite: new RsaSignature2018({key}), - purpose: new AssertionProofPurpose() -}); - -console.log('Signed document:', signed); -// we will need the documentLoader to verify the controller -const {node: documentLoader} = documentLoaders; - -// verify the signed document -const result = await jsigs.verify(signed, { - documentLoader, - suite: new RsaSignature2018(key), - purpose: new AssertionProofPurpose({controller}) -}); -if(result.verified) { - console.log('Signature verified.'); -} else { - console.log('Signature verification error:', result.error); -} ``` - -Signing and verifying a document to authenticate to a website: - -```js -const publicKeyBase58 = 'GycSSui454dpYRKiFdsQ5uaE8Gy3ac6dSMPcAoQsk8yq'; -const privateKeyBase58 = '3Mmk4UzTRJTEtxaKk61LxtgUxAa2Dg36jF6Vog...SSiF'; - -// specify the public key object -const publicKey = { - '@context': jsigs.SECURITY_CONTEXT_URL, - type: 'Ed25519VerificationKey2018', - id: 'https://example.com/i/alice/keys/2', - controller: 'https://example.com/i/alice', - publicKeyBase58 -}; - -// specify the public key controller object -const controller = { - '@context': jsigs.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/alice', - publicKey: [publicKey], - // this authorizes this key to be used for authenticating - authentication: [publicKey.id] -}; - -// create the JSON-LD document that should be signed -const doc = { - '@context': { - schema: 'http://schema.org/', - action: 'schema:action' - }, - action: 'AuthenticateMe' -}; - -// sign the document for the purpose of authentication -const {Ed25519Signature2018} = jsigs.suites; -const {AuthenticationProofPurpose} = jsigs.purposes; -const {Ed25519KeyPair} = require('crypto-ld'); -const {documentLoaders} = require('jsonld'); - -const signed = await jsigs.sign(doc, { - suite: new Ed25519Signature2018({ - verificationMethod: publicKey.id, - key: new Ed25519KeyPair({privateKeyBase58}) - }), - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com' - }) -}); - -console.log('Signed document:', signed); -// we will need the documentLoader to verify the controller -const {node: documentLoader} = documentLoaders; - -// verify the signed document -const result = await jsigs.verify(signed, { - documentLoader, - suite: new Ed25519Signature2018({ - key: new Ed25519KeyPair(publicKey) - }), - purpose: new AuthenticationProofPurpose({ - controller, - challenge: 'abc', - domain: 'example.com' - }) -}); -if(result.verified) { - console.log('Signature verified.'); -} else { - console.log('Signature verification error:', result.error); -} +git clone https://github.com/digitalbazaar/jsonld-signatures.git +cd jsonld-signatures +npm install ``` -Node.js Native Canonize Bindings --------------------------------- - -Specialized use cases may wish to use the native canonize bindings. This mode -can be enabled by setting the `useNativeCanonize` option to `true`. See the -[jsonld.js notes](https://github.com/digitalbazaar/jsonld.js#nodejs-native-canonize-bindings) -on this feature and note you should benchmark performance before using it. - -Commercial Support ------------------- - -Commercial support for this library is available upon request from -Digital Bazaar: support@digitalbazaar.com - -Source ------- +## Usage -The source code for the JavaScript implementation of the JSON-LD Signatures API -is available at: +`jsonld-signatures` (version `8.x` and above) is not meant for standalone use. +Instead, it's generally used through an individual _crypto suite_. +For detailed usage instructions, see the READMEs of the supported suites: -https://github.com/digitalbazaar/jsonld-signatures +* [`Ed25519Signature2020`](https://github.com/digitalbazaar/ed25519-signature-2020) +* [`Ed25519Signature2018`](https://github.com/digitalbazaar/ed25519-signature-2018) -Tests ------ +Most of the usages with individual suites and key types will have elements in +common. You'll need to: -This library includes a sample testing utility which may be used to verify -that changes to the processor maintain the correct output. +* Generate or import cryptographic keys to sign with (see + the [`@digitalbazaar/crypto-ld >=v5.0`](https://github.com/digitalbazaar/crypto-ld)) + library), or use a secure `signer()` function provided by your secure + cryptographic module. +* _Authorize_ those keys for the specific purpose you're using + them for (see section on Proof Purpose below), using a Controller Document + (such as a DID Document or similar). +* Pair those keys with a corresponding cryptographic Signature Suite. + For greenfield development, we recommend the [`Ed25519Signature2020`](https://github.com/digitalbazaar/ed25519-signature-2020) + suite, and for legacy/compatibility work, you can use + [`Ed25519Signature2018`](https://github.com/digitalbazaar/ed25519-signature-2018) suite. + See also the [Choosing a Key Type](https://github.com/digitalbazaar/crypto-ld#choosing-a-key-type) + section of `crypto-ld` documentation. +* Set up your `documentLoader` to fetch contexts and documents securely. +* Lastly, perform the `jsigs.sign()` or `jsigs.verify()` operations. -To run the sample tests you will need to get the test suite files by cloning -the [jsonld-signatures repository][jsonld-signatures] hosted on GitHub. +### Node.js Native Canonize Bindings -https://github.com/digitalbazaar/jsonld-signatures/ - -Run the Node.js tests using the following command: +Specialized use cases may wish to use the native canonize bindings. This mode +can be enabled by setting the `useNativeCanonize` option to `true`. See the +[jsonld.js notes](https://github.com/digitalbazaar/jsonld.js#nodejs-native-canonize-bindings) +on this feature and note you should benchmark performance before using it. - npm run test +## Contribute -Run browser tests using ChromeHeadless using the following command: +See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)! - npm run test-karma +PRs accepted. -Run browser tests using a selection of browsers using the following command: +If editing the Readme, please conform to the +[standard-readme](https://github.com/RichardLitt/standard-readme) specification. - npm run test-karma -- --browsers Firefox,Chrome,ChromeHeadless +## Commercial Support -Code coverage of node tests can be generated in `coverage/`: +Commercial support for this library is available upon request from +Digital Bazaar: support@digitalbazaar.com - npm run coverage +## License -[jsonld-signatures]: https://github.com/digitalbazaar/jsonld-signatures/ -[key-example]: https://github.com/digitalbazaar/jsonld-signatures/blob/44f1f67db2cfb0b166b7d5f63c40e10cc4642416/tests/test.js#L73 +[New BSD License (3-clause)](LICENSE) © Digital Bazaar diff --git a/karma.conf.js b/karma.conf.js index 650de3d1..ce6f71dc 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,25 +1,7 @@ -/** - * Karam configuration for jsonld-signatures. - * - * Copyright (c) 2011-2018 Digital Bazaar, Inc. All rights reserved. +/* + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ module.exports = function(config) { - // bundler to test: webpack, browserify - const bundler = process.env.BUNDLER || 'webpack'; - - const frameworks = ['mocha']; - // main bundle preprocessors - const preprocessors = ['babel']; - - if(bundler === 'browserify') { - frameworks.push(bundler); - preprocessors.push(bundler); - } else if(bundler === 'webpack') { - preprocessors.push(bundler); - preprocessors.push('sourcemap'); - } else { - throw Error('Unknown bundler'); - } config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) @@ -27,72 +9,26 @@ module.exports = function(config) { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks, + frameworks: ['mocha', 'chai'], // list of files / patterns to load in the browser files: [ - { - pattern: 'tests/test-karma.js', - watched: false, served: true, included: true - } + 'test/*.spec.js' ], // list of files to exclude exclude: [], // preprocess matching files before serving them to the browser - // available preprocessors: - // https://npmjs.org/browse/keyword/karma-preprocessor + // preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - //'tests/*.js': ['webpack', 'babel'] //preprocessors - 'tests/*.js': preprocessors + 'test/*.js': ['webpack', 'sourcemap'] }, webpack: { + //mode: 'production', mode: 'development', - devtool: 'inline-source-map', - module: { - rules: [ - { - test: /\.js$/, - include: [{ - // exclude node_modules by default - exclude: /(node_modules)/ - }/*, { - // include jsonld and rdf-canonize - include: /(node_modules\/jsonld)/, - include: /(node_modules\/rdf-canonize)/ - }*/], - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - plugins: [ - [ - '@babel/plugin-proposal-object-rest-spread', - {useBuiltIns: true} - ], - '@babel/plugin-transform-modules-commonjs', - '@babel/plugin-transform-runtime' - ] - } - } - } - ] - }, - node: { - Buffer: false, - process: false, - crypto: false, - setImmediate: false - }, - }, - - browserify: { - debug: true, - plugin: [ - [require('esmify')] - ] + devtool: 'inline-source-map' }, // test results reporter to use @@ -108,34 +44,21 @@ module.exports = function(config) { colors: true, // level of logging - // possible values: - // config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || - // config.LOG_INFO || config.LOG_DEBUG + // possible values: config.LOG_DISABLE || config.LOG_ERROR || + // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, - // enable / disable watching file and executing tests whenever any file - // changes + // enable / disable watching file and executing test whenever any + // file changes autoWatch: false, // start these browsers - // available browser launchers: - // https://npmjs.org/browse/keyword/karma-launcher + // browser launchers: https://npmjs.org/browse/keyword/karma-launcher //browsers: ['ChromeHeadless', 'Chrome', 'Firefox', 'Safari'], browsers: ['ChromeHeadless'], - customLaunchers: { - IE9: { - base: 'IE', - 'x-ua-compatible': 'IE=EmulateIE9' - }, - IE8: { - base: 'IE', - 'x-ua-compatible': 'IE=EmulateIE8' - } - }, - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits + // if true, Karma captures browsers, runs the test and exits singleRun: true, // Concurrency level @@ -150,9 +73,6 @@ module.exports = function(config) { reporter: 'html' //delay: true } - }, - - // Proxied paths - proxies: {} + } }); }; diff --git a/lib/ProofSet.js b/lib/ProofSet.js index 508bfb36..a5a2f689 100644 --- a/lib/ProofSet.js +++ b/lib/ProofSet.js @@ -8,7 +8,6 @@ const jsonld = require('jsonld'); const {extendContextLoader, strictDocumentLoader} = require('./documentLoader'); const {serializeError} = require('serialize-error'); const strictExpansionMap = require('./expansionMap'); -const PublicKeyProofPurpose = require('./purposes/PublicKeyProofPurpose'); module.exports = class ProofSet { /** @@ -43,19 +42,11 @@ module.exports = class ProofSet { * 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 [compactProof] {boolean} `true` instructs this call to compact - * the resulting proof to the same JSON-LD `@context` as the input - * document; this is the default behavior. Setting this flag to `false` can - * be used as an optimization to prevent an unnecessary compaction when the - * caller knows that all used proof terms have the same definition in the - * document's `@context` as the `constants.SECURITY_CONTEXT_URL` `@context`. * * @return {Promise} resolves with the signed document, with * the signature in the top-level `proof` property. */ - async add(document, { - suite, purpose, documentLoader, expansionMap, - compactProof = true} = {}) { + async add(document, {suite, purpose, documentLoader, expansionMap} = {}) { if(!suite) { throw new TypeError('"options.suite" is required.'); } @@ -63,14 +54,6 @@ module.exports = class ProofSet { throw new TypeError('"options.purpose" is required.'); } - if(suite.legacy) { - if(!(purpose instanceof PublicKeyProofPurpose)) { - throw new TypeError( - `The "${suite.type}" suite requires "options.purpose" to be ` + - 'an instance of "PublicKeyProofPurpose".'); - } - } - if(documentLoader) { documentLoader = extendContextLoader(documentLoader); } else { @@ -86,65 +69,19 @@ module.exports = class ProofSet { } // preprocess document to prepare to remove existing proofs - let input; - if(compactProof) { - // cannot assume security context terms, so do full compaction - input = await jsonld.compact( - document, constants.SECURITY_CONTEXT_URL, - {documentLoader, expansionMap, compactToRelative: false}); - } else { - // TODO: optimize to modify document in place to maximize optimization - - // shallow copy document to allow removal of existing proofs - input = {...document}; - } + // let input; + // shallow copy document to allow removal of existing proofs + const input = {...document}; - // save but exclude any existing proof(s) - const proofProperty = suite.legacy ? 'signature' : 'proof'; - //const existingProofs = input[proofProperty]; - delete input[proofProperty]; + delete input.proof; // create the new proof (suites MUST output a proof using the security-v2 // `@context`) const proof = await suite.createProof({ - document: input, purpose, documentLoader, - expansionMap, compactProof}); + document: input, purpose, documentLoader, expansionMap + }); - if(compactProof) { - // compact proof to match document's context - let expandedProof; - if(suite.legacy) { - expandedProof = { - [constants.SECURITY_SIGNATURE_URL]: proof - }; - } else { - expandedProof = { - [constants.SECURITY_PROOF_URL]: {'@graph': proof} - }; - } - // account for type-scoped `proof` definition by getting document types - const {types, alias} = await _getTypeInfo( - {document, documentLoader, expansionMap}); - expandedProof['@type'] = types; - const ctx = jsonld.getValues(document, '@context'); - const compactProof = await jsonld.compact( - expandedProof, ctx, - {documentLoader, expansionMap, compactToRelative: false}); - delete compactProof[alias]; - delete compactProof['@context']; - - // add proof to document - const key = Object.keys(compactProof)[0]; - jsonld.addValue(document, key, compactProof[key]); - } else { - // in-place restore any existing proofs - /*if(existingProofs) { - document[proofProperty] = existingProofs; - }*/ - // add new proof - delete proof['@context']; - jsonld.addValue(document, proofProperty, proof); - } + jsonld.addValue(document, 'proof', proof); return document; } @@ -177,14 +114,6 @@ module.exports = class ProofSet { * 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 {boolean} [compactProof=true] - Indicates that this method cannot - * assume that the incoming document has defined all proof terms in the - * same way as the `constants.SECURITY_CONTEXT_URL` JSON-LD `@context`. - * This means that this method must compact any found proofs to this - * context for internal and extension processing; this is the default - * behavior. To override this behavior and optimize away this step because - * the caller knows that the input document's JSON-LD `@context` defines - * the proof terms in the same way, set this flag to `false`. * * @return {Promise<{verified: boolean, results: Array, error: *}>} resolves * with an object with a `verified`boolean property that is `true` if at @@ -192,9 +121,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, - compactProof = true} = {}) { + async verify(document, {suite, purpose, documentLoader, expansionMap} = {}) { if(!suite) { throw new TypeError('"options.suite" is required.'); } @@ -206,18 +133,6 @@ module.exports = class ProofSet { throw new TypeError('At least one suite is required.'); } - const legacy = suites.some(s => s.legacy); - if(legacy) { - if(suites.some(s => !s.legacy)) { - throw new Error( - 'Legacy suites may not be combined with current suites.'); - } else if(!(purpose instanceof PublicKeyProofPurpose)) { - throw new TypeError( - '"options.purpose" must be an instance of "PublicKeyProofPurpose"' + - 'to use a legacy suite.'); - } - } - if(documentLoader) { documentLoader = extendContextLoader(documentLoader); } else { @@ -241,13 +156,14 @@ module.exports = class ProofSet { // get proofs from document const {proofSet, document: doc} = await _getProofs({ - document, legacy, documentLoader, expansionMap, compactProof}); + document, documentLoader, expansionMap + }); document = doc; // verify proofs const results = await _verify({ - document, suites, proofSet, - purpose, documentLoader, expansionMap, compactProof}); + document, suites, proofSet, purpose, documentLoader, expansionMap + }); if(results.length === 0) { throw new Error( 'Could not verify any proofs; no proofs matched the required ' + @@ -273,20 +189,11 @@ module.exports = class ProofSet { } }; -async function _getProofs({ - document, legacy, documentLoader, expansionMap, compactProof}) { +async function _getProofs({document}) { // handle document preprocessing to find proofs - const proofProperty = legacy ? 'signature' : 'proof'; let proofSet; - if(compactProof) { - // if we must compact the proof(s) then we must first compact the input - // document to find the proof(s) - document = await jsonld.compact( - document, constants.SECURITY_CONTEXT_URL, - {documentLoader, expansionMap, compactToRelative: false}); - } - proofSet = jsonld.getValues(document, proofProperty); - delete document[proofProperty]; + proofSet = jsonld.getValues(document, 'proof'); + delete document.proof; if(proofSet.length === 0) { // no possible matches @@ -305,8 +212,8 @@ async function _getProofs({ } async function _verify({ - document, suites, proofSet, purpose, - documentLoader, expansionMap, compactProof}) { + document, suites, proofSet, purpose, documentLoader, expansionMap +}) { // filter out matching proofs const result = await Promise.all(proofSet.map(proof => purpose.match(proof, {document, documentLoader, expansionMap}))); @@ -321,8 +228,8 @@ async function _verify({ for(const s of suites) { if(await s.matchProof({proof, document, documentLoader, expansionMap})) { return s.verifyProof({ - proof, document, purpose, documentLoader, expansionMap, - compactProof}).catch(error => ({verified: false, error})); + proof, document, purpose, documentLoader, expansionMap + }).catch(error => ({verified: false, error})); } } }))).map((r, i) => { @@ -347,20 +254,3 @@ function _addToJSON(error) { writable: true }); } - -async function _getTypeInfo({document, documentLoader, expansionMap}) { - // determine `@type` alias, if any - const ctx = jsonld.getValues(document, '@context'); - const compacted = await jsonld.compact( - {'@type': '_:b0'}, ctx, {documentLoader, expansionMap}); - delete compacted['@context']; - const alias = Object.keys(compacted)[0]; - - // optimize: expand only `@type` and `type` values - const toExpand = {'@context': ctx}; - toExpand['@type'] = jsonld.getValues(document, '@type') - .concat(jsonld.getValues(document, alias)); - const expanded = (await jsonld.expand( - toExpand, {documentLoader, expansionMap}))[0] || {}; - return {types: jsonld.getValues(expanded, '@type'), alias}; -} diff --git a/lib/env.js b/lib/env.js deleted file mode 100644 index 1c5ee500..00000000 --- a/lib/env.js +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -// determine if using node.js or browser -const nodejs = ( - typeof process !== 'undefined' && process.versions && process.versions.node); -const browser = !nodejs && - (typeof window !== 'undefined' || typeof self !== 'undefined'); - -module.exports = { - nodejs, - browser -}; diff --git a/lib/jsonld-signatures.js b/lib/jsonld-signatures.js index 540b88a8..e142b7c1 100644 --- a/lib/jsonld-signatures.js +++ b/lib/jsonld-signatures.js @@ -38,22 +38,15 @@ const VerificationError = require('./VerificationError'); * 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 {boolean} [compactProof=true] - Indicates that this method cannot - * assume that the incoming document has defined all proof terms in the - * same way as the `constants.SECURITY_CONTEXT_URL` JSON-LD `@context`. - * This means that this method must compact any found proofs to this - * context for internal and extension processing; this is the default - * behavior. To override this behavior and optimize away this step because - * the caller knows that the input document's JSON-LD `@context` defines - * the proof terms in the same way, set this flag to `false`. * * @returns {Promise} Resolves with signed document. */ api.sign = async function sign(document, { - suite, purpose, documentLoader, expansionMap, compactProof} = {}) { + suite, purpose, documentLoader, expansionMap +} = {}) { try { return await new ProofSet().add( - document, {suite, purpose, documentLoader, expansionMap, compactProof}); + document, {suite, purpose, documentLoader, expansionMap}); } catch(e) { if(!documentLoader && e.name === 'jsonld.InvalidUrl') { const {details: {url}} = e; @@ -67,6 +60,7 @@ api.sign = async function sign(document, { } }; +/* eslint-disable max-len */ /** * Verifies the linked data signature on the provided document. * @@ -90,14 +84,6 @@ api.sign = async function sign(document, { * 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 {boolean} [compactProof=true] - Indicates that this method cannot - * assume that the incoming document has defined all proof terms in the - * same way as the `constants.SECURITY_CONTEXT_URL` JSON-LD `@context`. - * This means that this method must compact any found proofs to this - * context for internal and extension processing; this is the default - * behavior. To override this behavior and optimize away this step because - * the caller knows that the input document's JSON-LD `@context` defines - * the proof terms in the same way, set this flag to `false`. * * @return {Promise<{verified: boolean, results: Array, * error: VerificationError}>} @@ -107,10 +93,11 @@ api.sign = async function sign(document, { * if `false` an `error` property will be present, with `error.errors` * containing all of the errors that occurred during the verification process. */ +/* eslint-enable */ api.verify = async function verify(document, { - suite, purpose, documentLoader, expansionMap, compactProof} = {}) { + suite, purpose, documentLoader, expansionMap} = {}) { const result = await new ProofSet().verify( - document, {suite, purpose, documentLoader, expansionMap, compactProof}); + document, {suite, purpose, documentLoader, expansionMap}); const {error} = result; if(error) { if(!documentLoader && error.name === 'jsonld.InvalidUrl') { @@ -132,8 +119,5 @@ api.suites = require('./suites').suites; // expose ProofPurpose classes to enable extensions api.purposes = require('./purposes').purposes; -// expose LDKeyPair classes -Object.assign(api, require('crypto-ld')); - // expose document loader helpers Object.assign(api, require('./documentLoader')); diff --git a/lib/purposes.js b/lib/purposes.js index 674217c2..34aa6cb3 100644 --- a/lib/purposes.js +++ b/lib/purposes.js @@ -11,6 +11,5 @@ api.purposes = { AssertionProofPurpose: require('./purposes/AssertionProofPurpose'), AuthenticationProofPurpose: require('./purposes/AuthenticationProofPurpose'), ControllerProofPurpose: require('./purposes/ControllerProofPurpose'), - ProofPurpose: require('./purposes/ProofPurpose'), - PublicKeyProofPurpose: require('./purposes/PublicKeyProofPurpose') + ProofPurpose: require('./purposes/ProofPurpose') }; diff --git a/lib/purposes/ControllerProofPurpose.js b/lib/purposes/ControllerProofPurpose.js index c75a45d9..4a0805f0 100644 --- a/lib/purposes/ControllerProofPurpose.js +++ b/lib/purposes/ControllerProofPurpose.js @@ -62,8 +62,7 @@ module.exports = class ControllerProofPurpose extends ProofPurpose { if(this.controller) { result.controller = this.controller; } else { - // support legacy `owner` property - const {controller, owner} = verificationMethod; + const {controller} = verificationMethod; let controllerId; if(controller) { if(typeof controller === 'object') { @@ -74,15 +73,6 @@ module.exports = class ControllerProofPurpose extends ProofPurpose { } else { controllerId = controller; } - } else if(owner) { - if(typeof owner === 'object') { - controllerId = owner.id; - } else if(typeof owner !== 'string') { - throw new TypeError( - '"owner" must be a string representing a URL.'); - } else { - controllerId = owner; - } } // Note: `expansionMap` is intentionally not passed; we can safely drop // properties here and must allow for it @@ -98,6 +88,7 @@ module.exports = class ControllerProofPurpose extends ProofPurpose { }, {documentLoader, compactToRelative: false}); result.controller = framed; } + const verificationMethods = jsonld.getValues( result.controller, this.term); result.valid = verificationMethods.some(vm => diff --git a/lib/purposes/PublicKeyProofPurpose.js b/lib/purposes/PublicKeyProofPurpose.js deleted file mode 100644 index 09fa3858..00000000 --- a/lib/purposes/PublicKeyProofPurpose.js +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const ControllerProofPurpose = require('./ControllerProofPurpose'); - -module.exports = class PublicKeyProofPurpose extends ControllerProofPurpose { - constructor({controller, date, maxTimestampDelta = Infinity} = {}) { - super({term: 'publicKey', controller, date, maxTimestampDelta}); - } - - async update(proof) { - // do not add `term` to proof - return proof; - } - - async match(proof) { - // `proofPurpose` must not be present in the proof to match as this - // proof purpose is a legacy, non-descript purpose for signing - return proof.proofPurpose === undefined; - } -}; diff --git a/lib/sha256digest-browser.js b/lib/sha256digest-browser.js new file mode 100644 index 00000000..1f7d4123 --- /dev/null +++ b/lib/sha256digest-browser.js @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +/* eslint-env browser */ +'use strict'; +const crypto = self && (self.crypto || self.msCrypto); + +module.exports = { + /** + * Hashes a string of data using SHA-256. + * + * @param {string} string - the string to hash. + * + * @return {Uint8Array} the hash digest. + */ + async sha256digest({string}) { + const bytes = new TextEncoder().encode(string); + return new Uint8Array( + await crypto.subtle.digest('SHA-256', bytes) + ); + } +}; diff --git a/lib/sha256digest.js b/lib/sha256digest.js new file mode 100644 index 00000000..2f669cd5 --- /dev/null +++ b/lib/sha256digest.js @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +'use strict'; +const crypto = require('crypto'); + +module.exports = { + /** + * Hashes a string of data using SHA-256. + * + * @param {string} string - the string to hash. + * + * @return {Uint8Array} the hash digest. + */ + async sha256digest({string}) { + return new Uint8Array( + crypto.createHash('sha256').update(string).digest() + ); + } +}; diff --git a/lib/suites.js b/lib/suites.js index 0530b6ca..be927028 100644 --- a/lib/suites.js +++ b/lib/suites.js @@ -8,11 +8,6 @@ module.exports = api; // TODO: only require dynamically as needed or according to build api.suites = { - Ed25519Signature2018: require('./suites/Ed25519Signature2018'), - JwsLinkedDataSignature: require('./suites/JwsLinkedDataSignature'), LinkedDataProof: require('./suites/LinkedDataProof'), - LinkedDataSignature: require('./suites/LinkedDataSignature'), - LinkedDataSignature2015: require('./suites/LinkedDataSignature2015'), - GraphSignature2012: require('./suites/GraphSignature2012'), - RsaSignature2018: require('./suites/RsaSignature2018') + LinkedDataSignature: require('./suites/LinkedDataSignature') }; diff --git a/lib/suites/Ed25519Signature2018.js b/lib/suites/Ed25519Signature2018.js deleted file mode 100644 index ab56578f..00000000 --- a/lib/suites/Ed25519Signature2018.js +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const {Ed25519KeyPair} = require('crypto-ld'); -const JwsLinkedDataSignature = require('./JwsLinkedDataSignature'); - -module.exports = class Ed25519Signature2018 extends JwsLinkedDataSignature { - /** - * @param type {string} Provided by subclass. - * - * One of these parameters is required to use a suite for signing: - * - * @param [creator] {string} A key id URL to the paired public key. - * @param [verificationMethod] {string} A key id URL to the paired public key. - * - * This parameter is required for signing: - * - * @param [signer] {function} an optional signer. - * - * Advanced optional parameters and overrides: - * - * @param [proof] {object} a JSON-LD document with options to use for - * the `proof` node (e.g. any other custom fields can be provided here - * using a context different from security-v2). - * @param [date] {string|Date} signing date to use if not passed. - * @param [key] {LDKeyPair} an optional crypto-ld KeyPair. - * @param [useNativeCanonize] {boolean} true to use a native canonize - * algorithm. - */ - constructor({ - signer, key, creator, verificationMethod, proof, date, useNativeCanonize - } = {}) { - super({ - type: 'Ed25519Signature2018', alg: 'EdDSA', LDKeyClass: Ed25519KeyPair, - creator, verificationMethod, signer, key, proof, date, - useNativeCanonize}); - this.requiredKeyType = 'Ed25519VerificationKey2018'; - } -}; diff --git a/lib/suites/GraphSignature2012.js b/lib/suites/GraphSignature2012.js deleted file mode 100644 index 18039766..00000000 --- a/lib/suites/GraphSignature2012.js +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const jsonld = require('jsonld'); -const forge = require('node-forge'); -const LinkedDataSignature2015 = require('./LinkedDataSignature2015'); - -module.exports = class GraphSignature2012 extends LinkedDataSignature2015 { - constructor({ - privateKeyPem, publicKeyPem, creator, date, domain, nonce} = {}) { - super({ - type: 'GraphSignature2012', - privateKeyPem, publicKeyPem, - creator, date, domain, nonce}); - } - - async canonize( - input, {documentLoader, expansionMap, skipExpansion}) { - return jsonld.canonize(input, { - algorithm: 'URGNA2012', - format: 'application/n-quads', - documentLoader, - expansionMap, - skipExpansion - }); - } - - /** - * @param document {object} to be signed/verified. - * @param proof {object} - * @param documentLoader {function} - * @param expansionMap {function} - * @param compactProof {boolean} - * - * @returns {Promise<{Uint8Array}>}. - */ - async createVerifyData({ - document, proof, documentLoader, expansionMap}) { - const c14n = await this.canonize(document, { - documentLoader, - expansionMap - }); - - let verifyData = ''; - if(proof.nonce !== null && proof.nonce !== undefined) { - verifyData += proof.nonce; - } - verifyData += proof.created; - verifyData += c14n; - if(proof.domain !== null && proof.domain !== undefined) { - verifyData += '@' + proof.domain; - } - const buffer = new forge.util.ByteBuffer(verifyData, 'utf8'); - return forge.util.binary.raw.decode(buffer.getBytes()); - } -}; diff --git a/lib/suites/JwsLinkedDataSignature.js b/lib/suites/JwsLinkedDataSignature.js deleted file mode 100644 index f6e26402..00000000 --- a/lib/suites/JwsLinkedDataSignature.js +++ /dev/null @@ -1,190 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const LinkedDataSignature = require('./LinkedDataSignature'); -const jsonld = require('jsonld'); -const util = require('../util'); - -module.exports = class JwsLinkedDataSignature extends LinkedDataSignature { - /** - * @param type {string} Provided by subclass. - * @param alg {string} JWS alg provided by subclass. - * @param [LDKeyClass] {LDKeyClass} provided by subclass or subclass - * overrides `getVerificationMethod`. - * - * One of these parameters is required to use a suite for signing: - * - * @param [creator] {string} A key id URL to the paired public key. - * @param [verificationMethod] {string} A key id URL to the paired public key. - * - * This parameter is required for signing: - * - * @param [signer] {function} an optional signer. - * - * Advanced optional parameters and overrides: - * - * @param [proof] {object} a JSON-LD document with options to use for - * the `proof` node (e.g. any other custom fields can be provided here - * using a context different from security-v2). - * @param [date] {string|Date} signing date to use if not passed. - * @param [key] {LDKeyPair} an optional crypto-ld KeyPair. - * @param [useNativeCanonize] {boolean} true to use a native canonize - * algorithm. - */ - constructor({ - type, alg, LDKeyClass, creator, verificationMethod, signer, key, proof, - date, useNativeCanonize} = {}) { - super({type, creator, verificationMethod, proof, date, useNativeCanonize}); - this.alg = alg; - this.LDKeyClass = LDKeyClass; - this.signer = signer; - if(key) { - if(verificationMethod === undefined && creator === undefined) { - const publicKey = key.publicNode(); - if(publicKey.owner) { - // use legacy signature terms - this.creator = publicKey.id; - } else { - // use newer signature terms - this.verificationMethod = publicKey.id; - } - } - this.key = key; - if(typeof key.signer === 'function') { - this.signer = key.signer(); - } - if(typeof key.verifier === 'function') { - this.verifier = key.verifier(); - } - } - } - - /** - * @param verifyData {Uint8Array}. - * @param proof {object} - * - * @returns {Promise<{object}>} the proof containing the signature value. - */ - async sign({verifyData, proof}) { - if(!(this.signer && typeof this.signer.sign === 'function')) { - throw new Error('A signer API has not been specified.'); - } - // JWS header - const header = { - alg: this.alg, - b64: false, - crit: ['b64'] - }; - - /* - +-------+-----------------------------------------------------------+ - | "b64" | JWS Signing Input Formula | - +-------+-----------------------------------------------------------+ - | true | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || | - | | BASE64URL(JWS Payload)) | - | | | - | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') || | - | | JWS Payload | - +-------+-----------------------------------------------------------+ - */ - - // create JWS data and sign - const encodedHeader = util.encodeBase64Url(JSON.stringify(header)); - const data = util.createJws({encodedHeader, verifyData}); - const signature = await this.signer.sign({data}); - - // create detached content signature - const encodedSignature = util.encodeBase64Url(signature); - proof.jws = encodedHeader + '..' + encodedSignature; - return proof; - } - - /** - * @param verifyData {Uint8Array}. - * @param verificationMethod {object}. - * @param document {object} the document the proof applies to. - * @param proof {object} the proof to be verified. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} - * - * @returns {Promise<{boolean}>} Resolves with the verification result. - */ - async verifySignature({verifyData, verificationMethod, proof}) { - if(!(proof.jws && typeof proof.jws === 'string' && - proof.jws.includes('.'))) { - throw new TypeError('The proof does not include a valid "jws" property.'); - } - // add payload into detached content signature - const [encodedHeader, /*payload*/, encodedSignature] = proof.jws.split('.'); - - let header; - try { - header = JSON.parse(util.decodeBase64UrlToString(encodedHeader)); - } catch(e) { - throw new Error('Could not parse JWS header; ' + e); - } - if(!(header && typeof header === 'object')) { - throw new Error('Invalid JWS header.'); - } - - // confirm header matches all expectations - if(!(header.alg === this.alg && header.b64 === false && - Array.isArray(header.crit) && header.crit.length === 1 && - header.crit[0] === 'b64') && Object.keys(header).length === 3) { - throw new Error( - `Invalid JWS header parameters for ${this.type}.`); - } - - // do signature verification - const signature = util.decodeBase64Url(encodedSignature); - const data = util.createJws({encodedHeader, verifyData}); - let {verifier} = this; - if(!verifier) { - const key = await this.LDKeyClass.from(verificationMethod); - verifier = key.verifier(); - } - return verifier.verify({data, signature}); - } - - async assertVerificationMethod({verificationMethod}) { - if(!jsonld.hasValue(verificationMethod, 'type', this.requiredKeyType)) { - throw new Error( - `Invalid key type. Key type must be "${this.requiredKeyType}".`); - } - } - - async getVerificationMethod({proof, documentLoader}) { - if(this.key) { - return this.key.publicNode(); - } - - const verificationMethod = await super.getVerificationMethod( - {proof, documentLoader}); - await this.assertVerificationMethod({verificationMethod}); - return verificationMethod; - } - - async matchProof({proof, document, purpose, documentLoader, expansionMap}) { - if(!await super.matchProof( - {proof, document, purpose, documentLoader, expansionMap})) { - return false; - } - if(!this.key) { - // no key specified, so assume this suite matches and it can be retrieved - return true; - } - - let {verificationMethod} = proof; - if(!verificationMethod) { - verificationMethod = proof.creator; - } - // only match if the key specified matches the one in the proof - if(typeof verificationMethod === 'object') { - return verificationMethod.id === this.key.id; - } - return verificationMethod === this.key.id; - } -}; diff --git a/lib/suites/LinkedDataProof.js b/lib/suites/LinkedDataProof.js index f5d86b2d..2a55c7c7 100644 --- a/lib/suites/LinkedDataProof.js +++ b/lib/suites/LinkedDataProof.js @@ -16,12 +16,11 @@ module.exports = class LinkedDataProof { * @param purpose {ProofPurpose} * @param documentLoader {function} * @param expansionMap {function} - * @param compactProof {boolean} * * @returns {Promise} Resolves with the created proof object. */ async createProof({ - /* document, purpose, documentLoader, expansionMap, compactProof */ + /* document, purpose, documentLoader, expansionMap */ }) { throw new Error('"createProof" must be implemented in a derived class.'); } diff --git a/lib/suites/LinkedDataSignature.js b/lib/suites/LinkedDataSignature.js index 832c5de1..de0caa1e 100644 --- a/lib/suites/LinkedDataSignature.js +++ b/lib/suites/LinkedDataSignature.js @@ -6,15 +6,13 @@ const constants = require('../constants'); const jsonld = require('jsonld'); const util = require('../util'); +const {sha256digest} = require('../sha256digest'); const LinkedDataProof = require('./LinkedDataProof'); module.exports = class LinkedDataSignature extends LinkedDataProof { /** * @param type {string} Provided by subclass. * - * One of these parameters is required to use a suite for signing: - * - * @param [creator] {string} A key id URL to the paired public key. * @param [verificationMethod] {string} A key id URL to the paired public key. * * Advanced optional parameters and overrides: @@ -27,15 +25,13 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { * algorithm. */ constructor({ - type, creator, verificationMethod, proof, date, - useNativeCanonize} = {}) { + type, verificationMethod, proof, date, useNativeCanonize} = {}) { // validate common options if(verificationMethod !== undefined && typeof verificationMethod !== 'string') { throw new TypeError('"verificationMethod" must be a URL string.'); } super({type}); - this.creator = creator; this.verificationMethod = verificationMethod; this.proof = proof; if(date !== undefined) { @@ -52,22 +48,19 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { * @param purpose {ProofPurpose} * @param documentLoader {function} * @param expansionMap {function} - * @param compactProof {boolean} * * @returns {Promise} Resolves with the created proof object. */ async createProof( - {document, purpose, documentLoader, expansionMap, compactProof}) { + {document, purpose, documentLoader, expansionMap}) { // build proof (currently known as `signature options` in spec) let proof; if(this.proof) { - // use proof JSON-LD document passed to API - proof = await jsonld.compact( - this.proof, constants.SECURITY_CONTEXT_URL, - {documentLoader, expansionMap, compactToRelative: false}); + // shallow copy + proof = {...this.proof}; } else { // create proof JSON-LD document - proof = {'@context': constants.SECURITY_CONTEXT_URL}; + proof = {}; } // ensure proof type is set @@ -80,26 +73,21 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { } // ensure date is in string format - if(date !== undefined && typeof date !== 'string') { + if(date && typeof date !== 'string') { date = util.w3cDate(date); } // add API overrides - if(date !== undefined) { + if(date) { proof.created = date; } - // `verificationMethod` is for newer suites, `creator` for legacy - if(this.verificationMethod !== undefined) { - proof.verificationMethod = this.verificationMethod; - } - if(this.creator !== undefined) { - proof.creator = this.creator; - } + + proof.verificationMethod = this.verificationMethod; // add any extensions to proof (mostly for legacy support) proof = await this.updateProof({ - document, proof, purpose, - documentLoader, expansionMap, compactProof}); + document, proof, purpose, documentLoader, expansionMap + }); // allow purpose to update the proof; the `proof` is in the // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must @@ -108,8 +96,9 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { proof, {document, suite: this, documentLoader, expansionMap}); // create data to sign - const verifyData = await this.createVerifyData( - {document, proof, documentLoader, expansionMap, compactProof}); + const verifyData = await this.createVerifyData({ + document, proof, documentLoader, expansionMap + }); // sign data proof = await this.sign( @@ -123,7 +112,6 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { * @param purpose {ProofPurpose} * @param documentLoader {function} * @param expansionMap {function} - * @param compactProof {boolean} * * @returns {Promise} Resolves with the created proof object. */ @@ -138,17 +126,16 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { * @param purpose {ProofPurpose} * @param documentLoader {function} * @param expansionMap {function} - * @param compactProof {boolean} * * @returns {Promise<{object}>} Resolves with the verification result. */ async verifyProof({ proof, document, purpose, documentLoader, expansionMap, - compactProof}) { + }) { try { // create data to verify const verifyData = await this.createVerifyData( - {document, proof, documentLoader, expansionMap, compactProof}); + {document, proof, documentLoader, expansionMap}); // fetch verification method const verificationMethod = await this.getVerificationMethod( @@ -187,10 +174,13 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { }); } - async canonizeProof(proof, {documentLoader, expansionMap}) { + async canonizeProof(proof, {document, documentLoader, expansionMap}) { // `jws`,`signatureValue`,`proofValue` must not be included in the proof // options - proof = {...proof}; + proof = { + '@context': document['@context'] || constants.SECURITY_CONTEXT_URL, + ...proof + }; delete proof.jws; delete proof.signatureValue; delete proof.proofValue; @@ -206,7 +196,6 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { * @param proof {object} * @param documentLoader {function} * @param expansionMap {function} - * @param compactProof {boolean} * * @returns {Promise<{Uint8Array}>}. */ @@ -214,37 +203,30 @@ module.exports = class LinkedDataSignature extends LinkedDataProof { document, proof, documentLoader, expansionMap}) { // concatenate hash of c14n proof options and hash of c14n document const c14nProofOptions = await this.canonizeProof( - proof, {documentLoader, expansionMap}); + proof, {document, documentLoader, expansionMap}); const c14nDocument = await this.canonize(document, { documentLoader, expansionMap }); return util.concat( - util.sha256(c14nProofOptions), - util.sha256(c14nDocument)); + await sha256digest({string: c14nProofOptions}), + await sha256digest({string: c14nDocument})); } /** * @param document {object} to be signed. * @param proof {object} * @param documentLoader {function} - * @param expansionMap {function} */ async getVerificationMethod({proof, documentLoader}) { let {verificationMethod} = proof; - if(!verificationMethod) { - // backwards compatibility support for `creator` - const {creator} = proof; - verificationMethod = creator; - } - if(typeof verificationMethod === 'object') { verificationMethod = verificationMethod.id; } if(!verificationMethod) { - throw new Error('No "verificationMethod" or "creator" found in proof.'); + throw new Error('No "verificationMethod" found in proof.'); } // Note: `expansionMap` is intentionally not passed; we can safely drop diff --git a/lib/suites/LinkedDataSignature2015.js b/lib/suites/LinkedDataSignature2015.js deleted file mode 100644 index b288cf87..00000000 --- a/lib/suites/LinkedDataSignature2015.js +++ /dev/null @@ -1,183 +0,0 @@ -/*! - * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const env = require('../env'); -const forge = require('node-forge'); -const LinkedDataSignature = require('./LinkedDataSignature'); - -module.exports = class LinkedDataSignature2015 extends LinkedDataSignature { - /** - * Advanced optional parameters and overrides: - * - * @param [domain] {string} domain to include in the signature. - * @param [nonce] {string} nonce to include in the signature. - * @param [useNativeCanonize] {boolean} true to use a native canonize - * algorithm. - */ - constructor({ - type = 'LinkedDataSignature2015', - privateKeyPem, publicKeyPem, creator, date, domain, nonce, - useNativeCanonize} = {}) { - if(domain !== undefined && typeof domain !== 'string') { - throw new TypeError('"domain" must be a string.'); - } - if(nonce !== undefined && typeof nonce !== 'string') { - throw new TypeError('"nonce" must be a string.'); - } - super({type, creator, date, domain, nonce, useNativeCanonize}); - this.legacy = true; - this.privateKeyPem = privateKeyPem; - this.publicKeyPem = publicKeyPem; - this.nonce = nonce; - this.domain = domain; - } - - /** - * @param document {object} to be signed. - * @param purpose {ProofPurpose} - * @param documentLoader {function} - * @param expansionMap {function} - * @param compactProof {boolean} - * - * @returns {Promise} Resolves with the created proof object. - */ - async updateProof({proof}) { - if(this.domain !== undefined) { - proof.domain = this.domain; - } - if(this.nonce !== undefined) { - proof.nonce = this.nonce; - } - return proof; - } - - /** - * @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 compactProof {boolean} - * - * @returns {Promise<{object}>} Resolves with the verification result. - */ - async verifyProof({ - proof, document, purpose, documentLoader, expansionMap, - compactProof}) { - try { - // check domain - if(this.domain !== undefined && proof.domain !== this.domain) { - throw new Error('The domain is not as expected; ' + - `domain="${proof.domain}", expected="${this.domain}"`); - } - - return super.verifyProof({ - proof, document, purpose, documentLoader, expansionMap, - compactProof}); - } catch(error) { - return {verified: false, error}; - } - } - - /** - * @param document {object} to be signed/verified. - * @param proof {object} - * @param documentLoader {function} - * @param expansionMap {function} - * @param compactProof {boolean} - * - * @returns {Promise<{Uint8Array}>}. - */ - async createVerifyData({ - document, proof, documentLoader, expansionMap}) { - const c14n = await this.canonize(document, { - documentLoader, - expansionMap - }); - - let verifyData = ''; - const headers = { - 'http://purl.org/dc/elements/1.1/created': proof.created, - 'https://w3id.org/security#domain': proof.domain, - 'https://w3id.org/security#nonce': proof.nonce - }; - // add headers in lexicographical order - const keys = Object.keys(headers).sort(); - for(let i = 0; i < keys.length; ++i) { - const key = keys[i]; - const value = headers[key]; - if(!(value === null || value === undefined)) { - verifyData += key + ': ' + value + '\n'; - } - } - verifyData += c14n; - const buffer = new forge.util.ByteBuffer(verifyData, 'utf8'); - return forge.util.binary.raw.decode(buffer.getBytes()); - } - - async sign({verifyData, proof}) { - const {privateKeyPem} = this; - if(typeof privateKeyPem !== 'string') { - throw new TypeError('"privateKeyPem" must be a PEM formatted string.'); - } - - let signature; - if(env.nodejs) { - // optimize using node libraries - const crypto = require('crypto'); - const signer = crypto.createSign('RSA-SHA256'); - signer.update(Buffer.from( - verifyData.buffer, verifyData.byteOffset, verifyData.length)); - signature = signer.sign(privateKeyPem, 'base64'); - } else { - // browser or other environment - const privateKey = forge.pki.privateKeyFromPem(privateKeyPem); - const md = forge.md.sha256.create(); - md.update(forge.util.binary.raw.encode(verifyData), 'binary'); - signature = forge.util.encode64(privateKey.sign(md)); - } - - proof.signatureValue = signature; - return proof; - } - - async verifySignature({verifyData, proof}) { - const {publicKeyPem} = this; - if(typeof publicKeyPem !== 'string') { - throw new TypeError( - 'Could not verify signature; invalid "publicKeyPem".'); - } - - if(env.nodejs) { - // optimize using node libraries - const crypto = require('crypto'); - const verifier = crypto.createVerify('RSA-SHA256'); - verifier.update(Buffer.from( - verifyData.buffer, verifyData.byteOffset, verifyData.length)); - return verifier.verify(publicKeyPem, proof.signatureValue, 'base64'); - } - - // browser or other environment - const publicKey = forge.pki.publicKeyFromPem(publicKeyPem); - const md = forge.md.sha256.create(); - md.update(forge.util.binary.raw.encode(verifyData), 'binary'); - try { - return publicKey.verify( - md.digest().bytes(), forge.util.decode64(proof.signatureValue)); - } catch(e) { - // simply return false, do return information about malformed signature - return false; - } - } - - async getVerificationMethod({proof, documentLoader}) { - const verificationMethod = await super.getVerificationMethod( - {proof, documentLoader}); - if(!this.publicKeyPem) { - this.publicKeyPem = verificationMethod.publicKeyPem; - } - return verificationMethod; - } -}; diff --git a/lib/suites/RsaSignature2018.js b/lib/suites/RsaSignature2018.js deleted file mode 100644 index 355dae1b..00000000 --- a/lib/suites/RsaSignature2018.js +++ /dev/null @@ -1,41 +0,0 @@ -/*!s - * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const {RSAKeyPair} = require('crypto-ld'); -const JwsLinkedDataSignature = require('./JwsLinkedDataSignature'); - -module.exports = class RsaSignature2018 extends JwsLinkedDataSignature { - /** - * @param type {string} Provided by subclass. - * - * One of these parameters is required to use a suite for signing: - * - * @param [creator] {string} A key id URL to the paired public key. - * @param [verificationMethod] {string} A key id URL to the paired public key. - * - * This parameter is required for signing: - * - * @param [signer] {function} an optional signer. - * - * Advanced optional parameters and overrides: - * - * @param [proof] {object} a JSON-LD document with options to use for - * the `proof` node (e.g. any other custom fields can be provided here - * using a context different from security-v2). - * @param [date] {string|Date} signing date to use if not passed. - * @param [key] {LDKeyPair} an optional crypto-ld KeyPair. - * @param [useNativeCanonize] {boolean} true to use a native canonize - * algorithm. - */ - constructor({ - signer, key, creator, verificationMethod, proof, date, useNativeCanonize - } = {}) { - super({ - type: 'RsaSignature2018', alg: 'PS256', LDKeyClass: RSAKeyPair, - creator, verificationMethod, signer, key, proof, date, - useNativeCanonize}); - this.requiredKeyType = 'RsaVerificationKey2018'; - } -}; diff --git a/lib/util.js b/lib/util.js index 2240cebb..c53cccee 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,16 +1,11 @@ /* - * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2017-2021 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; -const env = require('./env'); -const forge = require('node-forge'); - const api = {}; module.exports = api; -api.createJws = createJwsFactory(); - /** * Converts the given date into W3C datetime format (eg: 2011-03-09T21:55:41Z). * @@ -28,49 +23,6 @@ api.w3cDate = date => { return str.substr(0, str.length - 5) + 'Z'; }; -/** - * Encodes input according to the "Base64url Encoding" format as specified - * in JSON Web Signature (JWS) RFC7517. A URL safe character set is used and - * trailing '=', line breaks, whitespace, and other characters are omitted. - * - * @param data {Uint8Array} the data to encode. - * - * @return {String} the encoded value. - */ -api.encodeBase64Url = base64urlEncodeFactory(); - -/** - * Decodes input according to the "Base64url Encoding" format as specified - * in JSON Web Signature (JWS) RFC7517. A URL safe character set is used and - * trailing '=', line breaks, whitespace, and other characters are omitted. - * - * @param string {String} the string to decode. - * - * @return {Uint8Array} the decoded value. - */ -api.decodeBase64Url = base64urlDecodeFactory(); - -/** - * Decodes input according to the "Base64url Encoding" format as specified - * in JSON Web Signature (JWS) RFC7517. A URL safe character set is used and - * trailing '=', line breaks, whitespace, and other characters are omitted. - * - * @param string {String} the string to decode. - * - * @return {String} the decoded value as a string. - */ -api.decodeBase64UrlToString = base64urlDecodeToStringFactory(); - -/** - * Hashes a string of data using SHA-256. - * - * @param string {String} the string to hash. - * @param encoding {String} the string's encoding (e.g. 'utf8'). - * - * @return {Uint8Array} the hash digest. - */ -api.sha256 = sha256Factory(); - /** * Concatenates two Uint8Arrays. * @@ -79,152 +31,9 @@ api.sha256 = sha256Factory(); * * @return {Uint8Array} the result. */ -api.concat = concatFactory(); - -/** - * Converts a string to a Uint8Array. - * - * @param string {String}. - * @param encoding {String}, e.g. 'utf8'. - * - * @return {Uint8Array} the result. - */ -api.stringToBytes = stringToBytesFactory(); - -function stringToBytesFactory() { - if(env.nodejs) { - return (string, encoding) => { - const buffer = Buffer.from(string, encoding); - return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); - }; - } - return (string, encoding) => { - const buffer = new forge.util.ByteBuffer(string, encoding); - return forge.util.binary.raw.decode(buffer.getBytes()); - }; -} - -function concatFactory() { - if(env.nodejs) { - return (b1, b2) => { - const buffer = Buffer.concat([ - Buffer.from(b1.buffer, b1.byteOffset, b1.length), - Buffer.from(b2.buffer, b2.byteOffset, b2.length)]); - return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); - }; - } - return (b1, b2) => { - const rval = new Uint8Array(b1.length + b2.length); - rval.set(b1, 0); - rval.set(b2, b1.length); - return rval; - }; -} - -function sha256Factory() { - if(env.nodejs) { - const crypto = require('crypto'); - return (string, encoding) => { - const hash = crypto.createHash('sha256'); - hash.update(string, encoding); - const buffer = hash.digest(); - return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); - }; - } - return (string, encoding) => { - const md = forge.md.sha256.create(); - md.update(string, encoding || 'utf8'); - const buffer = md.digest(); - return forge.util.binary.raw.decode(buffer.getBytes()); - }; -} - -function createJwsFactory() { - if(env.nodejs) { - return ({encodedHeader, verifyData}) => { - const buffer = Buffer.concat([ - Buffer.from(encodedHeader + '.', 'utf8'), - Buffer.from(verifyData.buffer, verifyData.byteOffset, verifyData.length) - ]); - return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.length); - }; - } - return ({encodedHeader, verifyData}) => { - const buffer = new forge.util.ByteBuffer(encodedHeader + '.', 'utf8'); - const binaryString = forge.util.binary.raw.encode(verifyData); - buffer.putBytes(binaryString); - return forge.util.binary.raw.decode(buffer.getBytes()); - }; -} - -function base64urlEncodeFactory() { - if(env.nodejs) { - const base64url = require('base64url'); - return data => { - if(typeof data === 'string') { - return base64url(data); - } - return base64url(Buffer.from(data.buffer, data.byteOffset, data.length)); - }; - } - return data => { - let binaryString; - if(typeof data === 'string') { - binaryString = forge.util.encodeUtf8(data); - } else { - binaryString = forge.util.binary.raw.encode(data); - } - const enc = forge.util.encode64(binaryString); - return enc.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); - }; -} - -function base64urlDecodeFactory() { - if(env.nodejs) { - const base64url = require('base64url'); - return string => { - const buffer = base64url.toBuffer(string); - return new Uint8Array(buffer.buffer, buffer.offset, buffer.length); - }; - } - return string => { - // FIXME: forge supports alternative alphabets now -- use that instead? - // convert to regular base64 encoding and then decode - let base64 = string.replace(/-/g, '+').replace(/_/g, '/'); - const mod4 = base64.length % 4; - if(mod4 === 0) { - // pass - } else if(mod4 === 2) { - base64 = base64 + '=='; - } else if(mod4 === 3) { - base64 = base64 + '='; - } else { - throw new Error('Illegal base64 string.'); - } - return forge.util.binary.base64.decode(base64); - }; -} - -function base64urlDecodeToStringFactory() { - if(env.nodejs) { - const base64url = require('base64url'); - return string => base64url.decode(string); - } - return string => { - // FIXME: forge supports alternative alphabets now -- use that instead? - // convert to regular base64 encoding and then decode - let base64 = string.replace(/-/g, '+').replace(/_/g, '/'); - const mod4 = base64.length % 4; - if(mod4 === 0) { - // pass - } else if(mod4 === 2) { - base64 = base64 + '=='; - } else if(mod4 === 3) { - base64 = base64 + '='; - } else { - throw new Error('Illegal base64 string.'); - } - const binaryString = forge.util.decode64(base64); - return forge.util.decodeUtf8(binaryString); - }; -} +api.concat = (b1, b2) => { + const rval = new Uint8Array(b1.length + b2.length); + rval.set(b1, 0); + rval.set(b2, b1.length); + return rval; +}; diff --git a/package.json b/package.json index 14c2a789..7c381c02 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "contributors": [ "Dave Longley ", "Manu Sporny ", - "David I. Lehn " + "David I. Lehn ", + "Dmitri Zagidulin " ], "repository": { "type": "git", @@ -24,57 +25,43 @@ "license": "BSD-3-Clause", "main": "lib/jsonld-signatures.js", "files": [ - "browser/*.js", "dist/*.js", "dist/*.js.map", "lib/*.js", "lib/**/*.js" ], "dependencies": { - "base64url": "^3.0.1", - "crypto-ld": "^3.7.0", - "jsonld": "^4.0.1", - "node-forge": "^0.10.0", + "jsonld": "^5.0.0", "security-context": "^4.0.0", - "serialize-error": "^5.0.0" + "serialize-error": "^8.0.1" }, "devDependencies": { - "@babel/core": "^7.6.4", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-transform-modules-commonjs": "^7.6.0", - "@babel/plugin-transform-runtime": "^7.6.2", - "@babel/preset-env": "^7.6.3", - "@babel/runtime": "^7.6.3", - "babel-loader": "^8.0.6", - "browserify": "^16.5.0", - "chai": "^4.1.2", - "core-js": "^2.6.9", - "cross-env": "^6.0.3", - "eslint": "^7.18.0", + "@babel/core": "^7.13.8", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/plugin-transform-runtime": "^7.13.9", + "@babel/preset-env": "^7.13.9", + "@babel/runtime": "^7.13.9", + "babel-loader": "^8.2.2", + "chai": "^4.3.3", + "cross-env": "^7.0.3", + "eslint": "^7.21.0", "eslint-config-digitalbazaar": "^2.6.1", - "esmify": "^2.1.1", - "karma": "^4.3.0", + "eslint-plugin-jsdoc": "^32.2.0", + "karma": "^6.1.1", "karma-babel-preprocessor": "^8.0.1", - "karma-browserify": "^6.1.0", + "karma-chai": "^0.1.0", "karma-chrome-launcher": "^3.1.0", - "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.2.0", - "karma-ie-launcher": "^1.0.0", - "karma-mocha": "^1.3.0", + "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-sourcemap-loader": "^0.3.7", - "karma-tap-reporter": "0.0.6", - "karma-webpack": "^4.0.2", - "mocha": "^6.2.1", + "karma-sourcemap-loader": "^0.3.8", + "karma-webpack": "^5.0.0", + "mocha": "^8.3.1", "mocha-lcov-reporter": "^1.3.0", - "nyc": "^14.1.1", - "webpack": "^4.41.1", - "webpack-cli": "^3.3.9", - "webpack-merge": "^4.2.2" + "nyc": "^15.1.0", + "webpack": "^5.24.3" }, "engines": { - "node": ">=10" + "node": ">=12" }, "keywords": [ "JSON", @@ -86,16 +73,13 @@ "digital signatures" ], "scripts": { - "prepublish": "npm run build", - "build": "npm run build-webpack", - "build-webpack": "webpack", - "test": "npm run test-node", - "test-node": "cross-env NODE_ENV=test mocha --delay -t 30000 -A -R ${REPORTER:-spec} tests/test.js", - "test-karma": "karma start", + "test": "npm run lint && npm run test-node && npm run test-karma", + "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js", + "test-karma": "karma start karma.conf.js", "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text-summary npm run test-node", "coverage-ci": "cross-env NODE_ENV=test nyc --reporter=lcovonly npm run test-node", "coverage-report": "nyc report", - "lint": "eslint '*.js' 'lib/*.js' 'lib/**/*.js' 'tests/*.js' 'tests/**/*.js'" + "lint": "eslint ." }, "nyc": { "exclude": [ @@ -104,6 +88,6 @@ }, "browser": { "crypto": false, - "base64url": false + "./lib/sha256digest.js": "./lib/sha256digest-browser.js" } } diff --git a/tests/.eslintrc b/test/.eslintrc similarity index 100% rename from tests/.eslintrc rename to test/.eslintrc diff --git a/test/jsonld-signatures.spec.js b/test/jsonld-signatures.spec.js new file mode 100644 index 00000000..8962f083 --- /dev/null +++ b/test/jsonld-signatures.spec.js @@ -0,0 +1,18 @@ +/*! + * Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved. + */ +const chai = require('chai'); +chai.should(); +const {expect} = chai; + +const jsigs = require('../lib/jsonld-signatures'); + +/** + * NOTE: The existing test suite has been extracted to each individual signature + * suite's repository. + */ +describe('jsonld-signatures', () => { + it('should exist', async () => { + expect(jsigs).to.exist; + }); +}); diff --git a/tests/mock/Ed25519Signature2018.js b/tests/mock/Ed25519Signature2018.js deleted file mode 100644 index 6f1a6bab..00000000 --- a/tests/mock/Ed25519Signature2018.js +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const constants = require('../../lib/constants'); -const {Ed25519KeyPair} = require('crypto-ld'); -const {NOOP_PROOF_PURPOSE_URI} = require('./noop-purpose'); -const {nonSecurityContextTestDoc, securityContextTestDoc} = - require('./test-document'); -const {controllers, publicKeys, privateKeys} = require('./keys'); - -const mock = {}; -module.exports = mock; - -mock.nonSecurityContextSigned = { - ...nonSecurityContextTestDoc, - 'https://w3id.org/security#proof': { - '@graph': { - '@type': 'https://w3id.org/security#Ed25519Signature2018', - 'http://purl.org/dc/terms/created': { - '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2018-02-13T21:26:08Z' - }, - 'http://purl.org/dc/terms/creator': { - '@id': publicKeys.carol.id - }, - 'https://w3id.org/security#jws': - 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'UNcNI6x6KDA_hHux2RLM8_i9aoZY34GwcZevOjkSh22WoNB4FcP6dNgf2nKzX' + - 'XJIr-IqUnEwMYeD36fc8jv1AA', - 'https://w3id.org/security#proofPurpose': { - '@id': NOOP_PROOF_PURPOSE_URI - } - } - } -}; - -mock.securityContextSigned = { - ...securityContextTestDoc, - proof: { - type: 'Ed25519Signature2018', - created: '2018-02-13T21:26:08Z', - creator: publicKeys.carol.id, - jws: - 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'UNcNI6x6KDA_hHux2RLM8_i9aoZY34GwcZevOjkSh22WoNB4FcP6dNgf2nKzX' + - 'XJIr-IqUnEwMYeD36fc8jv1AA', - proofPurpose: NOOP_PROOF_PURPOSE_URI - } -}; - -mock.securityContextInvalidSignature = { - ...securityContextTestDoc, - proof: { - type: 'Ed25519Signature2018', - created: '2018-02-13T21:26:08Z', - creator: publicKeys.carol.id, - jws: - 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'ANcNI6x6KDA_hHux2RLM8_i9aoZY34GwcZevOjkSh22WoNB4FcP6dNgf2nKzX' + - 'XJIr-IqUnEwMYeD36fc8jv1AA', - proofPurpose: NOOP_PROOF_PURPOSE_URI - } -}; - -mock.parameters = {}; - -mock.parameters.sign = { - creator: publicKeys.carol.id, - date: '2018-02-13T21:26:08Z', - key: new Ed25519KeyPair({ - privateKeyBase58: privateKeys.carol.privateKeyBase58, - ...publicKeys.carol - }) -}; - -// this links back to a key with a controller object -mock.parameters.controllerObject = { - creator: publicKeys.ned.id, - date: '2018-02-13T21:26:08Z', - key: new Ed25519KeyPair({ - privateKeyBase58: privateKeys.ned.privateKeyBase58, - ...publicKeys.ned - }) -}; - -mock.parameters.verify = { - creator: publicKeys.carol.id, - date: '2018-02-13T21:26:08Z' -}; - -mock.parameters.verifyWithPassedKey = mock.parameters.sign; - -mock.parameters.authenticationController = { - '@context': constants.SECURITY_CONTEXT_URL, - id: controllers.carol.id, - authentication: publicKeys.carol.id -}; - -mock.parameters.assertionController = { - '@context': constants.SECURITY_CONTEXT_URL, - id: controllers.carol.id, - assertionMethod: publicKeys.carol.id -}; diff --git a/tests/mock/GraphSignature2012.js b/tests/mock/GraphSignature2012.js deleted file mode 100644 index ef2d3ad8..00000000 --- a/tests/mock/GraphSignature2012.js +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const {nonSecurityContextTestDoc, securityContextTestDoc} = - require('./test-document'); -const {publicKeys, privateKeys} = require('./keys'); - -const mock = {}; -module.exports = mock; - -mock.nonSecurityContextSigned = { - ...nonSecurityContextTestDoc, - 'https://w3id.org/security#signature': { - '@type': 'https://w3id.org/security#GraphSignature2012', - 'http://purl.org/dc/terms/created': { - '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2018-02-22T15:16:04Z' - }, - 'http://purl.org/dc/terms/creator': { - '@id': 'https://example.com/i/alice/keys/1' - }, - 'https://w3id.org/security#signatureValue': - 'JCsAA1dvXCNR7Ey3qLc4mLAy8JvdE0RSklNTWlIMqVZ8hrCpVSRoq86sJd4HS' + - 'eRFaor6YwwOiLZ4yQbNPvxkS4b85vlTrivz4OrLPi8XKT4ArUZUreHPEcaHmU' + - '6rIVnt3ySwOwMqSjfjzRrpxkoZzQbBnqFbqojF1hul1XuTT6AvFiwXyHY3z2w' + - 'qAJeetILLzIZoSDnpSrUKEEyZ/erfS7t/BXuSbmGjRm9p/MYXLSHk6oWI7Ydb' + - 'i8xXhpb4sIOdnCK3EES6CTGVdwWE7gJUPZ8lcGHhLo4fqGrVhqbAuWXvfBow4' + - 'YWDjiXgvJsVbeDRV1WM+abFMpvBRuvAn8ej2A==' - } -}; - -mock.securityContextSigned = { - ...securityContextTestDoc, - signature: { - type: 'GraphSignature2012', - created: '2018-02-22T15:16:04Z', - creator: 'https://example.com/i/alice/keys/1', - signatureValue: - 'JCsAA1dvXCNR7Ey3qLc4mLAy8JvdE0RSklNTWlIMqVZ8hrCpVSRoq86sJd4HS' + - 'eRFaor6YwwOiLZ4yQbNPvxkS4b85vlTrivz4OrLPi8XKT4ArUZUreHPEcaHmU' + - '6rIVnt3ySwOwMqSjfjzRrpxkoZzQbBnqFbqojF1hul1XuTT6AvFiwXyHY3z2w' + - 'qAJeetILLzIZoSDnpSrUKEEyZ/erfS7t/BXuSbmGjRm9p/MYXLSHk6oWI7Ydb' + - 'i8xXhpb4sIOdnCK3EES6CTGVdwWE7gJUPZ8lcGHhLo4fqGrVhqbAuWXvfBow4' + - 'YWDjiXgvJsVbeDRV1WM+abFMpvBRuvAn8ej2A==' - } -}; - -mock.securityContextInvalidSignature = { - ...securityContextTestDoc, - signature: { - type: 'GraphSignature2012', - created: '2018-02-22T15:16:04Z', - creator: 'https://example.com/i/alice/keys/1', - signatureValue: - 'jCsAA1dvXCNR7Ey3qLc4mLAy8JvdE0RSklNTWlIMqVZ8hrCpVSRoq86sJd4HS' + - 'eRFaor6YwwOiLZ4yQbNPvxkS4b85vlTrivz4OrLPi8XKT4ArUZUreHPEcaHmU' + - '6rIVnt3ySwOwMqSjfjzRrpxkoZzQbBnqFbqojF1hul1XuTT6AvFiwXyHY3z2w' + - 'qAJeetILLzIZoSDnpSrUKEEyZ/erfS7t/BXuSbmGjRm9p/MYXLSHk6oWI7Ydb' + - 'i8xXhpb4sIOdnCK3EES6CTGVdwWE7gJUPZ8lcGHhLo4fqGrVhqbAuWXvfBow4' + - 'YWDjiXgvJsVbeDRV1WM+abFMpvBRuvAn8ej2A==' - } -}; - -mock.parameters = {}; - -mock.parameters.sign = { - creator: publicKeys.alice.id, - date: '2018-02-22T15:16:04Z', - privateKeyPem: privateKeys.alice.privateKeyPem, - publicKeyPem: privateKeys.alice.publicKeyPem -}; - -mock.parameters.verify = { - creator: publicKeys.alice.id, - date: '2018-12-26T18:08:04Z' -}; - -mock.parameters.verifyWithPassedKey = mock.parameters.sign; diff --git a/tests/mock/LinkedDataSignature2015.js b/tests/mock/LinkedDataSignature2015.js deleted file mode 100644 index dc0f6d56..00000000 --- a/tests/mock/LinkedDataSignature2015.js +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const {nonSecurityContextTestDoc, securityContextTestDoc} = - require('./test-document'); -const {publicKeys, privateKeys} = require('./keys'); - -const mock = {}; -module.exports = mock; - -mock.nonSecurityContextSigned = { - ...nonSecurityContextTestDoc, - 'https://w3id.org/security#signature': { - '@type': 'https://w3id.org/security#LinkedDataSignature2015', - 'http://purl.org/dc/terms/created': { - '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2018-02-22T15:16:04Z' - }, - 'http://purl.org/dc/terms/creator': { - '@id': 'https://example.com/i/alice/keys/1' - }, - 'https://w3id.org/security#signatureValue': - 'hztABpsP4e/emXsUYFUQ8HstjrgMfbMXbVcUhfYBHCCYuIgfsmTTOT4uaL5zwF' + - 'EpkSBNxskB46IAnqdkIYU2KF+7pDyBbIe7GMm60zIAavmIsAcxh034ENJreUgX' + - 'eIQhpx/ZPKcQCBPwDHrbxXiBxS/VmkvJLbtjfy0qj96f6Gc2AvaB43ZHwO88TV' + - 'lzxQFa8gnJt2embI41u4Gz7+jmx2yc/1lEH/iOmm1g3OTmnJu3OmvLGvygLogu' + - 'n17ueUbTA1Fam2H78V1UI/E90i/PY3Doy1VGDkdLsW8UJGTZOVSpmf0acqik91' + - 'pS1OuHcvyzN+HdRolszzdkrORzM8mxmg==' - } -}; - -mock.securityContextSigned = { - ...securityContextTestDoc, - signature: { - type: 'LinkedDataSignature2015', - created: '2018-02-22T15:16:04Z', - creator: 'https://example.com/i/alice/keys/1', - signatureValue: - 'hztABpsP4e/emXsUYFUQ8HstjrgMfbMXbVcUhfYBHCCYuIgfsmTTOT4uaL5zwF' + - 'EpkSBNxskB46IAnqdkIYU2KF+7pDyBbIe7GMm60zIAavmIsAcxh034ENJreUgX' + - 'eIQhpx/ZPKcQCBPwDHrbxXiBxS/VmkvJLbtjfy0qj96f6Gc2AvaB43ZHwO88TV' + - 'lzxQFa8gnJt2embI41u4Gz7+jmx2yc/1lEH/iOmm1g3OTmnJu3OmvLGvygLogu' + - 'n17ueUbTA1Fam2H78V1UI/E90i/PY3Doy1VGDkdLsW8UJGTZOVSpmf0acqik91' + - 'pS1OuHcvyzN+HdRolszzdkrORzM8mxmg==' - } -}; - -mock.securityContextInvalidSignature = { - ...securityContextTestDoc, - signature: { - type: 'LinkedDataSignature2015', - created: '2018-02-22T15:16:04Z', - creator: 'https://example.com/i/alice/keys/1', - signatureValue: - 'HztABpsP4e/emXsUYFUQ8HstjrgMfbMXbVcUhfYBHCCYuIgfsmTTOT4uaL5zwF' + - 'EpkSBNxskB46IAnqdkIYU2KF+7pDyBbIe7GMm60zIAavmIsAcxh034ENJreUgX' + - 'eIQhpx/ZPKcQCBPwDHrbxXiBxS/VmkvJLbtjfy0qj96f6Gc2AvaB43ZHwO88TV' + - 'lzxQFa8gnJt2embI41u4Gz7+jmx2yc/1lEH/iOmm1g3OTmnJu3OmvLGvygLogu' + - 'n17ueUbTA1Fam2H78V1UI/E90i/PY3Doy1VGDkdLsW8UJGTZOVSpmf0acqik91' + - 'pS1OuHcvyzN+HdRolszzdkrORzM8mxmg==' - } -}; - -mock.parameters = {}; - -mock.parameters.sign = { - creator: publicKeys.alice.id, - date: '2018-02-22T15:16:04Z', - privateKeyPem: privateKeys.alice.privateKeyPem, - publicKeyPem: privateKeys.alice.publicKeyPem -}; - -mock.parameters.verify = { - creator: publicKeys.alice.id, - date: '2018-02-22T15:16:04Z' -}; - -mock.parameters.verifyWithPassedKey = mock.parameters.sign; diff --git a/tests/mock/RsaSignature2018.js b/tests/mock/RsaSignature2018.js deleted file mode 100644 index 307743b6..00000000 --- a/tests/mock/RsaSignature2018.js +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const constants = require('../../lib/constants'); -const {NOOP_PROOF_PURPOSE_URI} = require('./noop-purpose'); -const {nonSecurityContextTestDoc, securityContextTestDoc} = - require('./test-document'); -const {controllers, publicKeys, privateKeys} = require('./keys'); -const {RSAKeyPair} = require('crypto-ld'); - -const mock = {}; -module.exports = mock; - -mock.nonSecurityContextSigned = { - ...nonSecurityContextTestDoc, - 'https://w3id.org/security#proof': { - '@graph': { - '@type': 'https://w3id.org/security#RsaSignature2018', - 'http://purl.org/dc/terms/created': { - '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2018-02-22T15:16:04Z' - }, - 'http://purl.org/dc/terms/creator': { - '@id': publicKeys.alice.id - }, - 'https://w3id.org/security#jws': - 'eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'dJBMvvFAIC00nSGB6Tn0XKbbF9XrsaJZREWvR2aONYTQQxnyXirtXnlewJMB' + - 'Bn2h9hfcGZrvnC1b6PgWmukzFJ1IiH1dWgnDIS81BH-IxXnPkbuYDeySorc4' + - 'QU9MJxdVkY5EL4HYbcIfwKj6X4LBQ2_ZHZIu1jdqLcRZqHcsDF5KKylKc1TH' + - 'n5VRWy5WhYg_gBnyWny8E6Qkrze53MR7OuAmmNJ1m1nN8SxDrG6a08L78J0-' + - 'Fbas5OjAQz3c17GY8mVuDPOBIOVjMEghBlgl3nOi1ysxbRGhHLEK4s0KKbeR' + - 'ogZdgt1DkQxDFxxn41QWDw_mmMCjs9qxg0zcZzqEJw', - 'https://w3id.org/security#proofPurpose': { - '@id': NOOP_PROOF_PURPOSE_URI - } - } - } -}; - -mock.securityContextSigned = { - ...securityContextTestDoc, - proof: { - type: 'RsaSignature2018', - created: '2018-02-22T15:16:04Z', - creator: publicKeys.alice.id, - jws: - 'eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'dJBMvvFAIC00nSGB6Tn0XKbbF9XrsaJZREWvR2aONYTQQxnyXirtXnlewJMB' + - 'Bn2h9hfcGZrvnC1b6PgWmukzFJ1IiH1dWgnDIS81BH-IxXnPkbuYDeySorc4' + - 'QU9MJxdVkY5EL4HYbcIfwKj6X4LBQ2_ZHZIu1jdqLcRZqHcsDF5KKylKc1TH' + - 'n5VRWy5WhYg_gBnyWny8E6Qkrze53MR7OuAmmNJ1m1nN8SxDrG6a08L78J0-' + - 'Fbas5OjAQz3c17GY8mVuDPOBIOVjMEghBlgl3nOi1ysxbRGhHLEK4s0KKbeR' + - 'ogZdgt1DkQxDFxxn41QWDw_mmMCjs9qxg0zcZzqEJw', - proofPurpose: NOOP_PROOF_PURPOSE_URI - } -}; - -mock.securityContextInvalidSignature = { - ...securityContextTestDoc, - proof: { - type: 'RsaSignature2018', - created: '2018-02-22T15:16:04Z', - creator: publicKeys.alice.id, - jws: - 'eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19' + - '..' + - 'DJBMvvFAIC00nSGB6Tn0XKbbF9XrsaJZREWvR2aONYTQQxnyXirtXnlewJMB' + - 'Bn2h9hfcGZrvnC1b6PgWmukzFJ1IiH1dWgnDIS81BH-IxXnPkbuYDeySorc4' + - 'QU9MJxdVkY5EL4HYbcIfwKj6X4LBQ2_ZHZIu1jdqLcRZqHcsDF5KKylKc1TH' + - 'n5VRWy5WhYg_gBnyWny8E6Qkrze53MR7OuAmmNJ1m1nN8SxDrG6a08L78J0-' + - 'Fbas5OjAQz3c17GY8mVuDPOBIOVjMEghBlgl3nOi1ysxbRGhHLEK4s0KKbeR' + - 'ogZdgt1DkQxDFxxn41QWDw_mmMCjs9qxg0zcZzqEJw', - proofPurpose: NOOP_PROOF_PURPOSE_URI - } -}; - -mock.parameters = {}; - -mock.parameters.sign = { - creator: publicKeys.alice.id, - date: '2018-02-22T15:16:04Z', - key: new RSAKeyPair({ - privateKeyPem: privateKeys.alice.privateKeyPem, - ...publicKeys.alice - }) -}; - -// this links back to a key with a controller object -mock.parameters.controllerObject = { - creator: publicKeys.alex.id, - date: '2018-02-13T21:26:08Z', - key: new RSAKeyPair({ - privateKeyPem: privateKeys.alex.privateKeyPem, - ...publicKeys.alex - }) -}; -mock.parameters.verify = { - creator: publicKeys.alice.id, - date: '2018-02-22T15:16:04Z' -}; - -mock.parameters.verifyWithPassedKey = mock.parameters.sign; - -mock.parameters.authenticationController = { - '@context': constants.SECURITY_CONTEXT_URL, - id: controllers.alice.id, - authentication: publicKeys.alice.id -}; - -mock.parameters.assertionController = { - '@context': constants.SECURITY_CONTEXT_URL, - id: controllers.alice.id, - assertionMethod: publicKeys.alice.id -}; diff --git a/tests/mock/keys.js b/tests/mock/keys.js deleted file mode 100644 index df77247e..00000000 --- a/tests/mock/keys.js +++ /dev/null @@ -1,282 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const constants = require('../../lib/constants'); - -const controllers = {}; -const publicKeys = {}; -const privateKeys = {}; - -module.exports = {controllers, publicKeys, privateKeys}; - -publicKeys.alice = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/alice/keys/1', - type: ['RsaVerificationKey2018'], - owner: 'https://example.com/i/alice', - publicKeyPem: - '-----BEGIN PUBLIC KEY-----\n' + - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\n' + - 'keJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n' + - '+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\n' + - 'CH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\n' + - 'JX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\n' + - 'z1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\n' + - 'cwIDAQAB\n' + - '-----END PUBLIC KEY-----' -}; -privateKeys.alice = { - privateKeyPem: - '-----BEGIN RSA PRIVATE KEY-----\n' + - 'MIIEowIBAAKCAQEAj+uWAsdsMZhH+DE9d0JekeJ6GVlb8C0tnvT+wW9vNJhg/Zb3\n' + - 'qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP+SqKGx5fdrCeEwR0G2tzsUo2B4/H\n' + - '3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmqCH3SHrqcmzlVcX3pnE0ARkP2trHO\n' + - 'DQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3JX2wx81dv7Ujhse7ZKX9UEJ1FmrS\n' + - 'a/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GUz1I2akGMkSxzBMJEw9wXd01GJXw+\n' + - 'Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8wcwIDAQABAoIBAFBNy65RR/WEWuQJ\n' + - '1Zot1kbgb/ClA7/H9aS0X1Hfs9VNERFuo1MOAoFESwZLNrtDn1U3iJoq7cSiAMRF\n' + - 'Jy8NrDwDmHv5PpsjgZBq8744/pz2I5+kgohChnUTo/kOjiHzujsB8H+d5KFq21vm\n' + - '4PBa/R0v14Z96dRS8XIaJ7em33hUradmuYQYNn9IgP5Y334DebTaTE4+yeFkR0z5\n' + - 'KLm78o/3uoH7+a2C2u2ERimaLO4mpqQXHtmzhulbW2aBIQsR8wGzrBH/AnIej+h/\n' + - 'FJ2CF1XrChq6a2k+Nu9mLRDKxHYN4uQq9qSB7js6p8ZSUC7HkOT6tge69uNn1jZZ\n' + - 'lpKLNQECgYEAwNtNRphFMA6oYLS5FaUY8l/Th66ToDMzVGK3DWXnoHA3vBU/1LW2\n' + - 'VPwV/PJVdTY5mXoERAI75QHCrLcdH07ppHusc6pFdzdVvO8Q5XnwUTfb6dcG7Ips\n' + - 'vniDd3AMWUFgbK2qNOOOeM7Qe0OPXNWzHHcmtL2uLOno8Y4J32cBwqMCgYEAvwqT\n' + - 'ECUjQmtoWHOWcO5M0SCv6YMBrigBY3Y8zFztDWltFhCKUT9WLAMOIHh5CKGnfLgG\n' + - '4PV9kjTLEefxtUCqBm00SifkfRujfUQyZjfZIV9UBhSDceiM9phAK8JsTAKbop/h\n' + - 'FTDkknyqzsM7biLZjflGNWXvuwASKu0ssJjRh/ECgYBvsNJhNyCiw2pqj1+9lF8N\n' + - 'R8gXBVkD54MrtPv0q3bo6PSuXdQY2aAeOdx2INazSlMzeoHr7StI5qsbIfWgwy/3\n' + - 'DZUDa7JNZ+OkxwOPEv7F2sbm95xP858k9GCXFHJiYsV4S1+Ov9csSgJd0PO/PRg9\n' + - 'PRhShqPP6Sv6cVtwYZSYZwKBgHMa7Pb6WV9IletNYaSTgEc02ajpnVaQlh2WfRVp\n' + - 'HA9LqUV1G9HORp5oDNf1nn9b3y1fOA3M/Cbelkgop1LdLlSG8c2IcbwLrhrovzEl\n' + - 'jzbzWA39yCEWy/A8VdXH5DZ8D8gRaq248s9sPAIuUZ2Pc+N+ARZlX+cdKNUiaB3T\n' + - 'RdQRAoGBAIc/UaN3A8ya1+dZ5orrQkjuPQXB7+UzR128vzsKb3F8nt4F92bRMu3D\n' + - 'vBHZCT4QDhv4CCyYlu//LqVBQDdUo4BNayZmjK8J0XUQ/YY77CE35YRRqQAphvvz\n' + - 'fCwRbNd/EW88Pg8ioO1WWcIgmA0296qEBv079qOWqPQq/BbUjH/3\n' + - '-----END RSA PRIVATE KEY-----' -}; -publicKeys.aliceBtc = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'ecdsa-koblitz-pubkey:1LGpGhGK8whX23ZNdxrgtjKrek9rP4xWER', - type: 'CryptographicKey', - owner: 'https://example.com/i/alice', - publicKeyWif: '1LGpGhGK8whX23ZNdxrgtjKrek9rP4xWER' -}; -privateKeys.aliceBtc = { - privateKeyWif: 'L4mEi7eEdTNNFQEWaa7JhUKAbtHdVvByGAqvpJKC53mfiqunjBjw' -}; -controllers.alice = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.alice.owner, - publicKey: [publicKeys.alice, publicKeys.aliceBtc], - 'https://example.org/special-authentication': { - publicKey: publicKeys.alice.id - } -}; - -privateKeys.alex = { - privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' + - 'MIIEpQIBAAKCAQEA0MG729HDdieuzyFT+vdgMXDjTdCniWv64evMXydjfaYlTsmd\r\n' + - '1FfFQYJdrKJaFzB4y9vm37yKvsw7FJFymSzmk4T62yMqCIe19UNGHqk5TDVSKf0X\r\n' + - 'ZTZX+5i9qhQOaL7yFzzLunI8bNxAzJZ63cGWf4uJI+513SN9IKvh45vWlgsbZ/ek\r\n' + - 'ELHF0YXrupeTzQZMq4fl2/vQxPPmpooNXZ3Fud9DZLAyWhKg69u996XjYP0QcjkE\r\n' + - '7H1PC1Um+CYDGe65pzBQlYlwgYtztK64kK3A2FGVQufyQ+19FlHTJTYdyy/zKtyE\r\n' + - '2+22wuANiLkg9JQEWroRQaGBLCmjwaA+AMQmfQIDAQABAoIBAQCezfIZy93UgWWS\r\n' + - '/jiDnzHXCphv9r2sZa9Js/YZoL4ntH+HCwr8oPRW3FRkYnEEWQRbmGJua2Bkurpq\r\n' + - '8CZsbeLN8AhhMcPlD1AVTuMFqhgDaECj3nuwrAGMTOpjerRnbHJ/yOj2Ybaj3X2R\r\n' + - '5Rt8nKrfRgfChMG2wyuJ8hd57W/1XQf0l5Zn3kdRBK5NzUp9fRBJxFEKag8z+Zij\r\n' + - 'X4ENJmR66gCNLlE87+XMhcyHoJJmmhijC1Hq5Ph4rAsHKN6AMWviW59EyViA2gAb\r\n' + - 'iwwNlmg0W9qYxsbcrApJo/PAncCiNLvOshr8CwCdY1k9WOvAxPgBtAtEEXoH/01r\r\n' + - 'SJQ/wSElAoGBAOlUoOn2CQztW+Wm9yyRXGjYxl5a/3Nn3JHCAKj37KX+4aknoIv2\r\n' + - 'LckuA6pJCbW1Q8VVP8OH1lj43wb3siHzmH2jM6f0GIbSVFn/Z0mfhSGHGRHOuJXK\r\n' + - 'AXurraSIoPKi5G1ZhMNQB8RBq/Os1LrRCJ9L8pWDQHhsGT9PIp9q7fGLAoGBAOUJ\r\n' + - '6Ig/RG/V7NRfoqP8Lupi820Q0EdLL9Jnr6utKtPxaGFSi9MrmCJK+9YD1VWw9XZ8\r\n' + - 'pFxIkz8aNRPnJgAWT66SLLPiW7wsQezLqpcWprsvfpbusPpfMf6K7FSWHYJJVL1i\r\n' + - 'pS/MBdeDgl/DBJgfm4OrIuuodaKFvC7jJnUEQrkXAoGBAMPykzQHr7AQgVVKM1dV\r\n' + - 'N5LBQU2qA87qERzDHITJuA3rD51brwL7GZZSszdFIQddE23b2rGdGNAdKEcUqp7C\r\n' + - 'kHQqI05Pum02oyn1R8tXUJlIeDAxN2hrfXVbRnbfWrKJQ2XlgI35XpxdPkdkBD5j\r\n' + - 'H2ePg0g2MmUu+sDk90GDrhFjAoGBAIS//m/hw6fSZScemyTSyNp/KbogUafQ01Hv\r\n' + - 'WOl3P+iB9k7aSkLF9LKDpX2A0UiOfWcEjTsTsYyUgwkbI3JPfDWhcZl9bFAfksJN\r\n' + - 'tX1G2rKJr6SJijhDrrVrDdlk/IuEN0Jhh36xkP09svYQEXyebUOekGnoRO5C9zRx\r\n' + - '4dtW8dlXAoGAZe/1usB8YV7fOdGCJJ/IBLXG5xbtoSVD8yM/HVllazfr6fKlGqPO\r\n' + - 'ORhqr5estS7IVwZjcArkqiwJeXXUYPt0m9Oasqf1+g3UibR2SFRs1ZCLq1hSIuwf\r\n' + - '4/MYqzY1568+/4+QkLFkjdT18HbkL7cRZrYmAoonb1KeDEbh0THsFHw=\r\n' + - '-----END RSA PRIVATE KEY-----\r\n' -}; - -publicKeys.alex = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/alex/keys/1', - type: ['RsaVerificationKey2018'], - // this test ensures that controller.id works - controller: {id: 'https://example.com/i/alex'}, - publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' + - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0MG729HDdieuzyFT+vdg\r\n' + - 'MXDjTdCniWv64evMXydjfaYlTsmd1FfFQYJdrKJaFzB4y9vm37yKvsw7FJFymSzm\r\n' + - 'k4T62yMqCIe19UNGHqk5TDVSKf0XZTZX+5i9qhQOaL7yFzzLunI8bNxAzJZ63cGW\r\n' + - 'f4uJI+513SN9IKvh45vWlgsbZ/ekELHF0YXrupeTzQZMq4fl2/vQxPPmpooNXZ3F\r\n' + - 'ud9DZLAyWhKg69u996XjYP0QcjkE7H1PC1Um+CYDGe65pzBQlYlwgYtztK64kK3A\r\n' + - '2FGVQufyQ+19FlHTJTYdyy/zKtyE2+22wuANiLkg9JQEWroRQaGBLCmjwaA+AMQm\r\n' + - 'fQIDAQAB\r\n-----END PUBLIC KEY-----\r\n' -}; - -// RsaKey with an assertionMethod -controllers.alex = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.alex.controller.id, - assertionMethod: [publicKeys.alex.id], - authentication: [publicKeys.alex.id], - publicKey: [publicKeys.alex] -}; - -publicKeys.bob = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/bob/keys/1', - type: ['RsaVerificationKey2018'], - owner: 'https://example.com/i/bob', - publicKeyPem: - '-----BEGIN PUBLIC KEY-----\n' + - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlsOUSgEA9NZdtxFmra5\n' + - 'tbdQQkcLcOTqLNBjXm275/Vdoz5Bcwfipty3As2b2nxJt8I9co4lmE4wsDHp5dyu\n' + - '34SFKn4/Y9SQzQWAvmkSBkgcRXCBS91cakW7Wx3O9/Yr66hSO7pAbt2TEW3Jf3Xl\n' + - '3NZcnCDpNCYc40UOWRh0pmMMeyKMedHki6rWD6fgT/0Qm+LeN7E9Aelqy/5OwW38\n' + - 'aKXCuf6J9J2bBzGTc9nof7Ordnllz/XS7dLm6qNT3lkx+VMFOa9L1JXo77p7DI+L\n' + - 'z7CnswIQ8Yq9ukZZzjLvX6RN1pEB9CW9rvU9r2k2VPN8bTY3yXjolo1s6bG69lc3\n' + - 'vQIDAQAB\n' + - '-----END PUBLIC KEY-----' -}; -privateKeys.bob = { - privateKeyPem: - '-----BEGIN RSA PRIVATE KEY-----\r\n' + - 'MIIEpQIBAAKCAQEAwlsOUSgEA9NZdtxFmra5tbdQQkcLcOTqLNBjXm275/Vdoz5B\n' + - 'cwfipty3As2b2nxJt8I9co4lmE4wsDHp5dyu34SFKn4/Y9SQzQWAvmkSBkgcRXCB\n' + - 'S91cakW7Wx3O9/Yr66hSO7pAbt2TEW3Jf3Xl3NZcnCDpNCYc40UOWRh0pmMMeyKM\n' + - 'edHki6rWD6fgT/0Qm+LeN7E9Aelqy/5OwW38aKXCuf6J9J2bBzGTc9nof7Ordnll\n' + - 'z/XS7dLm6qNT3lkx+VMFOa9L1JXo77p7DI+Lz7CnswIQ8Yq9ukZZzjLvX6RN1pEB\n' + - '9CW9rvU9r2k2VPN8bTY3yXjolo1s6bG69lc3vQIDAQABAoIBAC68FIpBVA3TcYza\n' + - 'VMZqL+fZR6xYRxEDiqfyCCL5whh58OVDIBvYBpFXO46qAFMeVd+hDoOQWMvx6VVE\n' + - '+1hxo39N73OTXgzUXWlfbGDdBR+LkXjFH+ItPX60e+PiHBWWFWOaWwPPupSuJSIo\n' + - 'wy4qHHbo+OX2J/2JOKMRxOx5q/siI+vrzYKEdRU+P338vWpvlBK9GiodIY29t71Z\n' + - 'qTV+2eA1v5rmDK/pa8+WXUNKyKrIZQ8qxdf8LbD/1QkspvCqcyQ+XTl+qkRM8hp8\n' + - 'ONfhLFPrIN0BOonwGNh9u9bsYGZGmoV8YzdgJoNJ1jWRyuKhO9Px5hQmnixuBdkO\n' + - 'XcdkOiECgYEA/y5vsNeUgwTkolYSIs2QqHuLqxZZ1U5JyPKVipuqgrSgAV20A3Ah\n' + - 'Bvnp+GpqrConLrvjoYKRCWf9IRI+MfxFiLTgKdWxc6PlDXAFpaSZAgYVBTRudgd/\n' + - 'CLpr7fC1w9rx5S/VHaDu89aLBTsSHjQBIKZaWhmFM00Y+tqkxtqrBjkCgYEAwvqq\n' + - '3/MbOZHEOXjDzbwsZPg+8q8eyBE0bPzp4tjxBPvxnWqwhC3NoKhZP/E2gojVDgdH\n' + - 'ZvsEO+o8JXH2DKFBEXc80c77Gl8hhiRsFab1rIRl7vCUjgNksu1ChzXnvwJuRAB4\n' + - 'mFHsuxJi83kRQD8HqgIfuDnsS5kl6gpvAlel3aUCgYEAjBxjFyZHVOkK4FeB/boB\n' + - 'A4FSXs4W5RfnS35mvYRbSwkCEb3xaTHX8Iyn+s3zZDSA7xgbFEMsf42pXs81dxyc\n' + - '0UL/EflTRbtnuMkZUKnfmUzdnc38GLJk/dXeDPdt1ewRhVWOHoaOrTPPgT+94veK\n' + - '5vJwCaiZimF6pcIHV2gZH4ECgYEAmcq4b07FIaKdYSulXijX54h7tlZ09B/F91WC\n' + - 'ciDl8yV6zcyykH/EWr2PMEVl1o5xZtBM/KhwDYZTjMGX7xxeQ5WGjoMxQvrYaYNf\n' + - 'EbEQxNPlxxNSSbXZftxwBlB5jAsxyEeK17J/BIubKypKdh+BPxLPzDM78+FHq5Qx\n' + - 'PWq+9NUCgYEAqm0LdhkoqdKgbkU/rgNjX3CgINQ/OhbUGpqq78EAbw/90MCXGdOB\n' + - '5pxB4HwKFtDPNtquIQ3UCIVVCJlDZfW7mJJQ9LkD21uqwxXOf1uPH2cb651yeLqd\n' + - 'TSz1b9F4+GFdKxjk8JKywWAD2fIamcx2W0Wfgfyvr6Kd+kJrkyWn+ZM=\n' + - '-----END RSA PRIVATE KEY-----' -}; -controllers.bob = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.bob.owner, - publicKey: [publicKeys.bob] -}; - -publicKeys.sally = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/sally/keys/1', - type: ['RsaVerificationKey2018'], - owner: 'https://example.com/i/sally', - publicKeyPem: - '-----BEGIN PUBLIC KEY-----\n' + - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwIjS6bkpr+xR/+JCL0KF\n' + - '24ZOHEmX/4ASBhSfKh0vGb5plKFuAOumNj5y/CzdgkqenhtcbrMunHuzPqYdTUJB\n' + - 'NXDqpVzXh7bZDHDjFcHgHcU8xxCvchL9EDKyFP39JJG9/sTr6SEkKz8OH48lZoFh\n' + - 'GsXvsYTCMKJRZ0+vECTvEb2gd6OGhXwQqPk402Kk0hMq/5LjceUaxDfcBDJ8WYim\n' + - 'BWy9YO+xeEu3nFrPk2I1aMFDdD6vHO7l7P6tMAY/U+H1wrsDPuv3A/stalSHjZyh\n' + - 'DaBD1ZoEtAk03kOSvwLQb2LI3kAwYqoNApNsLVI+U9HsP/UuKk2/3kZS8Oa70b97\n' + - 'RwIDAQAB\n' + - '-----END PUBLIC KEY-----' -}; -privateKeys.sally = { - privateKeyPem: - '-----BEGIN RSA PRIVATE KEY-----\r\n' + - 'MIIEogIBAAKCAQEAwIjS6bkpr+xR/+JCL0KF24ZOHEmX/4ASBhSfKh0vGb5plKFu\n' + - 'AOumNj5y/CzdgkqenhtcbrMunHuzPqYdTUJBNXDqpVzXh7bZDHDjFcHgHcU8xxCv\n' + - 'chL9EDKyFP39JJG9/sTr6SEkKz8OH48lZoFhGsXvsYTCMKJRZ0+vECTvEb2gd6OG\n' + - 'hXwQqPk402Kk0hMq/5LjceUaxDfcBDJ8WYimBWy9YO+xeEu3nFrPk2I1aMFDdD6v\n' + - 'HO7l7P6tMAY/U+H1wrsDPuv3A/stalSHjZyhDaBD1ZoEtAk03kOSvwLQb2LI3kAw\n' + - 'YqoNApNsLVI+U9HsP/UuKk2/3kZS8Oa70b97RwIDAQABAoIBADJKCr0drjPTSD/L\n' + - '+3mYqJoEZJai6l7ENvD7pe88HDdfMvitiawX4Rw+B46ysVD86J1njCcmCkC5VsJA\n' + - 'ZVruuVWaHs/+hhVevyauvcHLGBzujcd5Jjpnl04Jz9YH2X0ZzESlbvE/xNC+8ZNw\n' + - 'slYp6REzLj5x7L8DRrvzZkiTPRamuiDQrxr6d27TWPZIAwfPYuoy/OMx9hMgZyKk\n' + - 'pxsAvMmVRyy2NZK428oU5rwF/mWsURS05oWyBqicgaeWlqJ9swnak1OnF5z0N196\n' + - 'fU4bVHjtyAMS/DCNI+4qjpg7G+PPUfK4RXtJ/0AC0ZRDu35khXeI5u1U3F5Ks6ms\n' + - 'XUTQDhECgYEA/gltTiKTlZhGxx9K1P5DQ+ZFHns+NsonbBS1i9Io6dFK0QfS4xOa\n' + - 'TjP1nOKFIlB1TS2kqOylkxbS/Jf1bzSOk/rwIFfDnE0q4zIfiGEnlmnqefmJ4Qac\n' + - 'LXsfwTQ4WiHuQcqOMlM3PgWm8r1zhPQaY2yFXzgCBpsD82cLcaPa8R0CgYEAwgW5\n' + - 'US/UBB+j8OLyjeDgZvhvIfsgL8hREaS3U+Uk72ei+UT2XhjdV4mVyiQ3N5cTHyXC\n' + - 'vkamozmp90zHSnAyjDq+GNt04A1n3nz45VKNlstG/NfrqP5QCfCjEwiJfuclD2+q\n' + - 'VRrpbHbWBJ9B/8e+andl5rixNoI72n44n/k7NLMCgYB/6HM20kYJHoEUpXbiQ5vO\n' + - 'xlSrAlbS83ph+xNl8U1UXWMUWKIgX7BkC9lxQsTSADzvvTmZLH45z1YwhLq5YXcg\n' + - 'n0rkngwJ2PjtKEGkQ3bRT0cWX0TDHrboV4QnnYl6KHd0fO6X/DpmaiYjNqzBlr7q\n' + - 'rKuCxAqRFOAqYAntEBmfKQKBgDjYNnhL3AEtR/nudAQPa4+fn+fDzKVTOjVCHhgt\n' + - 'XYnqwjvn8YqWHFtmSwWDYM4frBGHHaxjxLSz01FKJGVxw82D9GgR/Accxl7QHJgL\n' + - 'fMI+Ylj35eqIP+j5oL2V1brhe+Eu5Se0D8mgc4m9IzgOTIKi4q8bU4hV1bVpH6v2\n' + - '+FqzAoGAHM2v90bEbN/TNFv7OODWeK7HBRKBNigMVktXBpfCAFOm+cSfMlsoQTr3\n' + - '4xiV0oxUFjPHA6qt0hGsk7/0P1Pe15Kg5n6+w2JzFpN5ix7DWus57PBKbMUkE64y\n' + - 'KBFLr5ANLqWLaVrSw5Uep1s5VvXyOrltUN/1SUoCoNZuM/FakRc=\n' + - '-----END RSA PRIVATE KEY-----' -}; -controllers.sally = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.sally.owner, - publicKey: [publicKeys.sally] -}; - -publicKeys.carol = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/carol/keys/1', - type: ['Ed25519VerificationKey2018'], - owner: 'https://example.com/i/carol', - publicKeyBase58: 'GycSSui454dpYRKiFdsQ5uaE8Gy3ac6dSMPcAoQsk8yq' -}; -privateKeys.carol = { - privateKeyBase58: - '3Mmk4UzTRJTEtxaKk61LxtgUxAa2Dg36jF6VogPtRiKvfpsQWKPCLesKSV182RMmvM' + - 'JKk6QErH3wgdHp8itkSSiF' -}; -controllers.carol = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.carol.owner, - publicKey: [publicKeys.carol], - 'https://example.org/special-authentication': { - publicKey: publicKeys.carol.id - } -}; - -publicKeys.ned = { - '@context': constants.SECURITY_CONTEXT_URL, - id: 'https://example.com/i/ned/keys/1', - type: ['Ed25519VerificationKey2018'], - // this test ensures that controller.id works - controller: {id: 'https://example.com/i/ned'}, - publicKeyBase58: '39GT26rnBupnnwBhwqHxsCgqoMNYauRStTQCN5JNaPL7' -}; - -privateKeys.ned = { - privateKeyBase58: - '4EyMEq4hqVznTz1uNiuubvC4zach1G2mKMoeWdeN37jvTvCwinrmcBgyoJsgheC9oG' + - 'uVYVntB4K8ePdmyMfH12vX' -}; - -// controller with an assertionMethod on it -// that's publicKey is in the Ed25519 format. -controllers.ned = { - '@context': constants.SECURITY_CONTEXT_URL, - id: publicKeys.ned.controller.id, - assertionMethod: [publicKeys.ned.id], - authentication: [publicKeys.ned.id], - publicKey: [publicKeys.ned] -}; diff --git a/tests/mock/mock.js b/tests/mock/mock.js deleted file mode 100644 index 65eb5947..00000000 --- a/tests/mock/mock.js +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const mock = {}; -module.exports = mock; - -({ - nonSecurityContextTestDoc: mock.nonSecurityContextTestDoc, - securityContextTestDoc: mock.securityContextTestDoc -} = require('./test-document')); - -({ - controllers: mock.controllers, - publicKeys: mock.publicKeys, - privateKeys: mock.privateKeys, -} = require('./keys')); - -mock.testLoader = require('./test-loader'); - -({ - NOOP_PROOF_PURPOSE_URI: mock.NOOP_PROOF_PURPOSE_URI, - NoOpProofPurpose: mock.NoOpProofPurpose -} = require('./noop-purpose')); - -mock.suites = { - Ed25519Signature2018: require('./Ed25519Signature2018'), - GraphSignature2012: require('./GraphSignature2012'), - LinkedDataSignature2015: require('./LinkedDataSignature2015'), - RsaSignature2018: require('./RsaSignature2018') -}; diff --git a/tests/mock/noop-purpose.js b/tests/mock/noop-purpose.js deleted file mode 100644 index d1fd919e..00000000 --- a/tests/mock/noop-purpose.js +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const ProofPurpose = require('../../lib/purposes/ProofPurpose'); - -const mock = {}; -module.exports = mock; - -mock.NOOP_PROOF_PURPOSE_URI = 'https://example.org/special-authentication'; - -class NoOpProofPurpose extends ProofPurpose { - constructor() { - super({term: mock.NOOP_PROOF_PURPOSE_URI}); - } - async validate() { - return {valid: true}; - } -} - -mock.NoOpProofPurpose = NoOpProofPurpose; diff --git a/tests/mock/test-document.js b/tests/mock/test-document.js deleted file mode 100644 index c23ac82e..00000000 --- a/tests/mock/test-document.js +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const constants = require('../../lib/constants'); - -const mock = {}; -module.exports = mock; - -mock.nonSecurityContextTestDoc = { - '@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' -}; - -mock.securityContextTestDoc = { - ...mock.nonSecurityContextTestDoc, - '@context': [ - {'@version': 1.1}, - mock.nonSecurityContextTestDoc['@context'], - constants.SECURITY_CONTEXT_URL] -}; diff --git a/tests/mock/test-loader.js b/tests/mock/test-loader.js deleted file mode 100644 index b439d5e7..00000000 --- a/tests/mock/test-loader.js +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * Copyright (c) 2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const {controllers, publicKeys} = require('./keys'); - -const documents = {}; -for(const key in publicKeys) { - documents[publicKeys[key].id] = publicKeys[key]; -} -for(const key in controllers) { - documents[controllers[key].id] = controllers[key]; -} - -module.exports = async url => { - if(url in documents) { - return { - contextUrl: null, - document: documents[url], - documentUrl: url - }; - } - throw new Error(`Document "${url}" not found.`); -}; diff --git a/tests/test-common.js b/tests/test-common.js deleted file mode 100644 index f697d83d..00000000 --- a/tests/test-common.js +++ /dev/null @@ -1,1721 +0,0 @@ -/*! - * Copyright (c) 2014-2018 Digital Bazaar, Inc. All rights reserved. - */ -/* eslint-disable indent */ -module.exports = async function(options) { - -'use strict'; - -const {assert, constants, jsigs, mock, suites, util} = options; -const { - AssertionProofPurpose, - AuthenticationProofPurpose, - PublicKeyProofPurpose -} = jsigs.purposes; -const {LinkedDataProof} = jsigs.suites; -const {NoOpProofPurpose} = mock; - -// helper: -function clone(obj) { - return JSON.parse(JSON.stringify(obj)); -} - -const {testLoader} = mock; - -// run tests -describe('JSON-LD Signatures', () => { - context('util', () => { - it('should base64url encode', async () => { - const inputs = [ - '', - '1', - '12', - '123', - '1234', - '12345', - [], - [97], - [97, 98], - [97, 98, 99], - [97, 98, 99, 100], - [97, 98, 99, 100, 101], - [0xc3, 0xbb, 0xc3, 0xb0, 0x00], - [0xc3, 0xbb, 0xc3, 0xb0], - [0xc3, 0xbb] - ]; - inputs.forEach(function(input) { - input = new Uint8Array(input); - const enc = util.encodeBase64Url(input); - const dec = util.decodeBase64Url(enc); - /* - console.log('E', input, '|', Buffer.from(input)); - console.log(' enc', enc, '|', Buffer.from(enc)); - console.log(' dec', dec, '|', Buffer.from(dec)); - */ - assert.equal(enc.indexOf('+'), -1); - assert.equal(enc.indexOf('/'), -1); - assert.equal(enc.indexOf('='), -1); - assert.equal(input.length, dec.length); - for(let i = 0; i < input.length; ++i) { - assert.equal(input[i], dec[i]); - } - }); - }); - - it('should base64url decode', async () => { - const inputs = [ - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', - '_-E', - '_-E=', - 'AA', - 'eA', - 'eA=', - 'eA==', - ]; - inputs.forEach(input => { - const dec = util.decodeBase64Url(input); - const enc = util.encodeBase64Url(dec); - /* - console.log('D', input, '|', Buffer.from(input)); - console.log(' dec', dec, '|', Buffer.from(dec)); - console.log(' enc', enc, '|', Buffer.from(enc)); - */ - assert.equal(input.replace(/=/g, ''), enc); - }); - }); - }); - - context('common', () => { - it('should fail to sign a document when missing a suite', async () => { - const testDoc = clone(mock.securityContextTestDoc); - let err; - try { - await jsigs.sign(testDoc); - } catch(e) { - err = e; - } - assert.exists(err); - assert.equal(err.message, '"options.suite" is required.'); - }); - - it('should fail to sign a document when missing a purpose', async () => { - const testDoc = clone(mock.securityContextTestDoc); - let err; - try { - await jsigs.sign(testDoc, { - suite: new suites.Ed25519Signature2018() - }); - } catch(e) { - err = e; - } - assert.exists(err); - assert.equal(err.message, '"options.purpose" is required.'); - }); - - it('should fail to verify a document when missing a suite', async () => { - const testDoc = clone(mock.securityContextTestDoc); - let err; - try { - await jsigs.verify(testDoc); - } catch(e) { - err = e; - } - assert.exists(err); - assert.equal(err.message, '"options.suite" is required.'); - }); - - it('should fail to verify a document when missing a purpose', async () => { - const testDoc = clone(mock.securityContextTestDoc); - let err; - try { - await jsigs.verify(testDoc, { - suite: new suites.Ed25519Signature2018() - }); - } catch(e) { - err = e; - } - assert.exists(err); - assert.equal(err.message, '"options.purpose" is required.'); - }); - }); - - context('custom suite', () => { - class CustomSuite extends LinkedDataProof { - constructor({match = true} = {}) { - super({type: 'example:CustomSuite'}); - this.match = match; - } - async createProof() { - return { - '@context': constants.SECURITY_CONTEXT_URL, - type: this.type - }; - } - async verifyProof() { - return {verified: true}; - } - async matchProof() { - return this.match; - } - } - - it('should sign a document', async () => { - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: new CustomSuite(), - purpose: new NoOpProofPurpose() - }); - const expected = clone(mock.securityContextTestDoc); - expected.proof = {type: 'example:CustomSuite'}; - assert.deepEqual(signed, expected); - }); - - it('should sign a document w/type-scoped `proof` term', async () => { - const testDoc = clone(mock.securityContextTestDoc); - const specialCtx = { - '@context': { - '@version': 1.1, - proof: 'ex:invalid', - TypedDocument: { - '@id': 'ex:TypedDocument', - '@context': { - '@version': 1.1, - proof: { - '@id': 'sec:proof', - '@type': '@id', - '@container': '@graph' - } - } - } - } - }; - testDoc['@context'].push(specialCtx); - testDoc.type = 'TypedDocument'; - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: new CustomSuite(), - purpose: new NoOpProofPurpose() - }); - const expected = clone(mock.securityContextTestDoc); - expected['@context'].push(specialCtx); - expected.type = 'TypedDocument'; - expected.proof = {type: 'example:CustomSuite'}; - assert.deepEqual(signed, expected); - }); - - it('should verify a document', async () => { - const signed = clone(mock.securityContextTestDoc); - signed.proof = { - proofPurpose: 'https://example.org/special-authentication', - type: 'example:CustomSuite' - }; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: new CustomSuite(), - purpose: new NoOpProofPurpose() - }); - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should not verify a document with non-matching suite', async () => { - const signed = clone(mock.securityContextTestDoc); - signed.proof = { - proofPurpose: 'https://example.org/unknown-authentication', - type: 'example:CustomSuite' - }; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: new CustomSuite({match: false}), - purpose: new NoOpProofPurpose() - }); - assert.equal(result.verified, false); - assert.ok(result.error); - assert.equal(result.error.name, 'VerificationError'); - - assert.equal(result.error.errors[0].message.includes( - 'no proofs matched the required suite and purpose'), true); - - // errors should be serialized properly in the verification report - const {error} = JSON.parse(JSON.stringify(result)); - assert.typeOf(error, 'object'); - assert.sameMembers(Object.keys(error), ['name', 'errors']); - }); - - it('should not verify a document with non-matching purpose', async () => { - const signed = clone(mock.securityContextTestDoc); - signed.proof = { - proofPurpose: 'https://example.org/unknown-authentication', - type: 'example:CustomSuite' - }; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: new CustomSuite(), - purpose: new NoOpProofPurpose() - }); - assert.equal(result.verified, false); - assert.ok(result.error); - assert.equal(result.error.errors[0].message.includes( - 'no proofs matched the required suite and purpose'), true); - - // errors should be serialized properly in the verification report - const {error} = JSON.parse(JSON.stringify(result)); - assert.typeOf(error, 'object'); - assert.sameMembers(Object.keys(error), ['name', 'errors']); - }); - }); - - const commonSuiteTests = [ - 'Ed25519Signature2018', - 'RsaSignature2018', - 'LinkedDataSignature2015', - 'GraphSignature2012' - ]; - - for(const suiteName of commonSuiteTests) { - const pseudorandom = ['RsaSignature2018']; - - context(suiteName, () => { - it('should sign a document w/security context', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - let expected = mock.suites[suiteName].securityContextSigned; - if(pseudorandom.includes(suiteName)) { - expected = clone(expected); - if(suite.legacy) { - expected.signature.signatureValue = signed.signature.signatureValue; - } else { - expected.proof.jws = signed.proof.jws; - } - } - assert.deepEqual(signed, expected); - }); - - it('should sign a document when `compactProof` is `false`', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose(), - compactProof: false - }); - let expected = mock.suites[suiteName].securityContextSigned; - if(pseudorandom.includes(suiteName)) { - expected = clone(expected); - if(suite.legacy) { - expected.signature.signatureValue = signed.signature.signatureValue; - } else { - expected.proof.jws = signed.proof.jws; - } - } - assert.deepEqual(signed, expected); - }); - - it('should sign a document w/o security context', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.nonSecurityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - let expected = mock.suites[suiteName].nonSecurityContextSigned; - if(pseudorandom.includes(suiteName)) { - expected = clone(expected); - if(suite.legacy) { - expected[constants.SECURITY_SIGNATURE_URL] - ['https://w3id.org/security#signatureValue'] = - signed[constants.SECURITY_SIGNATURE_URL] - ['https://w3id.org/security#signatureValue']; - } else { - expected[constants.SECURITY_PROOF_URL]['@graph'] - ['https://w3id.org/security#jws'] = - signed[constants.SECURITY_PROOF_URL]['@graph'] - ['https://w3id.org/security#jws']; - } - } - assert.deepEqual(signed, expected); - }); - - it('should verify a document w/security context', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = mock.suites[suiteName].securityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should verify a document when `compactProof` is `false`', - async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = mock.suites[suiteName].securityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose(), - compactProof: false - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should verify a document w/o security context', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = mock.suites[suiteName].nonSecurityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...mock.suites[suiteName].securityContextSigned[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should fail to verify when `compactProof` is `false`', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = mock.suites[suiteName].nonSecurityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose(), - compactProof: false - }); - assert.isObject(result); - assert.equal(result.verified, false); - assert.exists(result.error); - }); - - it('should verify a document w/security context w/passed key', - async () => { - const Suite = suites[suiteName]; - const suite = new Suite( - mock.suites[suiteName].parameters.verifyWithPassedKey); - const signed = mock.suites[suiteName].securityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should verify a document w/o security context w/passed key', - async () => { - const Suite = suites[suiteName]; - const suite = new Suite( - mock.suites[suiteName].parameters.verifyWithPassedKey); - const signed = mock.suites[suiteName].nonSecurityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...mock.suites[suiteName].securityContextSigned[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should detect an invalid signature', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = mock.suites[suiteName].securityContextInvalidSignature; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed[property] - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal(result.results[0].verified, expected.results[0].verified); - assert.equal( - result.results[0].error.message, - 'Invalid signature.'); - }); - - it('should sign a document with multiple signatures', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.suites[suiteName].securityContextSigned); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const property = suite.legacy ? 'signature' : 'proof'; - assert.isArray(signed[property]); - assert.equal(signed[property].length, 2); - const expected = clone(mock.suites[suiteName].securityContextSigned); - expected[property] = [expected[property], clone(expected[property])]; - if(suite.legacy) { - expected[property][1].signatureValue = - signed[property][1].signatureValue; - } else { - expected[property][1].jws = signed[property][1].jws; - } - assert.deepEqual(signed, expected); - }); - - it('should verify a document with multiple set signatures', async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const testDoc = clone(mock.suites[suiteName].securityContextSigned); - const property = suite.legacy ? 'signature' : 'proof'; - testDoc[property] = [testDoc[property], clone(testDoc[property])]; - const result = await jsigs.verify(testDoc, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - const expectedPurposeResult = { - Ed25519Signature2018: { - valid: true - }, - RsaSignature2018: { - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...mock.suites[suiteName].securityContextSigned[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }, { - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...mock.suites[suiteName].securityContextSigned[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should sign and verify a document w/public key proof purpose', - async () => { - const Suite = suites[suiteName]; - - const signSuite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new PublicKeyProofPurpose() - }); - - const verifySuite = new Suite(mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new PublicKeyProofPurpose() - }); - const property = verifySuite.legacy ? 'signature' : 'proof'; - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/carol/keys/1', - owner: 'https://example.com/i/carol', - /* eslint-disable-next-line max-len */ - publicKeyBase58: 'GycSSui454dpYRKiFdsQ5uaE8Gy3ac6dSMPcAoQsk8yq', - type: 'Ed25519VerificationKey2018' - } - }, - id: 'https://example.com/i/carol', - publicKey: 'https://example.com/i/carol/keys/1' - }, - valid: true - }, - LinkedDataSignature2015: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - }, - GraphSignature2012: { - controller: { - '@context': 'https://w3id.org/security/v2', - 'https://example.org/special-authentication': { - publicKey: { - id: 'https://example.com/i/alice/keys/1', - owner: 'https://example.com/i/alice', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+uWAsdsMZhH+DE9d0Je\nkeJ6GVlb8C0tnvT+wW9vNJhg/Zb3qsT0ENli7GLFvm8wSEt61Ng8Xt8M+ytCnqQP\n+SqKGx5fdrCeEwR0G2tzsUo2B4/H3DEp45656hBKtu0ZeTl8ZgfCKlYdDttoDWmq\nCH3SHrqcmzlVcX3pnE0ARkP2trHODQDpX1gFF7Ct/uRyEppplK2c/SkElVuAD5c3\nJX2wx81dv7Ujhse7ZKX9UEJ1FmrSa/O3JjdOSa5/hK0/oRHmBDK46RMdr94S7/GU\nz1I2akGMkSxzBMJEw9wXd01GJXw+Xv8TkFF5ae+iQ0I7hkrww8x+G9EQCRKylV8w\ncwIDAQAB\n-----END PUBLIC KEY-----', - type: 'RsaVerificationKey2018' - } - }, - id: 'https://example.com/i/alice', - publicKey: 'https://example.com/i/alice/keys/1' - }, - valid: true - } - }; - - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed[property] - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - if(['Ed25519Signature2018', 'RsaSignature2018'].includes(suiteName)) { - it('should fail to verify a proof without a "jws" property', - async () => { - const Suite = suites[suiteName]; - const suite = new Suite(mock.suites[suiteName].parameters.verify); - const signed = clone(mock.suites[suiteName].securityContextSigned); - delete signed.proof.jws; - - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: suite.legacy ? - new PublicKeyProofPurpose() : new NoOpProofPurpose() - }); - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, 1); - assert.equal( - result.results[0].error.message, - 'The proof does not include a valid "jws" property.'); - }); - } - }); - } - - const legacySuiteTests = [ - 'LinkedDataSignature2015', - 'GraphSignature2012' - ]; - - for(const suiteName of legacySuiteTests) { - context(`Legacy suite tests: ${suiteName}`, () => { - it('should detect an expired date', async () => { - const Suite = suites[suiteName]; - const suite = new Suite({ - ...mock.suites[suiteName].parameters.verify - }); - const signed = mock.suites[suiteName].securityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: new PublicKeyProofPurpose({ - date: new Date('01-01-1970'), - maxTimestampDelta: 0 - }) - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.signature - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal(result.results[0].verified, expected.results[0].verified); - assert.equal( - result.results[0].error.message, - 'The proof\'s created timestamp is out of range.'); - }); - - it('should detect a non-matching domain', async () => { - const Suite = suites[suiteName]; - const suite = new Suite({ - ...mock.suites[suiteName].parameters.verify, - date: new Date('01-01-1970'), - domain: 'example.com' - }); - const signed = mock.suites[suiteName].securityContextSigned; - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite, - purpose: new PublicKeyProofPurpose() - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.signature - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal(result.results[0].verified, expected.results[0].verified); - const expectedMessage = 'The domain is not as expected'; - assert.equal( - result.results[0].error.message.substr(0, expectedMessage.length), - expectedMessage); - }); - }); - } - - const currentSuiteTests = [ - 'Ed25519Signature2018', - 'RsaSignature2018' - ]; - - for(const suiteName of currentSuiteTests) { - context(`Current suite tests: ${suiteName}`, () => { - it('should fail to verify a document w/public key missing a type', - async () => { - const Suite = suites[suiteName]; - - const signSuite = new Suite(mock.suites[suiteName].parameters.sign); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new PublicKeyProofPurpose() - }); - - let keyType; - const parameters = mock.suites[suiteName].parameters.verify; - const verifySuite = new Suite(parameters); - const documentLoader = jsigs.extendContextLoader(async url => { - if(url === parameters.creator) { - const remoteDoc = await testLoader(url); - keyType = remoteDoc.document.type; - remoteDoc.document = {...remoteDoc.document}; - delete remoteDoc.document.type; - return remoteDoc; - } - return testLoader(url); - }); - const result = await jsigs.verify(signed, { - documentLoader, - suite: verifySuite, - purpose: new PublicKeyProofPurpose() - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - assert.equal( - result.results[0].error.message, - `Invalid key type. Key type must be "${keyType}".`); - }); - - context('AuthenticationProofPurpose', () => { - it('should detect an expired date', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-2018'), - maxTimestampDelta: 0 - }) - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - assert.equal( - result.results[0].error.message, - 'The proof\'s created timestamp is out of range.'); - - // errors should be serialized properly in the verification report - const {error} = JSON.parse(JSON.stringify(result)); - assert.typeOf(error, 'object'); - assert.sameMembers(Object.keys(error), ['name', 'errors']); - }); - - it('should detect a non-matching challenge', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'invalid', - domain: 'example.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-2018') - }) - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - const expectedMessage = 'The challenge is not as expected'; - assert.equal( - result.results[0].error.message.substr(0, expectedMessage.length), - expectedMessage); - }); - - it('should detect a non-matching domain', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'invalid.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-2018') - }) - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - const expectedMessage = 'The domain is not as expected'; - assert.equal( - result.results[0].error.message.substr(0, expectedMessage.length), - expectedMessage); - }); - - it('should fail to verify because the purpose is not authorized', - async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-1970'), - maxTimestampDelta: 0 - }) - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - const expectedMessage = 'Verification method'; - assert.equal( - result.results[0].error.message.substr(0, expectedMessage.length), - expectedMessage); - }); - - it('should sign and verify', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-1970'), - maxTimestampDelta: 0, - controller: mock.suites[suiteName].parameters - .authenticationController - }) - }); - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - authentication: 'https://example.com/i/alice/keys/1', - id: 'https://example.com/i/alice', - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - authentication: 'https://example.com/i/carol/keys/1', - id: 'https://example.com/i/carol' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - }); - - it('should sign and verify without a controller passed to purpose', - async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.controllerObject, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com' - }) - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AuthenticationProofPurpose({ - challenge: 'abc', - domain: 'example.com', - date: new Date('01-01-1970'), - maxTimestampDelta: 0, - }) - }); - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: [ - { - controller: 'https://example.com/i/alex', - id: 'https://example.com/i/alex/keys/1', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0MG729HDdieuzyFT+vdg\r\nMXDjTdCniWv64evMXydjfaYlTsmd1FfFQYJdrKJaFzB4y9vm37yKvsw7FJFymSzm\r\nk4T62yMqCIe19UNGHqk5TDVSKf0XZTZX+5i9qhQOaL7yFzzLunI8bNxAzJZ63cGW\r\nf4uJI+513SN9IKvh45vWlgsbZ/ekELHF0YXrupeTzQZMq4fl2/vQxPPmpooNXZ3F\r\nud9DZLAyWhKg69u996XjYP0QcjkE7H1PC1Um+CYDGe65pzBQlYlwgYtztK64kK3A\r\n2FGVQufyQ+19FlHTJTYdyy/zKtyE2+22wuANiLkg9JQEWroRQaGBLCmjwaA+AMQm\r\nfQIDAQAB\r\n-----END PUBLIC KEY-----\r\n', - type: 'RsaVerificationKey2018' - } - ], - authentication: [ - 'https://example.com/i/alex/keys/1' - ], - id: 'https://example.com/i/alex', - publicKey: 'https://example.com/i/alex/keys/1' - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: [{ - controller: 'https://example.com/i/ned', - id: 'https://example.com/i/ned/keys/1', - /* eslint-disable-next-line max-len */ - publicKeyBase58: '39GT26rnBupnnwBhwqHxsCgqoMNYauRStTQCN5JNaPL7', - type: 'Ed25519VerificationKey2018' - }], - authentication: [ - 'https://example.com/i/ned/keys/1' - ], - id: 'https://example.com/i/ned', - publicKey: 'https://example.com/i/ned/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - context('AssertionProofPurpose', () => { - it('should detect an expired date', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AssertionProofPurpose() - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AssertionProofPurpose({ - date: new Date('01-01-2018'), - maxTimestampDelta: 0 - }) - }); - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - authentication: 'https://example.com/i/alice/keys/1', - id: 'https://example.com/i/alice', - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: [ - 'https://example.com/i/ned/keys/1' - ], - authentication: [ - { - controller: 'https://example.com/i/ned', - id: 'https://example.com/i/ned/keys/1', - /* eslint-disable-next-line max-len */ - publicKeyBase58: '39GT26rnBupnnwBhwqHxsCgqoMNYauRStTQCN5JNaPL7', - type: 'Ed25519VerificationKey2018' - } - ], - id: 'https://example.com/i/ned', - publicKey: 'https://example.com/i/ned/keys/1' - }, - valid: true - } - }; - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - purposeResult: expectedPurposeResult[suiteName], - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - assert.equal( - result.results[0].error.message, - 'The proof\'s created timestamp is out of range.'); - }); - - it('should fail to verify because the purpose is not authorized', - async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AssertionProofPurpose() - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AssertionProofPurpose() - }); - const expected = { - verified: false, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - verified: false - }] - }; - assert.isFalse(result.verified); - assert.isArray(result.results); - assert.equal(result.results.length, expected.results.length); - assert.deepEqual(result.results[0].proof, expected.results[0].proof); - assert.equal( - result.results[0].verified, expected.results[0].verified); - const expectedMessage = 'Verification method'; - assert.equal( - result.results[0].error.message.substr(0, expectedMessage.length), - expectedMessage); - }); - - it('should sign and verify', async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.sign, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AssertionProofPurpose() - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AssertionProofPurpose({ - date: new Date('01-01-1970'), - maxTimestampDelta: 0, - controller: mock.suites[suiteName].parameters - .assertionController - }) - }); - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: 'https://example.com/i/alice/keys/1', - id: 'https://example.com/i/alice' - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: 'https://example.com/i/carol/keys/1', - id: 'https://example.com/i/carol' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - - it('should sign and verify without a controller passed to purpose', - async () => { - const Suite = suites[suiteName]; - const signSuite = new Suite({ - ...mock.suites[suiteName].parameters.controllerObject, - date: new Date('01-01-1970') - }); - const testDoc = clone(mock.securityContextTestDoc); - const signed = await jsigs.sign(testDoc, { - documentLoader: testLoader, - suite: signSuite, - purpose: new AssertionProofPurpose() - }); - - const verifySuite = new Suite( - mock.suites[suiteName].parameters.verify); - const result = await jsigs.verify(signed, { - documentLoader: testLoader, - suite: verifySuite, - purpose: new AssertionProofPurpose({ - date: new Date('01-01-1970'), - maxTimestampDelta: 0, - }) - }); - const expectedPurposeResult = { - RsaSignature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: [ - 'https://example.com/i/alex/keys/1' - ], - authentication: [ - { - controller: 'https://example.com/i/alex', - id: 'https://example.com/i/alex/keys/1', - /* eslint-disable-next-line max-len */ - publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0MG729HDdieuzyFT+vdg\r\nMXDjTdCniWv64evMXydjfaYlTsmd1FfFQYJdrKJaFzB4y9vm37yKvsw7FJFymSzm\r\nk4T62yMqCIe19UNGHqk5TDVSKf0XZTZX+5i9qhQOaL7yFzzLunI8bNxAzJZ63cGW\r\nf4uJI+513SN9IKvh45vWlgsbZ/ekELHF0YXrupeTzQZMq4fl2/vQxPPmpooNXZ3F\r\nud9DZLAyWhKg69u996XjYP0QcjkE7H1PC1Um+CYDGe65pzBQlYlwgYtztK64kK3A\r\n2FGVQufyQ+19FlHTJTYdyy/zKtyE2+22wuANiLkg9JQEWroRQaGBLCmjwaA+AMQm\r\nfQIDAQAB\r\n-----END PUBLIC KEY-----\r\n', - type: 'RsaVerificationKey2018' - } - ], - id: 'https://example.com/i/alex', - publicKey: 'https://example.com/i/alex/keys/1' - }, - valid: true - }, - Ed25519Signature2018: { - controller: { - '@context': 'https://w3id.org/security/v2', - assertionMethod: [ - 'https://example.com/i/ned/keys/1' - ], - authentication: [ - { - controller: 'https://example.com/i/ned', - id: 'https://example.com/i/ned/keys/1', - /* eslint-disable-next-line max-len */ - publicKeyBase58: '39GT26rnBupnnwBhwqHxsCgqoMNYauRStTQCN5JNaPL7', - type: 'Ed25519VerificationKey2018' - } - ], - id: 'https://example.com/i/ned', - publicKey: 'https://example.com/i/ned/keys/1' - }, - valid: true - } - }; - const expected = { - verified: true, - results: [{ - proof: { - '@context': constants.SECURITY_CONTEXT_URL, - ...signed.proof - }, - purposeResult: expectedPurposeResult[suiteName], - verified: true - }] - }; - assert.deepEqual(result, expected); - }); - }); - }); - } -}); -}; diff --git a/tests/test-karma.js b/tests/test-karma.js deleted file mode 100644 index 58dd6997..00000000 --- a/tests/test-karma.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Karma test runner for jsonld-signatures. - * - * Use environment vars to control, set via karma.conf.js/webpack: - * - * Set dirs, manifests, or js to run: - * JSONLD_TESTS="r1 r2 ..." - * Output an EARL report: - * EARL=filename - * Bail with tests fail: - * BAIL=true - * - * Copyright (c) 2011-2018 Digital Bazaar, Inc. All rights reserved. - */ -// FIXME: hack to ensure delay is set first -//mocha.setup({delay: true, ui: 'bdd'}); - -// test suite compatibility -require('core-js/fn/string/ends-with'); -require('core-js/fn/string/starts-with'); - -// jsonld compatibility -require('core-js/fn/array/from'); -require('core-js/fn/array/includes'); -require('core-js/fn/map'); -require('core-js/fn/object/assign'); -require('core-js/fn/promise'); -require('core-js/fn/set'); -require('core-js/fn/symbol'); - -const assert = require('chai').assert; -const common = require('./test-common'); -const constants = require('../lib/constants'); -const jsigs = require('..'); -const mock = require('./mock/mock'); -const {suites} = require('../lib/suites'); -const util = require('../lib/util'); - -const options = { - assert, - constants, - jsigs, - mock, - suites, - util, - nodejs: false -}; - -common(options).catch(err => { - console.error(err); -}); diff --git a/tests/test.js b/tests/test.js deleted file mode 100644 index 788ec234..00000000 --- a/tests/test.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Node.js test runner for jsonld-signatures. - * - * @author Dave Longley - * @author David I. Lehn - * - * Copyright (c) 2011-2018 Digital Bazaar, Inc. All rights reserved. - */ -'use strict'; - -const assert = require('chai').assert; -const common = require('./test-common'); -const constants = require('../lib/constants'); -const jsigs = require('..'); -const mock = require('./mock/mock'); -const {suites} = require('../lib/suites'); -const util = require('../lib/util'); - -const options = { - assert, - constants, - jsigs, - mock, - suites, - util, - nodejs: true -}; - -common(options).then(() => { - run(); -}).catch(err => { - console.error(err); -}); - -process.on('unhandledRejection', (reason, p) => { - console.error('Unhandled Rejection at:', p, 'reason:', reason); -}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 7fd681d7..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * jsonld-signatures webpack build rules. - * - * @author Digital Bazaar, Inc. - * - * Copyright 2010-2017 Digital Bazaar, Inc. - */ -const path = require('path'); -const webpackMerge = require('webpack-merge'); - -// build multiple outputs -module.exports = []; - -// custom setup for each output -// all built files will export the "jsonld-signatures" library but with -// different content -const outputs = [ - // core jsonld-signatures library - { - entry: [ - // '@babel/polyfill' is very large, list features explicitly - 'core-js/fn/object/assign', - 'core-js/fn/promise', - // main lib - './lib/jsonld-signatures.js' - ], - filenameBase: 'jsonld-signatures' - }, - /* - // core jsonld library + extra utils and networking support - { - entry: ['./lib/index.all.js'], - filenameBase: 'jsonld.all' - } - */ - // custom builds can be created by specifying the high level files you need - // webpack will pull in dependencies as needed - // Note: if using UMD or similar, add jsonld.js *last* to properly export - // the top level jsonld namespace. - //{ - // entry: ['./lib/FOO.js', ..., './lib/jsonld.js'], - // filenameBase: 'jsonld.custom' - // libraryTarget: 'umd' - //} -]; - -outputs.forEach(info => { - // common to bundle and minified - const common = { - // each output uses the "jsonld" name but with different contents - entry: { - 'jsonld-signatures': info.entry - }, - module: { - rules: [ - { - test: /\.js$/, - include: [{ - // exclude node_modules by default - exclude: /(node_modules)/ - }/*, { - // include jsonld - //include: /(node_modules\/jsonld)/ - }*/], - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - plugins: [ - [ - '@babel/plugin-proposal-object-rest-spread', - {useBuiltIns: true} - ], - '@babel/plugin-transform-modules-commonjs', - '@babel/plugin-transform-runtime' - ] - } - } - } - ] - }, - plugins: [ - //new webpack.DefinePlugin({ - //}) - ], - // disable various node shims as jsonld handles this manually - node: { - Buffer: false, - crypto: false, - process: false, - setImmediate: false - }, - externals: { - 'node-forge': { - amd: 'node-forge', - commonjs: 'node-forge', - commonjs2: 'node-forge', - root: 'forge' - }, - jsonld: 'jsonld' - } - }; - - // plain unoptimized unminified bundle - const bundle = webpackMerge(common, { - mode: 'development', - output: { - path: path.join(__dirname, 'dist'), - filename: info.filenameBase + '.js', - library: info.library || '[name]', - libraryTarget: info.libraryTarget || 'umd' - } - }); - if(info.library === null) { - delete bundle.output.library; - } - if(info.libraryTarget === null) { - delete bundle.output.libraryTarget; - } - - // optimized and minified bundle - const minify = webpackMerge(common, { - mode: 'production', - output: { - path: path.join(__dirname, 'dist'), - filename: info.filenameBase + '.min.js', - library: info.library || '[name]', - libraryTarget: info.libraryTarget || 'umd' - }, - devtool: 'cheap-module-source-map', - plugins: [ - /* - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: true - }, - output: { - comments: false - } - //beautify: true - }) - */ - ] - }); - if(info.library === null) { - delete minify.output.library; - } - if(info.libraryTarget === null) { - delete minify.output.libraryTarget; - } - - module.exports.push(bundle); - module.exports.push(minify); -});