@interop/did-web-resolver 1.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@ TBD
21
21
  ## Background
22
22
 
23
23
  A `did:web` method driver for use with in-browser and server-side on Node.js
24
- with the [`did-io`](https://github.com/digitalbazaar/did-io) resolver library.
24
+ with the [`did-io`](https://github.com/digitalcredentials/did-io) resolver library.
25
25
 
26
26
  Draft spec (W3C CCG Work Item):
27
27
 
@@ -37,9 +37,9 @@ Other implementations:
37
37
 
38
38
  ```js
39
39
  import { Ed25519VerificationKey2020 }
40
- from '@digitalbazaar/ed25519-verification-key-2020'
40
+ from '@digitalcredentials/ed25519-verification-key-2020'
41
41
  import { X25519KeyAgreementKey2020 }
42
- from '@digitalbazaar/x25519-key-agreement-key-2020'
42
+ from '@digitalcredentials/x25519-key-agreement-key-2020'
43
43
  import { CryptoLD } from 'crypto-ld'
44
44
 
45
45
  import * as didWeb from '@interop/did-web-resolver'
@@ -51,7 +51,7 @@ cryptoLd.use(X25519KeyAgreementKey2020)
51
51
  const didWebDriver = didWeb.driver({ cryptoLd })
52
52
 
53
53
  // Optionally use it with the CachedResolver from did-io
54
- import {CachedResolver} from '@digitalbazaar/did-io';
54
+ import {CachedResolver} from '@digitalcredentials/did-io';
55
55
  const resolver = new CachedResolver()
56
56
  resolver.use(didWebDriver)
57
57
  ```
package/build-dist.sh ADDED
@@ -0,0 +1,14 @@
1
+ mkdir ./dist/esm
2
+ cat >dist/esm/index.js <<!EOF
3
+ import cjsModule from '../index.js';
4
+ export const driver = cjsModule.driver;
5
+ export const DidWebResolver = cjsModule.DidWebResolver;
6
+ export const didFromUrl = cjsModule.didFromUrl;
7
+ export const urlFromDid = cjsModule.urlFromDid;
8
+ !EOF
9
+
10
+ cat >dist/esm/package.json <<!EOF
11
+ {
12
+ "type": "module"
13
+ }
14
+ !EOF
@@ -0,0 +1,336 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var httpClient = require('@digitalcredentials/http-client');
6
+ var didIo = require('@digitalcredentials/did-io');
7
+ var ed25519Context = require('ed25519-signature-2020-context');
8
+ var x25519Context = require('x25519-key-agreement-2020-context');
9
+ var didContext = require('did-context');
10
+
11
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n["default"] = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var didIo__namespace = /*#__PURE__*/_interopNamespace(didIo);
32
+ var ed25519Context__default = /*#__PURE__*/_interopDefaultLegacy(ed25519Context);
33
+ var x25519Context__default = /*#__PURE__*/_interopDefaultLegacy(x25519Context);
34
+ var didContext__default = /*#__PURE__*/_interopDefaultLegacy(didContext);
35
+
36
+ const { VERIFICATION_RELATIONSHIPS } = didIo__namespace;
37
+
38
+ const DEFAULT_KEY_MAP = {
39
+ capabilityInvocation: 'Ed25519VerificationKey2020',
40
+ authentication: 'Ed25519VerificationKey2020',
41
+ assertionMethod: 'Ed25519VerificationKey2020',
42
+ capabilityDelegation: 'Ed25519VerificationKey2020',
43
+ keyAgreement: 'X25519KeyAgreementKey2020'
44
+ };
45
+
46
+ function didFromUrl ({ url } = {}) {
47
+ if (!url) {
48
+ throw new TypeError('Cannot convert url to did, missing url.')
49
+ }
50
+ if (url.startsWith('http:')) {
51
+ throw new TypeError('did:web does not support non-HTTPS URLs.')
52
+ }
53
+
54
+ let parsedUrl;
55
+ try {
56
+ parsedUrl = new URL(url);
57
+ } catch (error) {
58
+ throw new TypeError(`Invalid url: "${url}".`)
59
+ }
60
+
61
+ const { host, pathname } = parsedUrl;
62
+
63
+ let pathComponent = '';
64
+ if (pathname && pathname !== '/' && pathname !== '/.well-known/did.json') {
65
+ pathComponent = pathname.split('/').map(encodeURIComponent).join(':');
66
+ }
67
+
68
+ return 'did:web:' + encodeURIComponent(host) + pathComponent
69
+ }
70
+
71
+ function urlFromDid ({ did } = {}) {
72
+ if (!did) {
73
+ throw new TypeError('Cannot convert did to url, missing did.')
74
+ }
75
+ if (!did.startsWith('did:web:')) {
76
+ throw new TypeError(`DID Method not supported: "${did}".`)
77
+ }
78
+
79
+ const [didUrl, hashFragment] = did.split('#');
80
+ // eslint-disable-next-line no-unused-vars
81
+ // const [didResource, query] = didUrl.split('?')
82
+
83
+ // eslint-disable-next-line no-unused-vars
84
+ const [_did, _web, urlNoProtocol] = didUrl.split(':');
85
+
86
+ let parsedUrl;
87
+ try {
88
+ // URI-decode the url (in case it contained a port number,
89
+ // for example, `did:web:localhost%3A8080`
90
+ parsedUrl = new URL('https://' + decodeURIComponent(urlNoProtocol));
91
+ } catch (error) {
92
+ throw new TypeError(`Cannot construct url from did: "${did}".`)
93
+ }
94
+
95
+ if (!parsedUrl.pathname || parsedUrl.pathname === '/') {
96
+ parsedUrl.pathname = '/.well-known/did.json';
97
+ } else {
98
+ const pathFragments = parsedUrl.pathname.split('/');
99
+ parsedUrl.pathname = pathFragments.map(decodeURIComponent).join('/');
100
+ }
101
+
102
+ if (hashFragment) {
103
+ parsedUrl.hash = hashFragment;
104
+ }
105
+ return parsedUrl.toString()
106
+ }
107
+
108
+ /**
109
+ * Initializes the DID Document's keys/proof methods.
110
+ *
111
+ * @example
112
+ * didDocument.id = 'did:ex:123';
113
+ * const {didDocument, keyPairs} = await initKeys({
114
+ * didDocument,
115
+ * cryptoLd,
116
+ * keyMap: {
117
+ * capabilityInvocation: someExistingKey,
118
+ * authentication: 'Ed25519VerificationKey2020',
119
+ * assertionMethod: 'Ed25519VerificationKey2020',
120
+ * keyAgreement: 'X25519KeyAgreementKey2019'
121
+ * }
122
+ * });.
123
+ *
124
+ * @param {object} options - Options hashmap.
125
+ * @param {object} options.didDocument - DID Document.
126
+ * @typedef {object} CryptoLD
127
+ * @param {CryptoLD} [options.cryptoLd] - CryptoLD driver instance,
128
+ * initialized with the key types this DID Document intends to support.
129
+ * @param {object} [options.keyMap] - Map of keys (or key types) by purpose.
130
+ *
131
+ * @returns {Promise<{didDocument: object, keyPairs: Map}>} Resolves with the
132
+ * DID Document initialized with keys, as well as the map of the corresponding
133
+ * key pairs (by key id).
134
+ */
135
+ async function initKeys ({ didDocument, cryptoLd, keyMap = {} } = {}) {
136
+ const doc = { ...didDocument };
137
+ if (!doc.id) {
138
+ throw new TypeError(
139
+ 'DID Document "id" property is required to initialize keys.')
140
+ }
141
+
142
+ const keyPairs = new Map();
143
+
144
+ // Set the defaults for the created keys (if needed)
145
+ const options = { controller: doc.id };
146
+
147
+ for (const purpose in keyMap) {
148
+ if (!VERIFICATION_RELATIONSHIPS.has(purpose)) {
149
+ throw new Error(`Unsupported key purpose: "${purpose}".`)
150
+ }
151
+
152
+ let key;
153
+ if (typeof keyMap[purpose] === 'string') {
154
+ if (!cryptoLd) {
155
+ throw new Error('Please provide an initialized CryptoLD instance.')
156
+ }
157
+ key = await cryptoLd.generate({ type: keyMap[purpose], ...options });
158
+ } else {
159
+ // An existing key has been provided
160
+ key = keyMap[purpose];
161
+ }
162
+
163
+ doc[purpose] = [key.export({ publicKey: true })];
164
+ keyPairs.set(key.id, key);
165
+ }
166
+
167
+ return { didDocument: doc, keyPairs }
168
+ }
169
+
170
+ class DidWebResolver {
171
+ /**
172
+ * @param cryptoLd {CryptoLD}
173
+ * @param keyMap {object}
174
+ * @param [logger] {object} Logger object (with .log, .error, .warn,
175
+ * etc methods).
176
+ */
177
+ constructor ({ cryptoLd, keyMap = DEFAULT_KEY_MAP, logger = console } = {}) {
178
+ this.method = 'web'; // did:web:... (used for didIo resolver harness)
179
+ this.cryptoLd = cryptoLd;
180
+ this.keyMap = keyMap;
181
+ this.logger = logger;
182
+ }
183
+
184
+ /**
185
+ * Generates a new DID Document and initializes various authentication
186
+ * and authorization proof purpose keys.
187
+ *
188
+ * @example
189
+ * const url = 'https://example.com'
190
+ * const { didDocument, didKeys } = await didWeb.generate({url})
191
+ * didDocument.id
192
+ * // -> 'did:web:example.com'
193
+ *
194
+ *
195
+ * Either an `id` or a `url` is required:
196
+ * @param [id] {string} - A did:web DID. If absent, will be converted from url
197
+ * @param [url] {string}
198
+ *
199
+ * @param [keyMap=DEFAULT_KEY_MAP] {object} A hashmap of key types by purpose.
200
+ *
201
+ * @parma [cryptoLd] {object} CryptoLD instance with support for supported
202
+ * crypto suites installed.
203
+ *
204
+ * @returns {Promise<{didDocument: object, keyPairs: Map,
205
+ * methodFor: Function}>} Resolves with the generated DID Document, along
206
+ * with the corresponding key pairs used to generate it (for storage in a
207
+ * KMS).
208
+ */
209
+ async generate ({ id, url, keyMap = this.keyMap, cryptoLd = this.cryptoLd } = {}) {
210
+ const did = id || didFromUrl({ url });
211
+
212
+ // Compose the DID Document
213
+ let didDocument = {
214
+ '@context': [
215
+ didContext__default["default"].constants.DID_CONTEXT_URL,
216
+ ed25519Context__default["default"].constants.CONTEXT_URL,
217
+ x25519Context__default["default"].constants.CONTEXT_URL
218
+ ],
219
+ id: did
220
+ };
221
+
222
+ const result = await initKeys({ didDocument, cryptoLd, keyMap });
223
+ const keyPairs = result.keyPairs;
224
+ didDocument = result.didDocument;
225
+
226
+ // Convenience function that returns the public/private key pair instance
227
+ // for a given purpose (authentication, assertionMethod, keyAgreement, etc).
228
+ const methodFor = ({ purpose }) => {
229
+ const { id: methodId } = didIo__namespace.findVerificationMethod({
230
+ doc: didDocument, purpose
231
+ });
232
+ return keyPairs.get(methodId)
233
+ };
234
+
235
+ return { didDocument, keyPairs, methodFor }
236
+ }
237
+
238
+ /**
239
+ * Fetches a DID Document for a given DID.
240
+ *
241
+ * @example
242
+ * // In Node.js tests, use an agent to avoid self-signed certificate errors
243
+ * const agent = new https.agent({rejectUnauthorized: false});
244
+ *
245
+ * @param {string} [did] For example, 'did:web:example.com'
246
+ * @param {string} [url]
247
+ * @param {https.Agent} [agent] Optional agent used to customize network
248
+ * behavior in Node.js (such as `rejectUnauthorized: false`).
249
+ * @param {object} [logger] Logger object (with .log, .error, .warn,
250
+ * etc methods).
251
+ *
252
+ * @throws {Error}
253
+ *
254
+ * @returns {Promise<object>} Plain parsed JSON object of the DID Document.
255
+ */
256
+ async get ({ did, url, agent, logger = this.logger }) {
257
+ const didUrl = url || urlFromDid({ did });
258
+ if (!didUrl) {
259
+ throw new TypeError('A DID or a URL is required.')
260
+ }
261
+
262
+ const [urlAuthority, keyIdFragment] = didUrl.split('#');
263
+
264
+ let didDocument;
265
+ try {
266
+ logger.info(`Fetching "${urlAuthority}" via http client.`);
267
+ const result = await httpClient.httpClient.get(urlAuthority, { agent });
268
+ didDocument = result.data;
269
+ } catch (e) {
270
+ // status is HTTP status code
271
+ // data is JSON error from the server if available
272
+ const { data, status } = e;
273
+ logger.error(`Http ${status} error:`, data);
274
+ throw e
275
+ }
276
+ if (didDocument && keyIdFragment) {
277
+ // resolve an individual key
278
+ // Keys are expected to have format: <did:web:...>#<keyIdFragment>
279
+ const didAuthority = didFromUrl({ url: urlAuthority });
280
+ const methodId = `${didAuthority}#${keyIdFragment}`;
281
+
282
+ const key = didIo__namespace.findVerificationMethod({ doc: didDocument, methodId });
283
+ if (!key) {
284
+ throw new Error(`Key id ${methodId} not found.`)
285
+ }
286
+
287
+ const keyPair = await this.cryptoLd.from(key);
288
+
289
+ return keyPair.export({ publicKey: true, includeContext: true })
290
+ }
291
+
292
+ return didDocument
293
+ }
294
+
295
+ /**
296
+ * Returns the public key (verification method) object for a given DID
297
+ * Document and purpose. Useful in conjunction with a `.get()` call.
298
+ *
299
+ * @example
300
+ * const didDocument = await didKeyDriver.get({did});
301
+ * const authKeyData = didDriver.publicMethodFor({
302
+ * didDocument, purpose: 'authentication'
303
+ * });
304
+ * // You can then create a suite instance object to verify signatures etc.
305
+ * const authPublicKey = await cryptoLd.from(authKeyData);
306
+ * const {verify} = authPublicKey.verifier();
307
+ *
308
+ * @param {object} options - Options hashmap.
309
+ * @param {object} options.didDocument - DID Document (retrieved via a
310
+ * `.get()` or from some other source).
311
+ * @param {string} options.purpose - Verification method purpose, such as
312
+ * 'authentication', 'assertionMethod', 'keyAgreement' and so on.
313
+ *
314
+ * @returns {object} Returns the public key object (obtained from the DID
315
+ * Document), without a `@context`.
316
+ */
317
+ publicMethodFor ({ didDocument, purpose } = {}) {
318
+ if (!didDocument) {
319
+ throw new TypeError('The "didDocument" parameter is required.')
320
+ }
321
+ if (!purpose) {
322
+ throw new TypeError('The "purpose" parameter is required.')
323
+ }
324
+
325
+ const method = didIo__namespace.findVerificationMethod({ doc: didDocument, purpose });
326
+ if (!method) {
327
+ throw new Error(`No verification method found for purpose "${purpose}"`)
328
+ }
329
+ return method
330
+ }
331
+ }
332
+
333
+ exports.DidWebResolver = DidWebResolver;
334
+ exports.didFromUrl = didFromUrl;
335
+ exports.initKeys = initKeys;
336
+ exports.urlFromDid = urlFromDid;
@@ -0,0 +1,5 @@
1
+ import cjsModule from '../index.js';
2
+ export const driver = cjsModule.driver;
3
+ export const DidWebResolver = cjsModule.DidWebResolver;
4
+ export const didFromUrl = cjsModule.didFromUrl;
5
+ export const urlFromDid = cjsModule.urlFromDid;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var DidWebResolver = require('./DidWebResolver.js');
6
+
7
+ const driver = options => {
8
+ return new DidWebResolver.DidWebResolver(options)
9
+ };
10
+
11
+ exports.DidWebResolver = DidWebResolver.DidWebResolver;
12
+ exports.didFromUrl = DidWebResolver.didFromUrl;
13
+ exports.urlFromDid = DidWebResolver.urlFromDid;
14
+ exports.driver = driver;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@interop/did-web-resolver",
3
3
  "description": "A did:web method Decentralized Identifier (DID) resolver for the did-io library.",
4
- "version": "1.0.1",
4
+ "version": "2.1.1",
5
5
  "author": {
6
6
  "name": "Dmitri Zagidulin",
7
7
  "url": "https://github.com/dmitrizagidulin/"
@@ -14,34 +14,56 @@
14
14
  "homepage": "https://github.com/interop-alliance/did-web-driver",
15
15
  "bugs": "https://github.com/interop-alliance/did-web-driver/issues",
16
16
  "scripts": {
17
+ "rollup": "rollup -c rollup.config.js",
18
+ "build": "npm run clear && npm run rollup && ./build-dist.sh",
19
+ "clear": "rimraf dist/ && mkdir dist",
20
+ "prepare": "npm run build",
21
+ "rebuild": "npm run clear && npm run build",
17
22
  "test": "npm run standard && npm run test-node",
18
23
  "test-node": "cross-env NODE_ENV=test mocha -r esm --preserve-symlinks -t 10000 test/**/*.spec.js",
19
24
  "test-karma": "karma start test/karma.conf.js",
20
25
  "nyc": "cross-env NODE_ENV=test nyc npm run test-node",
21
26
  "standard": "standard --fix"
22
27
  },
28
+ "files": [
29
+ "dist",
30
+ "src",
31
+ "rollup.config.js",
32
+ "build-dist.sh",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "main": "dist/index.js",
37
+ "module": "dist/esm/index.js",
38
+ "exports": {
39
+ ".": {
40
+ "require": "./dist/index.js",
41
+ "import": "./dist/esm/index.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
23
45
  "dependencies": {
24
- "@digitalbazaar/did-io": "^1.0.0",
25
- "@digitalbazaar/http-client": "^1.1.0",
26
- "did-context": "^3.0.1",
46
+ "@digitalcredentials/did-io": "^1.0.2",
47
+ "@digitalcredentials/http-client": "^1.2.2",
48
+ "did-context": "^3.1.1",
27
49
  "ed25519-signature-2020-context": "^1.1.0",
28
- "esm": "^3.2.25",
29
50
  "x25519-key-agreement-2020-context": "^1.0.0"
30
51
  },
31
52
  "devDependencies": {
32
- "@babel/core": "^7.13.16",
33
- "@babel/plugin-transform-modules-commonjs": "^7.13.8",
34
- "@babel/plugin-transform-runtime": "^7.13.15",
35
- "@babel/preset-env": "^7.13.15",
36
- "@babel/runtime": "^7.13.17",
37
- "@digitalbazaar/ed25519-verification-key-2020": "^2.1.1",
38
- "@digitalbazaar/x25519-key-agreement-key-2020": "^1.2.0",
39
- "babel-loader": "^8.2.2",
53
+ "@babel/core": "^7.16.7",
54
+ "@babel/plugin-transform-modules-commonjs": "^7.16.7",
55
+ "@babel/plugin-transform-runtime": "^7.16.7",
56
+ "@babel/preset-env": "^7.16.7",
57
+ "@babel/runtime": "^7.16.7",
58
+ "@digitalcredentials/ed25519-verification-key-2020": "^3.2.2",
59
+ "@digitalcredentials/x25519-key-agreement-key-2020": "^2.0.2",
60
+ "babel-loader": "^8.2.3",
40
61
  "chai": "^4.3.4",
41
62
  "cross-env": "^7.0.3",
42
- "crypto-ld": "^5.1.0",
63
+ "crypto-ld": "^6.0.0",
64
+ "esm": "^3.2.25",
43
65
  "dirty-chai": "^2.0.1",
44
- "karma": "^6.3.2",
66
+ "karma": "^6.3.9",
45
67
  "karma-babel-preprocessor": "^8.0.1",
46
68
  "karma-chai": "^0.1.0",
47
69
  "karma-chrome-launcher": "^3.1.0",
@@ -49,13 +71,14 @@
49
71
  "karma-mocha-reporter": "^2.2.5",
50
72
  "karma-sourcemap-loader": "^0.3.8",
51
73
  "karma-webpack": "^5.0.0",
52
- "mocha": "^8.3.2",
74
+ "mocha": "^8.4.0",
53
75
  "nyc": "^15.1.0",
54
- "sinon": "^10.0.0",
55
- "standard": "^16.0.3",
56
- "webpack": "^5.35.1"
76
+ "sinon": "^12.0.1",
77
+ "standard": "^16.0.4",
78
+ "rimraf": "^3.0.2",
79
+ "rollup": "^2.62.0",
80
+ "webpack": "^5.65.0"
57
81
  },
58
- "main": "src/main.js",
59
82
  "nyc": {
60
83
  "reporter": [
61
84
  "html",
@@ -0,0 +1,15 @@
1
+ import pkg from './package.json'
2
+
3
+ export default [
4
+ {
5
+ input: './src/index.js',
6
+ output: [
7
+ {
8
+ dir: 'dist',
9
+ format: 'cjs',
10
+ preserveModules: true
11
+ }
12
+ ],
13
+ external: Object.keys(pkg.dependencies).concat(['crypto', 'util'])
14
+ }
15
+ ]
@@ -1,5 +1,5 @@
1
- import { httpClient } from '@digitalbazaar/http-client'
2
- import * as didIo from '@digitalbazaar/did-io'
1
+ import { httpClient } from '@digitalcredentials/http-client'
2
+ import * as didIo from '@digitalcredentials/did-io'
3
3
  import ed25519Context from 'ed25519-signature-2020-context'
4
4
  import x25519Context from 'x25519-key-agreement-2020-context'
5
5
  import didContext from 'did-context'
@@ -49,25 +49,31 @@ export function urlFromDid ({ did } = {}) {
49
49
 
50
50
  const [didUrl, hashFragment] = did.split('#')
51
51
  // eslint-disable-next-line no-unused-vars
52
- const [didResource, query] = didUrl.split('?')
52
+ // const [didResource, query] = didUrl.split('?')
53
53
 
54
54
  // eslint-disable-next-line no-unused-vars
55
- const [_did, _web, host, ...pathFragments] = didResource.split(':')
55
+ const [_did, _web, urlNoProtocol] = didUrl.split(':')
56
56
 
57
- let pathname = ''
58
- if (pathFragments.length === 0) {
59
- pathname = '/.well-known/did.json'
57
+ let parsedUrl
58
+ try {
59
+ // URI-decode the url (in case it contained a port number,
60
+ // for example, `did:web:localhost%3A8080`
61
+ parsedUrl = new URL('https://' + decodeURIComponent(urlNoProtocol))
62
+ } catch (error) {
63
+ throw new TypeError(`Cannot construct url from did: "${did}".`)
64
+ }
65
+
66
+ if (!parsedUrl.pathname || parsedUrl.pathname === '/') {
67
+ parsedUrl.pathname = '/.well-known/did.json'
60
68
  } else {
61
- pathname = '/' + pathFragments.map(decodeURIComponent).join('/')
69
+ const pathFragments = parsedUrl.pathname.split('/')
70
+ parsedUrl.pathname = pathFragments.map(decodeURIComponent).join('/')
62
71
  }
63
72
 
64
- const url = new URL(pathname, 'https://' + decodeURIComponent(host))
65
73
  if (hashFragment) {
66
- url.hash = hashFragment
74
+ parsedUrl.hash = hashFragment
67
75
  }
68
- // TODO: Not passing on the query part currently; reserved for DID URLs?
69
-
70
- return url.toString()
76
+ return parsedUrl.toString()
71
77
  }
72
78
 
73
79
  /**
@@ -224,10 +230,13 @@ export class DidWebResolver {
224
230
  throw new TypeError('A DID or a URL is required.')
225
231
  }
226
232
 
227
- let result
233
+ const [urlAuthority, keyIdFragment] = didUrl.split('#')
234
+
235
+ let didDocument
228
236
  try {
229
- logger.info(`Fetching "${didUrl}" via http client.`)
230
- result = await httpClient.get(didUrl, { agent })
237
+ logger.info(`Fetching "${urlAuthority}" via http client.`)
238
+ const result = await httpClient.get(urlAuthority, { agent })
239
+ didDocument = result.data
231
240
  } catch (e) {
232
241
  // status is HTTP status code
233
242
  // data is JSON error from the server if available
@@ -235,7 +244,59 @@ export class DidWebResolver {
235
244
  logger.error(`Http ${status} error:`, data)
236
245
  throw e
237
246
  }
247
+ if (didDocument && keyIdFragment) {
248
+ // resolve an individual key
249
+ // Keys are expected to have format: <did:web:...>#<keyIdFragment>
250
+ const didAuthority = didFromUrl({ url: urlAuthority })
251
+ const methodId = `${didAuthority}#${keyIdFragment}`
252
+
253
+ const key = didIo.findVerificationMethod({ doc: didDocument, methodId })
254
+ if (!key) {
255
+ throw new Error(`Key id ${methodId} not found.`)
256
+ }
257
+
258
+ const keyPair = await this.cryptoLd.from(key)
259
+
260
+ return keyPair.export({ publicKey: true, includeContext: true })
261
+ }
238
262
 
239
- return result.data
263
+ return didDocument
264
+ }
265
+
266
+ /**
267
+ * Returns the public key (verification method) object for a given DID
268
+ * Document and purpose. Useful in conjunction with a `.get()` call.
269
+ *
270
+ * @example
271
+ * const didDocument = await didKeyDriver.get({did});
272
+ * const authKeyData = didDriver.publicMethodFor({
273
+ * didDocument, purpose: 'authentication'
274
+ * });
275
+ * // You can then create a suite instance object to verify signatures etc.
276
+ * const authPublicKey = await cryptoLd.from(authKeyData);
277
+ * const {verify} = authPublicKey.verifier();
278
+ *
279
+ * @param {object} options - Options hashmap.
280
+ * @param {object} options.didDocument - DID Document (retrieved via a
281
+ * `.get()` or from some other source).
282
+ * @param {string} options.purpose - Verification method purpose, such as
283
+ * 'authentication', 'assertionMethod', 'keyAgreement' and so on.
284
+ *
285
+ * @returns {object} Returns the public key object (obtained from the DID
286
+ * Document), without a `@context`.
287
+ */
288
+ publicMethodFor ({ didDocument, purpose } = {}) {
289
+ if (!didDocument) {
290
+ throw new TypeError('The "didDocument" parameter is required.')
291
+ }
292
+ if (!purpose) {
293
+ throw new TypeError('The "purpose" parameter is required.')
294
+ }
295
+
296
+ const method = didIo.findVerificationMethod({ doc: didDocument, purpose })
297
+ if (!method) {
298
+ throw new Error(`No verification method found for purpose "${purpose}"`)
299
+ }
300
+ return method
240
301
  }
241
302
  }
@@ -1,35 +0,0 @@
1
- name: Node.js CI
2
-
3
- on: [push]
4
-
5
- jobs:
6
- test-node:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- node-version: [14.x]
11
- steps:
12
- - uses: actions/checkout@v2
13
- - name: Use Node.js ${{ matrix.node-version }}
14
- uses: actions/setup-node@v1
15
- with:
16
- node-version: ${{ matrix.node-version }}
17
- - run: npm install
18
- - name: Run test with Node.js ${{ matrix.node-version }}
19
- run: npm run test-node
20
- env:
21
- CI: true
22
- lint:
23
- runs-on: ubuntu-latest
24
- strategy:
25
- matrix:
26
- node-version: [14.x]
27
- steps:
28
- - uses: actions/checkout@v2
29
- - name: Use Node.js ${{ matrix.node-version }}
30
- uses: actions/setup-node@v1
31
- with:
32
- node-version: ${{ matrix.node-version }}
33
- - run: npm install
34
- - name: Run Standard.js linter
35
- run: npm run standard
@@ -1,38 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <code_scheme name="Project" version="173">
3
- <option name="RIGHT_MARGIN" value="80" />
4
- <JSCodeStyleSettings version="0">
5
- <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
6
- <option name="FORCE_SEMICOLON_STYLE" value="true" />
7
- <option name="SPACE_BEFORE_GENERATOR_MULT" value="true" />
8
- <option name="USE_DOUBLE_QUOTES" value="false" />
9
- <option name="FORCE_QUOTE_STYlE" value="true" />
10
- <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
11
- <option name="SPACES_WITHIN_IMPORTS" value="true" />
12
- </JSCodeStyleSettings>
13
- <codeStyleSettings language="HTML">
14
- <indentOptions>
15
- <option name="INDENT_SIZE" value="2" />
16
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
17
- <option name="TAB_SIZE" value="2" />
18
- </indentOptions>
19
- </codeStyleSettings>
20
- <codeStyleSettings language="JavaScript">
21
- <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
22
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
23
- <option name="ALIGN_MULTILINE_FOR" value="false" />
24
- <option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
25
- <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
26
- <option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
27
- <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
28
- <indentOptions>
29
- <option name="INDENT_SIZE" value="2" />
30
- <option name="CONTINUATION_INDENT_SIZE" value="2" />
31
- <option name="TAB_SIZE" value="2" />
32
- </indentOptions>
33
- </codeStyleSettings>
34
- <codeStyleSettings language="Markdown">
35
- <option name="SOFT_MARGINS" value="80" />
36
- </codeStyleSettings>
37
- </code_scheme>
38
- </component>
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
- </state>
5
- </component>
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/temp" />
6
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
@@ -1,6 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
- </profile>
6
- </component>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptLibraryMappings">
4
- <includedPredefinedLibrary name="Node.js Core" />
5
- </component>
6
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/did-web-resolver.iml" filepath="$PROJECT_DIR$/.idea/did-web-resolver.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>
package/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - "12"
4
-
5
- sudo: false
6
-
7
- notifications:
8
- email:
9
- on_success: change
10
- on_failure: change
package/CHANGELOG.md DELETED
@@ -1,24 +0,0 @@
1
- # did-web-driver ChangeLog
2
-
3
- ## 1.0.1 - 2021-04-25
4
-
5
- ### Fixed
6
- - Fix handling of hash fragments by `urlFromDid()`.
7
- - Add logger to constructor.
8
-
9
- ## 1.0.0 - 2021-04-24
10
-
11
- ### Changed
12
- - **BREAKING** Update to latest DID Core context
13
- - **BREAKING** Update to use crypto-ld v5 API, latest crypto suites
14
- - Add support for X25519KeyAgreementKey suite
15
-
16
- ## 0.2.0 - 2020-08-01
17
-
18
- ### Changed
19
- - **BREAKING**: Update to use crypto-ld v4 API
20
-
21
- ## 0.0.1
22
-
23
- ### Added
24
- - Initial implementation.
package/src/main.js DELETED
@@ -1,3 +0,0 @@
1
- // translate `index.js` to CommonJS
2
- require = require('esm')(module) // eslint-disable-line no-native-reassign, no-global-assign
3
- module.exports = require('./index.js')
@@ -1,40 +0,0 @@
1
- module.exports = (config) => {
2
- const bundler = process.env.BUNDLER || 'webpack'
3
- const frameworks = ['mocha']
4
- const files = ['**/*.spec.js']
5
- const reporters = ['mocha']
6
- const browsers = ['ChromeHeadless']
7
- const client = {
8
- mocha: {
9
- timeout: 2000
10
- }
11
- }
12
- // main bundle preprocessors
13
- const preprocessors = []
14
- preprocessors.push(bundler)
15
- preprocessors.push('sourcemap')
16
-
17
- return config.set({
18
- frameworks,
19
- files,
20
- reporters,
21
- basePath: '',
22
- port: 9876,
23
- colors: true,
24
- browsers,
25
- client,
26
- singleRun: true,
27
- preprocessors: {
28
- 'unit/*.js': preprocessors
29
- },
30
- webpack: {
31
- devtool: 'inline-source-map',
32
- mode: 'development',
33
- node: {
34
- Buffer: false,
35
- crypto: false,
36
- setImmediate: false
37
- }
38
- }
39
- })
40
- }
@@ -1,175 +0,0 @@
1
- import chai from 'chai'
2
- import dirtyChai from 'dirty-chai'
3
-
4
- import { DidWebResolver, urlFromDid, didFromUrl } from '../../src'
5
-
6
- import { Ed25519VerificationKey2020 }
7
- from '@digitalbazaar/ed25519-verification-key-2020'
8
- import { X25519KeyAgreementKey2020 }
9
- from '@digitalbazaar/x25519-key-agreement-key-2020'
10
- import { CryptoLD } from 'crypto-ld'
11
- chai.use(dirtyChai)
12
- chai.should()
13
- const { expect } = chai
14
-
15
- const cryptoLd = new CryptoLD()
16
- cryptoLd.use(Ed25519VerificationKey2020)
17
- cryptoLd.use(X25519KeyAgreementKey2020)
18
-
19
- describe('DidWebDriver', () => {
20
- describe('constructor', () => {
21
- it('should exist', () => {
22
- expect(new DidWebResolver()).to.exist()
23
- })
24
- })
25
-
26
- describe('generate()', () => {
27
- let didWeb
28
-
29
- beforeEach(async () => {
30
- didWeb = new DidWebResolver({ cryptoLd })
31
- })
32
-
33
- it('should generate using default key map', async () => {
34
- const url = 'https://example.com'
35
- const { didDocument, keyPairs } = await didWeb.generate({ url })
36
-
37
- expect(didDocument).to.have.property('@context')
38
- expect(didDocument.id).to.equal('did:web:example.com')
39
- expect(didDocument.capabilityInvocation[0].type)
40
- .to.equal('Ed25519VerificationKey2020')
41
- expect(didDocument.authentication[0].type)
42
- .to.equal('Ed25519VerificationKey2020')
43
- expect(didDocument.assertionMethod[0].type)
44
- .to.equal('Ed25519VerificationKey2020')
45
- expect(didDocument.capabilityDelegation[0].type)
46
- .to.equal('Ed25519VerificationKey2020')
47
-
48
- expect(keyPairs).to.exist()
49
- })
50
-
51
- it('should return methodFor convenience function', async () => {
52
- const url = 'https://example.com'
53
- const { methodFor } = await didWeb.generate({ url })
54
-
55
- const keyAgreementKey = methodFor({ purpose: 'keyAgreement' })
56
-
57
- expect(keyAgreementKey).to.have.property('type', 'X25519KeyAgreementKey2020')
58
- expect(keyAgreementKey).to.have.property('controller', 'did:web:example.com')
59
- expect(keyAgreementKey).to.have.property('publicKeyMultibase')
60
- expect(keyAgreementKey).to.have.property('privateKeyMultibase')
61
- })
62
- })
63
-
64
- describe('urlFromDid()', () => {
65
- it('should error on missing did', () => {
66
- let error
67
- try {
68
- urlFromDid()
69
- } catch (e) {
70
- error = e
71
- }
72
- expect(error.message).to.equal('Cannot convert did to url, missing did.')
73
- })
74
-
75
- it('should error on non-did:web dids', () => {
76
- let error
77
- try {
78
- urlFromDid({ did: 'did:example:1234' })
79
- } catch (e) {
80
- error = e
81
- }
82
- expect(error.message)
83
- .to.equal('DID Method not supported: "did:example:1234".')
84
- })
85
-
86
- it('should convert first id fragment to pathname plus default path', () => {
87
- expect(urlFromDid({ did: 'did:web:example.com' }))
88
- .to.equal('https://example.com/.well-known/did.json')
89
- })
90
-
91
- it('should url-decode host', () => {
92
- expect(urlFromDid({ did: 'did:web:localhost%3A8080' }))
93
- .to.equal('https://localhost:8080/.well-known/did.json')
94
- })
95
-
96
- it('should url-decode path fragments', () => {
97
- expect(urlFromDid({ did: 'did:web:example.com:path:some%2Bsubpath' }))
98
- .to.equal('https://example.com/path/some+subpath')
99
- })
100
-
101
- it('should preserve hash fragments for dids without paths', () => {
102
- const url = urlFromDid({ did: 'did:web:localhost%3A8080#keyId' })
103
- expect(url).to.equal('https://localhost:8080/.well-known/did.json#keyId')
104
- })
105
-
106
- it('should preserve hash fragments for dids with paths', () => {
107
- const url = urlFromDid({ did: 'did:web:example.com:path:some%2Bsubpath#keyId' })
108
- expect(url).to.equal('https://example.com/path/some+subpath#keyId')
109
- })
110
- })
111
-
112
- describe('didFromUrl', () => {
113
- it('should error on missing url', () => {
114
- let error
115
- try {
116
- didFromUrl()
117
- } catch (e) {
118
- error = e
119
- }
120
- expect(error.message).to.equal('Cannot convert url to did, missing url.')
121
- })
122
-
123
- it('should error on http URLs', () => {
124
- let error
125
- try {
126
- didFromUrl({ url: 'http://example.com' })
127
- } catch (e) {
128
- error = e
129
- }
130
- expect(error.message).to.equal('did:web does not support non-HTTPS URLs.')
131
- })
132
-
133
- it('should error on invalid URLs', () => {
134
- let error
135
- try {
136
- didFromUrl({ url: 'non-url' })
137
- } catch (e) {
138
- error = e
139
- }
140
- expect(error.message).to.equal('Invalid url: "non-url".')
141
- })
142
-
143
- it('should convert host to did identifier', () => {
144
- expect(didFromUrl({ url: 'https://localhost' }))
145
- .to.equal('did:web:localhost')
146
- expect(didFromUrl({ url: 'https://example.com' }))
147
- .to.equal('did:web:example.com')
148
- })
149
-
150
- it('should url-encode host', () => {
151
- expect(didFromUrl({ url: 'https://localhost:8080' }))
152
- .to.equal('did:web:localhost%3A8080')
153
- })
154
-
155
- it('should leave off the default / path', () => {
156
- expect(didFromUrl({ url: 'https://example.com/' }))
157
- .to.equal('did:web:example.com')
158
- })
159
-
160
- it('should encode path / separators as :', () => {
161
- expect(didFromUrl({ url: 'https://example.com/path/subpath/did.json' }))
162
- .to.equal('did:web:example.com:path:subpath:did.json')
163
- })
164
-
165
- it('should drop the default /.well-known/did.json pathname', () => {
166
- expect(didFromUrl({ url: 'https://example.com/.well-known/did.json' }))
167
- .to.equal('did:web:example.com')
168
- })
169
-
170
- it('should url-encode path fragments', () => {
171
- expect(didFromUrl({ url: 'https://example.com/path/some+subpath' }))
172
- .to.equal('did:web:example.com:path:some%2Bsubpath')
173
- })
174
- })
175
- })