@enbox/agent 0.1.8 → 0.2.0

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.
Files changed (74) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/anonymous-dwn-api.js +1 -1
  4. package/dist/esm/anonymous-dwn-api.js.map +1 -1
  5. package/dist/esm/connect.js +4 -10
  6. package/dist/esm/connect.js.map +1 -1
  7. package/dist/esm/dwn-api.js +144 -195
  8. package/dist/esm/dwn-api.js.map +1 -1
  9. package/dist/esm/dwn-protocol-cache.js +149 -0
  10. package/dist/esm/dwn-protocol-cache.js.map +1 -0
  11. package/dist/esm/dwn-record-upgrade.js +3 -3
  12. package/dist/esm/dwn-record-upgrade.js.map +1 -1
  13. package/dist/esm/hd-identity-vault.js +6 -5
  14. package/dist/esm/hd-identity-vault.js.map +1 -1
  15. package/dist/esm/identity-api.js +0 -2
  16. package/dist/esm/identity-api.js.map +1 -1
  17. package/dist/esm/oidc.js +2 -1
  18. package/dist/esm/oidc.js.map +1 -1
  19. package/dist/esm/permissions-api.js +24 -6
  20. package/dist/esm/permissions-api.js.map +1 -1
  21. package/dist/esm/prototyping/crypto/jose/jwe-flattened.js +1 -1
  22. package/dist/esm/prototyping/crypto/jose/jwe-flattened.js.map +1 -1
  23. package/dist/esm/prototyping/crypto/jose/jwe.js +11 -3
  24. package/dist/esm/prototyping/crypto/jose/jwe.js.map +1 -1
  25. package/dist/esm/store-data-protocols.js +2 -2
  26. package/dist/esm/store-data-protocols.js.map +1 -1
  27. package/dist/esm/sync-api.js +3 -0
  28. package/dist/esm/sync-api.js.map +1 -1
  29. package/dist/esm/sync-engine-level.js +447 -29
  30. package/dist/esm/sync-engine-level.js.map +1 -1
  31. package/dist/esm/test-harness.js +3 -5
  32. package/dist/esm/test-harness.js.map +1 -1
  33. package/dist/esm/types/dwn.js.map +1 -1
  34. package/dist/types/anonymous-dwn-api.d.ts +3 -3
  35. package/dist/types/anonymous-dwn-api.d.ts.map +1 -1
  36. package/dist/types/connect.d.ts.map +1 -1
  37. package/dist/types/dwn-api.d.ts +11 -18
  38. package/dist/types/dwn-api.d.ts.map +1 -1
  39. package/dist/types/dwn-protocol-cache.d.ts +76 -0
  40. package/dist/types/dwn-protocol-cache.d.ts.map +1 -0
  41. package/dist/types/hd-identity-vault.d.ts.map +1 -1
  42. package/dist/types/identity-api.d.ts.map +1 -1
  43. package/dist/types/oidc.d.ts.map +1 -1
  44. package/dist/types/permissions-api.d.ts.map +1 -1
  45. package/dist/types/prototyping/crypto/jose/jwe-flattened.d.ts.map +1 -1
  46. package/dist/types/prototyping/crypto/jose/jwe.d.ts +12 -2
  47. package/dist/types/prototyping/crypto/jose/jwe.d.ts.map +1 -1
  48. package/dist/types/sync-api.d.ts +3 -4
  49. package/dist/types/sync-api.d.ts.map +1 -1
  50. package/dist/types/sync-engine-level.d.ts +63 -5
  51. package/dist/types/sync-engine-level.d.ts.map +1 -1
  52. package/dist/types/test-harness.d.ts.map +1 -1
  53. package/dist/types/types/dwn.d.ts +18 -19
  54. package/dist/types/types/dwn.d.ts.map +1 -1
  55. package/dist/types/types/sync.d.ts +47 -5
  56. package/dist/types/types/sync.d.ts.map +1 -1
  57. package/package.json +6 -6
  58. package/src/anonymous-dwn-api.ts +4 -4
  59. package/src/connect.ts +4 -10
  60. package/src/dwn-api.ts +192 -250
  61. package/src/dwn-protocol-cache.ts +216 -0
  62. package/src/dwn-record-upgrade.ts +3 -3
  63. package/src/hd-identity-vault.ts +6 -5
  64. package/src/identity-api.ts +0 -2
  65. package/src/oidc.ts +2 -1
  66. package/src/permissions-api.ts +28 -6
  67. package/src/prototyping/crypto/jose/jwe-flattened.ts +4 -1
  68. package/src/prototyping/crypto/jose/jwe.ts +24 -2
  69. package/src/store-data-protocols.ts +2 -2
  70. package/src/sync-api.ts +7 -3
  71. package/src/sync-engine-level.ts +509 -32
  72. package/src/test-harness.ts +3 -5
  73. package/src/types/dwn.ts +19 -21
  74. package/src/types/sync.ts +56 -5
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Protocol definition fetching and caching utilities for {@link AgentDwnApi}.
3
+ *
4
+ * Extracted from `dwn-api.ts` to keep protocol-resolution logic in its own
5
+ * module.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { DidUrlDereferencer } from '@enbox/dids';
11
+ import type { PublicKeyJwk } from '@enbox/crypto';
12
+ import type { TtlCache } from '@enbox/common';
13
+ import type {
14
+ ProtocolDefinition,
15
+ ProtocolsQueryReply,
16
+ RecordsQueryReply,
17
+ } from '@enbox/dwn-sdk-js';
18
+
19
+ import type {
20
+ DwnInterface,
21
+ DwnMessage,
22
+ DwnMessageReply,
23
+ DwnSigner,
24
+ MessageHandler,
25
+ } from './types/dwn.js';
26
+
27
+ import { KeyDerivationScheme } from '@enbox/dwn-sdk-js';
28
+
29
+ import { getDwnServiceEndpointUrls } from './utils.js';
30
+ import { DwnInterface as DwnInterfaceEnum, dwnMessageConstructors } from './types/dwn.js';
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Dependency signatures — keep the extracted code free of `this` references.
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Callback to obtain a DWN signer for a given DID. */
37
+ type GetSignerFn = (author: string) => Promise<DwnSigner>;
38
+
39
+ /** Callback to send a raw DWN request to a remote endpoint. */
40
+ type SendDwnRpcRequestFn = <T extends DwnInterface>(params: {
41
+ targetDid: string;
42
+ dwnEndpointUrls: string[];
43
+ message: DwnMessage[T];
44
+ data?: Blob;
45
+ subscriptionHandler?: MessageHandler[T];
46
+ }) => Promise<DwnMessageReply[T]>;
47
+
48
+ /** Minimal DWN interface needed for local `processMessage` calls. */
49
+ interface DwnNode {
50
+ processMessage(tenant: string, message: unknown, options?: unknown): Promise<any>;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Exported functions
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /**
58
+ * Fetches a protocol definition from the **local** DWN, backed by a TTL cache.
59
+ *
60
+ * @param tenantDid - The DID whose DWN to query
61
+ * @param protocolUri - The protocol URI to look up
62
+ * @param dwn - The local DWN instance
63
+ * @param getSigner - Callback to obtain the signer for `tenantDid`
64
+ * @param cache - The shared protocol definition cache
65
+ * @returns The protocol definition, or `undefined` if not installed
66
+ */
67
+ export async function getProtocolDefinition(
68
+ tenantDid: string,
69
+ protocolUri: string,
70
+ dwn: DwnNode,
71
+ getSigner: GetSignerFn,
72
+ cache: TtlCache<string, ProtocolDefinition>,
73
+ ): Promise<ProtocolDefinition | undefined> {
74
+ const cacheKey = `${tenantDid}~${protocolUri}`;
75
+
76
+ const cached = cache.get(cacheKey);
77
+ if (cached) {
78
+ return cached;
79
+ }
80
+
81
+ const signer = await getSigner(tenantDid);
82
+ const protocolsQuery = await dwnMessageConstructors[
83
+ DwnInterfaceEnum.ProtocolsQuery
84
+ ].create({
85
+ filter: { protocol: protocolUri },
86
+ signer,
87
+ });
88
+
89
+ const reply = await dwn.processMessage(
90
+ tenantDid, protocolsQuery.message,
91
+ );
92
+ if (reply.status.code !== 200 || !reply.entries?.length) {
93
+ return undefined;
94
+ }
95
+
96
+ const definition = reply.entries[0].descriptor.definition;
97
+ cache.set(cacheKey, definition);
98
+ return definition;
99
+ }
100
+
101
+ /**
102
+ * Fetches a protocol definition from a **remote** DWN.
103
+ *
104
+ * Uses an unsigned `ProtocolsQuery` since public protocols can be queried
105
+ * anonymously.
106
+ *
107
+ * @param targetDid - The remote DWN owner
108
+ * @param protocolUri - The protocol URI to look up
109
+ * @param didDereferencer - A DID URL dereferencer for resolving service endpoints
110
+ * @param sendDwnRpcRequest - Callback to send the RPC query
111
+ * @param cache - The shared protocol definition cache
112
+ * @returns The protocol definition
113
+ * @throws If the protocol cannot be fetched
114
+ */
115
+ export async function fetchRemoteProtocolDefinition(
116
+ targetDid: string,
117
+ protocolUri: string,
118
+ didDereferencer: DidUrlDereferencer,
119
+ sendDwnRpcRequest: SendDwnRpcRequestFn,
120
+ cache: TtlCache<string, ProtocolDefinition>,
121
+ ): Promise<ProtocolDefinition> {
122
+ const cacheKey = `remote~${targetDid}~${protocolUri}`;
123
+ const cached = cache.get(cacheKey);
124
+ if (cached) { return cached; }
125
+
126
+ const protocolsQuery = await dwnMessageConstructors[
127
+ DwnInterfaceEnum.ProtocolsQuery
128
+ ].create({
129
+ filter: { protocol: protocolUri },
130
+ });
131
+
132
+ const reply = await sendDwnRpcRequest({
133
+ targetDid,
134
+ dwnEndpointUrls : await getDwnServiceEndpointUrls(targetDid, didDereferencer),
135
+ message : protocolsQuery.message,
136
+ }) as ProtocolsQueryReply;
137
+
138
+ if (reply.status.code !== 200 || !reply.entries?.length) {
139
+ throw new Error(
140
+ `AgentDwnApi: Failed to fetch protocol '${protocolUri}' from ` +
141
+ `'${targetDid}'. The recipient may not have the protocol installed.`
142
+ );
143
+ }
144
+
145
+ const definition = reply.entries[0].descriptor.definition;
146
+ cache.set(cacheKey, definition);
147
+ return definition;
148
+ }
149
+
150
+ /**
151
+ * Extracts the `derivedPublicKey` from an existing `ProtocolContext`-encrypted
152
+ * record in a context on a remote DWN.
153
+ *
154
+ * This key allows an external author to encrypt new records in the same
155
+ * context without knowing the context private key.
156
+ *
157
+ * @param targetDid - The DWN owner's DID
158
+ * @param protocolUri - The protocol URI to search
159
+ * @param rootContextId - The root context ID
160
+ * @param requesterDid - The DID of the requester (used for signing the query)
161
+ * @param didDereferencer - A DID URL dereferencer for resolving service endpoints
162
+ * @param getSigner - Callback to obtain the signer for `requesterDid`
163
+ * @param sendDwnRpcRequest - Callback to send the RPC query
164
+ * @returns The rootKeyId and derivedPublicKey, or `undefined` if no
165
+ * `ProtocolContext` record exists yet
166
+ */
167
+ export async function extractDerivedPublicKey(
168
+ targetDid: string,
169
+ protocolUri: string,
170
+ rootContextId: string,
171
+ requesterDid: string,
172
+ didDereferencer: DidUrlDereferencer,
173
+ getSigner: GetSignerFn,
174
+ sendDwnRpcRequest: SendDwnRpcRequestFn,
175
+ ): Promise<{ rootKeyId: string; derivedPublicKey: PublicKeyJwk } | undefined> {
176
+ const signer = await getSigner(requesterDid);
177
+
178
+ // Query the target's DWN for any record in this context
179
+ const recordsQuery = await dwnMessageConstructors[DwnInterfaceEnum.RecordsQuery].create({
180
+ signer,
181
+ filter: {
182
+ protocol : protocolUri,
183
+ contextId : rootContextId,
184
+ },
185
+ });
186
+
187
+ const dwnEndpointUrls = await getDwnServiceEndpointUrls(targetDid, didDereferencer);
188
+ const queryReply = await sendDwnRpcRequest<DwnInterfaceEnum.RecordsQuery>({
189
+ targetDid,
190
+ dwnEndpointUrls,
191
+ message: recordsQuery.message,
192
+ }) as RecordsQueryReply;
193
+
194
+ if (queryReply.status.code !== 200 || !queryReply.entries?.length) {
195
+ return undefined;
196
+ }
197
+
198
+ // Search entries for one with a ProtocolContext recipient entry
199
+ // that includes derivedPublicKey
200
+ for (const entry of queryReply.entries) {
201
+ if (entry.encryption?.recipients) {
202
+ const contextEntry = entry.encryption.recipients.find(
203
+ (r: { header: { derivationScheme: string; derivedPublicKey?: PublicKeyJwk } }) =>
204
+ r.header.derivationScheme === KeyDerivationScheme.ProtocolContext && r.header.derivedPublicKey
205
+ );
206
+ if (contextEntry?.header.derivedPublicKey) {
207
+ return {
208
+ rootKeyId : contextEntry.header.kid,
209
+ derivedPublicKey : contextEntry.header.derivedPublicKey,
210
+ };
211
+ }
212
+ }
213
+ }
214
+
215
+ return undefined;
216
+ }
@@ -118,7 +118,7 @@ export async function upgradeExternalRootRecord(
118
118
  // We must also update the state index and event stream to keep sync and
119
119
  // real-time subscribers consistent — without this, the upgraded record
120
120
  // would never propagate to remote DWNs or notify subscribers.
121
- const { messageStore, stateIndex, eventStream } = dwn.storage;
121
+ const { messageStore, stateIndex, eventLog } = dwn.storage;
122
122
 
123
123
  // Validate the upgrade only changed encryption and authorization fields.
124
124
  // The descriptor, recordId, contextId, and data must remain identical.
@@ -157,8 +157,8 @@ export async function upgradeExternalRootRecord(
157
157
  await stateIndex.delete(tenantDid, [originalCid]);
158
158
 
159
159
  // Notify real-time subscribers (mirrors handler behavior)
160
- if (eventStream !== undefined) {
161
- eventStream.emit(tenantDid, { message: upgradedMessage }, upgradedIndexes);
160
+ if (eventLog !== undefined) {
161
+ await eventLog.emit(tenantDid, { message: upgradedMessage }, upgradedIndexes);
162
162
  }
163
163
 
164
164
  // Cache context key info for subsequent writes in this context
@@ -250,7 +250,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
250
250
  jwe : cekJwe,
251
251
  key : Convert.string(oldPassword).toUint8Array(),
252
252
  crypto : this.crypto,
253
- keyManager : new LocalKeyManager()
253
+ keyManager : new LocalKeyManager(),
254
+ options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
254
255
  }));
255
256
  contentEncryptionKey = Convert.uint8Array(contentEncryptionKeyBytes).toObject() as Jwk;
256
257
 
@@ -297,7 +298,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
297
298
  jwe : didJwe,
298
299
  key : this._contentEncryptionKey!,
299
300
  crypto : this.crypto,
300
- keyManager : new LocalKeyManager()
301
+ keyManager : new LocalKeyManager(),
302
+ options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
301
303
  });
302
304
 
303
305
  // Convert the DID from a byte array to PortableDid format.
@@ -530,8 +532,6 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
530
532
  id : 'dwn',
531
533
  type : 'DecentralizedWebNode',
532
534
  serviceEndpoint : dwnEndpoints,
533
- enc : '#enc',
534
- sig : '#sig',
535
535
  }
536
536
  ];
537
537
  }
@@ -761,7 +761,8 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
761
761
  jwe : cekJwe,
762
762
  key : Convert.string(password).toUint8Array(),
763
763
  crypto : this.crypto,
764
- keyManager : new LocalKeyManager()
764
+ keyManager : new LocalKeyManager(),
765
+ options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
765
766
  });
766
767
  const contentEncryptionKey = Convert.uint8Array(contentEncryptionKeyBytes).toObject() as Jwk;
767
768
 
@@ -254,8 +254,6 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
254
254
  id : 'dwn',
255
255
  type : 'DecentralizedWebNode',
256
256
  serviceEndpoint : endpoints,
257
- enc : '#enc',
258
- sig : '#sig'
259
257
  };
260
258
 
261
259
  // if no other services exist, create a new array with the DWN service
package/src/oidc.ts CHANGED
@@ -410,7 +410,7 @@ async function verifyJwt({ jwt }: { jwt: string }): Promise<Record<string, unkno
410
410
  * using the encryption key passed via QR code.
411
411
  */
412
412
  const getAuthRequest = async (request_uri: string, encryption_key: string): Promise<Web5ConnectAuthRequest> => {
413
- const authRequest = await fetch(request_uri);
413
+ const authRequest = await fetch(request_uri, { signal: AbortSignal.timeout(30_000) });
414
414
  const jwe = await authRequest.text();
415
415
  const jwt = await decryptAuthRequest({
416
416
  jwe,
@@ -832,6 +832,7 @@ async function submitAuthResponse(
832
832
  headers : {
833
833
  'Content-Type': 'application/x-www-form-urlencoded',
834
834
  },
835
+ signal: AbortSignal.timeout(30_000),
835
836
  });
836
837
  }
837
838
 
@@ -114,7 +114,7 @@ export class AgentPermissionsApi implements PermissionsApi {
114
114
  if (revokedGrantIds.has(entry.recordId)) {
115
115
  continue;
116
116
  }
117
- const grant = await DwnPermissionGrant.parse(entry);
117
+ const grant = DwnPermissionGrant.parse(entry);
118
118
  grants.push({ grant, message: entry });
119
119
  }
120
120
 
@@ -193,7 +193,7 @@ export class AgentPermissionsApi implements PermissionsApi {
193
193
 
194
194
  const requests: PermissionRequestEntry[] = [];
195
195
  for (const entry of reply.entries! as DwnDataEncodedRecordsWriteMessage[]) {
196
- const request = await DwnPermissionRequest.parse(entry);
196
+ const request = DwnPermissionRequest.parse(entry);
197
197
  requests.push({ request, message: entry });
198
198
  }
199
199
 
@@ -275,7 +275,7 @@ export class AgentPermissionsApi implements PermissionsApi {
275
275
  encodedData: Convert.uint8Array(permissionsGrantBytes).toBase64Url()
276
276
  };
277
277
 
278
- const grant = await DwnPermissionGrant.parse(dataEncodedMessage);
278
+ const grant = DwnPermissionGrant.parse(dataEncodedMessage);
279
279
 
280
280
  return { grant, message: dataEncodedMessage };
281
281
  }
@@ -321,7 +321,7 @@ export class AgentPermissionsApi implements PermissionsApi {
321
321
  encodedData: Convert.uint8Array(permissionRequestBytes).toBase64Url()
322
322
  };
323
323
 
324
- const request = await DwnPermissionRequest.parse(dataEncodedMessage);
324
+ const request = DwnPermissionRequest.parse(dataEncodedMessage);
325
325
 
326
326
  return { request, message: dataEncodedMessage };
327
327
  }
@@ -388,6 +388,11 @@ export class AgentPermissionsApi implements PermissionsApi {
388
388
  grants: PermissionGrantEntry[],
389
389
  delegated: boolean = false
390
390
  ): Promise<PermissionGrantEntry | undefined> {
391
+ // Two-pass matching: prefer exact scope matches over unified Messages.Read fallback.
392
+ // This ensures that if both a Messages.Sync grant and a Messages.Read grant exist,
393
+ // the specific Messages.Sync grant is returned for MessagesSync lookups.
394
+ let unifiedFallback: PermissionGrantEntry | undefined;
395
+
391
396
  for (const entry of grants) {
392
397
  const { grant, message } = entry;
393
398
  if (delegated === true && grant.delegated !== true) {
@@ -396,9 +401,19 @@ export class AgentPermissionsApi implements PermissionsApi {
396
401
  const { messageType, protocol, protocolPath, contextId } = messageParams;
397
402
 
398
403
  if (this.matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId)) {
399
- return { grant, message };
404
+ const scopeMessageType = grant.scope.interface + grant.scope.method;
405
+ // Exact match — return immediately
406
+ if (scopeMessageType === messageType) {
407
+ return { grant, message };
408
+ }
409
+ // Unified fallback match — hold for later in case an exact match is found
410
+ if (!unifiedFallback) {
411
+ unifiedFallback = { grant, message };
412
+ }
400
413
  }
401
414
  }
415
+
416
+ return unifiedFallback;
402
417
  }
403
418
 
404
419
  private static matchScopeFromGrant<T extends DwnInterface>(
@@ -417,7 +432,14 @@ export class AgentPermissionsApi implements PermissionsApi {
417
432
 
418
433
  const scope = grant.scope;
419
434
  const scopeMessageType = scope.interface + scope.method;
420
- if (scopeMessageType === messageType) {
435
+
436
+ // Messages.Read is a unified scope that covers Messages.Read, Messages.Sync, and Messages.Subscribe.
437
+ // When looking for a MessagesSync or MessagesSubscribe grant, also accept a MessagesRead grant.
438
+ const isMessagesScopeMatch = scopeMessageType === messageType
439
+ || (scopeMessageType === DwnInterface.MessagesRead
440
+ && (messageType === DwnInterface.MessagesSync || messageType === DwnInterface.MessagesSubscribe));
441
+
442
+ if (isMessagesScopeMatch) {
421
443
  if (isRecordsType(messageType)) {
422
444
  const recordScope = scope as DwnRecordsPermissionScope;
423
445
  if (recordScope.protocol !== protocol) {
@@ -270,7 +270,10 @@ export class FlattenedJwe {
270
270
  ? Convert.base64Url(jwe.encrypted_key).toUint8Array()
271
271
  : undefined;
272
272
 
273
- cek = await JweKeyManagement.decrypt({ key, encryptedKey, joseHeader, keyManager, crypto });
273
+ cek = await JweKeyManagement.decrypt(
274
+ { key, encryptedKey, joseHeader, keyManager, crypto },
275
+ { minP2cCount: options.minP2cCount }
276
+ );
274
277
 
275
278
  } catch (error: any) {
276
279
  // If the error is a CryptoError with code "InvalidJwe" or "AlgorithmNotSupported", re-throw.
@@ -33,6 +33,15 @@ export interface JweDecryptOptions {
33
33
  *
34
34
  */
35
35
  allowedEncValues?: string[];
36
+
37
+ /**
38
+ * Minimum acceptable PBES2 iteration count ("p2c") for key derivation during decryption.
39
+ * Per RFC 7518 Section 4.8.1.2, a minimum of 1000 is RECOMMENDED. Set to a lower value
40
+ * only for test environments where speed is prioritized over security.
41
+ *
42
+ * @default 1000
43
+ */
44
+ minP2cCount?: number;
36
45
  }
37
46
 
38
47
  /**
@@ -281,11 +290,12 @@ export interface JweKeyManagementDecryptParams<TKeyManager, TCrypto> {
281
290
  */
282
291
  joseHeader: JweHeaderParams;
283
292
 
284
- /** Key Manager instanceß responsible for managing cryptographic keys. */
293
+ /** Key Manager instance responsible for managing cryptographic keys. */
285
294
  keyManager: TKeyManager;
286
295
 
287
296
  /** Crypto API instance that provides the necessary cryptographic operations. */
288
297
  crypto: TCrypto;
298
+
289
299
  }
290
300
 
291
301
  /**
@@ -424,8 +434,10 @@ export class JweKeyManagement {
424
434
  */
425
435
  public static async decrypt<TKeyManager extends KeyManager, TCrypto extends CryptoApi>({
426
436
  key, encryptedKey, joseHeader, crypto
427
- }: JweKeyManagementDecryptParams<TKeyManager, TCrypto>
437
+ }: JweKeyManagementDecryptParams<TKeyManager, TCrypto>,
438
+ options?: { minP2cCount?: number }
428
439
  ): Promise<KeyIdentifier | Jwk> {
440
+ const minP2cCount = options?.minP2cCount ?? 1000;
429
441
  // Determine the Key Management Mode employed by the algorithm specified by the "alg"
430
442
  // (algorithm) Header Parameter.
431
443
  switch (joseHeader.alg) {
@@ -460,6 +472,16 @@ export class JweKeyManagement {
460
472
  throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
461
473
  }
462
474
 
475
+ // Per RFC 7518, Section 4.8.1.2, a minimum iteration count of 1000 is RECOMMENDED.
476
+ // Enforce this floor to prevent an attacker from supplying a crafted JWE with a
477
+ // trivially low iteration count that would weaken key derivation.
478
+ if (joseHeader.p2c < minP2cCount) {
479
+ throw new CryptoError(
480
+ CryptoErrorCode.InvalidJwe,
481
+ `JOSE Header "p2c" (PBES2 Count) is ${joseHeader.p2c}, which is below the minimum of ${minP2cCount}.`
482
+ );
483
+ }
484
+
463
485
  if (typeof joseHeader.p2s !== 'string') {
464
486
  throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
465
487
  }
@@ -1,7 +1,7 @@
1
1
  import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
2
2
 
3
3
  export const IdentityProtocolDefinition: ProtocolDefinition = {
4
- protocol : 'http://identity.foundation/protocols/web5/identity-store',
4
+ protocol : 'https://identity.foundation/protocols/web5/identity-store',
5
5
  published : false,
6
6
  types : {
7
7
  portableDid: {
@@ -47,7 +47,7 @@ export const KeyDeliveryProtocolDefinition: ProtocolDefinition = {
47
47
  };
48
48
 
49
49
  export const JwkProtocolDefinition: ProtocolDefinition = {
50
- protocol : 'http://identity.foundation/protocols/web5/jwk-store',
50
+ protocol : 'https://identity.foundation/protocols/web5/jwk-store',
51
51
  published : false,
52
52
  types : {
53
53
  privateJwk: {
package/src/sync-api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Web5PlatformAgent } from './types/agent.js';
2
- import type { SyncEngine, SyncIdentityOptions } from './types/sync.js';
2
+ import type { StartSyncParams, SyncConnectivityState, SyncEngine, SyncIdentityOptions } from './types/sync.js';
3
3
 
4
4
  export type SyncApiParams = {
5
5
  agent?: Web5PlatformAgent;
@@ -41,6 +41,10 @@ export class AgentSyncApi implements SyncEngine {
41
41
  this._syncEngine.agent = agent;
42
42
  }
43
43
 
44
+ get connectivityState(): SyncConnectivityState {
45
+ return this._syncEngine.connectivityState;
46
+ }
47
+
44
48
  public async registerIdentity(params: { did: string; options?: SyncIdentityOptions }): Promise<void> {
45
49
  await this._syncEngine.registerIdentity(params);
46
50
  }
@@ -61,11 +65,11 @@ export class AgentSyncApi implements SyncEngine {
61
65
  return this._syncEngine.sync(direction);
62
66
  }
63
67
 
64
- public startSync(params: { interval: string; }): Promise<void> {
68
+ public startSync(params: StartSyncParams): Promise<void> {
65
69
  return this._syncEngine.startSync(params);
66
70
  }
67
71
 
68
72
  public stopSync(timeout?: number): Promise<void> {
69
73
  return this._syncEngine.stopSync(timeout);
70
74
  }
71
- }
75
+ }