@enbox/dwn-sdk-js 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/browser.mjs +19 -78
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +1997 -1517
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/constants.js +50 -0
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +28 -11
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/encryption-control.js +785 -0
- package/dist/esm/src/core/encryption-control.js.map +1 -0
- package/dist/esm/src/core/message.js +6 -0
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +36 -5
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-action.js +3 -12
- package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-validation.js +31 -30
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +6 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/recording-validation-state-reader.js +3 -3
- package/dist/esm/src/core/recording-validation-state-reader.js.map +1 -1
- package/dist/esm/src/core/replication-apply.js +62 -108
- package/dist/esm/src/core/replication-apply.js.map +1 -1
- package/dist/esm/src/core/validation-state-reader.js +5 -6
- package/dist/esm/src/core/validation-state-reader.js.map +1 -1
- package/dist/esm/src/dwn.js +1 -2
- package/dist/esm/src/dwn.js.map +1 -1
- package/dist/esm/src/event-stream/event-emitter-wake-publisher.js +11 -11
- package/dist/esm/src/event-stream/event-emitter-wake-publisher.js.map +1 -1
- package/dist/esm/src/handlers/messages-query.js +6 -16
- package/dist/esm/src/handlers/messages-query.js.map +1 -1
- package/dist/esm/src/handlers/messages-read.js +4 -3
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +26 -30
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +6 -4
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +85 -10
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +11 -0
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +140 -18
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +20 -4
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +127 -11
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +15 -0
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +7 -10
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +66 -9
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +43 -36
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/jose/algorithms/signing/ed25519.js +5 -5
- package/dist/esm/src/jose/algorithms/signing/ed25519.js.map +1 -1
- package/dist/esm/src/jose/jws/general/verifier.js +1 -1
- package/dist/esm/src/jose/jws/general/verifier.js.map +1 -1
- package/dist/esm/src/protocols/encryption.js +32 -274
- package/dist/esm/src/protocols/encryption.js.map +1 -1
- package/dist/esm/src/store/blockstore-level.js +14 -23
- package/dist/esm/src/store/blockstore-level.js.map +1 -1
- package/dist/esm/src/store/blockstore-mock.js +10 -27
- package/dist/esm/src/store/blockstore-mock.js.map +1 -1
- package/dist/esm/src/store/blockstore-utils.js +45 -0
- package/dist/esm/src/store/blockstore-utils.js.map +1 -0
- package/dist/esm/src/store/blockstore.js +2 -0
- package/dist/esm/src/store/blockstore.js.map +1 -0
- package/dist/esm/src/store/data-store-level.js +4 -12
- package/dist/esm/src/store/data-store-level.js.map +1 -1
- package/dist/esm/src/store/level.js +7 -0
- package/dist/esm/src/store/level.js.map +1 -0
- package/dist/esm/src/types/encryption-types.js +4 -1
- package/dist/esm/src/types/encryption-types.js.map +1 -1
- package/dist/esm/src/utils/data-stream.js +25 -0
- package/dist/esm/src/utils/data-stream.js.map +1 -1
- package/dist/esm/src/utils/encryption.js +107 -19
- package/dist/esm/src/utils/encryption.js.map +1 -1
- package/dist/esm/src/utils/grant-key-coverage.js +180 -0
- package/dist/esm/src/utils/grant-key-coverage.js.map +1 -0
- package/dist/esm/src/utils/hd-key.js +1 -2
- package/dist/esm/src/utils/hd-key.js.map +1 -1
- package/dist/esm/src/utils/jws.js +7 -1
- package/dist/esm/src/utils/jws.js.map +1 -1
- package/dist/esm/src/utils/memory-cache.js +29 -8
- package/dist/esm/src/utils/memory-cache.js.map +1 -1
- package/dist/esm/src/utils/protocols.js +27 -0
- package/dist/esm/src/utils/protocols.js.map +1 -1
- package/dist/esm/src/utils/record-limit-occupancy.js +23 -3
- package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -1
- package/dist/esm/src/utils/records.js +29 -0
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/src/utils/secp256k1.js +12 -11
- package/dist/esm/src/utils/secp256k1.js.map +1 -1
- package/dist/esm/src/utils/secp256r1.js +12 -20
- package/dist/esm/src/utils/secp256r1.js.map +1 -1
- package/dist/esm/src/utils/time.js +103 -25
- package/dist/esm/src/utils/time.js.map +1 -1
- package/dist/esm/tests/core/constants.spec.js +35 -0
- package/dist/esm/tests/core/constants.spec.js.map +1 -0
- package/dist/esm/tests/core/encryption-control.spec.js +100 -0
- package/dist/esm/tests/core/encryption-control.spec.js.map +1 -0
- package/dist/esm/tests/core/message.spec.js +35 -0
- package/dist/esm/tests/core/message.spec.js.map +1 -1
- package/dist/esm/tests/core/protocol-authorization.spec.js +1 -1
- package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/replication-apply.spec.js +157 -74
- package/dist/esm/tests/core/replication-apply.spec.js.map +1 -1
- package/dist/esm/tests/core/replication-replay-property.spec.js +2 -1
- package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
- package/dist/esm/tests/core/validation-read-closure.spec.js +14 -28
- package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
- package/dist/esm/tests/core/validation-state-reader.spec.js +19 -23
- package/dist/esm/tests/core/validation-state-reader.spec.js.map +1 -1
- package/dist/esm/tests/dwn.spec.js +2 -1
- package/dist/esm/tests/dwn.spec.js.map +1 -1
- package/dist/esm/tests/event-emitter-wake-publisher.spec.js +51 -0
- package/dist/esm/tests/event-emitter-wake-publisher.spec.js.map +1 -0
- package/dist/esm/tests/features/protocol-composition.spec.js +1 -1
- package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/encryption.fuzz.spec.js +9 -1
- package/dist/esm/tests/fuzz/encryption.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/process-message.fuzz.spec.js +1 -1
- package/dist/esm/tests/fuzz/process-message.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/protocols-utils.fuzz.spec.js +29 -1
- package/dist/esm/tests/fuzz/protocols-utils.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/secp256k1.fuzz.spec.js +2 -2
- package/dist/esm/tests/fuzz/secp256k1.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/time.fuzz.spec.js +7 -1
- package/dist/esm/tests/fuzz/time.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-query.spec.js +123 -11
- package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +95 -0
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +90 -0
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +66 -0
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +370 -0
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-delete.spec.js +70 -1
- package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +562 -1
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-read.spec.js +140 -2
- package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +241 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +1187 -29
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +290 -2
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +47 -5
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/protocols/encryption.spec.js +116 -472
- package/dist/esm/tests/protocols/encryption.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +13 -27
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
- package/dist/esm/tests/store/blockstore-level.spec.js +33 -10
- package/dist/esm/tests/store/blockstore-level.spec.js.map +1 -1
- package/dist/esm/tests/store/blockstore-mock.spec.js +31 -7
- package/dist/esm/tests/store/blockstore-mock.spec.js.map +1 -1
- package/dist/esm/tests/store/blockstore-utils.spec.js +115 -0
- package/dist/esm/tests/store/blockstore-utils.spec.js.map +1 -0
- package/dist/esm/tests/store/index-level.spec.js +3 -3
- package/dist/esm/tests/store/index-level.spec.js.map +1 -1
- package/dist/esm/tests/test-stores.js +2 -1
- package/dist/esm/tests/test-stores.js.map +1 -1
- package/dist/esm/tests/utils/cid.spec.js +15 -0
- package/dist/esm/tests/utils/cid.spec.js.map +1 -1
- package/dist/esm/tests/utils/data-stream.spec.js +30 -0
- package/dist/esm/tests/utils/data-stream.spec.js.map +1 -1
- package/dist/esm/tests/utils/encryption-control-test-utils.js +111 -0
- package/dist/esm/tests/utils/encryption-control-test-utils.js.map +1 -0
- package/dist/esm/tests/utils/encryption.spec.js +198 -5
- package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
- package/dist/esm/tests/utils/grant-key-coverage.spec.js +262 -0
- package/dist/esm/tests/utils/grant-key-coverage.spec.js.map +1 -0
- package/dist/esm/tests/utils/memory-cache.spec.js +12 -0
- package/dist/esm/tests/utils/memory-cache.spec.js.map +1 -1
- package/dist/esm/tests/utils/secp256r1.spec.js +2 -2
- package/dist/esm/tests/utils/secp256r1.spec.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js +2 -1
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/esm/tests/utils/test-validation-state-reader.js +1 -1
- package/dist/esm/tests/utils/test-validation-state-reader.js.map +1 -1
- package/dist/esm/tests/utils/time.spec.js +26 -0
- package/dist/esm/tests/utils/time.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/encryption/audience.spec.js +45 -0
- package/dist/esm/tests/validation/json-schemas/encryption/audience.spec.js.map +1 -0
- package/dist/esm/tests/validation/json-schemas/encryption/delivery.spec.js +47 -0
- package/dist/esm/tests/validation/json-schemas/encryption/delivery.spec.js.map +1 -0
- package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js +152 -0
- package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +46 -1
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts +4 -4
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +39 -0
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +28 -11
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/encryption-control.d.ts +128 -0
- package/dist/types/src/core/encryption-control.d.ts.map +1 -0
- package/dist/types/src/core/message.d.ts +4 -0
- package/dist/types/src/core/message.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +11 -0
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/recording-validation-state-reader.d.ts +2 -3
- package/dist/types/src/core/recording-validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/core/replication-apply.d.ts +10 -1
- package/dist/types/src/core/replication-apply.d.ts.map +1 -1
- package/dist/types/src/core/validation-state-reader.d.ts +2 -3
- package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/dwn.d.ts.map +1 -1
- package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts +1 -1
- package/dist/types/src/event-stream/event-emitter-wake-publisher.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-query.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +1 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +2 -0
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +3 -0
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +2 -0
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +10 -17
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts +13 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts +5 -1
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/jose/algorithms/signing/ed25519.d.ts.map +1 -1
- package/dist/types/src/protocols/encryption.d.ts +2 -19
- package/dist/types/src/protocols/encryption.d.ts.map +1 -1
- package/dist/types/src/store/blockstore-level.d.ts +11 -11
- package/dist/types/src/store/blockstore-level.d.ts.map +1 -1
- package/dist/types/src/store/blockstore-mock.d.ts +12 -12
- package/dist/types/src/store/blockstore-mock.d.ts.map +1 -1
- package/dist/types/src/store/blockstore-utils.d.ts +23 -0
- package/dist/types/src/store/blockstore-utils.d.ts.map +1 -0
- package/dist/types/src/store/blockstore.d.ts +3 -0
- package/dist/types/src/store/blockstore.d.ts.map +1 -0
- package/dist/types/src/store/data-store-level.d.ts.map +1 -1
- package/dist/types/src/store/level.d.ts +13 -0
- package/dist/types/src/store/level.d.ts.map +1 -0
- package/dist/types/src/types/cache.d.ts +2 -2
- package/dist/types/src/types/cache.d.ts.map +1 -1
- package/dist/types/src/types/encryption-types.d.ts +31 -2
- package/dist/types/src/types/encryption-types.d.ts.map +1 -1
- package/dist/types/src/types/validation-state-reader.d.ts +3 -5
- package/dist/types/src/types/validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/utils/data-stream.d.ts +5 -0
- package/dist/types/src/utils/data-stream.d.ts.map +1 -1
- package/dist/types/src/utils/encryption.d.ts +53 -24
- package/dist/types/src/utils/encryption.d.ts.map +1 -1
- package/dist/types/src/utils/grant-key-coverage.d.ts +36 -0
- package/dist/types/src/utils/grant-key-coverage.d.ts.map +1 -0
- package/dist/types/src/utils/hd-key.d.ts.map +1 -1
- package/dist/types/src/utils/jws.d.ts.map +1 -1
- package/dist/types/src/utils/memory-cache.d.ts +4 -2
- package/dist/types/src/utils/memory-cache.d.ts.map +1 -1
- package/dist/types/src/utils/protocols.d.ts +11 -0
- package/dist/types/src/utils/protocols.d.ts.map +1 -1
- package/dist/types/src/utils/record-limit-occupancy.d.ts +7 -0
- package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +2 -0
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/src/utils/secp256k1.d.ts.map +1 -1
- package/dist/types/src/utils/secp256r1.d.ts.map +1 -1
- package/dist/types/src/utils/time.d.ts +21 -13
- package/dist/types/src/utils/time.d.ts.map +1 -1
- package/dist/types/tests/core/constants.spec.d.ts +2 -0
- package/dist/types/tests/core/constants.spec.d.ts.map +1 -0
- package/dist/types/tests/core/encryption-control.spec.d.ts +2 -0
- package/dist/types/tests/core/encryption-control.spec.d.ts.map +1 -0
- package/dist/types/tests/dwn.spec.d.ts.map +1 -1
- package/dist/types/tests/event-emitter-wake-publisher.spec.d.ts +2 -0
- package/dist/types/tests/event-emitter-wake-publisher.spec.d.ts.map +1 -0
- package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-delete.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-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-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/end-to-end-tests.spec.d.ts.map +1 -1
- package/dist/types/tests/store/blockstore-utils.spec.d.ts +2 -0
- package/dist/types/tests/store/blockstore-utils.spec.d.ts.map +1 -0
- package/dist/types/tests/test-stores.d.ts.map +1 -1
- package/dist/types/tests/utils/encryption-control-test-utils.d.ts +46 -0
- package/dist/types/tests/utils/encryption-control-test-utils.d.ts.map +1 -0
- package/dist/types/tests/utils/grant-key-coverage.spec.d.ts +2 -0
- package/dist/types/tests/utils/grant-key-coverage.spec.d.ts.map +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/dist/types/tests/validation/json-schemas/encryption/audience.spec.d.ts +2 -0
- package/dist/types/tests/validation/json-schemas/encryption/audience.spec.d.ts.map +1 -0
- package/dist/types/tests/validation/json-schemas/encryption/delivery.spec.d.ts +2 -0
- package/dist/types/tests/validation/json-schemas/encryption/delivery.spec.d.ts.map +1 -0
- package/package.json +47 -28
- package/src/core/constants.ts +61 -0
- package/src/core/dwn-error.ts +28 -11
- package/src/core/encryption-control.ts +1249 -0
- package/src/core/message.ts +7 -0
- package/src/core/messages-grant-authorization.ts +60 -9
- package/src/core/protocol-authorization-action.ts +3 -13
- package/src/core/protocol-authorization-validation.ts +66 -46
- package/src/core/protocol-authorization.ts +7 -0
- package/src/core/recording-validation-state-reader.ts +4 -5
- package/src/core/replication-apply.ts +77 -133
- package/src/core/validation-state-reader.ts +6 -8
- package/src/dwn.ts +1 -2
- package/src/event-stream/event-emitter-wake-publisher.ts +10 -14
- package/src/handlers/messages-query.ts +6 -22
- package/src/handlers/messages-read.ts +4 -3
- package/src/handlers/messages-subscribe.ts +34 -30
- package/src/handlers/protocols-configure.ts +7 -4
- package/src/handlers/records-count.ts +111 -10
- package/src/handlers/records-delete.ts +15 -0
- package/src/handlers/records-query.ts +176 -20
- package/src/handlers/records-read.ts +19 -6
- package/src/handlers/records-subscribe.ts +158 -8
- package/src/handlers/records-write.ts +19 -0
- package/src/index.ts +48 -21
- package/src/interfaces/protocols-configure.ts +102 -11
- package/src/interfaces/records-write.ts +49 -43
- package/src/jose/algorithms/signing/ed25519.ts +7 -6
- package/src/jose/jws/general/verifier.ts +3 -3
- package/src/protocols/encryption.ts +50 -420
- package/src/store/blockstore-level.ts +33 -37
- package/src/store/blockstore-mock.ts +24 -38
- package/src/store/blockstore-utils.ts +85 -0
- package/src/store/blockstore.ts +8 -0
- package/src/store/data-store-level.ts +5 -12
- package/src/store/level.ts +19 -0
- package/src/types/cache.ts +3 -3
- package/src/types/encryption-types.ts +39 -2
- package/src/types/validation-state-reader.ts +3 -5
- package/src/utils/data-stream.ts +29 -1
- package/src/utils/encryption.ts +190 -60
- package/src/utils/grant-key-coverage.ts +303 -0
- package/src/utils/hd-key.ts +1 -2
- package/src/utils/jws.ts +8 -2
- package/src/utils/memory-cache.ts +42 -11
- package/src/utils/protocols.ts +32 -0
- package/src/utils/record-limit-occupancy.ts +34 -5
- package/src/utils/records.ts +36 -2
- package/src/utils/secp256k1.ts +12 -12
- package/src/utils/secp256r1.ts +12 -22
- package/src/utils/time.ts +145 -27
- package/dist/esm/tests/validation/json-schemas/encryption/audience-key.spec.js +0 -40
- package/dist/esm/tests/validation/json-schemas/encryption/audience-key.spec.js.map +0 -1
- package/dist/types/tests/validation/json-schemas/encryption/audience-key.spec.d.ts +0 -2
- package/dist/types/tests/validation/json-schemas/encryption/audience-key.spec.d.ts.map +0 -1
- package/src/types/mitt.d.ts +0 -28
|
@@ -19,6 +19,9 @@ import { Cid } from '../../src/utils/cid.js';
|
|
|
19
19
|
import { DataStream } from '../../src/utils/data-stream.js';
|
|
20
20
|
import { Dwn } from '../../src/dwn.js';
|
|
21
21
|
import { Encoder } from '../../src/utils/encoder.js';
|
|
22
|
+
import { ENCRYPTION_CONTROL_AUDIENCE_PATH } from '../../src/core/constants.js';
|
|
23
|
+
import { ENCRYPTION_CONTROL_DELIVERY_PATH } from '../../src/core/constants.js';
|
|
24
|
+
import { EncryptionControlDeliveryRecipientAuthority } from '../../src/types/encryption-types.js';
|
|
22
25
|
import { GeneralJwsBuilder } from '../../src/jose/jws/general/builder.js';
|
|
23
26
|
import { Jws } from '../../src/utils/jws.js';
|
|
24
27
|
import { Message } from '../../src/core/message.js';
|
|
@@ -31,8 +34,9 @@ import { TestEventLog } from '../test-event-stream.js';
|
|
|
31
34
|
import { TestStores } from '../test-stores.js';
|
|
32
35
|
import { TestStubGenerator } from '../utils/test-stub-generator.js';
|
|
33
36
|
import { Time } from '../../src/utils/time.js';
|
|
34
|
-
import { ContentEncryptionAlgorithm, Encryption, ROLE_AUDIENCE_DERIVATION_SCHEME } from '../../src/utils/encryption.js';
|
|
35
|
-
import { CoreProtocolRegistry,
|
|
37
|
+
import { ContentEncryptionAlgorithm, Encryption, KeyAgreementAlgorithm, ROLE_AUDIENCE_DERIVATION_SCHEME } from '../../src/utils/encryption.js';
|
|
38
|
+
import { CoreProtocolRegistry, DwnConstant, DwnInterfaceName, DwnMethodName, KeyDerivationScheme, PermissionsProtocol, Protocols, RecordsDelete, RecordsQuery } from '../../src/index.js';
|
|
39
|
+
import { DataStoreLevel, MessageStoreLevel } from '../../src/store/level.js';
|
|
36
40
|
import { defaultTestProtocolDefinition, TestDataGenerator } from '../utils/test-data-generator.js';
|
|
37
41
|
import { DidKey, UniversalResolver } from '@enbox/dids';
|
|
38
42
|
import { DwnError, DwnErrorCode } from '../../src/core/dwn-error.js';
|
|
@@ -70,6 +74,99 @@ export function testRecordsWriteHandler() {
|
|
|
70
74
|
afterAll(async () => {
|
|
71
75
|
await dwn.close();
|
|
72
76
|
});
|
|
77
|
+
async function installEncryptedProtocol(author, protocolDefinition) {
|
|
78
|
+
const encryptedProtocolDefinition = await Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, author.keyId, author.encryptionKeyPair.privateJwk);
|
|
79
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
80
|
+
author,
|
|
81
|
+
protocolDefinition: encryptedProtocolDefinition,
|
|
82
|
+
});
|
|
83
|
+
expect((await dwn.processMessage(author.did, protocolConfig.message)).status.code).toBe(202);
|
|
84
|
+
return encryptedProtocolDefinition;
|
|
85
|
+
}
|
|
86
|
+
async function createAudienceControlWrite(input) {
|
|
87
|
+
const audiencePublicKeyJwk = input.author.encryptionKeyPair.publicJwk;
|
|
88
|
+
const audienceKeyId = await Encryption.getKeyId(audiencePublicKeyJwk);
|
|
89
|
+
const sealKeyId = input.sealKeyId ?? await Encryption.getKeyId(input.roleRuleSet.$keyAgreement.publicKeyJwk);
|
|
90
|
+
const payload = {
|
|
91
|
+
protocol: input.protocol,
|
|
92
|
+
rolePath: input.rolePath,
|
|
93
|
+
contextId: input.contextId ?? '',
|
|
94
|
+
keyId: audienceKeyId,
|
|
95
|
+
publicKeyJwk: audiencePublicKeyJwk,
|
|
96
|
+
sealedPrivateKey: {
|
|
97
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
98
|
+
derivationScheme: 'seal',
|
|
99
|
+
keyId: sealKeyId,
|
|
100
|
+
ephemeralPublicKey: input.author.encryptionKeyPair.publicJwk,
|
|
101
|
+
encryptedKey: Encoder.bytesToBase64Url(TestDataGenerator.randomBytes(32)),
|
|
102
|
+
},
|
|
103
|
+
...input.payloadOverrides,
|
|
104
|
+
};
|
|
105
|
+
const dataBytes = Encoder.objectToBytes(payload);
|
|
106
|
+
const recordsWrite = await RecordsWrite.create({
|
|
107
|
+
data: dataBytes,
|
|
108
|
+
dataFormat: 'application/json',
|
|
109
|
+
dateCreated: input.dateCreated,
|
|
110
|
+
messageTimestamp: input.dateCreated,
|
|
111
|
+
delegatedGrant: input.delegatedGrant,
|
|
112
|
+
permissionGrantId: input.permissionGrantId,
|
|
113
|
+
protocol: input.protocol,
|
|
114
|
+
protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
|
|
115
|
+
protocolRole: input.protocolRole,
|
|
116
|
+
published: input.published,
|
|
117
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/audience.json',
|
|
118
|
+
signer: Jws.createSigner(input.signer ?? input.author),
|
|
119
|
+
tags: {
|
|
120
|
+
protocol: input.protocol,
|
|
121
|
+
rolePath: input.rolePath,
|
|
122
|
+
contextId: input.contextId ?? '',
|
|
123
|
+
keyId: audienceKeyId,
|
|
124
|
+
...input.tags,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
dataBytes,
|
|
129
|
+
keyId: audienceKeyId,
|
|
130
|
+
payload,
|
|
131
|
+
recordsWrite,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function createDeliveryControlWrite(input) {
|
|
135
|
+
const tags = {
|
|
136
|
+
protocol: input.protocol,
|
|
137
|
+
rolePath: input.rolePath,
|
|
138
|
+
contextId: input.contextId ?? '',
|
|
139
|
+
keyId: input.keyId,
|
|
140
|
+
recipientAuthority: input.recipientAuthority,
|
|
141
|
+
};
|
|
142
|
+
const dataBytes = Encoder.objectToBytes({
|
|
143
|
+
protocol: input.protocol,
|
|
144
|
+
rolePath: input.rolePath,
|
|
145
|
+
contextId: input.contextId ?? '',
|
|
146
|
+
keyId: input.keyId,
|
|
147
|
+
});
|
|
148
|
+
const recordsWrite = await RecordsWrite.create({
|
|
149
|
+
data: dataBytes,
|
|
150
|
+
dataFormat: 'application/json',
|
|
151
|
+
encryptionInput: {
|
|
152
|
+
initializationVector: TestDataGenerator.randomBytes(16),
|
|
153
|
+
key: TestDataGenerator.randomBytes(32),
|
|
154
|
+
keyEncryptionInputs: [{
|
|
155
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
156
|
+
keyId: await Encryption.getKeyId(input.roleRuleSet.$keyAgreement.publicKeyJwk),
|
|
157
|
+
publicKey: input.roleRuleSet.$keyAgreement.publicKeyJwk,
|
|
158
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
159
|
+
}],
|
|
160
|
+
},
|
|
161
|
+
protocol: input.protocol,
|
|
162
|
+
protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
|
|
163
|
+
recipient: input.recipient,
|
|
164
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/delivery.json',
|
|
165
|
+
signer: Jws.createSigner(input.author),
|
|
166
|
+
tags,
|
|
167
|
+
});
|
|
168
|
+
return { dataBytes, recordsWrite };
|
|
169
|
+
}
|
|
73
170
|
it('should dispatch preProcessWrite hook via CoreProtocolRegistry and abort before storage on failure', async () => {
|
|
74
171
|
// Register a mock core protocol whose preProcessWrite hook throws.
|
|
75
172
|
// Verify that authorization completes but processMessageWithDataStream is never reached.
|
|
@@ -98,6 +195,716 @@ export function testRecordsWriteHandler() {
|
|
|
98
195
|
// Cleanup: unregister the mock so it doesn't affect other tests
|
|
99
196
|
coreProtocols._protocols.delete(protocolUri);
|
|
100
197
|
});
|
|
198
|
+
it('should accept tenant-authored audience control records at the reserved source-protocol path', async () => {
|
|
199
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
200
|
+
const protocolDefinition = {
|
|
201
|
+
protocol: 'http://encryption-control-audience-accepted.xyz',
|
|
202
|
+
published: false,
|
|
203
|
+
types: {
|
|
204
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
205
|
+
message: { schema: 'http://message-schema', dataFormats: ['text/plain'] },
|
|
206
|
+
},
|
|
207
|
+
structure: {
|
|
208
|
+
member: { $role: true },
|
|
209
|
+
message: {},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
213
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
214
|
+
const audience = await createAudienceControlWrite({
|
|
215
|
+
author: alice,
|
|
216
|
+
protocol: protocolDefinition.protocol,
|
|
217
|
+
rolePath: 'member',
|
|
218
|
+
roleRuleSet,
|
|
219
|
+
});
|
|
220
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
221
|
+
expect(reply.status.code).toBe(202);
|
|
222
|
+
});
|
|
223
|
+
it('should reject published encryption control records', async () => {
|
|
224
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
225
|
+
const protocolDefinition = {
|
|
226
|
+
protocol: 'http://encryption-control-published-rejected.xyz',
|
|
227
|
+
published: false,
|
|
228
|
+
types: {
|
|
229
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
230
|
+
},
|
|
231
|
+
structure: {
|
|
232
|
+
member: { $role: true },
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
236
|
+
const audience = await createAudienceControlWrite({
|
|
237
|
+
author: alice,
|
|
238
|
+
protocol: protocolDefinition.protocol,
|
|
239
|
+
published: true,
|
|
240
|
+
rolePath: 'member',
|
|
241
|
+
roleRuleSet: encryptedProtocolDefinition.structure.member,
|
|
242
|
+
});
|
|
243
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
244
|
+
expect(reply.status.code).toBe(400);
|
|
245
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateUnexpectedRecord);
|
|
246
|
+
});
|
|
247
|
+
it('should reject dataless and update attempts for immutable audience control records', async () => {
|
|
248
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
249
|
+
const protocolDefinition = {
|
|
250
|
+
protocol: 'http://encryption-control-audience-immutable.xyz',
|
|
251
|
+
published: false,
|
|
252
|
+
types: {
|
|
253
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
254
|
+
},
|
|
255
|
+
structure: {
|
|
256
|
+
member: { $role: true },
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
260
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
261
|
+
const audience = await createAudienceControlWrite({
|
|
262
|
+
author: alice,
|
|
263
|
+
protocol: protocolDefinition.protocol,
|
|
264
|
+
rolePath: 'member',
|
|
265
|
+
roleRuleSet,
|
|
266
|
+
});
|
|
267
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
268
|
+
const updateData = Encoder.objectToBytes({ ...audience.payload, publicKeyJwk: roleRuleSet.$keyAgreement.publicKeyJwk });
|
|
269
|
+
const update = await RecordsWrite.createFrom({
|
|
270
|
+
data: updateData,
|
|
271
|
+
recordsWriteMessage: audience.recordsWrite.message,
|
|
272
|
+
signer: Jws.createSigner(alice),
|
|
273
|
+
});
|
|
274
|
+
const updateReply = await dwn.processMessage(alice.did, update.message, { dataStream: DataStream.fromBytes(updateData) });
|
|
275
|
+
expect(updateReply.status.code).toBe(400);
|
|
276
|
+
expect(updateReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateUnexpectedRecord);
|
|
277
|
+
const datalessAudience = await createAudienceControlWrite({
|
|
278
|
+
author: alice,
|
|
279
|
+
protocol: protocolDefinition.protocol,
|
|
280
|
+
rolePath: 'member',
|
|
281
|
+
roleRuleSet,
|
|
282
|
+
});
|
|
283
|
+
const datalessReply = await dwn.processMessage(alice.did, datalessAudience.recordsWrite.message);
|
|
284
|
+
expect(datalessReply.status.code).toBe(400);
|
|
285
|
+
expect(datalessReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateUnexpectedRecord);
|
|
286
|
+
});
|
|
287
|
+
it('should reject audience control records whose seal keyId does not match the role path key', async () => {
|
|
288
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
289
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
290
|
+
const protocolDefinition = {
|
|
291
|
+
protocol: 'http://encryption-control-audience-seal-mismatch.xyz',
|
|
292
|
+
published: false,
|
|
293
|
+
types: {
|
|
294
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
295
|
+
},
|
|
296
|
+
structure: {
|
|
297
|
+
member: { $role: true },
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
301
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
302
|
+
const audience = await createAudienceControlWrite({
|
|
303
|
+
author: alice,
|
|
304
|
+
protocol: protocolDefinition.protocol,
|
|
305
|
+
rolePath: 'member',
|
|
306
|
+
roleRuleSet,
|
|
307
|
+
sealKeyId: await Encryption.getKeyId(bob.encryptionKeyPair.publicJwk),
|
|
308
|
+
});
|
|
309
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
310
|
+
expect(reply.status.code).toBe(400);
|
|
311
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceSealKeyIdMismatch);
|
|
312
|
+
});
|
|
313
|
+
it('should reject malformed audience control tags as bad input before authorization', async () => {
|
|
314
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
315
|
+
const dataBytes = Encoder.objectToBytes({
|
|
316
|
+
protocol: 'http://encryption-control-audience-malformed-tags.xyz',
|
|
317
|
+
rolePath: 'member',
|
|
318
|
+
contextId: '',
|
|
319
|
+
keyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
320
|
+
publicKeyJwk: alice.encryptionKeyPair.publicJwk,
|
|
321
|
+
sealedPrivateKey: {
|
|
322
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
323
|
+
derivationScheme: 'seal',
|
|
324
|
+
keyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
325
|
+
ephemeralPublicKey: alice.encryptionKeyPair.publicJwk,
|
|
326
|
+
encryptedKey: Encoder.bytesToBase64Url(TestDataGenerator.randomBytes(32)),
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
const audience = await RecordsWrite.create({
|
|
330
|
+
data: dataBytes,
|
|
331
|
+
dataFormat: 'application/json',
|
|
332
|
+
protocol: 'http://encryption-control-audience-malformed-tags.xyz',
|
|
333
|
+
protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
|
|
334
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/audience.json',
|
|
335
|
+
signer: Jws.createSigner(alice),
|
|
336
|
+
tags: {
|
|
337
|
+
protocol: 'http://encryption-control-audience-malformed-tags.xyz',
|
|
338
|
+
rolePath: 'member',
|
|
339
|
+
contextId: '',
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const reply = await dwn.processMessage(alice.did, audience.message, { dataStream: DataStream.fromBytes(dataBytes) });
|
|
343
|
+
expect(reply.status.code).toBe(400);
|
|
344
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceMissingRequiredTag);
|
|
345
|
+
});
|
|
346
|
+
it('should reject delivery control records that reference a missing audience', async () => {
|
|
347
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
348
|
+
const protocolDefinition = {
|
|
349
|
+
protocol: 'http://encryption-control-delivery-missing-audience.xyz',
|
|
350
|
+
published: false,
|
|
351
|
+
types: {
|
|
352
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
353
|
+
},
|
|
354
|
+
structure: {
|
|
355
|
+
member: { $role: true },
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
359
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
360
|
+
const rolePublicKey = roleRuleSet.$keyAgreement.publicKeyJwk;
|
|
361
|
+
const missingAudienceKeyId = await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk);
|
|
362
|
+
const dataBytes = Encoder.objectToBytes({ encrypted: true });
|
|
363
|
+
const delivery = await RecordsWrite.create({
|
|
364
|
+
data: dataBytes,
|
|
365
|
+
dataFormat: 'application/json',
|
|
366
|
+
encryptionInput: {
|
|
367
|
+
initializationVector: TestDataGenerator.randomBytes(16),
|
|
368
|
+
key: TestDataGenerator.randomBytes(32),
|
|
369
|
+
keyEncryptionInputs: [{
|
|
370
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
371
|
+
keyId: await Encryption.getKeyId(rolePublicKey),
|
|
372
|
+
publicKey: rolePublicKey,
|
|
373
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
374
|
+
}],
|
|
375
|
+
},
|
|
376
|
+
protocol: protocolDefinition.protocol,
|
|
377
|
+
protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
|
|
378
|
+
recipient: alice.did,
|
|
379
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/delivery.json',
|
|
380
|
+
signer: Jws.createSigner(alice),
|
|
381
|
+
tags: {
|
|
382
|
+
protocol: protocolDefinition.protocol,
|
|
383
|
+
rolePath: 'member',
|
|
384
|
+
contextId: '',
|
|
385
|
+
keyId: missingAudienceKeyId,
|
|
386
|
+
recipientAuthority: 'roleHolder',
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
const reply = await dwn.processMessage(alice.did, delivery.message, { dataStream: DataStream.fromBytes(dataBytes) });
|
|
390
|
+
expect(reply.status.code).toBe(400);
|
|
391
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateDeliveryAudienceMissing);
|
|
392
|
+
});
|
|
393
|
+
it('should reject author-delegated audience control writes when the grant does not cover the role path', async () => {
|
|
394
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
395
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
396
|
+
const protocolDefinition = {
|
|
397
|
+
protocol: 'http://encryption-control-author-delegate-rejected.xyz',
|
|
398
|
+
published: false,
|
|
399
|
+
types: {
|
|
400
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
401
|
+
message: { schema: 'http://message-schema', dataFormats: ['text/plain'] },
|
|
402
|
+
},
|
|
403
|
+
structure: {
|
|
404
|
+
member: { $role: true },
|
|
405
|
+
message: {},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
const encryptedProtocolDefinition = await Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, alice.keyId, alice.encryptionKeyPair.privateJwk);
|
|
409
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
410
|
+
author: alice,
|
|
411
|
+
protocolDefinition: encryptedProtocolDefinition,
|
|
412
|
+
});
|
|
413
|
+
expect((await dwn.processMessage(alice.did, protocolConfig.message)).status.code).toBe(202);
|
|
414
|
+
const grant = await TestDataGenerator.generateGrantCreate({
|
|
415
|
+
author: alice,
|
|
416
|
+
grantedTo: bob,
|
|
417
|
+
delegated: true,
|
|
418
|
+
scope: {
|
|
419
|
+
interface: DwnInterfaceName.Records,
|
|
420
|
+
method: DwnMethodName.Write,
|
|
421
|
+
protocol: protocolDefinition.protocol,
|
|
422
|
+
protocolPath: 'message',
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
expect((await dwn.processMessage(alice.did, grant.message, { dataStream: grant.dataStream })).status.code).toBe(202);
|
|
426
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
427
|
+
const audiencePublicKeyJwk = alice.encryptionKeyPair.publicJwk;
|
|
428
|
+
const audienceKeyId = await Encryption.getKeyId(audiencePublicKeyJwk);
|
|
429
|
+
const sealKeyId = await Encryption.getKeyId(roleRuleSet.$keyAgreement.publicKeyJwk);
|
|
430
|
+
const dataBytes = Encoder.objectToBytes({
|
|
431
|
+
protocol: protocolDefinition.protocol,
|
|
432
|
+
rolePath: 'member',
|
|
433
|
+
contextId: '',
|
|
434
|
+
keyId: audienceKeyId,
|
|
435
|
+
publicKeyJwk: audiencePublicKeyJwk,
|
|
436
|
+
sealedPrivateKey: {
|
|
437
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
438
|
+
derivationScheme: 'seal',
|
|
439
|
+
keyId: sealKeyId,
|
|
440
|
+
ephemeralPublicKey: alice.encryptionKeyPair.publicJwk,
|
|
441
|
+
encryptedKey: Encoder.bytesToBase64Url(TestDataGenerator.randomBytes(32)),
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
const audience = await RecordsWrite.create({
|
|
445
|
+
data: dataBytes,
|
|
446
|
+
dataFormat: 'application/json',
|
|
447
|
+
delegatedGrant: grant.dataEncodedMessage,
|
|
448
|
+
protocol: protocolDefinition.protocol,
|
|
449
|
+
protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
|
|
450
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/audience.json',
|
|
451
|
+
signer: Jws.createSigner(bob),
|
|
452
|
+
tags: {
|
|
453
|
+
protocol: protocolDefinition.protocol,
|
|
454
|
+
rolePath: 'member',
|
|
455
|
+
contextId: '',
|
|
456
|
+
keyId: audienceKeyId,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
const reply = await dwn.processMessage(alice.did, audience.message, { dataStream: DataStream.fromBytes(dataBytes) });
|
|
460
|
+
expect(reply.status.code).toBe(401);
|
|
461
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceWriterUnauthorized);
|
|
462
|
+
});
|
|
463
|
+
it('should accept audience control records from authorized non-tenant role creators', async () => {
|
|
464
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
465
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
466
|
+
const carol = await TestDataGenerator.generateDidKeyPersona();
|
|
467
|
+
const anyoneProtocol = {
|
|
468
|
+
protocol: 'http://encryption-control-audience-anyone-writer.xyz',
|
|
469
|
+
published: false,
|
|
470
|
+
types: {
|
|
471
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
472
|
+
},
|
|
473
|
+
structure: {
|
|
474
|
+
member: {
|
|
475
|
+
$role: true,
|
|
476
|
+
$actions: [{ who: 'anyone', can: ['create'] }],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
const encryptedAnyoneProtocol = await installEncryptedProtocol(alice, anyoneProtocol);
|
|
481
|
+
const anyoneRoleRuleSet = encryptedAnyoneProtocol.structure.member;
|
|
482
|
+
const anyoneAudience = await createAudienceControlWrite({
|
|
483
|
+
author: bob,
|
|
484
|
+
protocol: anyoneProtocol.protocol,
|
|
485
|
+
rolePath: 'member',
|
|
486
|
+
roleRuleSet: anyoneRoleRuleSet,
|
|
487
|
+
});
|
|
488
|
+
expect((await dwn.processMessage(alice.did, anyoneAudience.recordsWrite.message, {
|
|
489
|
+
dataStream: DataStream.fromBytes(anyoneAudience.dataBytes),
|
|
490
|
+
})).status.code).toBe(202);
|
|
491
|
+
const roleProtocol = {
|
|
492
|
+
protocol: 'http://encryption-control-audience-role-writer.xyz',
|
|
493
|
+
published: false,
|
|
494
|
+
types: {
|
|
495
|
+
admin: { schema: 'http://admin-schema', dataFormats: ['application/json'] },
|
|
496
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
497
|
+
},
|
|
498
|
+
structure: {
|
|
499
|
+
admin: { $role: true },
|
|
500
|
+
member: {
|
|
501
|
+
$role: true,
|
|
502
|
+
$actions: [{ role: 'admin', can: ['create'] }],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
const encryptedRoleProtocol = await installEncryptedProtocol(alice, roleProtocol);
|
|
507
|
+
const memberRoleRuleSet = encryptedRoleProtocol.structure.member;
|
|
508
|
+
const adminRole = await TestDataGenerator.generateRecordsWrite({
|
|
509
|
+
author: alice,
|
|
510
|
+
data: Encoder.stringToBytes('bob is an admin'),
|
|
511
|
+
dataFormat: 'application/json',
|
|
512
|
+
protocol: roleProtocol.protocol,
|
|
513
|
+
protocolPath: 'admin',
|
|
514
|
+
recipient: bob.did,
|
|
515
|
+
schema: 'http://admin-schema',
|
|
516
|
+
});
|
|
517
|
+
expect((await dwn.processMessage(alice.did, adminRole.message, { dataStream: adminRole.dataStream })).status.code).toBe(202);
|
|
518
|
+
const roleAudience = await createAudienceControlWrite({
|
|
519
|
+
author: bob,
|
|
520
|
+
protocol: roleProtocol.protocol,
|
|
521
|
+
protocolRole: 'admin',
|
|
522
|
+
rolePath: 'member',
|
|
523
|
+
roleRuleSet: memberRoleRuleSet,
|
|
524
|
+
});
|
|
525
|
+
expect((await dwn.processMessage(alice.did, roleAudience.recordsWrite.message, {
|
|
526
|
+
dataStream: DataStream.fromBytes(roleAudience.dataBytes),
|
|
527
|
+
})).status.code).toBe(202);
|
|
528
|
+
const grantProtocol = {
|
|
529
|
+
protocol: 'http://encryption-control-audience-delegated-writer.xyz',
|
|
530
|
+
published: false,
|
|
531
|
+
types: {
|
|
532
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
533
|
+
},
|
|
534
|
+
structure: {
|
|
535
|
+
member: { $role: true },
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
const encryptedGrantProtocol = await installEncryptedProtocol(alice, grantProtocol);
|
|
539
|
+
const grantRoleRuleSet = encryptedGrantProtocol.structure.member;
|
|
540
|
+
const grant = await TestDataGenerator.generateGrantCreate({
|
|
541
|
+
author: alice,
|
|
542
|
+
grantedTo: carol,
|
|
543
|
+
delegated: true,
|
|
544
|
+
scope: {
|
|
545
|
+
interface: DwnInterfaceName.Records,
|
|
546
|
+
method: DwnMethodName.Write,
|
|
547
|
+
protocol: grantProtocol.protocol,
|
|
548
|
+
protocolPath: 'member',
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
expect((await dwn.processMessage(alice.did, grant.message, { dataStream: grant.dataStream })).status.code).toBe(202);
|
|
552
|
+
const delegatedAudience = await createAudienceControlWrite({
|
|
553
|
+
author: alice,
|
|
554
|
+
delegatedGrant: grant.dataEncodedMessage,
|
|
555
|
+
protocol: grantProtocol.protocol,
|
|
556
|
+
rolePath: 'member',
|
|
557
|
+
roleRuleSet: grantRoleRuleSet,
|
|
558
|
+
signer: carol,
|
|
559
|
+
});
|
|
560
|
+
expect((await dwn.processMessage(alice.did, delegatedAudience.recordsWrite.message, {
|
|
561
|
+
dataStream: DataStream.fromBytes(delegatedAudience.dataBytes),
|
|
562
|
+
})).status.code).toBe(202);
|
|
563
|
+
const directGrant = await TestDataGenerator.generateGrantCreate({
|
|
564
|
+
author: alice,
|
|
565
|
+
grantedTo: bob,
|
|
566
|
+
scope: {
|
|
567
|
+
interface: DwnInterfaceName.Records,
|
|
568
|
+
method: DwnMethodName.Write,
|
|
569
|
+
protocol: grantProtocol.protocol,
|
|
570
|
+
protocolPath: 'member',
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
expect((await dwn.processMessage(alice.did, directGrant.message, { dataStream: directGrant.dataStream })).status.code).toBe(202);
|
|
574
|
+
const directGrantAudience = await createAudienceControlWrite({
|
|
575
|
+
author: bob,
|
|
576
|
+
permissionGrantId: directGrant.message.recordId,
|
|
577
|
+
protocol: grantProtocol.protocol,
|
|
578
|
+
rolePath: 'member',
|
|
579
|
+
roleRuleSet: grantRoleRuleSet,
|
|
580
|
+
});
|
|
581
|
+
expect((await dwn.processMessage(alice.did, directGrantAudience.recordsWrite.message, {
|
|
582
|
+
dataStream: DataStream.fromBytes(directGrantAudience.dataBytes),
|
|
583
|
+
})).status.code).toBe(202);
|
|
584
|
+
});
|
|
585
|
+
it('should enforce publication conditions on delegated audience control writes', async () => {
|
|
586
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
587
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
588
|
+
const protocolDefinition = {
|
|
589
|
+
protocol: 'http://encryption-control-delegated-publication-condition.xyz',
|
|
590
|
+
published: false,
|
|
591
|
+
types: {
|
|
592
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
593
|
+
},
|
|
594
|
+
structure: {
|
|
595
|
+
member: { $role: true },
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
599
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
600
|
+
const grant = await TestDataGenerator.generateGrantCreate({
|
|
601
|
+
author: alice,
|
|
602
|
+
grantedTo: bob,
|
|
603
|
+
delegated: true,
|
|
604
|
+
scope: {
|
|
605
|
+
interface: DwnInterfaceName.Records,
|
|
606
|
+
method: DwnMethodName.Write,
|
|
607
|
+
protocol: protocolDefinition.protocol,
|
|
608
|
+
protocolPath: 'member',
|
|
609
|
+
},
|
|
610
|
+
conditions: {
|
|
611
|
+
publication: PermissionConditionPublication.Required,
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
expect((await dwn.processMessage(alice.did, grant.message, { dataStream: grant.dataStream })).status.code).toBe(202);
|
|
615
|
+
const audience = await createAudienceControlWrite({
|
|
616
|
+
author: alice,
|
|
617
|
+
delegatedGrant: grant.dataEncodedMessage,
|
|
618
|
+
protocol: protocolDefinition.protocol,
|
|
619
|
+
published: false,
|
|
620
|
+
rolePath: 'member',
|
|
621
|
+
roleRuleSet,
|
|
622
|
+
signer: bob,
|
|
623
|
+
});
|
|
624
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
625
|
+
expect(reply.status.code).toBe(401);
|
|
626
|
+
expect(reply.status.detail).toContain(DwnErrorCode.RecordsGrantAuthorizationConditionPublicationRequired);
|
|
627
|
+
});
|
|
628
|
+
it('should reject malformed audience control records with typed validation errors', async () => {
|
|
629
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
630
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
631
|
+
const protocolDefinition = {
|
|
632
|
+
protocol: 'http://encryption-control-audience-malformed-payload.xyz',
|
|
633
|
+
published: false,
|
|
634
|
+
types: {
|
|
635
|
+
chat: { schema: 'http://chat-schema', dataFormats: ['application/json'] },
|
|
636
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
637
|
+
},
|
|
638
|
+
structure: {
|
|
639
|
+
chat: {
|
|
640
|
+
member: { $role: true },
|
|
641
|
+
},
|
|
642
|
+
member: { $role: true },
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
646
|
+
const rootRoleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
647
|
+
const nestedRoleRuleSet = encryptedProtocolDefinition.structure.chat.member;
|
|
648
|
+
const payloadMismatch = await createAudienceControlWrite({
|
|
649
|
+
author: alice,
|
|
650
|
+
payloadOverrides: { rolePath: 'other' },
|
|
651
|
+
protocol: protocolDefinition.protocol,
|
|
652
|
+
rolePath: 'member',
|
|
653
|
+
roleRuleSet: rootRoleRuleSet,
|
|
654
|
+
});
|
|
655
|
+
const payloadMismatchReply = await dwn.processMessage(alice.did, payloadMismatch.recordsWrite.message, { dataStream: DataStream.fromBytes(payloadMismatch.dataBytes) });
|
|
656
|
+
expect(payloadMismatchReply.status.code).toBe(400);
|
|
657
|
+
expect(payloadMismatchReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceTagsMismatch);
|
|
658
|
+
const keyMismatch = await createAudienceControlWrite({
|
|
659
|
+
author: alice,
|
|
660
|
+
payloadOverrides: { publicKeyJwk: bob.encryptionKeyPair.publicJwk },
|
|
661
|
+
protocol: protocolDefinition.protocol,
|
|
662
|
+
rolePath: 'member',
|
|
663
|
+
roleRuleSet: rootRoleRuleSet,
|
|
664
|
+
});
|
|
665
|
+
const keyMismatchReply = await dwn.processMessage(alice.did, keyMismatch.recordsWrite.message, { dataStream: DataStream.fromBytes(keyMismatch.dataBytes) });
|
|
666
|
+
expect(keyMismatchReply.status.code).toBe(400);
|
|
667
|
+
expect(keyMismatchReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceKeyIdMismatch);
|
|
668
|
+
const rootWithContext = await createAudienceControlWrite({
|
|
669
|
+
author: alice,
|
|
670
|
+
contextId: 'root-context',
|
|
671
|
+
protocol: protocolDefinition.protocol,
|
|
672
|
+
rolePath: 'member',
|
|
673
|
+
roleRuleSet: rootRoleRuleSet,
|
|
674
|
+
});
|
|
675
|
+
const rootWithContextReply = await dwn.processMessage(alice.did, rootWithContext.recordsWrite.message, { dataStream: DataStream.fromBytes(rootWithContext.dataBytes) });
|
|
676
|
+
expect(rootWithContextReply.status.code).toBe(400);
|
|
677
|
+
expect(rootWithContextReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceContextIdInvalid);
|
|
678
|
+
const nestedWithoutContext = await createAudienceControlWrite({
|
|
679
|
+
author: alice,
|
|
680
|
+
protocol: protocolDefinition.protocol,
|
|
681
|
+
rolePath: 'chat/member',
|
|
682
|
+
roleRuleSet: nestedRoleRuleSet,
|
|
683
|
+
});
|
|
684
|
+
const nestedWithoutContextReply = await dwn.processMessage(alice.did, nestedWithoutContext.recordsWrite.message, { dataStream: DataStream.fromBytes(nestedWithoutContext.dataBytes) });
|
|
685
|
+
expect(nestedWithoutContextReply.status.code).toBe(400);
|
|
686
|
+
expect(nestedWithoutContextReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceContextIdInvalid);
|
|
687
|
+
const protocolTagMismatch = await createAudienceControlWrite({
|
|
688
|
+
author: alice,
|
|
689
|
+
protocol: protocolDefinition.protocol,
|
|
690
|
+
rolePath: 'member',
|
|
691
|
+
roleRuleSet: rootRoleRuleSet,
|
|
692
|
+
tags: { protocol: 'http://mismatched-control-tag.xyz' },
|
|
693
|
+
});
|
|
694
|
+
const protocolTagMismatchReply = await dwn.processMessage(alice.did, protocolTagMismatch.recordsWrite.message, { dataStream: DataStream.fromBytes(protocolTagMismatch.dataBytes) });
|
|
695
|
+
expect(protocolTagMismatchReply.status.code).toBe(400);
|
|
696
|
+
expect(protocolTagMismatchReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceTagsMismatch);
|
|
697
|
+
});
|
|
698
|
+
it('should reject audience control records for role paths without key agreement material', async () => {
|
|
699
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
700
|
+
const protocolDefinition = {
|
|
701
|
+
protocol: 'http://encryption-control-role-without-keyagreement.xyz',
|
|
702
|
+
published: false,
|
|
703
|
+
types: {
|
|
704
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
705
|
+
},
|
|
706
|
+
structure: {
|
|
707
|
+
member: { $role: true },
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
711
|
+
author: alice,
|
|
712
|
+
protocolDefinition,
|
|
713
|
+
});
|
|
714
|
+
expect((await dwn.processMessage(alice.did, protocolConfig.message)).status.code).toBe(202);
|
|
715
|
+
const audience = await createAudienceControlWrite({
|
|
716
|
+
author: alice,
|
|
717
|
+
protocol: protocolDefinition.protocol,
|
|
718
|
+
rolePath: 'member',
|
|
719
|
+
roleRuleSet: protocolDefinition.structure.member,
|
|
720
|
+
sealKeyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
721
|
+
});
|
|
722
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
723
|
+
expect(reply.status.code).toBe(400);
|
|
724
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateAudienceRolePathInvalid);
|
|
725
|
+
});
|
|
726
|
+
it('should reject oversized inline audience control records', async () => {
|
|
727
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
728
|
+
const protocolDefinition = {
|
|
729
|
+
protocol: 'http://encryption-control-oversized-audience.xyz',
|
|
730
|
+
published: false,
|
|
731
|
+
types: {
|
|
732
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
733
|
+
},
|
|
734
|
+
structure: {
|
|
735
|
+
member: { $role: true },
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
739
|
+
const roleRuleSet = encryptedProtocolDefinition.structure.member;
|
|
740
|
+
const audience = await createAudienceControlWrite({
|
|
741
|
+
author: alice,
|
|
742
|
+
payloadOverrides: { filler: 'x'.repeat(DwnConstant.maxDataSizeAllowedToBeEncoded) },
|
|
743
|
+
protocol: protocolDefinition.protocol,
|
|
744
|
+
rolePath: 'member',
|
|
745
|
+
roleRuleSet,
|
|
746
|
+
});
|
|
747
|
+
const reply = await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) });
|
|
748
|
+
expect(reply.status.code).toBe(400);
|
|
749
|
+
expect(reply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateUnexpectedRecord);
|
|
750
|
+
});
|
|
751
|
+
it('should accept delivery control records for role holders', async () => {
|
|
752
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
753
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
754
|
+
const protocolDefinition = {
|
|
755
|
+
protocol: 'http://encryption-control-delivery-authorities.xyz',
|
|
756
|
+
published: false,
|
|
757
|
+
types: {
|
|
758
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
759
|
+
},
|
|
760
|
+
structure: {
|
|
761
|
+
member: {
|
|
762
|
+
$role: true,
|
|
763
|
+
$actions: [{ who: 'anyone', can: ['create'] }],
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
768
|
+
const memberRuleSet = encryptedProtocolDefinition.structure.member;
|
|
769
|
+
const audience = await createAudienceControlWrite({
|
|
770
|
+
author: alice,
|
|
771
|
+
protocol: protocolDefinition.protocol,
|
|
772
|
+
rolePath: 'member',
|
|
773
|
+
roleRuleSet: memberRuleSet,
|
|
774
|
+
});
|
|
775
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
776
|
+
const memberRole = await TestDataGenerator.generateRecordsWrite({
|
|
777
|
+
author: alice,
|
|
778
|
+
data: Encoder.stringToBytes('bob is a member'),
|
|
779
|
+
dataFormat: 'application/json',
|
|
780
|
+
protocol: protocolDefinition.protocol,
|
|
781
|
+
protocolPath: 'member',
|
|
782
|
+
recipient: bob.did,
|
|
783
|
+
schema: 'http://member-schema',
|
|
784
|
+
});
|
|
785
|
+
expect((await dwn.processMessage(alice.did, memberRole.message, { dataStream: memberRole.dataStream })).status.code).toBe(202);
|
|
786
|
+
const roleHolderDelivery = await createDeliveryControlWrite({
|
|
787
|
+
author: alice,
|
|
788
|
+
keyId: audience.keyId,
|
|
789
|
+
protocol: protocolDefinition.protocol,
|
|
790
|
+
recipient: bob.did,
|
|
791
|
+
recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
|
|
792
|
+
rolePath: 'member',
|
|
793
|
+
roleRuleSet: memberRuleSet,
|
|
794
|
+
});
|
|
795
|
+
expect((await dwn.processMessage(alice.did, roleHolderDelivery.recordsWrite.message, {
|
|
796
|
+
dataStream: DataStream.fromBytes(roleHolderDelivery.dataBytes),
|
|
797
|
+
})).status.code).toBe(202);
|
|
798
|
+
});
|
|
799
|
+
it('should reject malformed delivery control records with typed validation errors', async () => {
|
|
800
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
801
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
802
|
+
const protocolDefinition = {
|
|
803
|
+
protocol: 'http://encryption-control-delivery-malformed.xyz',
|
|
804
|
+
published: false,
|
|
805
|
+
types: {
|
|
806
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
807
|
+
},
|
|
808
|
+
structure: {
|
|
809
|
+
member: { $role: true },
|
|
810
|
+
},
|
|
811
|
+
};
|
|
812
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
813
|
+
const memberRuleSet = encryptedProtocolDefinition.structure.member;
|
|
814
|
+
const audience = await createAudienceControlWrite({
|
|
815
|
+
author: alice,
|
|
816
|
+
protocol: protocolDefinition.protocol,
|
|
817
|
+
rolePath: 'member',
|
|
818
|
+
roleRuleSet: memberRuleSet,
|
|
819
|
+
});
|
|
820
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
821
|
+
const unencryptedData = Encoder.objectToBytes({ protocol: protocolDefinition.protocol });
|
|
822
|
+
const unencryptedDelivery = await RecordsWrite.create({
|
|
823
|
+
data: unencryptedData,
|
|
824
|
+
dataFormat: 'application/json',
|
|
825
|
+
protocol: protocolDefinition.protocol,
|
|
826
|
+
protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
|
|
827
|
+
recipient: bob.did,
|
|
828
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/delivery.json',
|
|
829
|
+
signer: Jws.createSigner(alice),
|
|
830
|
+
tags: {
|
|
831
|
+
protocol: protocolDefinition.protocol,
|
|
832
|
+
rolePath: 'member',
|
|
833
|
+
contextId: '',
|
|
834
|
+
keyId: audience.keyId,
|
|
835
|
+
recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
const unencryptedReply = await dwn.processMessage(alice.did, unencryptedDelivery.message, { dataStream: DataStream.fromBytes(unencryptedData) });
|
|
839
|
+
expect(unencryptedReply.status.code).toBe(400);
|
|
840
|
+
expect(unencryptedReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateUnexpectedRecord);
|
|
841
|
+
const noRecipientData = Encoder.objectToBytes({ protocol: protocolDefinition.protocol });
|
|
842
|
+
const noRecipientDelivery = await RecordsWrite.create({
|
|
843
|
+
data: noRecipientData,
|
|
844
|
+
dataFormat: 'application/json',
|
|
845
|
+
encryptionInput: {
|
|
846
|
+
initializationVector: TestDataGenerator.randomBytes(16),
|
|
847
|
+
key: TestDataGenerator.randomBytes(32),
|
|
848
|
+
keyEncryptionInputs: [{
|
|
849
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
850
|
+
keyId: await Encryption.getKeyId(memberRuleSet.$keyAgreement.publicKeyJwk),
|
|
851
|
+
publicKey: memberRuleSet.$keyAgreement.publicKeyJwk,
|
|
852
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
853
|
+
}],
|
|
854
|
+
},
|
|
855
|
+
protocol: protocolDefinition.protocol,
|
|
856
|
+
protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
|
|
857
|
+
schema: 'https://identity.foundation/dwn/json-schemas/encryption/delivery.json',
|
|
858
|
+
signer: Jws.createSigner(alice),
|
|
859
|
+
tags: {
|
|
860
|
+
protocol: protocolDefinition.protocol,
|
|
861
|
+
rolePath: 'member',
|
|
862
|
+
contextId: '',
|
|
863
|
+
keyId: audience.keyId,
|
|
864
|
+
recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
const noRecipientReply = await dwn.processMessage(alice.did, noRecipientDelivery.message, { dataStream: DataStream.fromBytes(noRecipientData) });
|
|
868
|
+
expect(noRecipientReply.status.code).toBe(400);
|
|
869
|
+
expect(noRecipientReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateDeliveryRecipientMissing);
|
|
870
|
+
const invalidAuthorityDelivery = await createDeliveryControlWrite({
|
|
871
|
+
author: alice,
|
|
872
|
+
keyId: audience.keyId,
|
|
873
|
+
protocol: protocolDefinition.protocol,
|
|
874
|
+
recipient: bob.did,
|
|
875
|
+
recipientAuthority: 'unknown',
|
|
876
|
+
rolePath: 'member',
|
|
877
|
+
roleRuleSet: memberRuleSet,
|
|
878
|
+
});
|
|
879
|
+
const invalidAuthorityReply = await dwn.processMessage(alice.did, invalidAuthorityDelivery.recordsWrite.message, { dataStream: DataStream.fromBytes(invalidAuthorityDelivery.dataBytes) });
|
|
880
|
+
expect(invalidAuthorityReply.status.code).toBe(400);
|
|
881
|
+
expect(invalidAuthorityReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateDeliveryRecipientAuthorityInvalid);
|
|
882
|
+
const unauthorizedRoleHolderDelivery = await createDeliveryControlWrite({
|
|
883
|
+
author: alice,
|
|
884
|
+
keyId: audience.keyId,
|
|
885
|
+
protocol: protocolDefinition.protocol,
|
|
886
|
+
recipient: bob.did,
|
|
887
|
+
recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
|
|
888
|
+
rolePath: 'member',
|
|
889
|
+
roleRuleSet: memberRuleSet,
|
|
890
|
+
});
|
|
891
|
+
const unauthorizedRoleHolderReply = await dwn.processMessage(alice.did, unauthorizedRoleHolderDelivery.recordsWrite.message, { dataStream: DataStream.fromBytes(unauthorizedRoleHolderDelivery.dataBytes) });
|
|
892
|
+
expect(unauthorizedRoleHolderReply.status.code).toBe(400);
|
|
893
|
+
expect(unauthorizedRoleHolderReply.status.detail).toContain(DwnErrorCode.EncryptionControlValidateDeliveryRecipientRoleRecordMissing);
|
|
894
|
+
});
|
|
895
|
+
it('should not treat lookalike encryption control paths as reserved paths', async () => {
|
|
896
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
897
|
+
await TestDataGenerator.installDefaultTestProtocol(dwn, alice);
|
|
898
|
+
const { message, dataStream } = await TestDataGenerator.generateRecordsWrite({
|
|
899
|
+
author: alice,
|
|
900
|
+
protocol: defaultTestProtocolDefinition.protocol,
|
|
901
|
+
protocolPath: '$encryptionx/audience',
|
|
902
|
+
});
|
|
903
|
+
const reply = await dwn.processMessage(alice.did, message, { dataStream });
|
|
904
|
+
expect(reply.status.code).toBe(400);
|
|
905
|
+
expect(reply.status.detail).toContain('SchemaValidatorFailure');
|
|
906
|
+
expect(reply.status.detail).toContain('protocolPath: must match pattern');
|
|
907
|
+
});
|
|
101
908
|
it('should only be able to overwrite existing record if new record has a later `messageTimestamp` value', async () => {
|
|
102
909
|
// write a message into DB
|
|
103
910
|
const author = await TestDataGenerator.generateDidKeyPersona();
|
|
@@ -2289,6 +3096,7 @@ export function testRecordsWriteHandler() {
|
|
|
2289
3096
|
initializationVector: dataEncryptionInitializationVector,
|
|
2290
3097
|
key: dataEncryptionKey,
|
|
2291
3098
|
keyEncryptionInputs: [{
|
|
3099
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2292
3100
|
keyId: await Encryption.getKeyId(encryptionPublicKey),
|
|
2293
3101
|
publicKey: encryptionPublicKey,
|
|
2294
3102
|
derivationScheme: KeyDerivationScheme.ProtocolPath
|
|
@@ -2352,6 +3160,7 @@ export function testRecordsWriteHandler() {
|
|
|
2352
3160
|
initializationVector: dataEncryptionInitializationVector,
|
|
2353
3161
|
key: dataEncryptionKey,
|
|
2354
3162
|
keyEncryptionInputs: [{
|
|
3163
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2355
3164
|
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
2356
3165
|
publicKey: protocolPathPublicKey,
|
|
2357
3166
|
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
@@ -2371,10 +3180,10 @@ export function testRecordsWriteHandler() {
|
|
|
2371
3180
|
expect(writeReply.status.code).toBe(400);
|
|
2372
3181
|
expect(writeReply.status.detail).toContain(DwnErrorCode.ProtocolAuthorizationEncryptionRoleAudienceEntryMissing);
|
|
2373
3182
|
});
|
|
2374
|
-
it('should
|
|
3183
|
+
it('should reject an encrypted role-readable RecordsWrite whose roleAudience entry references a missing audience record', async () => {
|
|
2375
3184
|
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
2376
3185
|
const protocolDefinition = {
|
|
2377
|
-
protocol: 'http://role-audience-
|
|
3186
|
+
protocol: 'http://role-audience-record-required.xyz',
|
|
2378
3187
|
published: false,
|
|
2379
3188
|
types: {
|
|
2380
3189
|
thread: { schema: 'http://thread-schema', dataFormats: ['application/json'] },
|
|
@@ -2407,35 +3216,380 @@ export function testRecordsWriteHandler() {
|
|
|
2407
3216
|
data: Encoder.stringToBytes('{"title":"secret"}'),
|
|
2408
3217
|
});
|
|
2409
3218
|
expect((await dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream })).status.code).toBe(202);
|
|
2410
|
-
const
|
|
3219
|
+
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
|
|
3220
|
+
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
|
|
3221
|
+
const encryptedData = await Encryption.encrypt(ContentEncryptionAlgorithm.A256CTR, dataEncryptionKey, dataEncryptionInitializationVector, Encoder.stringToBytes('encrypted chat'));
|
|
2411
3222
|
const threadRuleSet = encryptedProtocolDefinition.structure.thread;
|
|
2412
|
-
const
|
|
2413
|
-
const
|
|
2414
|
-
const
|
|
2415
|
-
const
|
|
3223
|
+
const chatRuleSet = threadRuleSet.chat;
|
|
3224
|
+
const protocolPathPublicKey = chatRuleSet.$keyAgreement.publicKeyJwk;
|
|
3225
|
+
const audiencePublicKey = alice.encryptionKeyPair.publicJwk;
|
|
3226
|
+
const encryptionInput = {
|
|
3227
|
+
initializationVector: dataEncryptionInitializationVector,
|
|
3228
|
+
key: dataEncryptionKey,
|
|
3229
|
+
keyEncryptionInputs: [
|
|
3230
|
+
{
|
|
3231
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3232
|
+
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
3233
|
+
publicKey: protocolPathPublicKey,
|
|
3234
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
3235
|
+
},
|
|
3236
|
+
{
|
|
3237
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3238
|
+
keyId: await Encryption.getKeyId(audiencePublicKey),
|
|
3239
|
+
publicKey: audiencePublicKey,
|
|
3240
|
+
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
3241
|
+
protocol: protocolDefinition.protocol,
|
|
3242
|
+
rolePath: 'thread/participant',
|
|
3243
|
+
},
|
|
3244
|
+
],
|
|
3245
|
+
};
|
|
3246
|
+
const recordsWrite = await TestDataGenerator.generateRecordsWrite({
|
|
3247
|
+
author: alice,
|
|
2416
3248
|
protocol: protocolDefinition.protocol,
|
|
3249
|
+
protocolPath: 'thread/chat',
|
|
3250
|
+
parentContextId: thread.message.contextId,
|
|
3251
|
+
schema: 'http://chat-schema',
|
|
3252
|
+
dataFormat: 'text/plain',
|
|
3253
|
+
data: encryptedData,
|
|
3254
|
+
encryptionInput,
|
|
3255
|
+
});
|
|
3256
|
+
const writeReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
|
|
3257
|
+
expect(writeReply.status.code).toBe(400);
|
|
3258
|
+
expect(writeReply.status.detail).toContain(DwnErrorCode.ProtocolAuthorizationEncryptionRoleAudienceMissing);
|
|
3259
|
+
});
|
|
3260
|
+
it('should accept an encrypted role-readable RecordsWrite when any source roleAudience entry matches an accepted audience record', async () => {
|
|
3261
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
3262
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
3263
|
+
const protocolDefinition = {
|
|
3264
|
+
protocol: 'http://role-audience-second-entry-accepted.xyz',
|
|
3265
|
+
published: false,
|
|
3266
|
+
types: {
|
|
3267
|
+
thread: { schema: 'http://thread-schema', dataFormats: ['application/json'] },
|
|
3268
|
+
participant: { schema: 'http://participant-schema', dataFormats: ['application/json'] },
|
|
3269
|
+
chat: { schema: 'http://chat-schema', dataFormats: ['text/plain'], encryptionRequired: true },
|
|
3270
|
+
},
|
|
3271
|
+
structure: {
|
|
3272
|
+
thread: {
|
|
3273
|
+
participant: { $role: true },
|
|
3274
|
+
chat: {
|
|
3275
|
+
$actions: [
|
|
3276
|
+
{ role: 'thread/participant', can: ['read'] },
|
|
3277
|
+
],
|
|
3278
|
+
},
|
|
3279
|
+
},
|
|
3280
|
+
},
|
|
3281
|
+
};
|
|
3282
|
+
const encryptedProtocolDefinition = await Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, alice.keyId, alice.encryptionKeyPair.privateJwk);
|
|
3283
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
3284
|
+
author: alice,
|
|
3285
|
+
protocolDefinition: encryptedProtocolDefinition,
|
|
3286
|
+
});
|
|
3287
|
+
expect((await dwn.processMessage(alice.did, protocolConfig.message)).status.code).toBe(202);
|
|
3288
|
+
const thread = await TestDataGenerator.generateRecordsWrite({
|
|
3289
|
+
author: alice,
|
|
3290
|
+
protocol: protocolDefinition.protocol,
|
|
3291
|
+
protocolPath: 'thread',
|
|
3292
|
+
schema: 'http://thread-schema',
|
|
3293
|
+
dataFormat: 'application/json',
|
|
3294
|
+
data: Encoder.stringToBytes('{"title":"secret"}'),
|
|
3295
|
+
});
|
|
3296
|
+
expect((await dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream })).status.code).toBe(202);
|
|
3297
|
+
const rolePath = 'thread/participant';
|
|
3298
|
+
const threadRuleSet = encryptedProtocolDefinition.structure.thread;
|
|
3299
|
+
const participantRuleSet = threadRuleSet.participant;
|
|
3300
|
+
const audience = await createAudienceControlWrite({
|
|
3301
|
+
author: alice,
|
|
2417
3302
|
contextId: thread.message.contextId,
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
publicKeyJwk: rolePublicKey,
|
|
3303
|
+
protocol: protocolDefinition.protocol,
|
|
3304
|
+
rolePath,
|
|
3305
|
+
roleRuleSet: participantRuleSet,
|
|
2422
3306
|
});
|
|
2423
|
-
|
|
2424
|
-
|
|
3307
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
3308
|
+
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
|
|
3309
|
+
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
|
|
3310
|
+
const encryptedData = await Encryption.encrypt(ContentEncryptionAlgorithm.A256CTR, dataEncryptionKey, dataEncryptionInitializationVector, Encoder.stringToBytes('encrypted chat'));
|
|
3311
|
+
const chatRuleSet = threadRuleSet.chat;
|
|
3312
|
+
const protocolPathPublicKey = chatRuleSet.$keyAgreement.publicKeyJwk;
|
|
3313
|
+
const encryptionInput = {
|
|
3314
|
+
initializationVector: dataEncryptionInitializationVector,
|
|
3315
|
+
key: dataEncryptionKey,
|
|
3316
|
+
keyEncryptionInputs: [
|
|
3317
|
+
{
|
|
3318
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3319
|
+
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
3320
|
+
publicKey: protocolPathPublicKey,
|
|
3321
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
3322
|
+
},
|
|
3323
|
+
{
|
|
3324
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3325
|
+
keyId: await Encryption.getKeyId(bob.encryptionKeyPair.publicJwk),
|
|
3326
|
+
publicKey: bob.encryptionKeyPair.publicJwk,
|
|
3327
|
+
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
3328
|
+
protocol: protocolDefinition.protocol,
|
|
3329
|
+
rolePath,
|
|
3330
|
+
},
|
|
3331
|
+
{
|
|
3332
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3333
|
+
keyId: audience.keyId,
|
|
3334
|
+
publicKey: alice.encryptionKeyPair.publicJwk,
|
|
3335
|
+
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
3336
|
+
protocol: protocolDefinition.protocol,
|
|
3337
|
+
rolePath,
|
|
3338
|
+
},
|
|
3339
|
+
],
|
|
3340
|
+
};
|
|
3341
|
+
const recordsWrite = await TestDataGenerator.generateRecordsWrite({
|
|
3342
|
+
author: alice,
|
|
3343
|
+
protocol: protocolDefinition.protocol,
|
|
3344
|
+
protocolPath: 'thread/chat',
|
|
3345
|
+
parentContextId: thread.message.contextId,
|
|
3346
|
+
schema: 'http://chat-schema',
|
|
3347
|
+
dataFormat: 'text/plain',
|
|
3348
|
+
data: encryptedData,
|
|
3349
|
+
encryptionInput,
|
|
3350
|
+
});
|
|
3351
|
+
const writeReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
|
|
3352
|
+
expect(writeReply.status.code).toBe(202);
|
|
3353
|
+
});
|
|
3354
|
+
it('should admit role-audience records and deliveries that reference a stored non-current audience key', async () => {
|
|
3355
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
3356
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
3357
|
+
const protocolDefinition = {
|
|
3358
|
+
protocol: 'http://role-audience-loser-key-admission.xyz',
|
|
3359
|
+
published: false,
|
|
3360
|
+
types: {
|
|
3361
|
+
member: { schema: 'http://member-schema', dataFormats: ['application/json'] },
|
|
3362
|
+
message: { schema: 'http://message-schema', dataFormats: ['text/plain'], encryptionRequired: true },
|
|
3363
|
+
},
|
|
3364
|
+
structure: {
|
|
3365
|
+
member: {
|
|
3366
|
+
$role: true,
|
|
3367
|
+
$actions: [{ who: 'anyone', can: ['create'] }],
|
|
3368
|
+
},
|
|
3369
|
+
message: {
|
|
3370
|
+
$actions: [
|
|
3371
|
+
{ role: 'member', can: ['read'] },
|
|
3372
|
+
],
|
|
3373
|
+
},
|
|
3374
|
+
},
|
|
3375
|
+
};
|
|
3376
|
+
const encryptedProtocolDefinition = await installEncryptedProtocol(alice, protocolDefinition);
|
|
3377
|
+
const memberRuleSet = encryptedProtocolDefinition.structure.member;
|
|
3378
|
+
const messageRuleSet = encryptedProtocolDefinition.structure.message;
|
|
3379
|
+
const rolePath = 'member';
|
|
3380
|
+
const loserAudience = await createAudienceControlWrite({
|
|
3381
|
+
author: bob,
|
|
3382
|
+
dateCreated: '2025-01-01T00:00:00.000000Z',
|
|
3383
|
+
protocol: protocolDefinition.protocol,
|
|
3384
|
+
rolePath,
|
|
3385
|
+
roleRuleSet: memberRuleSet,
|
|
3386
|
+
});
|
|
3387
|
+
expect((await dwn.processMessage(alice.did, loserAudience.recordsWrite.message, { dataStream: DataStream.fromBytes(loserAudience.dataBytes) })).status.code).toBe(202);
|
|
3388
|
+
const currentAudience = await createAudienceControlWrite({
|
|
3389
|
+
author: alice,
|
|
3390
|
+
dateCreated: '2025-01-02T00:00:00.000000Z',
|
|
3391
|
+
protocol: protocolDefinition.protocol,
|
|
3392
|
+
rolePath,
|
|
3393
|
+
roleRuleSet: memberRuleSet,
|
|
3394
|
+
});
|
|
3395
|
+
expect((await dwn.processMessage(alice.did, currentAudience.recordsWrite.message, { dataStream: DataStream.fromBytes(currentAudience.dataBytes) })).status.code).toBe(202);
|
|
3396
|
+
const bobMemberRole = await TestDataGenerator.generateRecordsWrite({
|
|
3397
|
+
author: bob,
|
|
3398
|
+
data: Encoder.stringToBytes('bob is a member'),
|
|
2425
3399
|
dataFormat: 'application/json',
|
|
2426
|
-
protocol:
|
|
2427
|
-
protocolPath:
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
3400
|
+
protocol: protocolDefinition.protocol,
|
|
3401
|
+
protocolPath: rolePath,
|
|
3402
|
+
recipient: bob.did,
|
|
3403
|
+
schema: 'http://member-schema',
|
|
3404
|
+
});
|
|
3405
|
+
expect((await dwn.processMessage(alice.did, bobMemberRole.message, { dataStream: bobMemberRole.dataStream })).status.code).toBe(202);
|
|
3406
|
+
const buildEncryptedMessage = async (plaintext) => {
|
|
3407
|
+
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
|
|
3408
|
+
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
|
|
3409
|
+
const encryptedData = await Encryption.encrypt(ContentEncryptionAlgorithm.A256CTR, dataEncryptionKey, dataEncryptionInitializationVector, Encoder.stringToBytes(plaintext));
|
|
3410
|
+
const protocolPathPublicKey = messageRuleSet.$keyAgreement.publicKeyJwk;
|
|
3411
|
+
const encryptionInput = {
|
|
3412
|
+
initializationVector: dataEncryptionInitializationVector,
|
|
3413
|
+
key: dataEncryptionKey,
|
|
3414
|
+
keyEncryptionInputs: [
|
|
3415
|
+
{
|
|
3416
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3417
|
+
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
3418
|
+
publicKey: protocolPathPublicKey,
|
|
3419
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
3420
|
+
},
|
|
3421
|
+
{
|
|
3422
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3423
|
+
keyId: loserAudience.keyId,
|
|
3424
|
+
publicKey: bob.encryptionKeyPair.publicJwk,
|
|
3425
|
+
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
3426
|
+
protocol: protocolDefinition.protocol,
|
|
3427
|
+
rolePath,
|
|
3428
|
+
},
|
|
3429
|
+
],
|
|
3430
|
+
};
|
|
3431
|
+
return TestDataGenerator.generateRecordsWrite({
|
|
3432
|
+
author: alice,
|
|
2431
3433
|
protocol: protocolDefinition.protocol,
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
3434
|
+
protocolPath: 'message',
|
|
3435
|
+
schema: 'http://message-schema',
|
|
3436
|
+
dataFormat: 'text/plain',
|
|
3437
|
+
data: encryptedData,
|
|
3438
|
+
encryptionInput,
|
|
3439
|
+
});
|
|
3440
|
+
};
|
|
3441
|
+
const directWrite = await buildEncryptedMessage('direct write to loser key');
|
|
3442
|
+
const directReply = await dwn.processMessage(alice.did, directWrite.message, { dataStream: directWrite.dataStream });
|
|
3443
|
+
expect(directReply.status.code).toBe(202);
|
|
3444
|
+
const replicatedWrite = await buildEncryptedMessage('replicated write to loser key');
|
|
3445
|
+
const replicatedResult = await dwn.applyReplicatedMessage(alice.did, replicatedWrite.message, { dataStream: replicatedWrite.dataStream });
|
|
3446
|
+
expect(replicatedResult).toEqual(expect.objectContaining({ kind: 'Applied' }));
|
|
3447
|
+
const delivery = await createDeliveryControlWrite({
|
|
3448
|
+
author: alice,
|
|
3449
|
+
keyId: loserAudience.keyId,
|
|
3450
|
+
protocol: protocolDefinition.protocol,
|
|
3451
|
+
recipient: bob.did,
|
|
3452
|
+
recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
|
|
3453
|
+
rolePath,
|
|
3454
|
+
roleRuleSet: memberRuleSet,
|
|
3455
|
+
});
|
|
3456
|
+
const deliveryReply = await dwn.processMessage(alice.did, delivery.recordsWrite.message, { dataStream: DataStream.fromBytes(delivery.dataBytes) });
|
|
3457
|
+
expect(deliveryReply.status.code).toBe(202);
|
|
3458
|
+
});
|
|
3459
|
+
it('should reject an encrypted role-readable RecordsWrite when the roleAudience keyId does not match the audience record', async () => {
|
|
3460
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
3461
|
+
const bob = await TestDataGenerator.generateDidKeyPersona();
|
|
3462
|
+
const protocolDefinition = {
|
|
3463
|
+
protocol: 'http://role-audience-key-id-bound.xyz',
|
|
3464
|
+
published: false,
|
|
3465
|
+
types: {
|
|
3466
|
+
thread: { schema: 'http://thread-schema', dataFormats: ['application/json'] },
|
|
3467
|
+
participant: { schema: 'http://participant-schema', dataFormats: ['application/json'] },
|
|
3468
|
+
chat: { schema: 'http://chat-schema', dataFormats: ['text/plain'], encryptionRequired: true },
|
|
3469
|
+
},
|
|
3470
|
+
structure: {
|
|
3471
|
+
thread: {
|
|
3472
|
+
participant: { $role: true },
|
|
3473
|
+
chat: {
|
|
3474
|
+
$actions: [
|
|
3475
|
+
{ role: 'thread/participant', can: ['read'] },
|
|
3476
|
+
],
|
|
3477
|
+
},
|
|
3478
|
+
},
|
|
2436
3479
|
},
|
|
3480
|
+
};
|
|
3481
|
+
const encryptedProtocolDefinition = await Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, alice.keyId, alice.encryptionKeyPair.privateJwk);
|
|
3482
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
3483
|
+
author: alice,
|
|
3484
|
+
protocolDefinition: encryptedProtocolDefinition,
|
|
3485
|
+
});
|
|
3486
|
+
expect((await dwn.processMessage(alice.did, protocolConfig.message)).status.code).toBe(202);
|
|
3487
|
+
const thread = await TestDataGenerator.generateRecordsWrite({
|
|
3488
|
+
author: alice,
|
|
3489
|
+
protocol: protocolDefinition.protocol,
|
|
3490
|
+
protocolPath: 'thread',
|
|
3491
|
+
schema: 'http://thread-schema',
|
|
3492
|
+
dataFormat: 'application/json',
|
|
3493
|
+
data: Encoder.stringToBytes('{"title":"secret"}'),
|
|
2437
3494
|
});
|
|
2438
|
-
expect((await dwn.processMessage(alice.did,
|
|
3495
|
+
expect((await dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream })).status.code).toBe(202);
|
|
3496
|
+
const rolePath = 'thread/participant';
|
|
3497
|
+
const threadRuleSet = encryptedProtocolDefinition.structure.thread;
|
|
3498
|
+
const participantRuleSet = threadRuleSet.participant;
|
|
3499
|
+
const audience = await createAudienceControlWrite({
|
|
3500
|
+
author: alice,
|
|
3501
|
+
contextId: thread.message.contextId,
|
|
3502
|
+
protocol: protocolDefinition.protocol,
|
|
3503
|
+
rolePath,
|
|
3504
|
+
roleRuleSet: participantRuleSet,
|
|
3505
|
+
});
|
|
3506
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
3507
|
+
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
|
|
3508
|
+
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
|
|
3509
|
+
const encryptedData = await Encryption.encrypt(ContentEncryptionAlgorithm.A256CTR, dataEncryptionKey, dataEncryptionInitializationVector, Encoder.stringToBytes('encrypted chat'));
|
|
3510
|
+
const chatRuleSet = threadRuleSet.chat;
|
|
3511
|
+
const protocolPathPublicKey = chatRuleSet.$keyAgreement.publicKeyJwk;
|
|
3512
|
+
const encryptionInput = {
|
|
3513
|
+
initializationVector: dataEncryptionInitializationVector,
|
|
3514
|
+
key: dataEncryptionKey,
|
|
3515
|
+
keyEncryptionInputs: [
|
|
3516
|
+
{
|
|
3517
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3518
|
+
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
3519
|
+
publicKey: protocolPathPublicKey,
|
|
3520
|
+
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
3521
|
+
},
|
|
3522
|
+
{
|
|
3523
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3524
|
+
keyId: await Encryption.getKeyId(bob.encryptionKeyPair.publicJwk),
|
|
3525
|
+
publicKey: bob.encryptionKeyPair.publicJwk,
|
|
3526
|
+
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
3527
|
+
protocol: protocolDefinition.protocol,
|
|
3528
|
+
rolePath,
|
|
3529
|
+
},
|
|
3530
|
+
],
|
|
3531
|
+
};
|
|
3532
|
+
const recordsWrite = await TestDataGenerator.generateRecordsWrite({
|
|
3533
|
+
author: alice,
|
|
3534
|
+
protocol: protocolDefinition.protocol,
|
|
3535
|
+
protocolPath: 'thread/chat',
|
|
3536
|
+
parentContextId: thread.message.contextId,
|
|
3537
|
+
schema: 'http://chat-schema',
|
|
3538
|
+
dataFormat: 'text/plain',
|
|
3539
|
+
data: encryptedData,
|
|
3540
|
+
encryptionInput,
|
|
3541
|
+
});
|
|
3542
|
+
const writeReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
|
|
3543
|
+
expect(writeReply.status.code).toBe(400);
|
|
3544
|
+
expect(writeReply.status.detail).toContain(DwnErrorCode.ProtocolAuthorizationEncryptionRoleAudienceMissing);
|
|
3545
|
+
});
|
|
3546
|
+
it('should accept an encrypted role-readable RecordsWrite with a matching audience record', async () => {
|
|
3547
|
+
const alice = await TestDataGenerator.generateDidKeyPersona();
|
|
3548
|
+
const protocolDefinition = {
|
|
3549
|
+
protocol: 'http://role-audience-entry-accepted.xyz',
|
|
3550
|
+
published: false,
|
|
3551
|
+
types: {
|
|
3552
|
+
thread: { schema: 'http://thread-schema', dataFormats: ['application/json'] },
|
|
3553
|
+
participant: { schema: 'http://participant-schema', dataFormats: ['application/json'] },
|
|
3554
|
+
chat: { schema: 'http://chat-schema', dataFormats: ['text/plain'], encryptionRequired: true },
|
|
3555
|
+
},
|
|
3556
|
+
structure: {
|
|
3557
|
+
thread: {
|
|
3558
|
+
participant: { $role: true },
|
|
3559
|
+
chat: {
|
|
3560
|
+
$actions: [
|
|
3561
|
+
{ role: 'thread/participant', can: ['read'] },
|
|
3562
|
+
],
|
|
3563
|
+
},
|
|
3564
|
+
},
|
|
3565
|
+
},
|
|
3566
|
+
};
|
|
3567
|
+
const encryptedProtocolDefinition = await Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, alice.keyId, alice.encryptionKeyPair.privateJwk);
|
|
3568
|
+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
|
|
3569
|
+
author: alice,
|
|
3570
|
+
protocolDefinition: encryptedProtocolDefinition,
|
|
3571
|
+
});
|
|
3572
|
+
expect((await dwn.processMessage(alice.did, protocolConfig.message)).status.code).toBe(202);
|
|
3573
|
+
const thread = await TestDataGenerator.generateRecordsWrite({
|
|
3574
|
+
author: alice,
|
|
3575
|
+
protocol: protocolDefinition.protocol,
|
|
3576
|
+
protocolPath: 'thread',
|
|
3577
|
+
schema: 'http://thread-schema',
|
|
3578
|
+
dataFormat: 'application/json',
|
|
3579
|
+
data: Encoder.stringToBytes('{"title":"secret"}'),
|
|
3580
|
+
});
|
|
3581
|
+
expect((await dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream })).status.code).toBe(202);
|
|
3582
|
+
const rolePath = 'thread/participant';
|
|
3583
|
+
const threadRuleSet = encryptedProtocolDefinition.structure.thread;
|
|
3584
|
+
const participantRuleSet = threadRuleSet.participant;
|
|
3585
|
+
const audience = await createAudienceControlWrite({
|
|
3586
|
+
author: alice,
|
|
3587
|
+
contextId: thread.message.contextId,
|
|
3588
|
+
protocol: protocolDefinition.protocol,
|
|
3589
|
+
rolePath,
|
|
3590
|
+
roleRuleSet: participantRuleSet,
|
|
3591
|
+
});
|
|
3592
|
+
expect((await dwn.processMessage(alice.did, audience.recordsWrite.message, { dataStream: DataStream.fromBytes(audience.dataBytes) })).status.code).toBe(202);
|
|
2439
3593
|
const data = Encoder.stringToBytes('encrypted chat');
|
|
2440
3594
|
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
|
|
2441
3595
|
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
|
|
@@ -2447,17 +3601,18 @@ export function testRecordsWriteHandler() {
|
|
|
2447
3601
|
key: dataEncryptionKey,
|
|
2448
3602
|
keyEncryptionInputs: [
|
|
2449
3603
|
{
|
|
3604
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2450
3605
|
keyId: await Encryption.getKeyId(protocolPathPublicKey),
|
|
2451
3606
|
publicKey: protocolPathPublicKey,
|
|
2452
3607
|
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
2453
3608
|
},
|
|
2454
3609
|
{
|
|
2455
|
-
|
|
2456
|
-
|
|
3610
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
3611
|
+
keyId: audience.keyId,
|
|
3612
|
+
publicKey: alice.encryptionKeyPair.publicJwk,
|
|
2457
3613
|
derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
|
|
2458
3614
|
protocol: protocolDefinition.protocol,
|
|
2459
|
-
|
|
2460
|
-
epoch: 1,
|
|
3615
|
+
rolePath,
|
|
2461
3616
|
},
|
|
2462
3617
|
],
|
|
2463
3618
|
};
|
|
@@ -2746,6 +3901,7 @@ export function testRecordsWriteHandler() {
|
|
|
2746
3901
|
initializationVector: dataEncryptionInitializationVector,
|
|
2747
3902
|
key: dataEncryptionKey,
|
|
2748
3903
|
keyEncryptionInputs: [{
|
|
3904
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2749
3905
|
keyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
2750
3906
|
publicKey: alice.encryptionKeyPair.publicJwk,
|
|
2751
3907
|
derivationScheme: KeyDerivationScheme.ProtocolPath
|
|
@@ -2788,6 +3944,7 @@ export function testRecordsWriteHandler() {
|
|
|
2788
3944
|
initializationVector: dataEncryptionInitializationVector,
|
|
2789
3945
|
key: dataEncryptionKey,
|
|
2790
3946
|
keyEncryptionInputs: [{
|
|
3947
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2791
3948
|
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
2792
3949
|
keyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
2793
3950
|
publicKey: alice.encryptionKeyPair.publicJwk,
|
|
@@ -2835,6 +3992,7 @@ export function testRecordsWriteHandler() {
|
|
|
2835
3992
|
initializationVector: dataEncryptionInitializationVector,
|
|
2836
3993
|
key: dataEncryptionKey,
|
|
2837
3994
|
keyEncryptionInputs: [{
|
|
3995
|
+
algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
|
|
2838
3996
|
derivationScheme: KeyDerivationScheme.ProtocolPath,
|
|
2839
3997
|
keyId: await Encryption.getKeyId(alice.encryptionKeyPair.publicJwk),
|
|
2840
3998
|
publicKey: alice.encryptionKeyPair.publicJwk,
|