@enbox/dwn-sdk-js 0.4.4 → 0.4.5
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 +15 -15
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +5316 -2425
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/json-schemas/definitions.json +18 -0
- package/dist/esm/src/core/constants.js +3 -3
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +29 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +20 -0
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-validation.js +80 -3
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
- package/dist/esm/src/core/recording-validation-state-reader.js +5 -0
- package/dist/esm/src/core/recording-validation-state-reader.js.map +1 -1
- package/dist/esm/src/core/replication-apply.js +163 -7
- package/dist/esm/src/core/replication-apply.js.map +1 -1
- package/dist/esm/src/core/validation-state-reader.js +20 -0
- package/dist/esm/src/core/validation-state-reader.js.map +1 -1
- package/dist/esm/src/dwn.js +19 -2
- package/dist/esm/src/dwn.js.map +1 -1
- package/dist/esm/src/handlers/messages-query.js +3 -5
- package/dist/esm/src/handlers/messages-query.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +10 -4
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +1 -1
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +2 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +23 -10
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/records-write-signing.js +2 -8
- package/dist/esm/src/interfaces/records-write-signing.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +43 -65
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/encryption.js +411 -0
- package/dist/esm/src/protocols/encryption.js.map +1 -0
- package/dist/esm/src/protocols/permission-grant.js +0 -5
- package/dist/esm/src/protocols/permission-grant.js.map +1 -1
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/types/protocols-types.js.map +1 -1
- package/dist/esm/src/utils/encryption.js +135 -187
- package/dist/esm/src/utils/encryption.js.map +1 -1
- package/dist/esm/src/utils/hd-key.js +0 -1
- package/dist/esm/src/utils/hd-key.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +9 -6
- package/dist/esm/src/utils/permission-scope.js.map +1 -1
- package/dist/esm/src/utils/protocols.js +12 -9
- package/dist/esm/src/utils/protocols.js.map +1 -1
- package/dist/esm/src/utils/records.js +54 -56
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/src/utils/replication.js +27 -4
- package/dist/esm/src/utils/replication.js.map +1 -1
- package/dist/esm/tests/core/replication-apply.spec.js +141 -1
- package/dist/esm/tests/core/replication-apply.spec.js.map +1 -1
- package/dist/esm/tests/core/replication-replay-property.spec.js +1 -0
- package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
- package/dist/esm/tests/core/validation-read-closure.spec.js +111 -0
- package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
- package/dist/esm/tests/core/validation-state-reader.spec.js +89 -0
- package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-composition.spec.js +30 -31
- package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/encryption.fuzz.spec.js +29 -121
- package/dist/esm/tests/fuzz/encryption.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-query.spec.js +71 -3
- package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +2 -2
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-read.spec.js +9 -135
- package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +279 -19
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +17 -22
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +170 -150
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/protocols/encryption.spec.js +914 -0
- package/dist/esm/tests/protocols/encryption.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +100 -100
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store-level.spec.js +20 -2
- package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
- package/dist/esm/tests/utils/encryption-callbacks.spec.js +88 -125
- package/dist/esm/tests/utils/encryption-callbacks.spec.js.map +1 -1
- package/dist/esm/tests/utils/encryption.spec.js +125 -255
- package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +5 -1
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -1
- package/dist/esm/tests/utils/records.spec.js +15 -5
- package/dist/esm/tests/utils/records.spec.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js +7 -44
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js +63 -0
- package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js.map +1 -0
- package/dist/types/generated/precompiled-validators.d.ts +140 -92
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +2 -2
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/core-protocol.d.ts +1 -1
- package/dist/types/src/core/core-protocol.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +29 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +2 -0
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-validation.d.ts +2 -2
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
- package/dist/types/src/core/recording-validation-state-reader.d.ts +9 -0
- package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/core/replication-apply.d.ts +4 -3
- package/dist/types/src/core/replication-apply.d.ts.map +1 -1
- package/dist/types/src/core/validation-state-reader.d.ts +9 -0
- package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/dwn.d.ts +1 -0
- package/dist/types/src/dwn.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-query.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -5
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write-signing.d.ts +4 -8
- package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts +10 -26
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/encryption.d.ts +43 -0
- package/dist/types/src/protocols/encryption.d.ts.map +1 -0
- package/dist/types/src/protocols/permission-grant.d.ts +0 -7
- package/dist/types/src/protocols/permission-grant.d.ts.map +1 -1
- package/dist/types/src/types/encryption-types.d.ts +5 -38
- package/dist/types/src/types/encryption-types.d.ts.map +1 -1
- package/dist/types/src/types/message-types.d.ts +0 -13
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +0 -9
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/protocols-types.d.ts +9 -14
- package/dist/types/src/types/protocols-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +3 -3
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/validation-state-reader.d.ts +12 -0
- package/dist/types/src/types/validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/utils/encryption.d.ts +54 -145
- package/dist/types/src/utils/encryption.d.ts.map +1 -1
- package/dist/types/src/utils/hd-key.d.ts +1 -1
- package/dist/types/src/utils/hd-key.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +4 -3
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -1
- package/dist/types/src/utils/protocols.d.ts +3 -5
- package/dist/types/src/utils/protocols.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +5 -13
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/src/utils/replication.d.ts +3 -0
- package/dist/types/src/utils/replication.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/protocols/encryption.spec.d.ts +2 -0
- package/dist/types/tests/protocols/encryption.spec.d.ts.map +1 -0
- package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
- package/dist/types/tests/utils/test-data-generator.d.ts +0 -5
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts +2 -0
- package/dist/types/tests/validation/json-schemas/encryption/grant-key.spec.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/core/constants.ts +3 -3
- package/src/core/core-protocol.ts +1 -1
- package/src/core/dwn-error.ts +29 -1
- package/src/core/messages-grant-authorization.ts +27 -0
- package/src/core/protocol-authorization-validation.ts +145 -6
- package/src/core/recording-validation-state-reader.ts +13 -0
- package/src/core/replication-apply.ts +222 -8
- package/src/core/validation-state-reader.ts +30 -0
- package/src/dwn.ts +24 -2
- package/src/handlers/messages-query.ts +6 -5
- package/src/handlers/protocols-configure.ts +19 -3
- package/src/handlers/records-write.ts +1 -1
- package/src/index.ts +15 -5
- package/src/interfaces/protocols-configure.ts +39 -12
- package/src/interfaces/records-write-signing.ts +5 -12
- package/src/interfaces/records-write.ts +75 -92
- package/src/protocols/encryption.ts +649 -0
- package/src/protocols/permission-grant.ts +0 -6
- package/src/types/encryption-types.ts +9 -38
- package/src/types/message-types.ts +0 -13
- package/src/types/permission-types.ts +0 -10
- package/src/types/protocols-types.ts +9 -16
- package/src/types/records-types.ts +3 -3
- package/src/types/validation-state-reader.ts +13 -0
- package/src/utils/encryption.ts +213 -283
- package/src/utils/hd-key.ts +1 -1
- package/src/utils/permission-scope.ts +10 -6
- package/src/utils/protocols.ts +16 -15
- package/src/utils/records.ts +86 -71
- package/src/utils/replication.ts +30 -4
- package/dist/esm/tests/vectors/protocol-definitions/chat.json +0 -70
|
@@ -15,7 +15,7 @@ import { Time } from '../utils/time.js';
|
|
|
15
15
|
import { validateProtocolTagSchemaDefinition } from '../utils/protocol-tags.js';
|
|
16
16
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
17
17
|
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
18
|
-
import { isCrossProtocolRef, parseCrossProtocolRef } from '../utils/protocols.js';
|
|
18
|
+
import { getRuleSetAtPath, isCrossProtocolRef, parseCrossProtocolRef } from '../utils/protocols.js';
|
|
19
19
|
import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js';
|
|
20
20
|
import { ProtocolAction, ProtocolActor, ProtocolRecordLimitStrategy } from '../types/protocols-types.js';
|
|
21
21
|
|
|
@@ -150,6 +150,14 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
150
150
|
// gather all declared record types
|
|
151
151
|
const recordTypes = Object.keys(definition.types);
|
|
152
152
|
|
|
153
|
+
const hasEncryptedTypes = Object.values(definition.types).some((type): boolean => type.encryptionRequired === true);
|
|
154
|
+
if (hasEncryptedTypes && definition.$keyAgreement === undefined) {
|
|
155
|
+
throw new DwnError(
|
|
156
|
+
DwnErrorCode.ProtocolsConfigureMissingTopLevelKeyAgreement,
|
|
157
|
+
`Protocol '${definition.protocol}' declares encrypted types but has no top-level $keyAgreement.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
153
161
|
// gather all roles (local roles only — cross-protocol roles are validated by alias existence)
|
|
154
162
|
const roles = ProtocolsConfigure.fetchAllRolePathsRecursively('', definition.structure as ProtocolRuleSet, []);
|
|
155
163
|
|
|
@@ -161,6 +169,7 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
161
169
|
roles,
|
|
162
170
|
uses,
|
|
163
171
|
types : definition.types,
|
|
172
|
+
rootStructure : definition.structure,
|
|
164
173
|
});
|
|
165
174
|
}
|
|
166
175
|
|
|
@@ -207,10 +216,10 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
207
216
|
private static validateRuleSetRecursively(
|
|
208
217
|
input: {
|
|
209
218
|
ruleSet: ProtocolRuleSet, ruleSetProtocolPath: string, recordTypes: string[],
|
|
210
|
-
roles: string[], uses?: ProtocolUses, types: ProtocolTypes
|
|
219
|
+
roles: string[], uses?: ProtocolUses, types: ProtocolTypes, rootStructure: { [key: string]: ProtocolRuleSet }
|
|
211
220
|
}
|
|
212
221
|
): void {
|
|
213
|
-
const { ruleSet, ruleSetProtocolPath, recordTypes, roles, uses, types } = input;
|
|
222
|
+
const { ruleSet, ruleSetProtocolPath, recordTypes, roles, uses, types, rootStructure } = input;
|
|
214
223
|
|
|
215
224
|
// Validate $ref constraints: $ref is only supported at root level (no `/` in protocol path),
|
|
216
225
|
// and a $ref node is a pure attachment point with no other directives.
|
|
@@ -412,23 +421,40 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
412
421
|
}
|
|
413
422
|
}
|
|
414
423
|
|
|
415
|
-
// Warn when `encryptionRequired: true` is combined with `{ who: 'anyone', can: ['read'] }`.
|
|
416
|
-
// Authorization allows anyone to read the record, but encryption prevents them from
|
|
417
|
-
// decrypting the data — almost certainly unintentional. (issue #115)
|
|
418
424
|
if (ruleSetProtocolPath !== '') {
|
|
419
425
|
const typeName = ruleSetProtocolPath.split('/').pop()!;
|
|
420
426
|
const protocolType = types[typeName];
|
|
421
427
|
if (protocolType?.encryptionRequired === true) {
|
|
428
|
+
if (ruleSet.$keyAgreement === undefined) {
|
|
429
|
+
throw new DwnError(
|
|
430
|
+
DwnErrorCode.ProtocolsConfigureMissingEncryptedPathKeyAgreement,
|
|
431
|
+
`Encrypted protocol path '${ruleSetProtocolPath}' has no $keyAgreement.`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
422
435
|
const anyoneCanRead = actionRules.some(
|
|
423
436
|
(rule: ProtocolActionRule): boolean => rule.who === ProtocolActor.Anyone && rule.can.includes(ProtocolAction.Read)
|
|
424
437
|
);
|
|
425
438
|
if (anyoneCanRead) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
`
|
|
429
|
-
`Anyone can read the record but no one outside the key holders can decrypt it.`
|
|
439
|
+
throw new DwnError(
|
|
440
|
+
DwnErrorCode.ProtocolsConfigureInvalidEncryptedAnyoneRead,
|
|
441
|
+
`Encrypted protocol path '${ruleSetProtocolPath}' allows { who: 'anyone', can: ['read'] }.`
|
|
430
442
|
);
|
|
431
443
|
}
|
|
444
|
+
|
|
445
|
+
for (const actionRule of actionRules) {
|
|
446
|
+
if (actionRule.role === undefined || !actionRule.can.includes(ProtocolAction.Read) || isCrossProtocolRef(actionRule.role)) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const roleRuleSet = getRuleSetAtPath(actionRule.role, rootStructure);
|
|
451
|
+
if (roleRuleSet?.$keyAgreement === undefined) {
|
|
452
|
+
throw new DwnError(
|
|
453
|
+
DwnErrorCode.ProtocolsConfigureInvalidEncryptedRoleMissingKeyAgreement,
|
|
454
|
+
`Encrypted protocol path '${ruleSetProtocolPath}' references role '${actionRule.role}' with no $keyAgreement.`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
432
458
|
}
|
|
433
459
|
}
|
|
434
460
|
|
|
@@ -490,13 +516,14 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
490
516
|
roles,
|
|
491
517
|
uses,
|
|
492
518
|
types,
|
|
519
|
+
rootStructure,
|
|
493
520
|
});
|
|
494
521
|
}
|
|
495
522
|
}
|
|
496
523
|
|
|
497
524
|
/**
|
|
498
525
|
* Validates that a `$ref` node is a pure attachment point: it must NOT have
|
|
499
|
-
* `$actions`, `$role`, `$size`, `$tags`, or `$
|
|
526
|
+
* `$actions`, `$role`, `$size`, `$tags`, or `$keyAgreement`.
|
|
500
527
|
* Also validates that the `$ref` alias exists in the `uses` map.
|
|
501
528
|
*/
|
|
502
529
|
private static validateRefNode(ruleSet: ProtocolRuleSet, ruleSetProtocolPath: string, uses: ProtocolUses | undefined): void {
|
|
@@ -519,7 +546,7 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
|
|
|
519
546
|
}
|
|
520
547
|
|
|
521
548
|
// validate that `$ref` nodes do not have other directives
|
|
522
|
-
const forbiddenDirectives = ['$actions', '$role', '$size', '$tags', '$encryption', '$recordLimit', '$immutable', '$delivery', '$squash'] as const;
|
|
549
|
+
const forbiddenDirectives = ['$actions', '$role', '$size', '$tags', '$keyAgreement', '$encryption', '$recordLimit', '$immutable', '$delivery', '$squash'] as const;
|
|
523
550
|
for (const directive of forbiddenDirectives) {
|
|
524
551
|
if (ruleSet[directive] !== undefined) {
|
|
525
552
|
throw new DwnError(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GeneralJws } from '../types/jws-types.js';
|
|
2
2
|
import type { MessageSigner } from '../types/signer.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { DwnEncryption, EncryptionInput } from '../utils/encryption.js';
|
|
4
4
|
import type { RecordsWriteAttestationPayload, RecordsWriteMessage, RecordsWriteSignaturePayload } from '../types/records-types.js';
|
|
5
5
|
|
|
6
6
|
import { Cid } from '../utils/cid.js';
|
|
@@ -11,22 +11,15 @@ import { Jws } from '../utils/jws.js';
|
|
|
11
11
|
import { removeUndefinedProperties } from '@enbox/common';
|
|
12
12
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Creates the JWE `encryption` property if encryption input is given. Else `undefined` is returned.
|
|
16
|
-
* Uses ECDH-ES+A256KW key agreement with X25519 and AEAD content encryption (A256GCM or XC20P).
|
|
17
|
-
* @param encryptionInput The encryption input containing CEK, IV, authentication tag, and recipient key encryption inputs.
|
|
18
|
-
*/
|
|
14
|
+
/** Creates the RecordsWrite `encryption` property if encryption input is given. */
|
|
19
15
|
export async function createEncryptionProperty(
|
|
20
16
|
encryptionInput: EncryptionInput | undefined,
|
|
21
|
-
): Promise<
|
|
17
|
+
): Promise<DwnEncryption | undefined> {
|
|
22
18
|
if (encryptionInput === undefined) {
|
|
23
19
|
return undefined;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
const jwe = await Encryption.buildJwe(encryptionInput, encryptionInput.authenticationTag);
|
|
28
|
-
|
|
29
|
-
return jwe;
|
|
22
|
+
return Encryption.buildEncryptionProperty(encryptionInput);
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
/**
|
|
@@ -52,7 +45,7 @@ export async function createSignerSignature(input: {
|
|
|
52
45
|
contextId: string,
|
|
53
46
|
descriptorCid: string,
|
|
54
47
|
attestation: GeneralJws | undefined,
|
|
55
|
-
encryption:
|
|
48
|
+
encryption: DwnEncryption | undefined,
|
|
56
49
|
signer: MessageSigner,
|
|
57
50
|
delegatedGrantId?: string,
|
|
58
51
|
permissionGrantId?: string,
|
|
@@ -3,7 +3,6 @@ import type { KeyValues } from '../types/query-types.js';
|
|
|
3
3
|
import type { MessageInterface } from '../types/message-interface.js';
|
|
4
4
|
import type { MessageSigner } from '../types/signer.js';
|
|
5
5
|
import type { MessageStore } from '../types/message-store.js';
|
|
6
|
-
import type { PublicKeyJwk } from '../types/jose-types.js';
|
|
7
6
|
import type { ValidationStateReader } from '../types/validation-state-reader.js';
|
|
8
7
|
import type {
|
|
9
8
|
DataEncodedRecordsWriteMessage,
|
|
@@ -13,10 +12,11 @@ import type {
|
|
|
13
12
|
RecordsWriteSignaturePayload,
|
|
14
13
|
RecordsWriteTags
|
|
15
14
|
} from '../types/records-types.js';
|
|
16
|
-
import type {
|
|
15
|
+
import type { DwnEncryption, EncryptionInput } from '../utils/encryption.js';
|
|
17
16
|
import type { GenericMessage, GenericSignaturePayload } from '../types/message-types.js';
|
|
18
17
|
|
|
19
18
|
import { Cid } from '../utils/cid.js';
|
|
19
|
+
import { Encryption } from '../utils/encryption.js';
|
|
20
20
|
import { Jws } from '../utils/jws.js';
|
|
21
21
|
import { Message } from '../core/message.js';
|
|
22
22
|
import { PermissionGrant } from '../protocols/permission-grant.js';
|
|
@@ -77,6 +77,11 @@ export type RecordsWriteOptions = {
|
|
|
77
77
|
delegatedGrant?: DataEncodedRecordsWriteMessage;
|
|
78
78
|
|
|
79
79
|
attestationSigners?: MessageSigner[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pre-built encryption envelope. Use `encryptionInput` when wrapping a CEK from plaintext/ciphertext inputs.
|
|
83
|
+
*/
|
|
84
|
+
encryption?: DwnEncryption;
|
|
80
85
|
encryptionInput?: EncryptionInput;
|
|
81
86
|
permissionGrantId?: string;
|
|
82
87
|
|
|
@@ -87,19 +92,14 @@ export type RecordsWriteOptions = {
|
|
|
87
92
|
*/
|
|
88
93
|
squash?: true;
|
|
89
94
|
|
|
90
|
-
/**
|
|
91
|
-
* The author's ProtocolPath-derived public key for key delivery.
|
|
92
|
-
* When set, this is attached to the authorization model so the DWN owner
|
|
93
|
-
* can encrypt context keys back to the author without querying the
|
|
94
|
-
* author's DWN.
|
|
95
|
-
*/
|
|
96
|
-
authorKeyDeliveryPublicKey?: {
|
|
97
|
-
rootKeyId: string;
|
|
98
|
-
publicKeyJwk: PublicKeyJwk;
|
|
99
|
-
};
|
|
100
95
|
};
|
|
101
96
|
|
|
102
|
-
export type {
|
|
97
|
+
export type {
|
|
98
|
+
EncryptionInput,
|
|
99
|
+
KeyEncryptionInput,
|
|
100
|
+
ProtocolPathKeyEncryptionInput,
|
|
101
|
+
RoleAudienceKeyEncryptionInput,
|
|
102
|
+
} from '../utils/encryption.js';
|
|
103
103
|
|
|
104
104
|
export type CreateFromOptions = {
|
|
105
105
|
recordsWriteMessage: RecordsWriteMessage,
|
|
@@ -262,6 +262,9 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
262
262
|
// - `initialWrite`: the initial RecordsWrite when this message is an update
|
|
263
263
|
const { encodedData: _, initialWrite: __, ...messageToValidate } = message as RecordsWriteMessage & Record<string, unknown>;
|
|
264
264
|
Message.validateJsonSchema(messageToValidate);
|
|
265
|
+
if (message.encryption !== undefined) {
|
|
266
|
+
Encryption.validateEncryptionProperty(message.encryption);
|
|
267
|
+
}
|
|
265
268
|
|
|
266
269
|
// asynchronous checks that are required by the constructor to initialize members properly
|
|
267
270
|
|
|
@@ -298,23 +301,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
298
301
|
* If not given, it means this write is for a root protocol record.
|
|
299
302
|
*/
|
|
300
303
|
public static async create(options: RecordsWriteOptions): Promise<RecordsWrite> {
|
|
301
|
-
|
|
302
|
-
throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingProtocol, '`protocol` and `protocolPath` are required');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if ((options.data === undefined && options.dataCid === undefined) ||
|
|
306
|
-
(options.data !== undefined && options.dataCid !== undefined)) {
|
|
307
|
-
throw new DwnError(DwnErrorCode.RecordsWriteCreateDataAndDataCidMutuallyExclusive, 'one and only one parameter between `data` and `dataCid` is required');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if ((options.dataCid === undefined && options.dataSize !== undefined) ||
|
|
311
|
-
(options.dataCid !== undefined && options.dataSize === undefined)) {
|
|
312
|
-
throw new DwnError(DwnErrorCode.RecordsWriteCreateDataCidAndDataSizeMutuallyInclusive, '`dataCid` and `dataSize` must both be defined or undefined at the same time');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (options.signer === undefined && options.delegatedGrant !== undefined) {
|
|
316
|
-
throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingSigner, '`signer` must be given when `delegatedGrant` is given');
|
|
317
|
-
}
|
|
304
|
+
RecordsWrite.validateCreateOptions(options);
|
|
318
305
|
|
|
319
306
|
const dataCid = options.dataCid ?? await Cid.computeDagPbCidFromBytes(options.data!);
|
|
320
307
|
const dataSize = options.dataSize ?? options.data!.length;
|
|
@@ -362,7 +349,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
362
349
|
const attestation = await createAttestation(descriptorCid, options.attestationSigners);
|
|
363
350
|
|
|
364
351
|
// `encryption` generation
|
|
365
|
-
const encryption = await createEncryptionProperty(options.encryptionInput);
|
|
352
|
+
const encryption = options.encryption ?? await createEncryptionProperty(options.encryptionInput);
|
|
366
353
|
|
|
367
354
|
const message: InternalRecordsWriteMessage = {
|
|
368
355
|
recordId,
|
|
@@ -377,17 +364,43 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
377
364
|
|
|
378
365
|
if (options.signer !== undefined) {
|
|
379
366
|
await recordsWrite.sign({
|
|
380
|
-
signer
|
|
381
|
-
delegatedGrant
|
|
367
|
+
signer : options.signer,
|
|
368
|
+
delegatedGrant : options.delegatedGrant,
|
|
382
369
|
...permissionGrantInvocation,
|
|
383
|
-
protocolRole
|
|
384
|
-
authorKeyDeliveryPublicKey : options.authorKeyDeliveryPublicKey,
|
|
370
|
+
protocolRole : options.protocolRole,
|
|
385
371
|
});
|
|
386
372
|
}
|
|
387
373
|
|
|
388
374
|
return recordsWrite;
|
|
389
375
|
}
|
|
390
376
|
|
|
377
|
+
private static validateCreateOptions(options: RecordsWriteOptions): void {
|
|
378
|
+
if (options.protocol === undefined || options.protocolPath === undefined) {
|
|
379
|
+
throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingProtocol, '`protocol` and `protocolPath` are required');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if ((options.data === undefined && options.dataCid === undefined) ||
|
|
383
|
+
(options.data !== undefined && options.dataCid !== undefined)) {
|
|
384
|
+
throw new DwnError(DwnErrorCode.RecordsWriteCreateDataAndDataCidMutuallyExclusive, 'one and only one parameter between `data` and `dataCid` is required');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if ((options.dataCid === undefined && options.dataSize !== undefined) ||
|
|
388
|
+
(options.dataCid !== undefined && options.dataSize === undefined)) {
|
|
389
|
+
throw new DwnError(DwnErrorCode.RecordsWriteCreateDataCidAndDataSizeMutuallyInclusive, '`dataCid` and `dataSize` must both be defined or undefined at the same time');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (options.signer === undefined && options.delegatedGrant !== undefined) {
|
|
393
|
+
throw new DwnError(DwnErrorCode.RecordsWriteCreateMissingSigner, '`signer` must be given when `delegatedGrant` is given');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (options.encryption !== undefined && options.encryptionInput !== undefined) {
|
|
397
|
+
throw new DwnError(
|
|
398
|
+
DwnErrorCode.RecordsWriteCreateEncryptionAndEncryptionInputMutuallyExclusive,
|
|
399
|
+
'`encryption` and `encryptionInput` cannot both be defined'
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
391
404
|
private static getRecordIdFromContextId(contextId: string | undefined): string | undefined {
|
|
392
405
|
return contextId?.split('/').filter(segment => segment !== '').pop();
|
|
393
406
|
}
|
|
@@ -454,7 +467,9 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
454
467
|
delegatedGrant : options.delegatedGrant,
|
|
455
468
|
// finally still need signers
|
|
456
469
|
signer : options.signer,
|
|
457
|
-
attestationSigners : options.attestationSigners
|
|
470
|
+
attestationSigners : options.attestationSigners,
|
|
471
|
+
encryption : options.encryptionInput === undefined && options.data === undefined ? sourceMessage.encryption : undefined,
|
|
472
|
+
encryptionInput : options.encryptionInput,
|
|
458
473
|
};
|
|
459
474
|
|
|
460
475
|
const recordsWrite = await RecordsWrite.create(createOptions);
|
|
@@ -498,51 +513,16 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
498
513
|
}
|
|
499
514
|
|
|
500
515
|
/**
|
|
501
|
-
* Encrypts the symmetric encryption key using the public keys given and
|
|
502
|
-
*
|
|
503
|
-
* @param options.append - When `true`, appends new `recipients` entries to the existing
|
|
504
|
-
* `encryption` property instead of replacing it. Requires `this._message.encryption` to
|
|
505
|
-
* already exist (i.e., the record must already be encrypted).
|
|
516
|
+
* Encrypts the symmetric encryption key using the public keys given and attaches the resulting `encryption` property.
|
|
506
517
|
*/
|
|
507
518
|
public async encryptSymmetricEncryptionKey(
|
|
508
519
|
encryptionInput: EncryptionInput,
|
|
509
|
-
options?: { append?: boolean },
|
|
510
520
|
): Promise<void> {
|
|
511
|
-
|
|
512
|
-
if (!this._message.encryption) {
|
|
513
|
-
throw new DwnError(
|
|
514
|
-
DwnErrorCode.RecordsWriteMissingEncryption,
|
|
515
|
-
'Cannot append recipients: record does not have an existing `encryption` property.'
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Build only the new recipients (reuses createEncryptionProperty for ECDH-ES+A256KW logic)
|
|
520
|
-
const newEncryption = await createEncryptionProperty(encryptionInput);
|
|
521
|
-
if (newEncryption) {
|
|
522
|
-
this._message.encryption.recipients.push(...newEncryption.recipients);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// In append mode, preserve the author's identity and authorization so
|
|
526
|
-
// that signAsOwner() can be called afterwards. The author's signature
|
|
527
|
-
// payload will have a stale encryptionCid (since we just appended new
|
|
528
|
-
// recipients), but the owner's signature vouches for the
|
|
529
|
-
// updated state. validateIntegrity() skips the encryptionCid check on
|
|
530
|
-
// the author's signature when an ownerSignature is present.
|
|
531
|
-
//
|
|
532
|
-
// NOTE: An alternative design would deliver the DEK out-of-band via the
|
|
533
|
-
// key-delivery protocol (as a field on the contextKey record) instead of
|
|
534
|
-
// mutating the record's encryption property. That avoids the stale
|
|
535
|
-
// encryptionCid issue entirely but adds complexity to the read path and
|
|
536
|
-
// the contextKey schema. We chose the in-record approach because it keeps
|
|
537
|
-
// records self-contained and the read/decrypt path unchanged.
|
|
538
|
-
} else {
|
|
539
|
-
this._message.encryption = await createEncryptionProperty(encryptionInput);
|
|
521
|
+
this._message.encryption = await createEncryptionProperty(encryptionInput);
|
|
540
522
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
this._author = undefined;
|
|
545
|
-
}
|
|
523
|
+
delete this._message.authorization;
|
|
524
|
+
this._signaturePayload = undefined;
|
|
525
|
+
this._author = undefined;
|
|
546
526
|
}
|
|
547
527
|
|
|
548
528
|
/**
|
|
@@ -553,9 +533,8 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
553
533
|
delegatedGrant?: DataEncodedRecordsWriteMessage,
|
|
554
534
|
permissionGrantId?: string,
|
|
555
535
|
protocolRole?: string,
|
|
556
|
-
authorKeyDeliveryPublicKey?: { rootKeyId: string; publicKeyJwk: PublicKeyJwk },
|
|
557
536
|
}): Promise<void> {
|
|
558
|
-
const { signer, delegatedGrant, permissionGrantId, protocolRole
|
|
537
|
+
const { signer, delegatedGrant, permissionGrantId, protocolRole } = options;
|
|
559
538
|
|
|
560
539
|
// compute delegated grant ID and author if delegated grant is given
|
|
561
540
|
let delegatedGrantId;
|
|
@@ -601,10 +580,6 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
601
580
|
this._message.authorization.authorDelegatedGrant = delegatedGrant;
|
|
602
581
|
}
|
|
603
582
|
|
|
604
|
-
if (authorKeyDeliveryPublicKey !== undefined) {
|
|
605
|
-
this._message.authorization.authorKeyDeliveryPublicKey = authorKeyDeliveryPublicKey;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
583
|
// there is opportunity to optimize here as the payload is constructed within `createAuthorization(...)`
|
|
609
584
|
this._signaturePayload = Jws.decodePlainObjectPayload(signature);
|
|
610
585
|
this._author = authorDid;
|
|
@@ -719,16 +694,24 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
719
694
|
}
|
|
720
695
|
}
|
|
721
696
|
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
697
|
+
// if `encryption` is given in message, make sure the correct `encryptionCid` is in the payload of the message signature
|
|
698
|
+
if (this.message.encryption !== undefined && signaturePayload.encryptionCid === undefined) {
|
|
699
|
+
throw new DwnError(
|
|
700
|
+
DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMissing,
|
|
701
|
+
'encryptionCid must be present in the authorization payload when encryption is present in the message'
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (this.message.encryption === undefined && signaturePayload.encryptionCid !== undefined) {
|
|
706
|
+
throw new DwnError(
|
|
707
|
+
DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMissing,
|
|
708
|
+
'encryption must be present in the message when encryptionCid is present in the authorization payload'
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (this.message.encryption !== undefined) {
|
|
730
713
|
const expectedEncryptionCid = await Cid.computeCid(this.message.encryption);
|
|
731
|
-
const actualEncryptionCid = signaturePayload.encryptionCid
|
|
714
|
+
const actualEncryptionCid = signaturePayload.encryptionCid!;
|
|
732
715
|
if (actualEncryptionCid !== expectedEncryptionCid) {
|
|
733
716
|
throw new DwnError(
|
|
734
717
|
DwnErrorCode.RecordsWriteValidateIntegrityEncryptionCidMismatch,
|
|
@@ -871,7 +854,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
|
|
|
871
854
|
contextId: string,
|
|
872
855
|
descriptorCid: string,
|
|
873
856
|
attestation: GeneralJws | undefined,
|
|
874
|
-
encryption:
|
|
857
|
+
encryption: DwnEncryption | undefined,
|
|
875
858
|
signer: MessageSigner,
|
|
876
859
|
delegatedGrantId?: string,
|
|
877
860
|
permissionGrantId?: string,
|