@enbox/dwn-sdk-js 0.4.4 → 0.4.6
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 +5406 -2391
- 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 +156 -188
- 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 +121 -271
- 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/audience-key.spec.js +40 -0
- package/dist/esm/tests/validation/json-schemas/encryption/audience-key.spec.js.map +1 -0
- package/dist/esm/tests/validation/json-schemas/encryption/grant-key.spec.js +72 -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 +61 -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/audience-key.spec.d.ts +2 -0
- package/dist/types/tests/validation/json-schemas/encryption/audience-key.spec.d.ts.map +1 -0
- 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 +258 -280
- 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
|
@@ -16,9 +16,9 @@ export type ProtocolScope = {
|
|
|
16
16
|
*
|
|
17
17
|
* Matching fails closed for invalid combinations: `protocolPath` and
|
|
18
18
|
* `contextId` are mutually exclusive, and either field requires `protocol`.
|
|
19
|
-
* `protocol`
|
|
20
|
-
* match
|
|
21
|
-
*
|
|
19
|
+
* `protocol` matches by exact equality. `protocolPath` and `contextId` scopes
|
|
20
|
+
* match subtrees: the target value must equal the scoped value or begin with the
|
|
21
|
+
* scoped value followed by `/`.
|
|
22
22
|
*/
|
|
23
23
|
export class PermissionScopeMatcher {
|
|
24
24
|
/**
|
|
@@ -38,7 +38,7 @@ export class PermissionScopeMatcher {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
if (scope.protocolPath !== undefined) {
|
|
41
|
-
return scope.protocolPath
|
|
41
|
+
return PermissionScopeMatcher.matchesSubtree(scope.protocolPath, target.protocolPath);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
if (scope.contextId !== undefined) {
|
|
@@ -49,7 +49,11 @@ export class PermissionScopeMatcher {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
private static matchesContextId(scopeContextId: string, candidateContextId: unknown): boolean {
|
|
52
|
-
return
|
|
53
|
-
|
|
52
|
+
return PermissionScopeMatcher.matchesSubtree(scopeContextId, candidateContextId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private static matchesSubtree(scopeValue: string, candidateValue: unknown): boolean {
|
|
56
|
+
return typeof candidateValue === 'string' &&
|
|
57
|
+
(candidateValue === scopeValue || candidateValue.startsWith(scopeValue + '/'));
|
|
54
58
|
}
|
|
55
59
|
}
|
package/src/utils/protocols.ts
CHANGED
|
@@ -77,11 +77,10 @@ export function getRuleSetAtPath(protocolPath: string, structure: { [key: string
|
|
|
77
77
|
*/
|
|
78
78
|
export class Protocols {
|
|
79
79
|
/**
|
|
80
|
-
* Derives public
|
|
81
|
-
* then returns the final encryption-enabled protocol definition.
|
|
80
|
+
* Derives public encryption keys and injects them in `$keyAgreement`.
|
|
82
81
|
* NOTE: The original definition passed in is unmodified.
|
|
83
82
|
*
|
|
84
|
-
* `$ref` nodes (cross-protocol attachment points) are skipped during `$
|
|
83
|
+
* `$ref` nodes (cross-protocol attachment points) are skipped during `$keyAgreement` injection
|
|
85
84
|
* because their records belong to the referenced protocol, whose own encryption keys govern them.
|
|
86
85
|
* Children of `$ref` nodes are still processed because they belong to the composing protocol.
|
|
87
86
|
*
|
|
@@ -94,8 +93,7 @@ export class Protocols {
|
|
|
94
93
|
): Promise<ProtocolDefinition>;
|
|
95
94
|
|
|
96
95
|
/**
|
|
97
|
-
* Overload 2 (raw-key
|
|
98
|
-
* Preserved for backward compatibility with tests and non-KMS callers.
|
|
96
|
+
* Overload 2 (raw-key): Takes rootKeyId and raw PrivateKeyJwk directly.
|
|
99
97
|
*/
|
|
100
98
|
public static async deriveAndInjectPublicEncryptionKeys(
|
|
101
99
|
protocolDefinition: ProtocolDefinition,
|
|
@@ -103,7 +101,7 @@ export class Protocols {
|
|
|
103
101
|
privateJwk: PrivateKeyJwk,
|
|
104
102
|
): Promise<ProtocolDefinition>;
|
|
105
103
|
|
|
106
|
-
// Implementation dispatches based on argument type
|
|
104
|
+
// Implementation dispatches based on argument type.
|
|
107
105
|
public static async deriveAndInjectPublicEncryptionKeys(
|
|
108
106
|
protocolDefinition: ProtocolDefinition,
|
|
109
107
|
rootKeyIdOrKeyDeriver: string | EncryptionKeyDeriver,
|
|
@@ -116,6 +114,9 @@ export class Protocols {
|
|
|
116
114
|
// Callback-based path
|
|
117
115
|
const keyDeriver = rootKeyIdOrKeyDeriver;
|
|
118
116
|
const basePath = [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol];
|
|
117
|
+
clone.$keyAgreement = {
|
|
118
|
+
publicKeyJwk: await keyDeriver.derivePublicKey(basePath),
|
|
119
|
+
};
|
|
119
120
|
|
|
120
121
|
async function injectKeysViaCallback(
|
|
121
122
|
ruleSet: ProtocolRuleSet, parentPath: string[],
|
|
@@ -133,10 +134,7 @@ export class Protocols {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
const publicKeyJwk = await keyDeriver.derivePublicKey(currentPath);
|
|
136
|
-
childRuleSet.$
|
|
137
|
-
rootKeyId: keyDeriver.rootKeyId,
|
|
138
|
-
publicKeyJwk,
|
|
139
|
-
};
|
|
137
|
+
childRuleSet.$keyAgreement = { publicKeyJwk };
|
|
140
138
|
await injectKeysViaCallback(childRuleSet, currentPath);
|
|
141
139
|
}
|
|
142
140
|
}
|
|
@@ -146,13 +144,13 @@ export class Protocols {
|
|
|
146
144
|
return clone;
|
|
147
145
|
}
|
|
148
146
|
|
|
149
|
-
// Raw-key path
|
|
147
|
+
// Raw-key path
|
|
150
148
|
const rootKeyId = rootKeyIdOrKeyDeriver;
|
|
151
149
|
|
|
152
|
-
// a function that recursively creates and adds `$
|
|
150
|
+
// a function that recursively creates and adds `$keyAgreement` property to every rule set
|
|
153
151
|
async function addEncryptionProperty(ruleSet: ProtocolRuleSet, parentKey: DerivedPrivateJwk): Promise<void> {
|
|
154
152
|
for (const key in ruleSet) {
|
|
155
|
-
// if we encounter a nested rule set (a property name that doesn't begin with '$'), recursively inject the `$
|
|
153
|
+
// if we encounter a nested rule set (a property name that doesn't begin with '$'), recursively inject the `$keyAgreement` property
|
|
156
154
|
if (!key.startsWith('$')) {
|
|
157
155
|
const derivedPrivateKey = await HdKey.derivePrivateKey(parentKey, [key]);
|
|
158
156
|
const childRuleSet = ruleSet[key] as ProtocolRuleSet;
|
|
@@ -166,7 +164,7 @@ export class Protocols {
|
|
|
166
164
|
|
|
167
165
|
const publicKeyJwk = await X25519.getPublicKey({ key: derivedPrivateKey.derivedPrivateKey }) as PublicKeyJwk;
|
|
168
166
|
|
|
169
|
-
childRuleSet.$
|
|
167
|
+
childRuleSet.$keyAgreement = { publicKeyJwk };
|
|
170
168
|
await addEncryptionProperty(childRuleSet, derivedPrivateKey);
|
|
171
169
|
}
|
|
172
170
|
}
|
|
@@ -179,8 +177,11 @@ export class Protocols {
|
|
|
179
177
|
rootKeyId
|
|
180
178
|
};
|
|
181
179
|
const protocolLevelDerivedKey = await HdKey.derivePrivateKey(rootKey, [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol]);
|
|
180
|
+
clone.$keyAgreement = {
|
|
181
|
+
publicKeyJwk: await X25519.getPublicKey({ key: protocolLevelDerivedKey.derivedPrivateKey }) as PublicKeyJwk,
|
|
182
|
+
};
|
|
182
183
|
await addEncryptionProperty(clone.structure as ProtocolRuleSet, protocolLevelDerivedKey);
|
|
183
184
|
|
|
184
185
|
return clone;
|
|
185
186
|
}
|
|
186
|
-
}
|
|
187
|
+
}
|
package/src/utils/records.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { DerivedPrivateJwk } from './hd-key.js';
|
|
2
2
|
import type { Jwk } from '@enbox/crypto';
|
|
3
3
|
import type { KeyDecrypter } from '../types/encryption-types.js';
|
|
4
|
+
import type { KeyEncryption } from './encryption.js';
|
|
5
|
+
import type { PublicKeyJwk } from '../types/jose-types.js';
|
|
4
6
|
import type { Filter, KeyValues, StartsWithFilter } from '../types/query-types.js';
|
|
5
7
|
import type { GenericMessage, GenericSignaturePayload, MessageSort } from '../types/message-types.js';
|
|
6
8
|
import type { RecordsCountMessage, RecordsDeleteMessage, RecordsFilter, RecordsQueryMessage, RecordsReadMessage, RecordsSubscribeMessage, RecordsWriteDescriptor, RecordsWriteMessage, RecordsWriteTags, RecordsWriteTagsFilter } from '../types/records-types.js';
|
|
7
9
|
|
|
10
|
+
import { Cid } from './cid.js';
|
|
11
|
+
import { DataStream } from './data-stream.js';
|
|
8
12
|
import { DateSort } from '../types/records-types.js';
|
|
9
13
|
import { Encoder } from './encoder.js';
|
|
10
|
-
import { Encryption } from './encryption.js';
|
|
11
14
|
import { FilterUtility } from './filter.js';
|
|
12
15
|
import { Jws } from './jws.js';
|
|
13
16
|
import { Message } from '../core/message.js';
|
|
@@ -17,6 +20,7 @@ import { SortDirection } from '../types/query-types.js';
|
|
|
17
20
|
import { X25519 } from '@enbox/crypto';
|
|
18
21
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
19
22
|
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
23
|
+
import { Encryption, ROLE_AUDIENCE_DERIVATION_SCHEME } from './encryption.js';
|
|
20
24
|
import { HdKey, KeyDerivationScheme } from './hd-key.js';
|
|
21
25
|
import { normalizeProtocolUrl, normalizeSchemaUrl } from './url.js';
|
|
22
26
|
|
|
@@ -60,7 +64,7 @@ export class Records {
|
|
|
60
64
|
* Decrypts the encrypted data in a message reply.
|
|
61
65
|
*
|
|
62
66
|
* Overload 1 (callback-based): Accepts a KeyDecrypter that performs
|
|
63
|
-
* HKDF
|
|
67
|
+
* X25519-HKDF key agreement + AES Key Unwrap internally.
|
|
64
68
|
*/
|
|
65
69
|
public static async decrypt(
|
|
66
70
|
recordsWrite: RecordsWriteMessage,
|
|
@@ -78,7 +82,7 @@ export class Records {
|
|
|
78
82
|
cipherStream: ReadableStream<Uint8Array>,
|
|
79
83
|
): Promise<ReadableStream<Uint8Array>>;
|
|
80
84
|
|
|
81
|
-
// Implementation dispatches based on argument type
|
|
85
|
+
// Implementation dispatches based on argument type.
|
|
82
86
|
public static async decrypt(
|
|
83
87
|
recordsWrite: RecordsWriteMessage,
|
|
84
88
|
keyOrDecrypter: DerivedPrivateJwk | KeyDecrypter,
|
|
@@ -92,81 +96,114 @@ export class Records {
|
|
|
92
96
|
);
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
const
|
|
99
|
+
const ciphertext = await DataStream.toBytes(cipherStream);
|
|
100
|
+
const actualDataCid = await Cid.computeDagPbCidFromBytes(ciphertext);
|
|
101
|
+
Records.validateEncryptedDataIntegrity(recordsWrite, actualDataCid, ciphertext.byteLength);
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const enc = protectedHeader.enc;
|
|
103
|
+
const { fullDerivationPath, leafPrivateKey, matchingKeyEncryption } =
|
|
104
|
+
await Records.findMatchingKeyEncryption(recordsWrite, keyOrDecrypter);
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
const matchingRecipient = encryption.recipients.find(r =>
|
|
103
|
-
r.header.kid === keyOrDecrypter.rootKeyId &&
|
|
104
|
-
r.header.derivationScheme === keyOrDecrypter.derivationScheme
|
|
105
|
-
);
|
|
106
|
-
if (matchingRecipient === undefined) {
|
|
106
|
+
if (matchingKeyEncryption === undefined) {
|
|
107
107
|
throw new DwnError(
|
|
108
108
|
DwnErrorCode.RecordsDecryptNoMatchingKeyEncryptedFound,
|
|
109
|
-
`Unable to find a
|
|
110
|
-
with ID '${keyOrDecrypter.rootKeyId}' and '${keyOrDecrypter.derivationScheme}' derivation scheme.`
|
|
109
|
+
`Unable to find a matching key encryption entry for '${keyOrDecrypter.rootKeyId}' and '${keyOrDecrypter.derivationScheme}'.`
|
|
111
110
|
);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
// Construct the full derivation path
|
|
115
|
-
const fullDerivationPath = Records.constructKeyDerivationPath(
|
|
116
|
-
matchingRecipient.header.derivationScheme, recordsWrite,
|
|
117
|
-
);
|
|
118
|
-
|
|
119
113
|
let cek: Uint8Array;
|
|
120
114
|
|
|
121
|
-
if (
|
|
122
|
-
// Callback-based: delegate HKDF + ECDH-ES + AES Key Unwrap to the KeyDecrypter
|
|
123
|
-
const encryptedKeyBytes = Encoder.base64UrlToBytes(matchingRecipient.encrypted_key);
|
|
124
|
-
|
|
115
|
+
if ('decrypt' in keyOrDecrypter) {
|
|
125
116
|
cek = await keyOrDecrypter.decrypt(fullDerivationPath, {
|
|
126
|
-
encryptedKey :
|
|
127
|
-
ephemeralPublicKey :
|
|
117
|
+
encryptedKey : Encoder.base64UrlToBytes(matchingKeyEncryption.encryptedKey),
|
|
118
|
+
ephemeralPublicKey : matchingKeyEncryption.ephemeralPublicKey,
|
|
119
|
+
keyEncryption : matchingKeyEncryption,
|
|
128
120
|
});
|
|
129
121
|
} else {
|
|
130
|
-
|
|
131
|
-
const leafPrivateKeyBytes = await Records.derivePrivateKey(keyOrDecrypter, fullDerivationPath);
|
|
132
|
-
const leafPrivateKeyJwk = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
|
|
133
|
-
const wrappedKeyBytes = Encoder.base64UrlToBytes(matchingRecipient.encrypted_key);
|
|
134
|
-
|
|
135
|
-
cek = await Encryption.ecdhEsUnwrapKey(
|
|
136
|
-
leafPrivateKeyJwk,
|
|
137
|
-
matchingRecipient.header.epk as Jwk,
|
|
138
|
-
wrappedKeyBytes,
|
|
139
|
-
);
|
|
122
|
+
cek = await Encryption.unwrapKey(leafPrivateKey!, matchingKeyEncryption);
|
|
140
123
|
}
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const plaintextStream = await Encryption.aeadDecryptStream(enc, cek, iv, cipherStream, tag);
|
|
125
|
+
const iv = Encoder.base64UrlToBytes(encryption.initializationVector);
|
|
126
|
+
const plaintext = await Encryption.decrypt(encryption.algorithm, cek, iv, ciphertext);
|
|
127
|
+
const plaintextStream = DataStream.fromBytes(plaintext);
|
|
146
128
|
|
|
147
129
|
return plaintextStream;
|
|
148
130
|
}
|
|
149
131
|
|
|
132
|
+
private static async findMatchingKeyEncryption(
|
|
133
|
+
recordsWrite: RecordsWriteMessage,
|
|
134
|
+
keyOrDecrypter: DerivedPrivateJwk | KeyDecrypter,
|
|
135
|
+
): Promise<{ fullDerivationPath: string[]; leafPrivateKey?: Jwk; matchingKeyEncryption?: KeyEncryption }> {
|
|
136
|
+
const { encryption } = recordsWrite;
|
|
137
|
+
const fullDerivationPath = Records.constructKeyDerivationPath(keyOrDecrypter.derivationScheme, recordsWrite);
|
|
138
|
+
|
|
139
|
+
if ('decrypt' in keyOrDecrypter) {
|
|
140
|
+
const publicKey = await keyOrDecrypter.derivePublicKey(fullDerivationPath);
|
|
141
|
+
const keyId = await Encryption.getKeyId(publicKey);
|
|
142
|
+
return {
|
|
143
|
+
fullDerivationPath,
|
|
144
|
+
matchingKeyEncryption: encryption!.keyEncryption.find((entry): boolean =>
|
|
145
|
+
entry.derivationScheme === keyOrDecrypter.derivationScheme && entry.keyId === keyId
|
|
146
|
+
),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const leafPrivateKeyBytes = await Records.derivePrivateKey(keyOrDecrypter, fullDerivationPath);
|
|
151
|
+
const leafPrivateKey = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
|
|
152
|
+
|
|
153
|
+
const publicKey = await X25519.getPublicKey({ key: leafPrivateKey });
|
|
154
|
+
const keyId = keyOrDecrypter.keyId ?? await Encryption.getKeyId(publicKey as PublicKeyJwk);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
fullDerivationPath,
|
|
158
|
+
leafPrivateKey,
|
|
159
|
+
matchingKeyEncryption: encryption!.keyEncryption.find((entry): boolean =>
|
|
160
|
+
entry.derivationScheme === keyOrDecrypter.derivationScheme && entry.keyId === keyId
|
|
161
|
+
),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private static validateEncryptedDataIntegrity(
|
|
166
|
+
recordsWrite: RecordsWriteMessage,
|
|
167
|
+
actualDataCid: string,
|
|
168
|
+
actualDataSize: number,
|
|
169
|
+
): void {
|
|
170
|
+
const { dataCid, dataSize } = recordsWrite.descriptor;
|
|
171
|
+
if (dataCid !== actualDataCid) {
|
|
172
|
+
throw new DwnError(
|
|
173
|
+
DwnErrorCode.RecordsWriteDataCidMismatch,
|
|
174
|
+
`actual data CID ${actualDataCid} does not match dataCid in descriptor: ${dataCid}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (dataSize !== actualDataSize) {
|
|
179
|
+
throw new DwnError(
|
|
180
|
+
DwnErrorCode.RecordsWriteDataSizeMismatch,
|
|
181
|
+
`actual data size ${actualDataSize} bytes does not match dataSize in descriptor: ${dataSize}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
150
186
|
/**
|
|
151
187
|
* Constructs full key derivation path using the specified scheme.
|
|
152
188
|
*/
|
|
153
189
|
public static constructKeyDerivationPath(
|
|
154
|
-
keyDerivationScheme: KeyDerivationScheme,
|
|
190
|
+
keyDerivationScheme: KeyDerivationScheme | typeof ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
155
191
|
recordsWriteMessage: RecordsWriteMessage
|
|
156
192
|
): string[] {
|
|
157
193
|
|
|
158
194
|
const descriptor = recordsWriteMessage.descriptor;
|
|
159
|
-
const contextId = recordsWriteMessage.contextId;
|
|
160
|
-
|
|
161
|
-
let fullDerivationPath;
|
|
162
195
|
if (keyDerivationScheme === KeyDerivationScheme.ProtocolPath) {
|
|
163
|
-
|
|
164
|
-
} else {
|
|
165
|
-
// `protocolContext` scheme
|
|
166
|
-
fullDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(contextId);
|
|
196
|
+
return Records.constructKeyDerivationPathUsingProtocolPathScheme(descriptor);
|
|
167
197
|
}
|
|
168
198
|
|
|
169
|
-
|
|
199
|
+
if (keyDerivationScheme === ROLE_AUDIENCE_DERIVATION_SCHEME) {
|
|
200
|
+
return [ROLE_AUDIENCE_DERIVATION_SCHEME];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw new DwnError(
|
|
204
|
+
DwnErrorCode.RecordsDecryptUnsupportedKeyDerivationScheme,
|
|
205
|
+
`Unsupported key derivation scheme '${keyDerivationScheme as string}'.`
|
|
206
|
+
);
|
|
170
207
|
}
|
|
171
208
|
|
|
172
209
|
/**
|
|
@@ -188,28 +225,6 @@ export class Records {
|
|
|
188
225
|
return fullDerivationPath;
|
|
189
226
|
}
|
|
190
227
|
|
|
191
|
-
/**
|
|
192
|
-
* Constructs the full key derivation path using `protocolContext` scheme.
|
|
193
|
-
*
|
|
194
|
-
* NOTE on protocol composition: When a context tree spans two protocols via `$ref` composition,
|
|
195
|
-
* the root `contextId` segment (the `$ref` parent record's ID) is shared across both protocols.
|
|
196
|
-
* This means ProtocolContext-encrypted records from the composing protocol and the referenced
|
|
197
|
-
* protocol derive the same context key. This is by design — it enables multi-party access within
|
|
198
|
-
* a shared context (e.g., thread participants can decrypt messages from both the threads protocol
|
|
199
|
-
* and composing protocols that attach to those threads).
|
|
200
|
-
*/
|
|
201
|
-
public static constructKeyDerivationPathUsingProtocolContextScheme(contextId: string): string[] {
|
|
202
|
-
// TODO: Extend key derivation support to include the full contextId (https://github.com/enboxorg/enbox/issues/99)
|
|
203
|
-
const firstContextSegment = contextId.split('/')[0];
|
|
204
|
-
|
|
205
|
-
const fullDerivationPath = [
|
|
206
|
-
KeyDerivationScheme.ProtocolContext,
|
|
207
|
-
firstContextSegment
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
return fullDerivationPath;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
228
|
/**
|
|
214
229
|
* Derives a descendant private key given an ancestor private key and the full absolute derivation path.
|
|
215
230
|
* Uses X25519 keys for encryption key derivation.
|
package/src/utils/replication.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GenericMessage } from '../types/message-types.js';
|
|
2
2
|
import type { KeyValues } from '../types/query-types.js';
|
|
3
3
|
|
|
4
|
+
import { EncryptionProtocol } from '../protocols/encryption.js';
|
|
4
5
|
import { PermissionsProtocol } from '../protocols/permissions.js';
|
|
5
6
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
6
7
|
|
|
@@ -24,6 +25,25 @@ export class Replication {
|
|
|
24
25
|
return `perm:${protocolUri}`;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
public static encryptionDomain(protocolUri: string): string {
|
|
29
|
+
return `enc:${protocolUri}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static taggedCoreProtocolDomains(protocolUri: string, protocolsInScope: ReadonlySet<string> = new Set()): string[] {
|
|
33
|
+
if (Replication.isCoreProtocolUri(protocolUri)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const domains: string[] = [];
|
|
38
|
+
if (!protocolsInScope.has(PermissionsProtocol.uri)) {
|
|
39
|
+
domains.push(Replication.permissionDomain(protocolUri));
|
|
40
|
+
}
|
|
41
|
+
if (!protocolsInScope.has(EncryptionProtocol.uri)) {
|
|
42
|
+
domains.push(Replication.encryptionDomain(protocolUri));
|
|
43
|
+
}
|
|
44
|
+
return domains;
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
public static async deriveStreamId(tenant: string): Promise<string> {
|
|
28
48
|
const bytes = new TextEncoder().encode(tenant);
|
|
29
49
|
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
|
|
@@ -39,11 +59,13 @@ export class Replication {
|
|
|
39
59
|
if (typeof protocol === 'string') {
|
|
40
60
|
scopes.push(Replication.protocolDomain(protocol));
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
62
|
+
const indexedTaggedProtocol = indexes['tag.protocol'];
|
|
63
|
+
const taggedProtocol = indexedTaggedProtocol ?? descriptor.tags?.protocol;
|
|
64
|
+
if (typeof taggedProtocol === 'string') {
|
|
65
|
+
if (protocol === PermissionsProtocol.uri) {
|
|
46
66
|
scopes.push(Replication.permissionDomain(taggedProtocol));
|
|
67
|
+
} else if (protocol === EncryptionProtocol.uri) {
|
|
68
|
+
scopes.push(Replication.encryptionDomain(taggedProtocol));
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
}
|
|
@@ -78,6 +100,10 @@ export class Replication {
|
|
|
78
100
|
);
|
|
79
101
|
}
|
|
80
102
|
|
|
103
|
+
private static isCoreProtocolUri(protocolUri: string): boolean {
|
|
104
|
+
return protocolUri === PermissionsProtocol.uri || protocolUri === EncryptionProtocol.uri;
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
public static async hashMessageCid(messageCid: string): Promise<Uint8Array> {
|
|
82
108
|
const bytes = new TextEncoder().encode(messageCid);
|
|
83
109
|
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"protocol": "http://chat-protocol.xyz",
|
|
3
|
-
"published": true,
|
|
4
|
-
"types": {
|
|
5
|
-
"thread": {
|
|
6
|
-
"schema": "thread",
|
|
7
|
-
"dataFormats": [
|
|
8
|
-
"application/json"
|
|
9
|
-
]
|
|
10
|
-
},
|
|
11
|
-
"message": {
|
|
12
|
-
"schema": "message",
|
|
13
|
-
"dataFormats": [
|
|
14
|
-
"application/json"
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"structure": {
|
|
19
|
-
"thread": {
|
|
20
|
-
"$actions": [
|
|
21
|
-
{
|
|
22
|
-
"who": "anyone",
|
|
23
|
-
"can": [
|
|
24
|
-
"create",
|
|
25
|
-
"update"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"who": "author",
|
|
30
|
-
"of": "thread",
|
|
31
|
-
"can": [
|
|
32
|
-
"read"
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"who": "recipient",
|
|
37
|
-
"of": "thread",
|
|
38
|
-
"can": [
|
|
39
|
-
"read"
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
],
|
|
43
|
-
"message": {
|
|
44
|
-
"$actions": [
|
|
45
|
-
{
|
|
46
|
-
"who": "anyone",
|
|
47
|
-
"can": [
|
|
48
|
-
"create",
|
|
49
|
-
"update"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"who": "author",
|
|
54
|
-
"of": "thread/message",
|
|
55
|
-
"can": [
|
|
56
|
-
"read"
|
|
57
|
-
]
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"who": "recipient",
|
|
61
|
-
"of": "thread/message",
|
|
62
|
-
"can": [
|
|
63
|
-
"read"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|