@enbox/dids 0.0.5 → 0.0.7
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/dist/browser.mjs +1 -1
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/did.js +4 -0
- package/dist/esm/did.js.map +1 -1
- package/dist/esm/methods/did-dht-pkarr.js +3 -2
- package/dist/esm/methods/did-dht-pkarr.js.map +1 -1
- package/dist/esm/methods/did-ion.js +3 -2
- package/dist/esm/methods/did-ion.js.map +1 -1
- package/dist/esm/methods/did-web.js +64 -1
- package/dist/esm/methods/did-web.js.map +1 -1
- package/dist/esm/utils.js +15 -19
- package/dist/esm/utils.js.map +1 -1
- package/dist/types/did.d.ts.map +1 -1
- package/dist/types/methods/did-dht-pkarr.d.ts.map +1 -1
- package/dist/types/methods/did-ion.d.ts.map +1 -1
- package/dist/types/methods/did-web.d.ts.map +1 -1
- package/dist/types/utils.d.ts +25 -25
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +4 -4
- package/package.json +4 -4
- package/src/did.ts +3 -0
- package/src/methods/did-dht-pkarr.ts +3 -2
- package/src/methods/did-ion.ts +3 -2
- package/src/methods/did-web.ts +52 -1
- package/src/utils.ts +31 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enbox/dids",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "TBD DIDs library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/esm/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test:node": "bun test tests/",
|
|
17
17
|
"test:node:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage tests/",
|
|
18
18
|
"test:browser": "bunx --bun vitest --config vitest.browser.config.ts --run",
|
|
19
|
-
"test:browser:coverage": "bunx --bun vitest --config vitest.browser.config.ts --run --coverage --coverage.provider=istanbul
|
|
19
|
+
"test:browser:coverage": "bunx --bun vitest --config vitest.browser.config.ts --run --coverage --coverage.provider=istanbul"
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/enboxorg/enbox/tree/main/packages/dids#readme",
|
|
22
22
|
"bugs": "https://github.com/enboxorg/enbox/issues",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@decentralized-identity/ion-sdk": "1.0.4",
|
|
82
82
|
"@dnsquery/dns-packet": "6.1.1",
|
|
83
|
-
"@enbox/common": "0.0.
|
|
84
|
-
"@enbox/crypto": "0.0.
|
|
83
|
+
"@enbox/common": "0.0.5",
|
|
84
|
+
"@enbox/crypto": "0.0.6",
|
|
85
85
|
"abstract-level": "1.0.4",
|
|
86
86
|
"bencode": "4.0.0",
|
|
87
87
|
"level": "8.0.1",
|
package/src/did.ts
CHANGED
|
@@ -145,6 +145,9 @@ export class Did {
|
|
|
145
145
|
// Return null if the input string is empty or not provided.
|
|
146
146
|
if (!didUri) {return null;}
|
|
147
147
|
|
|
148
|
+
// Guard against ReDoS: reject unreasonably long URIs before executing the regex.
|
|
149
|
+
if (didUri.length > 2048) {return null;}
|
|
150
|
+
|
|
148
151
|
// Execute the regex pattern on the input string to extract URI components.
|
|
149
152
|
const match = Did.DID_URI_PATTERN.exec(didUri);
|
|
150
153
|
|
|
@@ -44,7 +44,7 @@ export async function pkarrGet({ gatewayUri, publicKeyBytes }: {
|
|
|
44
44
|
// Transmit the Get request to the DID DHT Gateway or Pkarr Relay and get the response.
|
|
45
45
|
let response: Response;
|
|
46
46
|
try {
|
|
47
|
-
response = await fetch(url, { method: 'GET' });
|
|
47
|
+
response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(30_000) });
|
|
48
48
|
|
|
49
49
|
if (!response.ok) {
|
|
50
50
|
throw new DidError(DidErrorCode.NotFound, `Pkarr record not found for: ${identifier}`);
|
|
@@ -113,7 +113,8 @@ export async function pkarrPut({ gatewayUri, bep44Message }: {
|
|
|
113
113
|
response = await fetch(url, {
|
|
114
114
|
method : 'PUT',
|
|
115
115
|
headers : { 'Content-Type': 'application/octet-stream' },
|
|
116
|
-
body
|
|
116
|
+
body,
|
|
117
|
+
signal : AbortSignal.timeout(30_000),
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
} catch (error: any) {
|
package/src/methods/did-ion.ts
CHANGED
|
@@ -600,7 +600,8 @@ export class DidIon extends DidMethod {
|
|
|
600
600
|
method : 'POST',
|
|
601
601
|
mode : 'cors',
|
|
602
602
|
headers : { 'Content-Type': 'application/json' },
|
|
603
|
-
body : JSON.stringify(createOperation)
|
|
603
|
+
body : JSON.stringify(createOperation),
|
|
604
|
+
signal : AbortSignal.timeout(30_000),
|
|
604
605
|
});
|
|
605
606
|
|
|
606
607
|
// Return the result of processing the Create operation, including the updated DID metadata
|
|
@@ -681,7 +682,7 @@ export class DidIon extends DidMethod {
|
|
|
681
682
|
});
|
|
682
683
|
|
|
683
684
|
// Attempt to retrieve the DID document and metadata from the Sidetree node.
|
|
684
|
-
const response = await fetch(resolutionUrl);
|
|
685
|
+
const response = await fetch(resolutionUrl, { signal: AbortSignal.timeout(30_000) });
|
|
685
686
|
|
|
686
687
|
// If the DID document was not found, return an error.
|
|
687
688
|
if (!response.ok) {
|
package/src/methods/did-web.ts
CHANGED
|
@@ -4,6 +4,46 @@ import { Did } from '../did.js';
|
|
|
4
4
|
import { DidMethod } from './did-method.js';
|
|
5
5
|
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
|
|
6
6
|
|
|
7
|
+
/** Default fetch timeout for DID document retrieval (30 seconds). */
|
|
8
|
+
const FETCH_TIMEOUT_MS = 30_000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns `true` when the hostname is a private, loopback, or link-local
|
|
12
|
+
* address. Used to block SSRF via crafted `did:web` identifiers such as
|
|
13
|
+
* `did:web:169.254.169.254` or `did:web:localhost`.
|
|
14
|
+
*/
|
|
15
|
+
function isPrivateHostname(hostname: string): boolean {
|
|
16
|
+
const h = hostname.toLowerCase();
|
|
17
|
+
|
|
18
|
+
if (h === 'localhost' || h === 'localhost.') { return true; }
|
|
19
|
+
|
|
20
|
+
// IPv4 literal check
|
|
21
|
+
const parts = h.split('.');
|
|
22
|
+
if (parts.length === 4) {
|
|
23
|
+
const octets = parts.map(Number);
|
|
24
|
+
if (octets.every((o) => !Number.isNaN(o) && o >= 0 && o <= 255)) {
|
|
25
|
+
const [a, b] = octets;
|
|
26
|
+
if (a === 10) { return true; } // 10.0.0.0/8
|
|
27
|
+
if (a === 172 && b >= 16 && b <= 31) { return true; } // 172.16.0.0/12
|
|
28
|
+
if (a === 192 && b === 168) { return true; } // 192.168.0.0/16
|
|
29
|
+
if (a === 127) { return true; } // 127.0.0.0/8
|
|
30
|
+
if (a === 169 && b === 254) { return true; } // 169.254.0.0/16
|
|
31
|
+
if (a === 0) { return true; } // 0.0.0.0/8
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// IPv6 literal check (bracket-wrapped by URL parser)
|
|
36
|
+
let v6 = h;
|
|
37
|
+
if (v6.startsWith('[') && v6.endsWith(']')) { v6 = v6.slice(1, -1); }
|
|
38
|
+
if (v6.includes(':')) {
|
|
39
|
+
if (v6 === '::1' || v6 === '::' || v6 === '::0') { return true; }
|
|
40
|
+
if (v6.startsWith('fe80:') || v6.startsWith('fe80%')) { return true; }
|
|
41
|
+
if (v6.startsWith('fc') || v6.startsWith('fd')) { return true; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
7
47
|
/**
|
|
8
48
|
* The `DidWeb` class provides an implementation of the `did:web` DID method.
|
|
9
49
|
*
|
|
@@ -71,8 +111,19 @@ export class DidWeb extends DidMethod {
|
|
|
71
111
|
`${baseUrl}/.well-known/did.json`;
|
|
72
112
|
|
|
73
113
|
try {
|
|
114
|
+
// Block requests to private/loopback/link-local addresses (SSRF protection).
|
|
115
|
+
const parsedUrl = new URL(didDocumentUrl);
|
|
116
|
+
if (isPrivateHostname(parsedUrl.hostname)) {
|
|
117
|
+
return {
|
|
118
|
+
...EMPTY_DID_RESOLUTION_RESULT,
|
|
119
|
+
didResolutionMetadata: { error: 'notFound' }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
74
123
|
// Perform an HTTP GET request to obtain the DID document.
|
|
75
|
-
const response = await fetch(didDocumentUrl
|
|
124
|
+
const response = await fetch(didDocumentUrl, {
|
|
125
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
126
|
+
});
|
|
76
127
|
|
|
77
128
|
// If the response status code is not 200, return an error.
|
|
78
129
|
if (!response.ok) {throw new Error('HTTP error status code returned');}
|
package/src/utils.ts
CHANGED
|
@@ -19,38 +19,44 @@ import { DidError, DidErrorCode } from './did-error.js';
|
|
|
19
19
|
* Represents a Decentralized Web Node (DWN) service in a DID Document.
|
|
20
20
|
*
|
|
21
21
|
* A DWN DID service is a specialized type of DID service with the `type` set to
|
|
22
|
-
* `DecentralizedWebNode`.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* `DecentralizedWebNode`. Encryption and signing keys are resolved from the DID document's
|
|
23
|
+
* verification methods, not from the service entry.
|
|
24
|
+
*
|
|
25
|
+
* The `enc` and `sig` properties are optional legacy fields that may be present on existing
|
|
26
|
+
* DID documents for backward compatibility. When present, they contain verification method `id`
|
|
27
|
+
* values that hint at which keys to use for encryption and signing. New implementations should
|
|
28
|
+
* resolve keys from the DID document's verification methods by purpose (`keyAgreement` for
|
|
29
|
+
* encryption, `authentication`/`assertionMethod` for signing).
|
|
28
30
|
*
|
|
29
31
|
* @example
|
|
30
32
|
* ```ts
|
|
31
33
|
* const service: DwnDidService = {
|
|
32
34
|
* id: 'did:example:123#dwn',
|
|
33
35
|
* type: 'DecentralizedWebNode',
|
|
34
|
-
* serviceEndpoint: 'https://enbox-dwn.fly.dev'
|
|
35
|
-
* enc: 'did:example:123#key-1',
|
|
36
|
-
* sig: 'did:example:123#key-2'
|
|
36
|
+
* serviceEndpoint: 'https://enbox-dwn.fly.dev'
|
|
37
37
|
* }
|
|
38
38
|
* ```
|
|
39
39
|
*
|
|
40
|
-
* @see {@link https://
|
|
40
|
+
* @see {@link https://github.com/enboxorg/dwn-spec | Enbox DWN Specification}
|
|
41
41
|
*/
|
|
42
42
|
export interface DwnDidService extends DidService {
|
|
43
43
|
/**
|
|
44
|
+
* @deprecated Optional legacy field. Resolve encryption keys from the DID document's
|
|
45
|
+
* `keyAgreement` verification methods instead.
|
|
46
|
+
*
|
|
44
47
|
* One or more verification method `id` values that can be used to encrypt information
|
|
45
48
|
* intended for the DID subject.
|
|
46
49
|
*/
|
|
47
50
|
enc?: string | string[];
|
|
48
51
|
|
|
49
52
|
/**
|
|
53
|
+
* @deprecated Optional legacy field. Resolve signing keys from the DID document's
|
|
54
|
+
* `authentication` or `assertionMethod` verification methods instead.
|
|
55
|
+
*
|
|
50
56
|
* One or more verification method `id` values that will be used by the DID subject to sign data
|
|
51
57
|
* or by another entity to verify signatures created by the DID subject.
|
|
52
58
|
*/
|
|
53
|
-
sig
|
|
59
|
+
sig?: string | string[];
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/**
|
|
@@ -368,9 +374,11 @@ export function isDidService(obj: unknown): obj is DidService {
|
|
|
368
374
|
/**
|
|
369
375
|
* Checks if a given object is a {@link DwnDidService}.
|
|
370
376
|
*
|
|
371
|
-
* A {@link DwnDidService} is defined as {@link DidService} object with a `type` of
|
|
372
|
-
* "DecentralizedWebNode"
|
|
373
|
-
*
|
|
377
|
+
* A {@link DwnDidService} is defined as a {@link DidService} object with a `type` of
|
|
378
|
+
* `"DecentralizedWebNode"`. The `enc` and `sig` properties are optional — they may be present
|
|
379
|
+
* on existing DID documents for backward compatibility, but are not required per the DWN
|
|
380
|
+
* specification. Encryption and signing keys are resolved from the DID document's verification
|
|
381
|
+
* methods, not from the service entry.
|
|
374
382
|
*
|
|
375
383
|
* @example
|
|
376
384
|
* ```ts
|
|
@@ -382,33 +390,25 @@ export function isDidService(obj: unknown): obj is DidService {
|
|
|
382
390
|
* type: 'JsonWebKey2020',
|
|
383
391
|
* controller: 'did:example:123',
|
|
384
392
|
* publicKeyJwk: { ... }
|
|
385
|
-
* },
|
|
386
|
-
* {
|
|
387
|
-
* id: 'did:example:123#key-2',
|
|
388
|
-
* type: 'JsonWebKey2020',
|
|
389
|
-
* controller: 'did:example:123',
|
|
390
|
-
* publicKeyJwk: { ... }
|
|
391
393
|
* }
|
|
392
394
|
* ],
|
|
393
395
|
* service: [
|
|
394
396
|
* {
|
|
395
397
|
* id: 'did:example:123#dwn',
|
|
396
398
|
* type: 'DecentralizedWebNode',
|
|
397
|
-
* serviceEndpoint: 'https://enbox-dwn.fly.dev'
|
|
398
|
-
* enc: 'did:example:123#key-1',
|
|
399
|
-
* sig: 'did:example:123#key-2'
|
|
399
|
+
* serviceEndpoint: 'https://enbox-dwn.fly.dev'
|
|
400
400
|
* }
|
|
401
401
|
* ]
|
|
402
402
|
* };
|
|
403
403
|
*
|
|
404
|
-
* if (
|
|
404
|
+
* if (isDwnDidService(didDocument.service[0])) {
|
|
405
405
|
* console.log('The object is a DwnDidService');
|
|
406
406
|
* } else {
|
|
407
407
|
* console.log('The object is not a DwnDidService');
|
|
408
408
|
* }
|
|
409
409
|
* ```
|
|
410
410
|
*
|
|
411
|
-
* @see {@link https://
|
|
411
|
+
* @see {@link https://github.com/enboxorg/dwn-spec | Enbox DWN Specification}
|
|
412
412
|
*
|
|
413
413
|
* @param obj - The object to be checked.
|
|
414
414
|
* @returns `true` if `obj` is a DwnDidService; otherwise, `false`.
|
|
@@ -420,13 +420,14 @@ export function isDwnDidService(obj: unknown): obj is DwnDidService {
|
|
|
420
420
|
// Validate that the `type` property is `DecentralizedWebNode`.
|
|
421
421
|
if (obj.type !== 'DecentralizedWebNode') {return false;}
|
|
422
422
|
|
|
423
|
-
//
|
|
424
|
-
if (!('enc' in obj && 'sig' in obj)) {return false;}
|
|
425
|
-
|
|
426
|
-
// Validate that the `enc` and `sig` properties are either strings or arrays of strings.
|
|
423
|
+
// If `enc` or `sig` are present, validate they are strings or arrays of strings.
|
|
427
424
|
const isStringOrStringArray = (prop: any): boolean =>
|
|
428
425
|
typeof prop === 'string' || Array.isArray(prop) && prop.every(item => typeof item === 'string');
|
|
429
|
-
|
|
426
|
+
|
|
427
|
+
if ('enc' in obj && obj.enc !== undefined && !isStringOrStringArray(obj.enc)) {return false;}
|
|
428
|
+
if ('sig' in obj && obj.sig !== undefined && !isStringOrStringArray(obj.sig)) {return false;}
|
|
429
|
+
|
|
430
|
+
return true;
|
|
430
431
|
}
|
|
431
432
|
|
|
432
433
|
/**
|