Compare commits

...

1820 Commits

Author SHA1 Message Date
RiotRobot 69f6bba964 v9.2.0 2020-11-23 16:19:22 +00:00
RiotRobot 2a4e722f0f Prepare changelog for v9.2.0 2020-11-23 16:19:22 +00:00
J. Ryan Stinnett dd20828ded Merge pull request #1545 from matrix-org/jryans/fix-dehydration-method-release
[Release] Fix dehydration method name
2020-11-19 16:30:35 +00:00
J. Ryan Stinnett 30f86e2437 Fix dehydration method name
https://github.com/matrix-org/matrix-js-sdk/pull/1537 changed some dehydration
method names, but one call site was missed.
2020-11-19 16:22:38 +00:00
RiotRobot 45e9b3ac68 v9.2.0-rc.1 2020-11-18 15:54:10 +00:00
RiotRobot a076e3f0fc Prepare changelog for v9.2.0-rc.1 2020-11-18 15:54:09 +00:00
David Baker 34882cc438 Merge pull request #1532 from matrix-org/dbkr/call_hold
Implement call holding functionality
2020-11-10 08:41:26 +00:00
RiotRobot 5ac00e3465 Merge branch 'master' into develop 2020-11-09 16:21:25 +00:00
RiotRobot 622dd065ff v9.1.0 2020-11-09 16:17:53 +00:00
RiotRobot c5c98a6ac1 Prepare changelog for v9.1.0 2020-11-09 16:17:53 +00:00
David Baker da423ed508 Add comment that the timeout probably isn't
After a fair emount of investigation, it looks like there's not
really a particularly elegant way to have an environment where
setTimeout has a return type that makes sensible assertions in both
node and the browser. Ideally we want it to be something that asserts
that we don't try to use it as a NodeJS.Timeout (ie. call the methods)
because that will break in a browser. In practice, this involves making
wrappers or redefining the timeout functions or some similar kind of
hackery, the evils of which probably offset having perfect typing.

https://matrix.to/#/!bEWtlqtDwCLFIAKAcv:matrix.org/$vpFWl7p1_8A858RAccO0gud3sNVWIDNxNELRjdqaZQ4?via=matrix.org&via=mozilla.org&via=vector.modular.im
for the discussion
2020-11-09 15:59:54 +00:00
Bruno Windels 11c4337cfc Merge pull request #1537 from matrix-org/bwindels/dehydration
Support awaitable one-time dehydration
2020-11-04 15:22:09 +00:00
Bruno Windels 458164384d add client method for one-time dehydration that can be awaited 2020-11-04 16:05:31 +01:00
Bruno Windels 13c7f55a79 split up setKey and setKeyAndQueue
as dehydrating in the background prevents use-cases where you
want to await the creation of the dehydrated device
2020-11-04 16:02:31 +01:00
Bruno Windels 4ab675863a fix typo 2020-11-04 16:00:53 +01:00
RiotRobot 5414b3b39d v9.1.0-rc.1 2020-11-04 14:03:49 +00:00
RiotRobot c54db30dc8 Prepare changelog for v9.1.0-rc.1 2020-11-04 14:03:48 +00:00
Michael Telatynski f11103bfcc Merge pull request #1534 from matrix-org/t3chguy/fix/15604
Client set profile methods update own user
2020-11-04 13:59:58 +00:00
Bruno Windels b56936003d stop dehydration timer when stopping the client 2020-11-03 10:13:19 +01:00
Travis Ralston f61604a51e Merge pull request #1535 from matrix-org/travis/fix-acl-type
Fix spelling error in the server ACL event type
2020-11-02 12:54:14 -07:00
Travis Ralston ae77f900ef Fix spelling error in the server ACL event type 2020-11-02 12:51:15 -07:00
Michael Telatynski 645842f0fd Update comments 2020-11-02 18:09:32 +00:00
Bruno Windels 39d3640973 Merge pull request #1533 from matrix-org/bwindels/dehydration-await-crypto-store
await idb operations from crypto store for dehydration
2020-11-02 17:49:45 +00:00
Michael Telatynski 66aa9c4831 pass a presence event 2020-11-02 17:39:25 +00:00
Michael Telatynski 7de0ca2048 pass args 2020-11-02 17:29:14 +00:00
Michael Telatynski 5bbc5cad9f Client setProfile methods update own user 2020-11-02 17:20:03 +00:00
Bruno Windels 0a7a80d5a8 await idb operations from crypto store for dehydration 2020-11-02 17:35:32 +01:00
David Baker 33d1a33a17 Implement call holding functionality
Using m.call.negotiate
2020-10-29 17:54:54 +00:00
David Baker 7f130949c8 Merge pull request #1531 from matrix-org/dbkr/those_cached_promises_again
Fix stuck never-sending messages
2020-10-28 20:20:26 +00:00
David Baker ba7ee37899 Fix stuck never-sending messages
Another cached promise that wasn't cleared on failure
2020-10-28 18:29:05 +00:00
RiotRobot f8863d5c24 Merge branch 'master' into develop 2020-10-28 14:15:01 +00:00
RiotRobot 1d7954c831 v9.0.1 2020-10-28 14:11:30 +00:00
RiotRobot 2bb4e91dfd Prepare changelog for v9.0.1 2020-10-28 14:11:29 +00:00
J. Ryan Stinnett abe5bf4240 Merge pull request #1530 from matrix-org/jryans/await-key-cache-release
[Release] Await key cache check to avoid prompts
2020-10-28 11:42:19 +00:00
J. Ryan Stinnett c493bf7866 Deduplicate key backup signing paths 2020-10-28 11:07:56 +00:00
J. Ryan Stinnett 06bf0f22be Await key cache check to avoid prompts
`isStoredInKeyCache` is async, so we need to await the result to know whether to
proceed. In addition, this wraps the block in a try / catch, since signing the
key backup is an optional step.

Fixes https://github.com/vector-im/element-web/issues/15530
2020-10-28 11:07:56 +00:00
J. Ryan Stinnett 02d9fe1d30 Merge pull request #1529 from matrix-org/jryans/await-key-cache
Await key cache check to avoid prompts
2020-10-27 17:34:21 +00:00
J. Ryan Stinnett c3091c5aa4 Deduplicate key backup signing paths 2020-10-27 17:17:54 +00:00
J. Ryan Stinnett 677a427f1f Await key cache check to avoid prompts
`isStoredInKeyCache` is async, so we need to await the result to know whether to
proceed. In addition, this wraps the block in a try / catch, since signing the
key backup is an optional step.

Fixes https://github.com/vector-im/element-web/issues/15530
2020-10-27 17:07:04 +00:00
RiotRobot c416dd01a7 Merge branch 'master' into develop 2020-10-26 16:45:54 +00:00
RiotRobot 662fcb426b v9.0.0 2020-10-26 16:42:34 +00:00
RiotRobot fd0fe9e225 Prepare changelog for v9.0.0 2020-10-26 16:42:33 +00:00
David Baker 0ca8613896 Merge pull request #1524 from matrix-org/dbkr/optimise_ice_candidate_sending
Improve ICE candidate batching
2020-10-22 16:29:22 +01:00
David Baker 4b1817719e Merge pull request #1527 from matrix-org/dbkr/logger_ts_try_3
Convert logger to typescript
2020-10-22 16:08:09 +01:00
David Baker 295c591e95 Merge pull request #1528 from matrix-org/dbkr/debugl_rel
Fix logger typo
2020-10-22 16:06:31 +01:00
David Baker 9df0480b78 Merge remote-tracking branch 'origin/develop' into dbkr/logger_ts_try_3 2020-10-22 16:03:12 +01:00
David Baker 5260f40451 Merge pull request #1525 from matrix-org/dbkr/debugl
Fix logger typo
2020-10-22 16:02:10 +01:00
David Baker 0cbe35e41f Fix logger typo 2020-10-22 16:00:23 +01:00
David Baker 502745271d Convert logger to typescript
Because it's annoying for the IDE to have no idea what methods are
on the logger. `loglevel` has types so it should just pass them
through.
2020-10-22 15:59:38 +01:00
David Baker 95baa3cd27 Fix logger typo 2020-10-22 15:54:55 +01:00
David Baker 8fe4a29176 Stop typescript from trying to be clever 2020-10-22 10:10:06 +01:00
David Baker 1a1a0e7324 Abort if call has ended and remove stray self(!) 2020-10-21 18:55:57 +01:00
David Baker d00d07a1c1 Improve ICE candidate batching
Hopefully send fewer ICE candidate events by obeying the batching
guidelines in MSC2476.
2020-10-21 18:23:01 +01:00
Michael Telatynski f27db16e30 Merge pull request #1523 from matrix-org/t3chguy/fix/js-online
bind online listener to window instead of document
2020-10-21 16:08:18 +01:00
RiotRobot d4e107b3cd v9.0.0-rc.1 2020-10-21 14:33:50 +01:00
RiotRobot 881b60c85f Prepare changelog for v9.0.0-rc.1 2020-10-21 14:33:50 +01:00
Michael Telatynski c5e1aade12 bind online listener to window instead of document
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-21 13:58:34 +01:00
David Baker 28198a6f40 Merge pull request #1522 from matrix-org/dbke/select_answer
Support m.call.select_answer
2020-10-21 13:33:49 +01:00
David Baker 30a01e26de Make test pass
Don't send events all the way via the mock HTTP backend: we're not
trying to test that here. This meant we weren't actually getting
into the right state because the request to send the invite never
actually returned. Now this works, we need to clear the invite timer
otherwise jest has a timer hanging around at the end of the test
(plus we should be doing it anyway).
2020-10-21 12:41:36 +01:00
David Baker 8712703f7c Support m.call.select_answer
Only send one if the answer has a party_id set

Also change some event types to the enum and rename receivedAnswer
to be more consistent
2020-10-21 11:52:58 +01:00
David Baker e3a8631faa Merge pull request #1521 from matrix-org/dbkr/dont_cache_versions_failure
Don't cache failures when fetching /versions
2020-10-20 16:35:49 +01:00
David Baker 3eab51ce79 Don't cache failures when fetching /versions
Otherwise we never recover

Fixes https://github.com/vector-im/element-web/issues/15509
2020-10-20 16:25:50 +01:00
J. Ryan Stinnett 9db0fe0795 Merge pull request #1518 from matrix-org/jryans/release-install-first
Install deps first as part of release
2020-10-20 15:45:25 +01:00
Michael Telatynski 228af037e3 Merge pull request #1517 from matrix-org/t3chguy/fix/js-1504
[Breaking] Change hasPendingEvent to return false if pending ordering !detached
2020-10-20 15:28:29 +01:00
J. Ryan Stinnett 654f250fd8 Merge pull request #1519 from matrix-org/jryans/release-disable-editor
Skip editor prompts for merges
2020-10-20 12:06:34 +01:00
J. Ryan Stinnett a8693d9d68 Skip editor prompts for merges
The merges are never edited, and even with this change they will still abort if
there are conflicts.
2020-10-20 11:47:59 +01:00
J. Ryan Stinnett f9f345e428 Move cache clean above install 2020-10-20 11:42:28 +01:00
J. Ryan Stinnett e678706414 Install deps first as part of release
This ensures we always install (without running build scripts) as the first step
of release process, as otherwise it may fail later due to mismatched types or
any number of other errors.
2020-10-20 11:20:57 +01:00
Michael Telatynski 8018259480 Change hasPendingEvent to return false if pending event ordering !detached
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-19 14:56:58 +01:00
David Baker 9f713781cd Merge pull request #1516 from matrix-org/dbkr/aint_no_party_like_a_typescript_party
Convert call test to TypeScript
2020-10-19 09:57:52 +01:00
David Baker 48d56dc5c0 Merge pull request #1512 from matrix-org/dbkr/party_in_the_usa
Support party_id
2020-10-19 09:57:30 +01:00
David Baker 701dfa09b4 Merge pull request #1510 from matrix-org/dbkr/hang_up_your_hangups
Support m.call.reject
2020-10-19 09:57:00 +01:00
David Baker d965648fd7 Convert call test to TypeScript
Typescript tests basically just appear to work, apart from needing
the jest types imported so the typescript checker knows what's what.

DConvert the webrtc test to typescript, which actually mostly just
serves to point out that we're not mocking the whole of `document`,
but oh well.
2020-10-16 18:20:03 +01:00
David Baker 08c15e7203 Merge pull request #1515 from matrix-org/dbkr/remove_specbuild
Remove specbuild from .gitignore
2020-10-16 17:56:02 +01:00
David Baker 9b9e52d0a2 Remove specbuild from .gitignore
It's no longer a thing
2020-10-16 17:47:31 +01:00
David Baker 38cc8fe7dc Merge pull request #1514 from matrix-org/dbkr/log_candidate_send_error
Log the error when we failed to send candidates
2020-10-16 16:52:05 +01:00
David Baker 590fac0fa9 Log the error when we failed to send candidates 2020-10-16 16:46:07 +01:00
David Baker 9590c8aaf0 Don't ignore hangups if we don't have a partner 2020-10-16 14:37:50 +01:00
David Baker e2b79e4e7e linty lint lint 2020-10-16 13:02:47 +01:00
David Baker 2df588f95a Support party_id
Send party_id on events and check the party_id of incoming events matches

Includes a basic test to assert that it actually does: we should
build out a decent test suite for calls as there's a lot of edge-case
functionality that can break and slip through the cracks (eg. glare).
This is a start.

Fixes https://github.com/matrix-org/matrix-js-sdk/issues/1511
2020-10-16 12:53:08 +01:00
David Baker 7c3af91b42 Support m.call.reject
Start the migration to v1 VoIP by supporting m.call.reject, which
we'll send if the caller says they're v1. Our version stays as v0
for now, until we speak the rest of v1.

Honour the default reaosn in a hangup being user_hangup.
2020-10-15 14:51:05 +01:00
David Baker 4cc4b28c47 Merge pull request #1503 from matrix-org/dbkr/call_state_machine
Fixes for call state machine
2020-10-13 10:39:10 +01:00
David Baker fad9b4c67f Merge pull request #1506 from matrix-org/dbkr/fix_event_listener_remove
Fix call event handler listener removing
2020-10-12 14:17:53 +01:00
David Baker ade3b3a021 Fix call event handler listener removing
Wrong kind of event emitter...
2020-10-12 14:12:34 +01:00
RiotRobot d8c4101fdd Merge branch 'master' into develop 2020-10-12 13:18:23 +01:00
RiotRobot a12c250f2b v8.5.0 2020-10-12 13:15:07 +01:00
RiotRobot 57eef2d832 Prepare changelog for v8.5.0 2020-10-12 13:15:06 +01:00
David Baker b67a179a54 Merge pull request #1501 from matrix-org/dbkr/set_type_based_on_tracks
Set the type of the call based on the tracks
2020-10-12 11:42:40 +01:00
David Baker 06044b39c3 Merge pull request #1499 from matrix-org/dbkr/new_age_calls
Use new local timestamp for calls
2020-10-12 11:42:28 +01:00
J. Ryan Stinnett d16cf26c5f Merge pull request #1502 from matrix-org/jryans/sso-4s-integration
Adjust types and APIs to match React SDK
2020-10-12 11:41:06 +01:00
J. Ryan Stinnett b060c5af38 Tune crypto types 2020-10-12 11:01:00 +01:00
David Baker b28bad651e Make events names an enum 2020-10-12 10:15:58 +01:00
David Baker 5c4b7a3213 Add user_hangup error code
but special case it for now & don't send it: it needs voip v1
2020-10-09 18:51:35 +01:00
David Baker 7f21c591ae Fixes for call state machine
* Set 'connecting' state before sending answer, otherwise it can
   race with ICE connecting
 * Ignore completed ice connection state: connected is what we care about
 * Null-check remotestream when stopping media
 * Comments
2020-10-09 18:45:45 +01:00
David Baker 65b24f595c Missed a reference to callList 2020-10-09 18:14:00 +01:00
David Baker 9d9c2720c2 Hopefully make if statement clearer 2020-10-09 17:43:40 +01:00
David Baker f845100062 add 'public' to public method 2020-10-09 17:43:34 +01:00
David Baker e6155f9e37 Use MatrixClient as type
and different array syntax
2020-10-09 17:43:28 +01:00
J. Ryan Stinnett e9590e9093 Adjust types and APIs to match React SDK
Various small tweaks and alignments to match React SDK as part of TypeScript
conversion.

Part of https://github.com/vector-im/element-web/issues/15350
2020-10-09 17:21:14 +01:00
David Baker 49f2d1501c Set the type of the call based on the tracks
Remove the old hack of inspecting the SDP which no longer seems to
be necessary.
2020-10-08 15:42:39 +01:00
J. Ryan Stinnett c6819e0450 Omit stack trace if rehydration fails 2020-10-08 15:11:46 +01:00
David Baker f518ea95f4 Use getLocalAge() & add grace period
Use the new local-age field for deciding whether a call is still
valid or not. Also add a grace period so we don't ring half a second
before the call becomes invalid.
2020-10-08 11:49:54 +01:00
David Baker 92c6332143 use more enums 2020-10-08 11:32:32 +01:00
David Baker 0df1a7da21 copyright header 2020-10-08 11:12:39 +01:00
David Baker 487a9c0967 Extract the call event handler out to its own class
and convert it to TypeScript
2020-10-08 11:10:20 +01:00
David Baker fb89761671 Merge pull request #1495 from matrix-org/dbkr/age_is_just_a_number
Make an accurate version of 'age' for events
2020-10-08 09:29:16 +01:00
David Baker d1d3ae074d Add tests & fix some bugs found by said tests 2020-10-07 18:29:33 +01:00
David Baker 7dedaf90c3 Add a test for the age mangling 2020-10-07 18:04:20 +01:00
David Baker 687b98a09d Don't add localTs if an event has no age 2020-10-07 17:39:19 +01:00
David Baker c6b2e9873c Merge pull request #1498 from matrix-org/dbkr/call_options_is_optional
Make 'options' parameter optional
2020-10-07 17:15:26 +01:00
David Baker 1e80491675 Make 'options' parameter optional
It's deprecated so generally nothing should be passing it
2020-10-07 17:08:21 +01:00
Michael Telatynski a727da9193 Merge pull request #1497 from matrix-org/t3chguy/fix/14804
Create a giant event type enum
2020-10-07 16:48:39 +01:00
Michael Telatynski 0b7754581a Update src/@types/event.ts
Co-authored-by: Travis Ralston <travpc@gmail.com>
2020-10-07 16:32:04 +01:00
David Baker 452e8ea385 Merge pull request #1494 from matrix-org/revert-1493-revert-1487-dbkr/tsify_call
Convert call.js to Typescript & update WebRTC APIs (re-apply)
2020-10-07 15:36:27 +01:00
David Baker a189de9a2e Add comment on mass event copying 2020-10-07 15:32:22 +01:00
David Baker 8632ca6e37 Merge remote-tracking branch 'origin/develop' into revert-1493-revert-1487-dbkr/tsify_call 2020-10-07 15:16:21 +01:00
RiotRobot 293860b6c5 v8.5.0-rc.1 2020-10-07 14:18:52 +01:00
RiotRobot 07667172cd Prepare changelog for v8.5.0-rc.1 2020-10-07 14:18:52 +01:00
J. Ryan Stinnett f5240cdac6 Merge pull request #1467 from uhoreg/fallback_keys
Add support for olm fallback keys
2020-10-07 10:24:12 +01:00
Michael Telatynski 7529d2b638 Create a giant event type enum
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-07 09:57:56 +01:00
Michael Telatynski 03fc12e888 Merge pull request #1492 from matrix-org/t3chguy/fix/10545
Fix editing local echoes not updating them in real time
2020-10-07 00:15:16 +01:00
Hubert Chathi e05a50528e make test no longer dependent on emscripten internals 2020-10-06 19:01:01 -04:00
Hubert Chathi 356ee90417 lint 2020-10-06 18:33:45 -04:00
Hubert Chathi 5dced57724 bump olm release again and fix global.d.ts to work with the olm TS definitions 2020-10-06 17:55:40 -04:00
Michael Telatynski 8ec3b88c5d Update comments
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 22:55:34 +01:00
Michael Telatynski d7ef128510 Merge pull request #1491 from matrix-org/t3chguy/fix/9836
Fix re-emit of Event.replaced to be on client and not room
2020-10-06 22:50:14 +01:00
Hubert Chathi bb2502409b update yarn.lock 2020-10-06 15:34:02 -04:00
Hubert Chathi aa6aab4245 Merge remote-tracking branch 'origin/develop' into fallback_keys 2020-10-06 15:22:25 -04:00
Hubert Chathi 12aab0caeb depend on newer olm 2020-10-06 15:18:18 -04:00
David Baker 2f316c558d Merge pull request #1496 from matrix-org/dbkr/space_in_log_line
Add space to log line
2020-10-06 19:16:25 +01:00
David Baker f447273b75 Add space to log line 2020-10-06 18:59:34 +01:00
David Baker c8a18d51e6 Lint (& unused variable) 2020-10-06 17:45:21 +01:00
David Baker d77af1e67a Make an accurate version of 'age' for events
We've always had 'age' in events but it's never really been an
accurate representation of the event's age because we never did
anything with it. This transforms it into a local clock timestamp
when the event arives and when it comes out of the sync store, and
changes getLocalAge() to use it.

react-sdk doesn't appear to use getLocalAge() but any 3rd party apps
that do may notice a slight change in bahaviour.
2020-10-06 15:24:09 +01:00
David Baker 81c95224d1 Revert "Revert "Convert call.js to Typescript & update WebRTC APIs"" 2020-10-06 15:21:45 +01:00
David Baker a0e66291df Merge pull request #1493 from matrix-org/revert-1487-dbkr/tsify_call
Revert "Convert call.js to Typescript & update WebRTC APIs"
2020-10-06 15:21:38 +01:00
David Baker 5733f46f4c Revert "Convert call.js to Typescript & update WebRTC APIs" 2020-10-06 15:16:32 +01:00
David Baker da2128feff Merge pull request #1487 from matrix-org/dbkr/tsify_call
Convert call.js to Typescript & update WebRTC APIs
2020-10-06 15:09:10 +01:00
Michael Telatynski d413faefdb double sync-browserify jest timeout
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 12:39:18 +01:00
Michael Telatynski 0d1d767d96 Fix editing local echoes not updating them in real time
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 12:32:50 +01:00
David Baker 5619554023 use MatrixEvent for event types 2020-10-06 11:03:05 +01:00
David Baker 53880a4bb2 This is how plurals work 2020-10-06 10:54:51 +01:00
David Baker 812ae227b6 Make type enums PascalCase 2020-10-06 10:53:42 +01:00
David Baker c6b44098ac remove space before type colon 2020-10-06 09:55:02 +01:00
David Baker 92b95a98d0 comment other snake case identifiers
and also sto mixing quotes
2020-10-06 09:52:59 +01:00
David Baker a3505ff42d Use right case for private enum field 2020-10-06 09:49:51 +01:00
Michael Telatynski 15b2e3ff1d Fix re-emit of Event.replaced to be on client and not room
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 08:23:19 +01:00
Hubert Chathi 1845d1ac55 Merge pull request #1436 from uhoreg/dehydration
Dehydrate and rehydrate devices
2020-10-05 17:19:28 -04:00
J. Ryan Stinnett 463819caa7 Merge pull request #1490 from matrix-org/jryans/key-setup-device-undefined
Keep local device after processing device list sync
2020-10-05 14:49:18 +01:00
J. Ryan Stinnett a0317d9587 Fix tests 2020-10-05 14:29:30 +01:00
J. Ryan Stinnett fa6ce0cb70 Keep local device after processing device list sync
This change ensure we always keep the local device in the device list, even if
the server claims it doesn't exist. This is important for initial startup, as it
is possible the device key upload takes a while to apply, and thus might not
appear in the first device list sync.

Fixes https://github.com/vector-im/element-web/issues/15319
2020-10-05 14:09:16 +01:00
David Baker 7471ff4b0d Make setSinkId reliable
As per comment, this seems to be the way to make setSinkId work reliably on Chrome.

Also don't make our own mediastreams if we don't need to (ie. in a normal call
our tracks should come with their owen MediaStreams).
2020-10-03 16:57:33 +01:00
Hubert Chathi 65a5bfac88 update property name 2020-10-02 15:52:09 -04:00
David Baker d2321410f8 Pass stream to addTrack too
Otherwise we end up with separate, unbundled 'streamless tracks'
which is not what we want.
2020-10-02 15:12:12 +01:00
Hubert Chathi f90b5a99cd Merge remote-tracking branch 'origin/develop' into dehydration 2020-10-01 21:55:05 -04:00
David Baker cc4656b36a Update call.js to use new WebRTC APIs
and some other related & semi-related refactoring
2020-10-01 19:15:49 +01:00
J. Ryan Stinnett 34bc63a146 Merge pull request #1489 from matrix-org/jryans/no-console
Enforce logger module via lint rules
2020-10-01 14:56:15 +01:00
J. Ryan Stinnett 09bd91a588 Enforce logger module via lint rules
This adds lint rules (and fixes various errors) to ensure we use the `logger`
intermediary module, rather than accessing the console directly.
2020-10-01 14:28:24 +01:00
David Baker bb3e082e5b Make direction public, remove didConnect
Various flags that may or may not have been part of the poorly defined
external API before. Neither were used internally. Make direction a
public export but remove didConnect.
2020-10-01 10:15:27 +01:00
Hubert Chathi 60c863f829 lint 2020-09-30 18:33:27 -04:00
Hubert Chathi 2d2a73bf52 make dehydrated device name configurable 2020-09-30 18:05:52 -04:00
Hubert Chathi 7b9f73709d use "dehydration" as the cache name 2020-09-30 17:50:37 -04:00
Hubert Chathi 1dc89f642d use logger instead of console 2020-09-30 17:46:18 -04:00
David Baker a78b59010a Convert call.js to Typescript
This does a fairly basic rewriting of the whole file to bring it up
to a vaguely modern standard of code. Nothing should have changed
fundamentally, but it probably should do: as per the global type
overrides, we use a lot of deprecated WebRTC APIs and should probably
update.
2020-09-30 18:54:48 +01:00
Hubert Chathi a2e1d94fcf more lint 2020-09-30 01:22:46 -04:00
Hubert Chathi b181c83b93 lint 2020-09-30 01:16:57 -04:00
Hubert Chathi cf7c84c4ba more linting, because GitHub is showing me outdated CI info 2020-09-30 01:10:33 -04:00
Hubert Chathi 6e5230f9f9 fix lint again 2020-09-30 01:07:35 -04:00
Hubert Chathi f03f7c0acb fix lint 2020-09-30 00:58:37 -04:00
Hubert Chathi aa9b807b82 update to latest dehydration endpoints, and simplify API 2020-09-30 00:48:07 -04:00
Travis Ralston fa9921e091 Merge pull request #1462 from orangecms/develop
extend method redactEvent with reason
2020-09-28 15:05:00 -06:00
RiotRobot a9a6b2de48 Merge branch 'master' into develop 2020-09-28 16:09:28 +01:00
RiotRobot 1ef746658f v8.4.1 2020-09-28 16:06:30 +01:00
RiotRobot fe0099b497 Prepare changelog for v8.4.1 2020-09-28 16:06:30 +01:00
J. Ryan Stinnett 7e9c4146a5 Merge pull request #1486 from matrix-org/dbkr/catch_exceptions_from_call_event_handler_rel
Catch exception from call event handler
2020-09-28 16:01:07 +01:00
David Baker 3e978c64e4 Merge pull request #1484 from matrix-org/dbkr/catch_exceptions_from_call_event_handler
Catch exception from call event handler
2020-09-28 15:56:29 +01:00
David Baker 13c5920a46 Merge pull request #1485 from matrix-org/dbkr/ignore_invalid_candidates_rel
Ignore invalid candidates
2020-09-28 15:55:00 +01:00
David Baker 3b19203fd2 Catch exception from call event handler
Otherwise they leak out ot the sync loop. It's unfortunate that
exceptions from event handlers leak out to the emitter, and this
seems a bit clumsy, but having to wrap eveery event emit in a try
block seems worse.
2020-09-28 15:48:06 +01:00
David Baker 84cd05b218 Ignore invalid candidates 2020-09-28 15:47:02 +01:00
David Baker 9b9a9642ee Merge pull request #1483 from matrix-org/dbkr/ignore_invalid_candidates
Ignore invalid candidates
2020-09-28 15:41:32 +01:00
David Baker 639d2317ed Catch exception from call event handler
Otherwise they leak out ot the sync loop. It's unfortunate that
exceptions from event handlers leak out to the emitter, and this
seems a bit clumsy, but having to wrap eveery event emit in a try
block seems worse.
2020-09-28 15:39:21 +01:00
David Baker 52379d7655 Ignore invalid candidates 2020-09-28 15:22:01 +01:00
Michael Telatynski 29827362d6 Merge pull request #1478 from matrix-org/t3chguy-patch-1
Always push docs if they are generated
2020-09-28 14:32:07 +01:00
RiotRobot f33315f610 Merge branch 'master' into develop 2020-09-28 14:23:19 +01:00
RiotRobot aec92a41da v8.4.0 2020-09-28 14:20:06 +01:00
RiotRobot d570811ddc Prepare changelog for v8.4.0 2020-09-28 14:20:06 +01:00
J. Ryan Stinnett 9cd015a218 Merge pull request #1482 from matrix-org/jryans/guard-backup-sig-release
Only sign key backup with cross-signing keys when available
2020-09-28 12:13:23 +01:00
J. Ryan Stinnett 8d2cc5096e Fix default key cache check 2020-09-28 10:38:13 +01:00
J. Ryan Stinnett 7ec0bf69f3 Only sign key backup with cross-signing keys when available
This changes the key backup setup step to only sign with cross-signing keys when
both the public and private keys are already available without prompting. In
many cases down these paths, the cross-signing keys either may not exist or may
not be accessible. We always sign the key backup with your device key as well,
so there is always a route to trust the key backup even if this is skipped.

Fixes https://github.com/vector-im/element-web/issues/15230
2020-09-28 10:38:13 +01:00
J. Ryan Stinnett f3a8306107 Merge pull request #1481 from matrix-org/jryans/guard-backup-sig
Only sign key backup with cross-signing keys when available
2020-09-28 10:21:34 +01:00
J. Ryan Stinnett 2b29e9934c Fix default key cache check 2020-09-28 10:16:20 +01:00
J. Ryan Stinnett 4b1104e463 Only sign key backup with cross-signing keys when available
This changes the key backup setup step to only sign with cross-signing keys when
both the public and private keys are already available without prompting. In
many cases down these paths, the cross-signing keys either may not exist or may
not be accessible. We always sign the key backup with your device key as well,
so there is always a route to trust the key backup even if this is skipped.

Fixes https://github.com/vector-im/element-web/issues/15230
2020-09-25 17:36:47 +01:00
Hubert Chathi 60b9ef959d don't include an empty signatures field when calculating the signature 2020-09-24 18:46:17 -04:00
Hubert Chathi 0074c2cf57 mark fallback keys as fallback 2020-09-24 18:09:07 -04:00
Hubert Chathi b0204ab54e use unstable prefix 2020-09-24 17:41:08 -04:00
J. Ryan Stinnett bc5b07aa75 Merge pull request #1479 from matrix-org/jryans/upgrade-deps-2020-09-23
Upgrade dependencies
2020-09-24 16:40:36 +01:00
Michael Telatynski cfe90dbed5 Always push docs if they are generated 2020-09-24 12:47:23 +01:00
J. Ryan Stinnett fa913655bc Upgrade dependencies 2020-09-23 17:35:24 +01:00
RiotRobot af0b6fc6ac v8.4.0-rc.1 2020-09-23 15:10:06 +01:00
RiotRobot 6347031f61 Prepare changelog for v8.4.0-rc.1 2020-09-23 15:10:06 +01:00
J. Ryan Stinnett 3eda039898 Merge pull request #1477 from matrix-org/hs/fix-no-queryparams
If there are extraParams set, ensure that queryParams is defined
2020-09-23 14:20:55 +01:00
Will Hunt 5447d99481 If there are extraParams set, ensure that queryParams is defined 2020-09-23 14:06:39 +01:00
J. Ryan Stinnett 9d08744fe2 Merge pull request #1475 from matrix-org/jryans/security-diags
Add diagnostics to security bootstrap paths
2020-09-23 13:18:28 +01:00
J. Ryan Stinnett 848fa257ea Add diagnostics to security bootstrap paths
May help with https://github.com/vector-im/element-web/issues/15230
2020-09-23 12:37:13 +01:00
Michael Telatynski 54553fb671 Merge pull request #1459 from matrix-org/t3chguy/docdash
Switch to a combination of better-docs and docdash
2020-09-22 17:25:13 +01:00
Daniel Maslowski aad7484c8d Add optional reason to be passed to redactEvent
Signed-off-by: Daniel Maslowski <info@orangecms.org>
2020-09-21 21:50:38 +02:00
J. Ryan Stinnett ad658ead37 Merge pull request #1474 from matrix-org/jryans/stop-sudden-key-prompts
Undo attempts to cache private keys aggressively
2020-09-21 17:00:11 +01:00
J. Ryan Stinnett c787f0e9d0 Remove redundant comment 2020-09-21 16:29:23 +01:00
J. Ryan Stinnett 6f5da701aa Undo attempts to cache private keys aggressively
This was triggering security prompts at random times (after any device list
update).

Fixes https://github.com/vector-im/element-web/issues/15250
2020-09-21 16:21:41 +01:00
J. Ryan Stinnett a01368bd60 Merge pull request #1472 from matrix-org/jryans/cross-signing-half-cached
Repair secret storage reset, cache keys when missing
2020-09-21 13:03:10 +01:00
Hubert Chathi 16dccd75c1 fix string 2020-09-18 19:20:09 -04:00
Hubert Chathi 5151b52688 Merge remote-tracking branch 'origin/develop' into dehydration 2020-09-18 19:13:22 -04:00
Michael Telatynski f682097e45 Merge pull request #1471 from matrix-org/t3chguy/fix/15204
Prevent parallel getVersions calls
2020-09-18 18:08:50 +01:00
J. Ryan Stinnett 89ebffd69f Tweak error handling 2020-09-18 16:28:18 +01:00
David Baker cdeb4ead9e Merge pull request #1473 from matrix-org/dbkr/end_of_candidates
Send end-of-candidates
2020-09-18 15:46:31 +01:00
David Baker 79cc835874 Send end-of-candidates
As per https://github.com/matrix-org/matrix-doc/pull/2746
2020-09-18 15:06:41 +01:00
Travis Ralston 8df866865d Merge pull request #1470 from matrix-org/travis/e2e-default-serverside
Add a function for checking the /versions flag for forced e2ee
2020-09-18 07:50:50 -06:00
J. Ryan Stinnett fa7eff50e5 Cache cross-signing private keys if missing as well
We were only caching private keys locally on public key change, but we should
also do so if they are missing from the current session: e.g. for most users
that will be true for the master key, since previously we were not caching it.

Part of https://github.com/vector-im/element-web/issues/15230
2020-09-18 14:46:22 +01:00
Michael Telatynski 6558881952 update comment
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-09-18 14:29:50 +01:00
Michael Telatynski 9cbe2c985d Prevent parallel getVersions calls
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-09-18 14:28:37 +01:00
J. Ryan Stinnett 7769c53cd6 Skip over key when uncached
This change ensures we omit any uncached keys, rather than adding nulls. This is
helpful for future steps that might try to store all of the values in one call.

Fixes https://github.com/vector-im/element-web/issues/15230
2020-09-18 12:57:55 +01:00
Travis Ralston 7a5a47ecc7 Add a function for checking the /versions flag for forced e2ee 2020-09-17 22:44:34 -06:00
Hubert Chathi 492c1d77d8 style fixes 2020-09-16 17:09:33 -04:00
Will Hunt 8c315ebd15 Merge pull request #1469 from matrix-org/hs/usingE2EProxy-flag
Add option to allow users of pantialaimon to use the SDK
2020-09-16 17:01:11 +01:00
Will Hunt 6044b2cbad fix lint & tests 2020-09-16 16:49:51 +01:00
Will Hunt 7acfd0b423 Improve flag name and docstring
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2020-09-16 16:19:25 +01:00
Will Hunt e201aab440 Add 'usingE2EProxy' to allow users of pantialaimon to use the SDK 2020-09-15 17:43:17 +01:00
Hubert Chathi 3952768667 Merge remote-tracking branch 'origin/develop' into fallback_keys 2020-09-15 12:04:30 -04:00
J. Ryan Stinnett fb25fa3a27 Merge pull request #1468 from Charly98cma/develop
Fixed Yarn broken link
2020-09-15 16:26:13 +01:00
Carlos 857ad9b180 Fixed Yarn broken link
Yarn install guide link was broken, so I just fixed.
2020-09-15 17:08:36 +02:00
Hubert Chathi 14843a1bca Merge pull request #1466 from matrix-org/uhoreg/typescript-fixes
some TypeScript and doc fixes
2020-09-15 09:17:27 -04:00
Hubert Chathi 6331b34cf7 lint 2020-09-14 23:00:07 -04:00
Hubert Chathi 2ebe1dfa16 support olm fallback keys 2020-09-14 22:10:16 -04:00
Hubert Chathi 6bdbee533c TypeScript and doc fixes 2020-09-14 18:53:48 -04:00
RiotRobot b9886d4f34 Merge branch 'master' into develop 2020-09-14 13:29:32 +01:00
RiotRobot 666cbbce08 v8.3.0 2020-09-14 13:26:27 +01:00
RiotRobot 69c575a4be Prepare changelog for v8.3.0 2020-09-14 13:26:27 +01:00
J. Ryan Stinnett c920de0d28 Merge pull request #1464 from matrix-org/jryans/remove-travis-refs
Remove Travis CI reference
2020-09-10 15:32:10 +01:00
Travis Ralston 53cdf53f63 Merge pull request #1463 from matrix-org/travis/fix-3pid-createRoom
Inject identity server token for 3pid invites on createRoom
2020-09-10 07:05:01 -06:00
J. Ryan Stinnett d9dda6aebc Remove Travis CI reference
Fixes https://github.com/vector-im/element-web/issues/15098
2020-09-10 10:16:54 +01:00
Travis Ralston 3d4a9a24ce Inject identity server token for 3pid invites on createRoom
We only inject if needed to avoid potentially overwriting the value with something unknown. The docs in the react-sdk imply this was supposed to happen.

Fixes https://github.com/vector-im/element-web/issues/14814
2020-09-09 12:33:00 -06:00
RiotRobot de339d3098 v8.3.0-rc.1 2020-09-09 15:06:48 +01:00
RiotRobot 0f83234be9 Prepare changelog for v8.3.0-rc.1 2020-09-09 15:06:47 +01:00
Michael Telatynski bcd9b45589 Switch to a combination of better-docs and docdash 2020-09-04 11:08:26 +01:00
Hubert Chathi 19f3996e09 only cross-sign dehydrated device if cross-signing is available 2020-09-03 16:06:06 -04:00
Hubert Chathi 25c2cc1768 various fixes 2020-09-03 11:45:37 -04:00
J. Ryan Stinnett eec7c4c61b Merge pull request #1452 from dalcde/develop
Add missing options in ICreateClientOpts
2020-09-03 15:51:25 +01:00
J. Ryan Stinnett 25b4b049b7 Merge pull request #1457 from matrix-org/jryans/is-x-ready-bools
Ensure ready functions return boolean values
2020-09-02 17:30:16 +01:00
J. Ryan Stinnett 2191eb3f41 Ensure ready functions return boolean values
Fixes https://github.com/vector-im/element-web/issues/15087
2020-09-02 17:18:47 +01:00
J. Ryan Stinnett bebdbf7e05 Merge pull request #1456 from matrix-org/jryans/cross-signing-verif-errors
Handle missing cross-signing keys gracefully
2020-09-02 15:07:27 +01:00
J. Ryan Stinnett 646c091966 Skip self-signing if device already signed
When adding a device to your account, both devices attempt to sign each other
using the self-signing key, but in reality only the new device needs to be
signed. This avoids a case where web would fail verification while attempting to
sign the old device because of missing private keys (since they aren't in 4S and
it hasn't requested them from the other device yet), even though signing the old
device is redundant anyway.

Part of https://github.com/vector-im/element-web/issues/14970
2020-09-02 12:54:26 +01:00
J. Ryan Stinnett 952729cb1b Abort early if cross-signing key not found
This helps us print a better error message when the key does not exist.

Part of https://github.com/vector-im/element-web/issues/14970
2020-09-02 12:32:35 +01:00
RiotRobot c6992e2056 Merge branch 'master' into develop 2020-09-01 17:00:47 +01:00
RiotRobot 77ed79e9a9 v8.2.0 2020-09-01 16:57:34 +01:00
RiotRobot c4f4add0ec Prepare changelog for v8.2.0 2020-09-01 16:57:33 +01:00
Michael Telatynski 7eeb60c838 Merge pull request #1451 from matrix-org/t3chguy/lint-ts
Fix eslint ts override tsx matching
2020-09-01 09:10:22 +01:00
Hubert Chathi 438861ae5e separate dehydration functions to its own file and cache the key 2020-08-31 15:44:29 -04:00
Dexter Chua d42cdbbc5b Add missing options in ICreateClientOpts
This makes the examples in the README actually compile.

Signed-off-by: Dexter Chua <dec41@srcf.net>
2020-08-29 14:40:40 +08:00
Michael Telatynski 66237e1ea6 Fix eslint ts override tsx matching 2020-08-29 01:10:08 +01:00
J. Ryan Stinnett a51c0450c3 Merge pull request #1450 from matrix-org/jryans/defer-cross-signing-setup
Untangle cross-signing and secret storage
2020-08-28 12:40:28 +01:00
J. Ryan Stinnett 7a2416bb6d Add callback for app to cache secret storage key
This is useful when e.g. resetting both secret storage and cross-signing
together, as it avoids prompting for the secret storage key that was just
created.
2020-08-28 12:07:35 +01:00
J. Ryan Stinnett a1528e9e33 Change log messages for uniqueness 2020-08-28 11:38:19 +01:00
J. Ryan Stinnett 71cc4d535e Clean up comment 2020-08-28 11:36:02 +01:00
J. Ryan Stinnett c06723df3d Always store cross-signing keys when new 4S key ID 2020-08-28 11:35:25 +01:00
J. Ryan Stinnett 06b285c013 Add comment about trap with multiple devices 2020-08-28 11:11:51 +01:00
J. Ryan Stinnett 49c06ef0ca Check cross-signing reset branch first
If we're resetting keys, we have to check that branch first.
2020-08-28 11:00:10 +01:00
J. Ryan Stinnett 5f92357fec Tweak code style 2020-08-27 17:32:46 +01:00
J. Ryan Stinnett 3e0dd3d918 Copy docs to client.js as well 2020-08-27 14:09:12 +01:00
J. Ryan Stinnett f19d76b08d Untangle cross-signing and secret storage
This untangles cross-signing and secret storage setup into separate path that
can be invoked independently. There is no functional change with this patch, but
instead this just separates one giant monster API into two.

Part of https://github.com/vector-im/element-web/issues/13895
2020-08-27 13:32:54 +01:00
RiotRobot 8b6b16067b v8.2.0-rc.1 2020-08-26 11:45:48 +01:00
RiotRobot a2da0de17d Prepare changelog for v8.2.0-rc.1 2020-08-26 11:45:48 +01:00
J. Ryan Stinnett 93ff3edb6b Merge pull request #1449 from matrix-org/jryans/strict-enc-check
Add state event check
2020-08-26 11:03:42 +01:00
J. Ryan Stinnett 7c67fd69dd Add state event check
State events are never encrypted, so we can ignore them here.
2020-08-26 10:55:03 +01:00
J. Ryan Stinnett 5ef5412a55 Move storage methods up to constructor 2020-08-25 19:27:26 +01:00
J. Ryan Stinnett e88a384aa7 Update and reformat client.js docs copy 2020-08-25 12:56:30 +01:00
J. Ryan Stinnett 9067feeafb Update comment on 4S signatures
As part of changing 4S to symmetric encryption, we no longer sign the 4S key
with the cross-signing MSK.
2020-08-25 12:56:25 +01:00
J. Ryan Stinnett ed978f69fb Merge pull request #1444 from matrix-org/jryans/secure-backup-required
Add method to check whether client .well-known has been fetched
2020-08-24 17:32:49 +01:00
J. Ryan Stinnett 743f2465ea Document WellKnown.client event 2020-08-24 15:56:14 +01:00
J. Ryan Stinnett 41fffa233a Switch to promise-based API 2020-08-24 15:56:14 +01:00
J. Ryan Stinnett e45377166b Merge pull request #1443 from matrix-org/jryans/cross-signing-auth-errors
Handle auth errors during cross-signing key upload
2020-08-24 11:52:40 +01:00
David Baker 24939bf0b0 Merge pull request #1448 from matrix-org/dbkr/dont_fail_if_audio_output_unavailable
Don't fail if the requested audio output isn't available
2020-08-21 16:19:20 +01:00
David Baker 3221be4855 Don't fail if the requested audio output isn't available
This thorws an exception if the requested device isn't available,
in which case we should catch it  & carry on with the default device.

Fixes https://github.com/vector-im/element-web/issues/15019
2020-08-21 16:02:08 +01:00
David Baker 3135f1ed24 Merge pull request #1447 from matrix-org/dbkr/fix_log_fail
Fix logging failures
2020-08-21 11:12:11 +01:00
David Baker 1b0834ffb0 Fix logging failures 2020-08-21 11:01:34 +01:00
Hubert Chathi ad85740ae2 allow other login functions to be used 2020-08-19 18:01:31 -04:00
David Baker d79d613cb7 Merge pull request #1446 from matrix-org/dbkr/log_user_media_constraints
Log the constraints we pass to getUserMedia
2020-08-19 18:30:39 +01:00
David Baker d8cc1f7b7a Log the constraints we pass to getUserMedia
To help debug voip calls
2020-08-19 18:13:44 +01:00
J. Ryan Stinnett d7c8856fdd Add method to check whether client .well-known has been fetched
This allows clearly detecting whether we have _ever_ fetched .well-known at all.
Without this, it's hard to be sure whether the value is `undefined` because the
fetch has not been attempted yet or because an error occurred.
2020-08-19 16:10:10 +01:00
J. Ryan Stinnett 9d80a332aa Upload cross-signing keys first to handle failure
This changes to uploading cross-signing keys first, since they require a valid
UI auth session, and so are more likely to fail than other API calls. With the
new ordering, if they do fail, then by failing first, we won't have made any
changes to the user's account, so everything rolls back correctly.
2020-08-19 11:55:09 +01:00
J. Ryan Stinnett e14f7b63c7 Handle auth errors during cross-signing key upload
In order to handle auth errors (such as incorrect passwords), we need to ensure
we only try to upload cross-signing keys from within the auth flow helper
function.

This rearranges things to store that function in the builder to use it when the
actual upload happens.
2020-08-19 11:44:41 +01:00
Travis Ralston 3bd2880923 Revert "Merge pull request #1440 from matrix-org/travis/spec-i18n"
This reverts commit 2401ad7159.
2020-08-18 13:07:31 -06:00
Travis Ralston 2401ad7159 Merge pull request #1440 from matrix-org/travis/spec-i18n
Use SAS emoji data from matrix-doc
2020-08-18 11:41:36 -06:00
Hubert Chathi 6e8e3e4150 use newer version of dehydration proposal and add some comments 2020-08-17 18:09:23 -04:00
Travis Ralston 5d95398621 Use SAS emoji data from matrix-doc
Fixes https://github.com/vector-im/element-web/issues/14947

Much like element-web's Jitsi wrapper build steps, this downloads the emoji JSON at build time to ensure it gets reasonably updated. In the future, the spec might want to consider publishing a dedicated i18n package on npm for this, however this is fine for now. We download rather than copy/paste to ensure we always have an updated copy.
2020-08-17 15:35:03 -06:00
RiotRobot 64cdd73b93 v8.1.0 2020-08-17 12:43:32 +01:00
RiotRobot 48a9236ea8 Prepare changelog for v8.1.0 2020-08-17 12:43:31 +01:00
RiotRobot 8b3126e9d8 v8.1.0-rc.1 2020-08-13 12:05:35 +01:00
RiotRobot 74d497cd2d Prepare changelog for v8.1.0-rc.1 2020-08-13 12:05:35 +01:00
Michael Telatynski 5070a5c598 Merge pull request #1438 from brettz9/patch-1
Update on Promises
2020-08-09 13:11:51 +01:00
Brett Zamir 2e30b08e74 Update on Promises
It seems that with ES6 Promises being apparently used, the reference to `done()` is outdated.

Also clarifies that it is "problematic" to discard the results of promises rather than it being an "error" .
2020-08-09 17:39:53 +08:00
J. Ryan Stinnett 8bf63f5f0b Merge pull request #1437 from matrix-org/jryans/defer-cross-signing-setup
Store and request master cross-signing key
2020-08-07 16:06:28 +01:00
J. Ryan Stinnett 11665d18ee Cache all cross-signing private keys when checking trust
This ensures we try to get all private keys when e.g. logging in and using the
passphrase to unlock 4S.

Part of https://github.com/vector-im/element-web/issues/13896
2020-08-06 16:43:56 +01:00
J. Ryan Stinnett a8a9fc0c9d Log secret name received via sharing 2020-08-06 16:43:56 +01:00
J. Ryan Stinnett 098cd1b8d4 Request master cross-signing private key during verification
This change adds a request for the master cross-signing private key, in case the
other device is willing to share it.

Part of https://github.com/vector-im/element-web/issues/13896
2020-08-06 16:43:56 +01:00
J. Ryan Stinnett 3166a4880d Move cross-signing key request path out of verification
This was always quite awkwardly placed in verification, so this moves it to
somewhere more expected. No changes are made to the logic or features in this
commit.
2020-08-06 16:39:56 +01:00
J. Ryan Stinnett 9d1c7136cc Store master cross-signing private key in local cache
We now want to store all private keys in the local cache, including the master
key if available.

Part of https://github.com/vector-im/element-web/issues/13896
2020-08-06 16:39:56 +01:00
J. Ryan Stinnett e100943edf Fix typo in comments 2020-08-06 16:24:49 +01:00
RiotRobot a6fe4cdf1c Merge branch 'master' into develop 2020-08-05 15:30:42 +01:00
RiotRobot 8b5213c09a v8.0.1 2020-08-05 15:27:22 +01:00
RiotRobot 23a133c825 Prepare changelog for v8.0.1 2020-08-05 15:27:22 +01:00
J. Ryan Stinnett 69c4496dfe Merge pull request #1433 from matrix-org/bwindels/fixinvaliddisplaynames
Filter out non-string display names
2020-08-05 15:03:53 +01:00
Hubert Chathi 8a4440c314 initial work on dehydration 2020-08-04 16:06:19 -04:00
Bruno Windels 4d74cca206 Merge pull request #1434 from matrix-org/bwindels/fixinvaliddisplaynames-release
Filter out non-string display names
2020-08-04 13:55:40 +00:00
Bruno Windels 955e081699 respect signature 2020-08-04 15:48:48 +02:00
Bruno Windels b7e73422ab filter out non-string display names 2020-08-04 15:48:48 +02:00
Bruno Windels a9a4ba33aa respect signature 2020-08-04 15:43:57 +02:00
Bruno Windels 7116ad9f58 filter out non-string display names 2020-08-04 15:30:32 +02:00
J. Ryan Stinnett 3d18bdb2aa Merge pull request #1427 from matrix-org/dependabot/npm_and_yarn/elliptic-6.5.3
Bump elliptic from 6.5.2 to 6.5.3
2020-08-04 10:38:10 +01:00
J. Ryan Stinnett 4c14581606 Merge pull request #1431 from matrix-org/jryans/riot-to-element
Replace Riot with Element in docs and comments
2020-08-04 10:27:32 +01:00
J. Ryan Stinnett a9c9ec3977 Replace Riot with Element in docs and comments
This only covers the simple cases of references to issues and repos. More
complex areas, such as deployment scripts, will be handled separately.

Part of https://github.com/vector-im/element-web/issues/14864
2020-08-03 18:32:52 +01:00
J. Ryan Stinnett 694d1f9631 Merge pull request #1430 from matrix-org/jryans/rm-tslint
Remove leftover bits of TSLint
2020-07-31 20:30:44 +01:00
J. Ryan Stinnett 2b9cfae18a Remove leftover bits of TSLint 2020-07-31 14:31:19 +01:00
RiotRobot 800d8380ce v8.0.1-rc.1 2020-07-31 13:18:04 +01:00
RiotRobot 621ca28f68 Prepare changelog for v8.0.1-rc.1 2020-07-31 13:18:03 +01:00
J. Ryan Stinnett 4792241ff6 Merge pull request #1426 from matrix-org/jryans/clean-lint-deps
Remove redundant lint dependencies
2020-07-31 11:42:21 +01:00
David Baker 0b984df5f9 Merge pull request #1428 from matrix-org/dbkr/upload_keys_when_new_key_backup_seen
Upload all keys when we start using a new key backup version
2020-07-30 22:46:39 +01:00
David Baker 2e260155ea Merge pull request #1429 from matrix-org/dbkr/countSessionsNeedingBackup
Expose countSessionsNeedingBackup
2020-07-30 22:46:22 +01:00
David Baker b15c8a2d1c Expose countSessionsNeedingBackup
Useful to see the number of keys waiting for backup (it's exposed
via events but you couldn;t get it directly). Also clarify doc on
the return value of `flagAllGroupSessionsForBackup` which was not
technically incorrect...
2020-07-30 19:05:45 +01:00
David Baker 94ab317f23 Upload all keys when we start using a new key backup version
Although see comment and other bug created

Fixes https://github.com/vector-im/riot-web/issues/14828
2020-07-30 18:08:48 +01:00
dependabot[bot] 02b37e1219 Bump elliptic from 6.5.2 to 6.5.3
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-30 12:41:01 +00:00
J. Ryan Stinnett 9d25848a21 Remove unused Jest lint plugin 2020-07-29 11:55:50 +01:00
J. Ryan Stinnett 3958768e1f Remove redundant lint dependencies
These are no longer needed with the new standard lint repo.
2020-07-29 11:36:57 +01:00
Jorik Schellekens cec00cd303 Merge pull request #1422 from matrix-org/joriks/conigure-eslint
Configure and use new eslint package
2020-07-28 19:53:54 +01:00
Jorik Schellekens 45cfef4294 New lines for let statements 2020-07-28 14:31:21 +01:00
Jorik Schellekens 2a01e99635 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into joriks/conigure-eslint 2020-07-28 14:17:02 +01:00
Jorik Schellekens a9c3aee447 Revert quotes change in ts 2020-07-28 14:16:46 +01:00
RiotRobot c669382e12 v8.0.0 2020-07-27 20:50:09 +01:00
RiotRobot 19251c427a Prepare changelog for v8.0.0 2020-07-27 20:50:08 +01:00
Michael Telatynski a7aec9e2a4 Merge pull request #1424 from matrix-org/t3chguy/txnId
Properly support txnId
2020-07-24 00:01:31 +01:00
Michael Telatynski 99d7622b42 Properly support txnId
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-07-22 11:09:07 +01:00
Michael Telatynski 8728619d2c Merge pull request #1423 from matrix-org/t3chguy/depr/1
[BREAKING] Remove deprecated getIdenticonUri
2020-07-21 21:35:30 +01:00
Michael Telatynski f6d51fdfb8 remove outdated identicon tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-07-21 21:30:25 +01:00
Michael Telatynski 0ffaa8d617 Remove deprecated getIdenticonUri
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-07-21 21:18:17 +01:00
Jorik Schellekens 8a974172ab Fix babel 2020-07-21 10:40:02 +01:00
Jorik Schellekens 72675f7266 Lint ts 2020-07-21 10:33:16 +01:00
Jorik Schellekens 0f559050d8 Fix whitespace issues 2020-07-21 10:00:16 +01:00
Jorik Schellekens 58084774bf fix lock 2020-07-21 09:56:17 +01:00
Travis Ralston c7aee7cebd Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/lodash-4.17.19' into develop 2020-07-20 15:47:57 -06:00
Jorik Schellekens c68d135ae8 Use new eslint 2020-07-20 21:48:28 +01:00
dependabot[bot] 9ed6c99ec8 Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 19:28:57 +00:00
Travis Ralston 7d398d41d0 Merge pull request #1419 from matrix-org/travis/general/perf/js-roomstate-map
[BREAKING] Convert RoomState's stored state map to a real map
2020-07-16 12:42:31 -06:00
Travis Ralston 0af763252c Have the comment represent reality 2020-07-16 12:42:17 -06:00
Travis Ralston 1c9dbbbb19 [BREAKING] Convert RoomState's stored state map to a real map
Though the dictionary format works fine, it's slow. Access times are around the 1ms range for rooms like HQ, which doesn't seem like much, but when compared to the Map's access time of 0.05ms it's slow.

Converting things to a map means we lose index access and have to instead use `.keys()` and `.values()`, both of which return iterables and not arrays. Even doing the `Array.from()` conversion we see times in the 0.05ms range. This is also what makes this a breaking change.

Memory-wise there does not appear to be any measurable impact for a large account.
2020-07-08 22:28:14 -06:00
RiotRobot 2a688bdac8 v7.1.0 2020-07-03 13:15:07 +01:00
RiotRobot 3c53c818cb Prepare changelog for v7.1.0 2020-07-03 13:15:07 +01:00
RiotRobot 8e53cb324e v7.1.0-rc.1 2020-07-01 14:15:43 +01:00
RiotRobot efbd454775 Prepare changelog for v7.1.0-rc.1 2020-07-01 14:15:43 +01:00
Bruno Windels 68c7273f56 Merge pull request #1414 from matrix-org/bwindels/fixbootstraperror
Ask general crypto callbacks for 4S privkey if operation adapter doesn't have it yet
2020-06-26 12:53:11 +00:00
Bruno Windels 8b56ff4eff ask general crypto callbacks for 4S privkey if operation adapter doesn't 2020-06-26 10:59:04 +02:00
Michael Telatynski 8134eedd93 Merge pull request #1413 from matrix-org/t3chguy/hf
Fix ICreateClientOpts missing idBaseUrl
2020-06-25 23:34:31 +01:00
Michael Telatynski a87ae770cc Fix ICreateClientOpts missing idBaseUrl
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-06-25 21:58:15 +01:00
J. Ryan Stinnett 355da0f9a9 Merge pull request #1411 from matrix-org/jryans/more-room-listeners
Increase max event listeners for rooms
2020-06-25 13:54:11 +01:00
J. Ryan Stinnett 10bdd63762 Increase max event listeners for rooms
It's common to add listeners for every displayed Matrix event, so this increases
the default to remove some log noise.
2020-06-25 13:37:35 +01:00
Hubert Chathi 69dc518c2c Merge pull request #1406 from matrix-org/uhoreg/distrust_backup
Don't trust keys megolm received from backup for verifying the sender
2020-06-23 15:37:27 -04:00
Hubert Chathi fa1dddf06c apply feedback from review 2020-06-23 15:20:41 -04:00
RiotRobot f683f4544a Merge branch 'master' into develop 2020-06-23 14:45:24 +01:00
RiotRobot bc5b587651 v7.0.0 2020-06-23 14:42:09 +01:00
RiotRobot 8083031029 Prepare changelog for v7.0.0 2020-06-23 14:42:09 +01:00
Travis Ralston 7b8102a42c Merge pull request #1410 from matrix-org/travis/room-list/setting-diff
Raise the last known account data / state event for an update
2020-06-22 14:10:16 -06:00
Travis Ralston dbe2f5e4db Fix lint 2020-06-22 10:24:02 -06:00
Travis Ralston f27791b7de Raise the last known account data / state event for an update
This is to better support the react-sdk in trying to identify what actually changed in settings instead of re-firing everything. 

Overall this change differs from `prev_content` semantics as it's the last known *stored* event, not whatever the server thinks may or may not have changed.
2020-06-22 10:17:42 -06:00
Hubert Chathi 2a78170395 lint 2020-06-19 19:31:43 -04:00
Hubert Chathi cfca1c7b06 add unit tests and improve docs 2020-06-19 19:27:29 -04:00
Hubert Chathi fb7a67025f add information function to return information about event encryption 2020-06-18 21:43:50 -04:00
Bruno Windels 8a6cd48b8e Merge pull request #1380 from matrix-org/bwindels/bootstrap-operation
Isolate encryption bootstrap side-effects
2020-06-18 14:29:35 +00:00
David Baker e21d1f539d Merge pull request #1405 from matrix-org/dbkr/fix_verification_race
Add method to get current in-flight to-device requests
2020-06-18 15:10:58 +01:00
Bruno Windels f62049559c make authUploadDeviceSigningKeys required, as default is only used by tests
and make tests pass {} instead of undefined for authDict as otherwise
we'll enter in the "obtain flows" mode and not add the keys to the builder
2020-06-18 15:06:56 +02:00
Bruno Windels 1fe9dd03a3 apparently we call the callback also to obtain flows, so handle that 2020-06-18 14:39:16 +02:00
RiotRobot c3283a7297 v7.0.0-rc.1 2020-06-17 21:33:36 +01:00
RiotRobot f423164a1c Prepare changelog for v7.0.0-rc.1 2020-06-17 21:33:35 +01:00
Bruno Windels 75fe596e24 fix tests 2020-06-17 15:36:24 +02:00
Bruno Windels c5eb290e66 remove resetCrossSigningKeys
it was only used by the bootstrap method in js-sdk,
and was not used in react-sdk either.

This is a breaking change, in case anything other than react-sdk
was using this.
2020-06-17 15:34:30 +02:00
Bruno Windels d3b2c8246d remove try/finally as we don't monkey patch the callbacks anymore 2020-06-17 15:34:02 +02:00
Bruno Windels c11796af4b don't fix old key when we're force-creating a new one 2020-06-17 15:29:21 +02:00
Bruno Windels 8591815d66 add comment 2020-06-17 15:26:30 +02:00
Bruno Windels b82870adb2 move key backup bootstrap/migration over to builder 2020-06-17 15:25:42 +02:00
Bruno Windels 3c5b304b6b move cross-signing bootstrapping over to builder 2020-06-17 15:22:24 +02:00
Bruno Windels f656698061 fixup: secret storage indenting 2020-06-17 15:14:26 +02:00
Bruno Windels d4b4bc5031 move 4S creation to operation 2020-06-17 15:06:41 +02:00
Bruno Windels 9bcf33b6d3 Add EncryptionSetupBuilder to capture bootstrapping side-effects 2020-06-17 14:55:19 +02:00
Hubert Chathi fa550e8f03 fix jsdoc 2020-06-16 12:03:29 -04:00
Hubert Chathi 2d73564eba apply feedback from review, and improve js docs 2020-06-16 11:38:36 -04:00
David Baker 1bd80247fd Better variable name 2020-06-16 14:27:26 +01:00
RiotRobot 1c194e8163 Merge branch 'master' into develop 2020-06-16 11:04:39 +01:00
RiotRobot db7848c9ba v6.2.2 2020-06-16 11:00:41 +01:00
RiotRobot 97497ed9d5 Prepare changelog for v6.2.2 2020-06-16 11:00:41 +01:00
J. Ryan Stinnett 60fcc652de Merge pull request #1407 from matrix-org/jryans/1403-release
Use existing session id for fetching flows as to not get a new session
2020-06-16 10:56:00 +01:00
Michael Telatynski cb57717424 undo that because {} sux 2020-06-16 10:38:41 +01:00
Michael Telatynski 590f7786eb clean up 2020-06-16 10:38:41 +01:00
Michael Telatynski 0024edcb7f Use existing session id for fetching flows as to not get a new session id 2020-06-16 10:38:41 +01:00
Michael Telatynski aa2d0d9a08 Merge pull request #1404 from matrix-org/t3chguy/push-rules-device
Remove support for unspecced device-specific push rules
2020-06-16 10:26:08 +01:00
Hubert Chathi fd126b8563 lint 2020-06-15 17:53:37 -04:00
Hubert Chathi bc97e7a5ea don't trust keys megolm received from backup for verifying the sender 2020-06-15 17:47:25 -04:00
David Baker 3dece4f46b Add method to get current in-flight to-device requests 2020-06-15 17:38:50 +01:00
Michael Telatynski be05452c70 Fix tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-06-14 13:15:28 +01:00
Michael Telatynski 583650cf7d Remove support for unspecced device-specific push rules
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-06-13 14:49:49 +01:00
Michael Telatynski 505915528f Merge pull request #1403 from matrix-org/t3chguy/attemptAuth-existing-session
Use existing session id for fetching flows as to not get a new session
2020-06-12 22:33:26 +01:00
Michael Telatynski ace8a787b4 undo that because {} sux 2020-06-12 20:06:58 +01:00
Michael Telatynski ed2ea9ac8e clean up 2020-06-12 20:00:08 +01:00
Michael Telatynski 8d09a4abe6 Use existing session id for fetching flows as to not get a new session id 2020-06-12 19:50:38 +01:00
J. Ryan Stinnett 1da959ab02 Merge pull request #1400 from matrix-org/jryans/upgrade-deps-2020-06-05
Upgrade deps
2020-06-08 10:58:29 +01:00
J. Ryan Stinnett 997dd9b88a Upgrade deps 2020-06-08 10:13:30 +01:00
RiotRobot ebe66bdd6e Merge branch 'master' into develop 2020-06-05 15:10:44 +01:00
RiotRobot 12b573bc63 v6.2.1 2020-06-05 15:07:04 +01:00
RiotRobot cc8c163e0f Prepare changelog for v6.2.1 2020-06-05 15:07:04 +01:00
Bruno Windels abc7f76679 Merge pull request #1399 from matrix-org/bwindels/backupformatbug-rc
Bring back backup key format migration
2020-06-05 13:32:58 +00:00
Bruno Windels eee04895fe take into account key can be an object now 2020-06-05 15:26:17 +02:00
Bruno Windels f520b88f79 fix type check in migration code 2020-06-05 15:26:17 +02:00
Bruno Windels f0f1c113e4 Revert "remove key backup format migration"
This reverts commit 8d81240c58.
2020-06-05 15:26:17 +02:00
Bruno Windels 84a15761ad Revert "lint"
This reverts commit 9fe0e1e85f.
2020-06-05 15:26:17 +02:00
Bruno Windels 013fbb87a7 Merge pull request #1398 from matrix-org/bwindels/backupformatbug
Bring back backup key format migration
2020-06-05 13:25:00 +00:00
Bruno Windels 73764d23dc take into account key can be an object now 2020-06-05 13:38:46 +02:00
Bruno Windels 8f62703bf2 fix type check in migration code 2020-06-05 13:38:15 +02:00
Bruno Windels 6dedae2e4d Revert "remove key backup format migration"
This reverts commit 8d81240c58.
2020-06-05 11:23:04 +02:00
Bruno Windels d32131b2b8 Revert "lint"
This reverts commit 9fe0e1e85f.
2020-06-05 11:20:23 +02:00
Bruno Windels 0a790b2ae3 Merge pull request #1313 from matrix-org/bwindels/throwifcantdecrypt4s
Fix: more informative error message when we cant find a key to decrypt with
2020-06-05 08:30:43 +00:00
RiotRobot ef1d5e3d76 Merge branch 'master' into develop 2020-06-04 15:00:26 +01:00
RiotRobot 30720bfdd7 v6.2.0 2020-06-04 14:57:14 +01:00
RiotRobot 234a18f016 Prepare changelog for v6.2.0 2020-06-04 14:57:14 +01:00
Michael Telatynski c525a19df5 Merge pull request #1394 from matrix-org/t3chguy/e2eedefault
Add js-sdk mechanism for polling client well-known for config
2020-06-03 10:47:10 +01:00
Michael Telatynski a987a31667 Merge pull request #1388 from matrix-org/dbkr/timeouts
Fix verification request timeouts to match spec
2020-06-03 10:41:15 +01:00
Michael Telatynski 10329c3436 rename _clientWellKnownInterval to _clientWellKnownIntervalID
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-06-03 10:26:46 +01:00
Travis Ralston 29f10bcd44 Merge pull request #1391 from matrix-org/hs/drop-presence-list
Drop presence list methods
2020-06-02 19:50:12 -06:00
Travis Ralston 19fe9b8ac7 Merge pull request #1395 from matrix-org/travis/less-previews
Batch up URL previews to prevent excessive requests
2020-06-02 14:33:04 -06:00
Travis Ralston 76da708352 Call the callback on cached 2020-06-02 14:19:41 -06:00
Travis Ralston 145f01ff2d Batch up URL previews to prevent excessive requests
Cache expiration is still not implemented here.
2020-06-02 13:25:37 -06:00
RiotRobot 2c2d531e7f v6.2.0-rc.1 2020-06-02 13:48:12 +01:00
RiotRobot 91e0f7fbc4 Prepare changelog for v6.2.0-rc.1 2020-06-02 13:48:12 +01:00
J. Ryan Stinnett bebeec7d84 Merge pull request #1304 from MTRNord/fix-register-auth-with-new-spec
Make auth argument in the register request compliant with r0.6.0
2020-06-02 10:13:57 +01:00
Michael Telatynski 18b1e00875 Add js-sdk mechanism for polling client well-known for config
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-06-01 22:22:01 +01:00
Travis Ralston 86d448c285 Merge pull request #1393 from matrix-org/travis/uia-3pid
Send the wrong auth params with the right auth params
2020-06-01 09:14:39 -06:00
Travis Ralston 73f8867a6f Send the wrong auth params with the right auth params
Followon from https://github.com/matrix-org/matrix-react-sdk/pull/4667
2020-06-01 09:08:58 -06:00
Will Hunt d021498fa9 Drop presence lists 2020-06-01 11:29:06 +01:00
Michael Telatynski b83aa54661 Test the verification request timeouts of 10m and 2m
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-29 14:49:35 +01:00
Michael Telatynski 429550ca3e fix thing which got missed in the revert
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-29 13:54:08 +01:00
Michael Telatynski 2a6d8c2b1d revert to previous observeOnly behaviour
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-29 13:34:19 +01:00
Michael Telatynski 01c9159830 clean up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-29 13:17:22 +01:00
Michael Telatynski 3b3ed5159c Implement verification request timeout as per the spec
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-29 13:10:25 +01:00
David Baker 636661dd45 WIP code for https://github.com/vector-im/riot-web/issues/12430 2020-05-29 09:53:03 +01:00
Hubert Chathi 0d7cb2bf25 Merge pull request #1387 from matrix-org/uhoreg/picklekey
encrypt cached keys with pickle key
2020-05-28 13:51:25 -04:00
Hubert Chathi 991f590056 another unit fix test 2020-05-27 23:32:23 -04:00
Hubert Chathi 0e6758ccbc more unit test fixes 2020-05-27 23:21:15 -04:00
Hubert Chathi 1d9c6520e5 fix unit tests 2020-05-27 20:02:40 -04:00
Hubert Chathi c81f11df0a encrypt/decrypt cached private keys with pickle key 2020-05-27 19:34:28 -04:00
Travis Ralston f39a1e70de Fix the tests 2020-05-27 13:32:28 -06:00
Travis Ralston f0fa249d36 Request fresh flows on the initial registration request 2020-05-27 13:16:10 -06:00
Travis Ralston 8f72197817 Merge branch 'develop' into fix-register-auth-with-new-spec 2020-05-27 12:14:42 -06:00
Hubert Chathi 1556ac84da add a pickle key option to the client 2020-05-27 12:57:00 -04:00
David Baker b6da6bb835 Merge pull request #1385 from Sorunome/patch-1
Fix replying to key share requests
2020-05-26 12:10:44 +01:00
Sorunome b1a882ea79 Fix replying to key share requests
This check was meant to filter out key sharing requests from ourselves. Accidentally, it filtered out sharing requests from anyone *but* ourselves.
2020-05-26 13:04:31 +02:00
Michael Telatynski 3305f2cc72 Merge pull request #1384 from matrix-org/t3chguy/cdn
Add dist to package.json files so CDNs can serve it
2020-05-25 15:56:26 +01:00
Michael Telatynski c589a5211b Add dist to package.json files so CDNs can serve it
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-25 14:29:50 +01:00
Michael Telatynski ff0d91979b Merge pull request #1382 from matrix-org/t3chguy/fix_getVersion_warning
Fix getVersion warning saying undefined room
2020-05-22 14:44:11 +01:00
Michael Telatynski 9d6888cf74 Fix getVersion warning saying undefined room
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-22 14:21:51 +01:00
David Baker a4a7097c10 Merge pull request #1379 from matrix-org/dbkr/combine_default_rules_processing
Combine the two places we processed client-level default push rules
2020-05-21 15:11:46 +01:00
David Baker cf2b058e7c Update comment on default override rules 2020-05-21 10:06:12 +01:00
Hubert Chathi 048d7a9b63 Merge pull request #1378 from matrix-org/uhoreg/fix_mac_check
make MAC check robust against unpadded vs padded base64 differences
2020-05-20 12:05:35 -04:00
David Baker d6e37d0288 Combine the two places we processed client-level default push rules
We did this in two places: updated existing rules when the rules
were stored and then added new ones every time we computed push
action for an event. Just do both when the rules are saved.
2020-05-20 14:52:37 +01:00
Bruno Windels 3cd562fa96 Merge pull request #1375 from matrix-org/bwindels/remove-keybackup-format-migration
Remove key backup format migration
2020-05-20 08:05:28 +00:00
Michael Telatynski bd569cb041 Merge pull request #1241 from matrix-org/t3chguy/fix_unhomoglyph_import
Add simple browserify browser-matrix.js tests
2020-05-19 11:58:35 +01:00
RiotRobot e3c6a0e1a0 Merge branch 'master' into develop 2020-05-19 11:04:15 +01:00
RiotRobot b29176507c v6.1.0 2020-05-19 11:01:13 +01:00
RiotRobot db25e9719b Prepare changelog for v6.1.0 2020-05-19 11:01:13 +01:00
Michael Telatynski 661901b00d tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 20:11:32 +01:00
Michael Telatynski b9352cfcc1 Fix tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 20:03:04 +01:00
Michael Telatynski e381b1901e clean up test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-18 19:39:30 +01:00
Hubert Chathi ebdff505eb make MAC check robust against unpadded vs padded base64 differences 2020-05-15 16:52:53 -04:00
Michael Telatynski f806e4342e Add simple browserify sync test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-15 18:56:09 +01:00
Michael Telatynski 7f32d7d320 revert
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-05-15 16:55:00 +01:00
Michael Telatynski b1b4b21d45 Merge branches 'develop' and 't3chguy/fix_unhomoglyph_import' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix_unhomoglyph_import
 Conflicts:
	src/utils.ts
2020-05-15 16:50:23 +01:00
Hubert Chathi 486e8b5445 Merge pull request #1376 from matrix-org/uhoreg/sas2
support new key agreement method for SAS
2020-05-15 10:59:11 -04:00
Bruno Windels 9fe0e1e85f lint 2020-05-15 13:44:10 +02:00
Bruno Windels 8d81240c58 remove key backup format migration 2020-05-15 12:37:41 +02:00
Hubert Chathi bdadcd4532 support new key agreement method for SAS 2020-05-14 16:14:45 -04:00
RiotRobot 1de9a24677 v6.1.0-rc.1 2020-05-14 16:06:17 +01:00
RiotRobot 6335f14a34 Prepare changelog for v6.1.0-rc.1 2020-05-14 16:06:16 +01:00
Bruno Windels da2ef381ac Merge pull request #1373 from matrix-org/bwindels/remove-asym-4s
Remove support for asymmetric 4S encryption
2020-05-14 08:10:01 +00:00
Bruno Windels 7b173f4c74 don't sign 4S keys anymore 2020-05-13 10:38:03 +02:00
Bruno Windels d49eb0bbc4 remove upgrade path from asym to sym 4S algorithm 2020-05-13 10:38:03 +02:00
Bruno Windels 91556d5bcd fix lint 2020-05-13 10:38:03 +02:00
Bruno Windels 039abe1f75 remove tests for upgrade path 2020-05-12 14:53:50 +02:00
Bruno Windels bc32faf467 don't sign/verify 4s key with cross signing master key anymore 2020-05-12 14:49:20 +02:00
Bruno Windels 74a4dfeb67 remove support for asym 4s enc 2020-05-12 14:29:54 +02:00
RiotRobot f120533fad Merge branch 'master' into develop 2020-05-05 11:01:32 +01:00
RiotRobot a1baf39299 v6.0.0 2020-05-05 10:54:38 +01:00
RiotRobot 9f0f1bcc68 Prepare changelog for v6.0.0 2020-05-05 10:54:37 +01:00
J. Ryan Stinnett 246963e181 Merge pull request #1368 from matrix-org/foldleft/13167-spinner-progress-rc
Add progress callback for key backups
2020-05-04 16:34:38 +01:00
Zoe e3134ab0de satisfy linter 2, now with more self-hatred 2020-05-04 16:25:43 +01:00
Zoe b38d52da72 satisfy the linter 2020-05-04 16:25:43 +01:00
Zoe 411c4f40d9 docs 2020-05-04 16:25:43 +01:00
Zoe 694a85b652 lint 2020-05-04 16:25:43 +01:00
Zoe c84e72f53a Added a progressCallback for backup key loading 2020-05-04 16:25:43 +01:00
David Baker cb7e1a9d82 Merge pull request #1367 from matrix-org/dbkr/increase_olm_second_phase_timeout
Increase timeout for 2nd phase of Olm session creation
2020-05-01 18:32:25 +01:00
David Baker 3c9dfc195e Increase timeout for 2nd phase of Olm session creation
The timeouts on the two phases of olm session creation are 2 and 10
seconds respectively, so sessions will fail if servers take more
than 10s to respond. Now that we have two phases, we can afford to
wait longer on the second one because the user's isn't waiting for
it to finish before the message will send, so increase it to 30s
so servers have more of a chance to respond.
2020-05-01 18:25:25 +01:00
David Baker a5c13041c6 Merge pull request #1366 from matrix-org/dbkr/retry_decryption_logging
Add logging on decryption retries
2020-05-01 17:49:13 +01:00
David Baker d699e98346 Add logging on decryption retries
For https://github.com/vector-im/riot-web/issues/13473
2020-05-01 17:36:49 +01:00
RiotRobot 135a76febd v6.0.0-rc.2 2020-05-01 16:18:21 +01:00
RiotRobot fae2856e7e Prepare changelog for v6.0.0-rc.2 2020-05-01 16:18:21 +01:00
David Baker 98426b6186 Merge pull request #1365 from matrix-org/dbkr/emit_on_store_trusted_master_key_rel
Emit event when a trusted self-key is stored
2020-05-01 15:37:09 +01:00
David Baker a63d9d2ccd lint 2020-05-01 15:28:15 +01:00
David Baker c567139e28 Emit event when a trusted self-key is stored
This will cause our own user trust status to change, so emit an
event to say so.
2020-05-01 15:28:09 +01:00
David Baker 3d495b0753 Merge pull request #1364 from matrix-org/dbkr/emit_on_store_trusted_master_key
Emit event when a trusted self-key is stored
2020-05-01 15:27:36 +01:00
David Baker 4946b55c21 lint 2020-05-01 15:13:02 +01:00
David Baker ad7d7154f4 Emit event when a trusted self-key is stored
This will cause our own user trust status to change, so emit an
event to say so.
2020-05-01 15:01:13 +01:00
Zoe e5b6a9f8cb Merge pull request #1352 from matrix-org/foldleft/12252-large-err
Customize error payload for oversized messages
2020-05-01 09:44:43 +01:00
Zoe 04f27d36ef Update src/crypto/OlmDevice.js
Co-authored-by: Travis Ralston <travpc@gmail.com>
2020-05-01 09:40:11 +01:00
J. Ryan Stinnett 683fc98fdc Merge pull request #1363 from matrix-org/jryans/key-backup-network-fail
Return null for key backup state when we haven't checked yet
2020-04-30 17:21:09 +01:00
J. Ryan Stinnett c303e83444 Return null for key backup state when we haven't checked yet
This allows distinguishing the "unknown" case from "not present".

Part of https://github.com/vector-im/riot-web/issues/13404
2020-04-30 15:40:27 +01:00
J. Ryan Stinnett ae5a40d686 Only consider key backup checked in error if 404
Other error codes are likely to mean we never actually reached the server at
all, so we should not consider that as checked.

Part of https://github.com/vector-im/riot-web/issues/13404
2020-04-30 15:22:50 +01:00
Zoe 409b7068bb Merge pull request #1351 from matrix-org/foldleft/13167-spinner-progress
Added a progressCallback for backup key loading
2020-04-30 14:09:16 +01:00
Zoe f2a12c7154 satisfy linter 2, now with more self-hatred 2020-04-30 14:06:20 +01:00
Zoe efac2eac07 satisfy the linter 2020-04-30 12:04:29 +01:00
RiotRobot f3f6f3e39a v6.0.0-rc.1 2020-04-30 11:14:04 +01:00
RiotRobot f905586dbd Prepare changelog for v6.0.0-rc.1 2020-04-30 11:14:03 +01:00
David Baker f393cea2c2 Merge pull request #1362 from matrix-org/dbkr/sessions_there_on_login_are_old_rel
Add initialFetch param to willUpdateDevices / devicesUpdated
2020-04-29 18:14:13 +01:00
David Baker e937998b40 Add initialFetch param to willUpdateDevices / devicesUpdated
This indicates whether the device database is being populated initially
2020-04-29 17:44:25 +01:00
David Baker f85ec08886 Merge pull request #1360 from matrix-org/dbkr/sessions_there_on_login_are_old
Add initialFetch param to willUpdateDevices / devicesUpdated
2020-04-29 17:42:12 +01:00
J. Ryan Stinnett 7ff68f3844 Merge pull request #1361 from matrix-org/bwindels/fixrequestreadyrace-rc
Fix race between sending .request and receiving .ready over to_device
2020-04-29 17:26:11 +01:00
Bruno Windels 04529bd524 add to_device verif request to txnId -> request map before sending
so if the .ready event arrives before the request finishes,
it still ends up in the correct VerificationRequest
2020-04-29 18:20:16 +02:00
Bruno Windels 2c681d93d9 Merge pull request #1359 from matrix-org/bwindels/fixrequestreadyrace
Fix race between sending .request and receiving .ready over to_device
2020-04-29 16:19:31 +00:00
David Baker 1451fcb040 Add initialFetch param to willUpdateDevices / devicesUpdated
This indicates whether the device database is being populated initially
2020-04-29 17:01:50 +01:00
Zoe 4986f3c2ca docs 2020-04-29 16:55:27 +01:00
Bruno Windels e9edb85a32 add to_device verif request to txnId -> request map before sending
so if the .ready event arrives before the request finishes,
it still ends up in the correct VerificationRequest
2020-04-29 17:51:32 +02:00
J. Ryan Stinnett dca60ae4ae Merge pull request #1358 from matrix-org/bwindels/fixwaitforeventrace-rc
Handle race between sending and await next event from other party
2020-04-29 15:21:35 +01:00
Bruno Windels 025f964b0b Merge pull request #1357 from matrix-org/bwindels/fixwaitforeventrace
Handle race between sending and await next event from other party
2020-04-29 13:53:35 +00:00
Bruno Windels ff46a8fa9e handle race between sending and await next event from other party 2020-04-29 15:52:20 +02:00
Bruno Windels 59412aba51 handle race between sending and await next event from other party 2020-04-29 15:45:51 +02:00
David Baker a351ee9f76 Merge pull request #1356 from matrix-org/dbkr/another_round_of_toast_rel
Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
2020-04-29 12:50:00 +01:00
David Baker a5b14092cd Make getStoredDevice calls sync 2020-04-29 12:23:03 +01:00
David Baker 90f1de9c3f Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
Add an event fired before devices are updated, so apps can monitor
changes in devices by observing the state before and after.

BREAKING CHANGE:
Make getStoredDevices/getStoredDevicesForUser synchronous so they
can safely be called from the event listener without racing with
the data being updated. They didn't do any async work so didn't need
to be async.

Add doc for crypto.devicesUpdated
2020-04-29 12:23:00 +01:00
David Baker a516f06946 Merge pull request #1354 from matrix-org/dbkr/another_round_of_toast
Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
2020-04-29 12:09:11 +01:00
David Baker 7c1f3e4519 Make getStoredDevice calls sync 2020-04-29 11:50:18 +01:00
Michael Telatynski 3bb1a6336b Merge pull request #1350 from matrix-org/t3chguy/redaction_redesign
Fix sender of local echo events in unsigned redactions
2020-04-29 11:17:28 +01:00
J. Ryan Stinnett 7dc926596f Merge pull request #1355 from matrix-org/jryans/rm-extra-key-backup-setup-rc
Remove redundant key backup setup path
2020-04-29 09:46:17 +01:00
J. Ryan Stinnett e775515c38 Remove the other copy instead 2020-04-28 21:34:17 +01:00
J. Ryan Stinnett c116f2b1bc Remove redundant key backup setup path
Bootstrap was create key backup twice due to some code duplication we missed in
previous refactorings.

Fixes https://github.com/vector-im/riot-web/issues/13423
2020-04-28 21:34:17 +01:00
J. Ryan Stinnett f24993d6aa Merge pull request #1353 from matrix-org/jryans/rm-extra-key-backup-setup
Remove redundant key backup setup path
2020-04-28 21:33:49 +01:00
David Baker f053cf1fdb Add crypto.willUpdateDevices event and make getStoredDevices/getStoredDevicesForUser synchronous
Add an event fired before devices are updated, so apps can monitor
changes in devices by observing the state before and after.

BREAKING CHANGE:
Make getStoredDevices/getStoredDevicesForUser synchronous so they
can safely be called from the event listener without racing with
the data being updated. They didn't do any async work so didn't need
to be async.

Add doc for crypto.devicesUpdated
2020-04-28 18:31:04 +01:00
J. Ryan Stinnett 251318eaf0 Remove the other copy instead 2020-04-28 17:20:49 +01:00
J. Ryan Stinnett a66e7d79ad Remove redundant key backup setup path
Bootstrap was create key backup twice due to some code duplication we missed in
previous refactorings.

Fixes https://github.com/vector-im/riot-web/issues/13423
2020-04-28 16:56:37 +01:00
Zoe 38884083a2 Customize error payload for oversized messages 2020-04-28 16:39:35 +01:00
Zoe 0b1088d9a8 lint 2020-04-28 11:31:41 +01:00
Zoe 875f9ca388 Added a progressCallback for backup key loading 2020-04-28 11:28:39 +01:00
David Baker 25cd1f25f1 Merge pull request #1349 from matrix-org/dbkr/retryDecryption_remove_dead_code
Remove some dead code from _retryDecryption
2020-04-28 11:23:53 +01:00
Michael Telatynski 7152ece70a Fix sender of local echo events in unsigned redactions
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-27 23:52:22 +01:00
David Baker 896c76ce9d Merge pull request #1348 from matrix-org/dbkr/keyrequest_wait_sync_process
Don't send key requests until after sync processing is finished
2020-04-24 14:38:54 +01:00
David Baker 21d3dd4506 Remove some dead code from _retryDecryption
I think this was attempting to remove the events from _pendingEvents
but a) it wasn't and b) it probably shouldn't be because the retry
itself will re-add them when the decryption attempt starts and remove
them if it succeeds.

Also fix what was presumably a c+p fail.
2020-04-24 14:36:42 +01:00
David Baker 300b32c8d7 Let's keep starting the timer for the retries straight away
rather than leaving them until next time the queue is sent
2020-04-24 13:16:07 +01:00
David Baker 19e3b35fc7 Fix immediate key request sending 2020-04-24 11:26:48 +01:00
David Baker 320811f9ed Don't send key requests until after sync processing is finished
Key requests wait for a short time (500ms) before being sent as
an attempt to wait for the key to arrive before sending a request
for it, but client startup takes way longer than this so this
would still result in key requests being sent for keys that we'd
fetched but were still waiting to be read out of the sync response
and put into the database.
2020-04-24 11:19:52 +01:00
David Baker 991457496f Merge pull request #1346 from matrix-org/dbkr/dont_talk_to_ourself
Prevent attempts to send olm messages to ourselves
2020-04-23 16:10:36 +01:00
RiotRobot 1cb6134758 v5.3.1-rc.4 2020-04-23 15:41:07 +01:00
RiotRobot a255b8e450 Prepare changelog for v5.3.1-rc.4 2020-04-23 15:41:07 +01:00
Bruno Windels 324f036b35 Merge pull request #1347 from matrix-org/bwindels/retry4s-rc
Retry account data upload requests
2020-04-23 14:33:36 +00:00
Bruno Windels 479a284e10 move fn to http-api and add jsdoc 2020-04-23 16:29:08 +02:00
Bruno Windels e0660ce01c log while retrying 2020-04-23 16:29:07 +02:00
Bruno Windels 3a3f6cb7ca cleanup, return straight away 2020-04-23 16:29:07 +02:00
Bruno Windels 9797e6021b exclude aborted requests and matrix errors when creating connectionerror
- throw an AbortError when aborting is detected
 - don't turn AbortError's into a ConnectionError
 - also consider the string "aborted" an AbortError for unit tests
 - unit tests like to reject the request with a MatrixError,
   so exclude those too
2020-04-23 16:29:07 +02:00
Bruno Windels a9212d33b1 retry PUT account data requests 2020-04-23 16:29:07 +02:00
Bruno Windels d32d3fd864 retry util function 2020-04-23 16:29:07 +02:00
Bruno Windels a19cdd06cf throw ConnectionError when request fails 2020-04-23 16:29:07 +02:00
Bruno Windels f4d0f89fda cleanup: use class for MatrixError
this way we get stacktraces as well
2020-04-23 16:29:07 +02:00
Bruno Windels ca34cb951e Merge pull request #1345 from matrix-org/bwindels/retry4s
Retry account data upload requests
2020-04-23 14:28:31 +00:00
David Baker 4c2d1b0385 typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-04-23 14:40:17 +01:00
Bruno Windels 6b4fefc123 move fn to http-api and add jsdoc 2020-04-23 15:32:40 +02:00
Bruno Windels 86913dccb0 log while retrying 2020-04-23 15:25:10 +02:00
Bruno Windels 30101a4428 cleanup, return straight away 2020-04-23 15:22:46 +02:00
Bruno Windels 668e8f6f24 exclude aborted requests and matrix errors when creating connectionerror
- throw an AbortError when aborting is detected
 - don't turn AbortError's into a ConnectionError
 - also consider the string "aborted" an AbortError for unit tests
 - unit tests like to reject the request with a MatrixError,
   so exclude those too
2020-04-23 15:18:52 +02:00
David Baker 2f51e206c7 Prevent attempts to send olm messages to ourselves
The cause of this I've seen is us sending keyshare reqiests when
we didn't have keys but them arriving when we do, and us replying to
ourseles with keys, so exclude that explicity.

Also catch this lower down to catch any other code paths that
mistaklenly try this. Hopefully the comment explains enough as to why
it won't work.
2020-04-23 13:36:54 +01:00
Bruno Windels 78b8b36a87 retry PUT account data requests 2020-04-23 13:37:56 +02:00
Bruno Windels af3ef86d19 retry util function 2020-04-23 13:37:33 +02:00
Bruno Windels 50febaf477 throw ConnectionError when request fails 2020-04-23 13:37:10 +02:00
Bruno Windels 907f317b04 cleanup: use class for MatrixError
this way we get stacktraces as well
2020-04-23 13:36:40 +02:00
David Baker 6edb78d015 Merge pull request #1344 from matrix-org/dbkr/log_megolm_session_first_index
Log first known index with megolm session updates
2020-04-23 12:30:48 +01:00
David Baker b58846ab6e Log first known index with megolm session updates
Move the logging lower to a point where we know the first index.
2020-04-23 11:43:22 +01:00
David Baker 32233a2c7b Merge pull request #1343 from matrix-org/dbkr/prune_todevice_messages
Prune to_device messages to avoid sending empty messages
2020-04-22 13:54:29 +01:00
David Baker 8b2752441d Prune to_device messages to avoid sending empty messages
Fixes https://github.com/vector-im/riot-web/issues/13322
2020-04-22 13:48:57 +01:00
Michael Telatynski e6af29df94 Merge pull request #1335 from matrix-org/t3chguy/ts1
Convert bunch of things to TypeScript
2020-04-22 10:26:53 +01:00
David Baker 4ae5404fe4 Merge pull request #1342 from matrix-org/dbkr/log_new_olm_sessions
Add logging when making new Olm sessions
2020-04-22 09:47:44 +01:00
David Baker 7f0bdc8ddb Add logging when making new Olm sessions 2020-04-21 18:31:36 +01:00
Bruno Windels 39836f115b Merge pull request #1341 from matrix-org/bwindels/fixfilterlookup-rc
Fix: handle filter not found
2020-04-21 14:15:03 +00:00
Bruno Windels 4a699fe6a7 Merge pull request #1340 from matrix-org/bwindels/fixfilterlookup
Fix: handle filter not found
2020-04-21 14:13:33 +00:00
David Baker 7cc61a887c Merge pull request #1339 from matrix-org/dbkr/getAccountDataFromServer_404_rel
Make getAccountDataFromServer return null if not found
2020-04-21 15:10:20 +01:00
David Baker 8f73f1ed3c Merge pull request #1338 from matrix-org/dbkr/getAccountDataFromServer_404
Make getAccountDataFromServer return null if not found
2020-04-21 15:10:13 +01:00
Bruno Windels 67fb027d39 filter out invalid filter id values like 'undefined'
this way we don't attempt to fetch filters when validating them
with filterId==="undefined"
2020-04-21 16:00:45 +02:00
Bruno Windels c6b61ea0ea Fix unknown filters returning 400 rather than 404
This made getFilter in sync retrying continuously
I also refactored getOrCreateFilter to use await
2020-04-21 16:00:45 +02:00
Bruno Windels 32841234a7 filter out invalid filter id values like 'undefined'
this way we don't attempt to fetch filters when validating them
with filterId==="undefined"
2020-04-21 15:56:39 +02:00
Bruno Windels 6f6520ed0f Fix unknown filters returning 400 rather than 404
This made getFilter in sync retrying continuously
I also refactored getOrCreateFilter to use await
2020-04-21 15:54:50 +02:00
David Baker b3860f3754 Make getAccountDataFromServer return null if not found
Callers assume that if the account data isn't there, the method will
return null rather than throw an exception. This meant that this only
actually worked in these cases when the sync had completed and it was
using the locally stored account data, so this ceased to be a
problem in practice when we made it wait for the initial sync to finish
before entering the security setup flow.

Fixes https://github.com/vector-im/riot-web/issues/13169
2020-04-21 14:32:26 +01:00
David Baker 0958917317 Make getAccountDataFromServer return null if not found
Callers assume that if the account data isn't there, the method will
return null rather than throw an exception. This meant that this only
actually worked in these cases when the sync had completed and it was
using the locally stored account data, so this ceased to be a
problem in practice when we made it wait for the initial sync to finish
before entering the security setup flow.

Fixes https://github.com/vector-im/riot-web/issues/13169
2020-04-21 14:28:10 +01:00
David Baker f42fa7e791 Merge pull request #1337 from matrix-org/dbkr/setdefaultkey_fail_rel
Fix setDefaultKeyId to fail if the request fails
2020-04-21 14:21:42 +01:00
David Baker 7022d1f3bf Merge pull request #1336 from matrix-org/dbkr/setdefaultkey_fail
Fix setDefaultKeyId to fail if the request fails
2020-04-21 14:21:35 +01:00
David Baker b592c41f96 Fix setDefaultKeyId to fail if the request fails
It returned only when the echo came down the sync stream, but we
forgot to make it fail if the reuqest failed and it would just
never return in this case.

Fixes https://github.com/vector-im/riot-web/issues/13162
2020-04-21 13:52:48 +01:00
David Baker 62188571d7 Fix setDefaultKeyId to fail if the request fails
It returned only when the echo came down the sync stream, but we
forgot to make it fail if the reuqest failed and it would just
never return in this case.

Fixes https://github.com/vector-im/riot-web/issues/13162
2020-04-21 13:50:21 +01:00
Michael Telatynski 8cca3392f6 fix localStorage type
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 12:03:25 +01:00
Michael Telatynski 7082516664 Improve ts types and enable esModuleInterop
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 11:55:56 +01:00
Michael Telatynski 77a1fc9e60 Convert bunch of things to TypeScript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-04-21 11:45:54 +01:00
Travis Ralston e2b1dd6532 Merge pull request #1328 from MichaelKohler/issue905-setRoomEncryption-doc
Document setRoomEncryption not modifying room state
2020-04-21 00:30:14 -06:00
Bruno Windels 7b7fdb0a65 Merge pull request #1333 from matrix-org/bwindels/onlyonefilterplease-rc
Fix: don't do extra /filter request when enabling lazy loading of members
2020-04-20 16:36:26 +00:00
Bruno Windels 414279f644 Merge pull request #1332 from matrix-org/bwindels/onlyonefilterplease
Fix: don't do extra /filter request when enabling lazy loading of members
2020-04-20 16:24:52 +00:00
Bruno Windels be91c8580d rename build function 2020-04-20 18:22:30 +02:00
Bruno Windels befaa782f6 when enabling LL, set the flag on Filter, rather than doing /filter 2020-04-20 18:22:30 +02:00
Bruno Windels 8e29dae36a rename build function 2020-04-20 18:06:10 +02:00
Bruno Windels d2438d10a6 when enabling LL, set the flag on Filter, rather than doing /filter 2020-04-20 17:26:19 +02:00
David Baker b3752bb2c6 Merge pull request #1331 from matrix-org/dbkr/fix_reject_no_auth_flow_found_rel
Reject attemptAuth promise if no auth flow found
2020-04-20 13:38:49 +01:00
Michael Kohler 02e145b0de Reject attemptAuth promise if no auth flow found 2020-04-20 13:34:18 +01:00
David Baker 0fcf8777a1 Merge pull request #1329 from MichaelKohler/fix-reject-no-auth-flow-found
Reject attemptAuth promise if no auth flow found
2020-04-20 13:06:44 +01:00
J. Ryan Stinnett 26cae5543b Merge pull request #1330 from matrix-org/dbkr/serialise_prekey_decryptions_rel
Serialise Olm prekey decryptions
2020-04-20 10:14:24 +01:00
David Baker b0573dec77 Add a catch so the error doesn't propagate to later decryptions 2020-04-20 10:08:13 +01:00
David Baker c5f4e762e5 Serialise Olm prekey decryptions
If they overlap, they can both try to create new sessions, but this
removes the OTK so it will only work once.

Fixes https://github.com/vector-im/riot-web/issues/13229
2020-04-20 10:08:07 +01:00
Michael Kohler f4c08477d0 Reject attemptAuth promise if no auth flow found 2020-04-19 14:04:10 +02:00
Michael Kohler 4342ee6d4a Document setRoomEncrption not modifying room state (fixes #905) 2020-04-18 19:49:05 +02:00
Michael Telatynski 7b73561923 Merge pull request #1327 from nchamo/fix-filter-component-check
Fix FilterComponent allowed_values check
2020-04-18 14:45:31 +01:00
Nicolas Chamo 87c0cf233c Update src/filter-component.js
Co-Authored-By: Michael Telatynski <7t3chguy@googlemail.com>
2020-04-17 23:59:38 -03:00
Nicolas Chamo 145cd7894b More lint fixes 2020-04-17 19:56:32 -03:00
Nicolas Chamo 6cf8a76c29 linting 2020-04-17 19:50:15 -03:00
nchamo 7ea09ebe4a Add fix and test
Signed-off-by: Nicolas Chamo <nicolas@chamo.com.ar>
2020-04-17 19:37:33 -03:00
David Baker 1dcb16365b Merge pull request #1326 from matrix-org/dbkr/serialise_prekey_decryptions
Serialise Olm prekey decryptions
2020-04-17 18:07:24 +01:00
David Baker 83b33d9d7a Add a catch so the error doesn't propagate to later decryptions 2020-04-17 18:03:15 +01:00
David Baker 83879fa945 Serialise Olm prekey decryptions
If they overlap, they can both try to create new sessions, but this
removes the OTK so it will only work once.

Fixes https://github.com/vector-im/riot-web/issues/13229
2020-04-17 17:47:46 +01:00
RiotRobot c3903f2796 v5.3.1-rc.3 2020-04-17 17:35:49 +01:00
RiotRobot 18564553ad Prepare changelog for v5.3.1-rc.3 2020-04-17 17:35:48 +01:00
J. Ryan Stinnett fe3e87a9d6 Merge pull request #1323 from matrix-org/dbkr/fix_ssss_reset_rel
Fix cross-signing/SSSS reset
2020-04-17 13:42:47 +01:00
Bruno Windels 78c734d2e9 Merge pull request #1325 from matrix-org/bwindels/fixkeybackupcrash-rc
Fix: crash when backup key needs fixing from corruption issue
2020-04-17 12:23:08 +00:00
Bruno Windels 5afa16d454 method is on crypto 2020-04-17 14:19:55 +02:00
Bruno Windels 57233416d9 Merge pull request #1324 from matrix-org/bwindels/fixkeybackupcrash
Fix: crash when backup key needs fixing from corruption issue
2020-04-17 12:11:07 +00:00
Bruno Windels f56ce29210 method is on crypto 2020-04-17 13:51:12 +02:00
David Baker 149f59e9be lint 2020-04-16 20:42:00 +01:00
David Baker 761892d6b1 Fix cross-signing/SSSS reset
We re-used the old SSSS key even when resetting, meaning we prompted
the user to create a new passphrase but then ignored it and kept using
the old one.

Fixes https://github.com/vector-im/riot-web/issues/13212
2020-04-16 20:41:55 +01:00
David Baker 4b639d5f9a Merge pull request #1322 from matrix-org/dbkr/fix_ssss_reset
Fix cross-signing/SSSS reset
2020-04-16 20:38:47 +01:00
David Baker da8ed77aae lint 2020-04-16 20:18:27 +01:00
David Baker 12f46909f7 Fix cross-signing/SSSS reset
We re-used the old SSSS key even when resetting, meaning we prompted
the user to create a new passphrase but then ignored it and kept using
the old one.

Fixes https://github.com/vector-im/riot-web/issues/13212
2020-04-16 20:13:18 +01:00
RiotRobot faf18b1996 v5.3.1-rc.2 2020-04-16 18:39:06 +01:00
RiotRobot 218deddd00 Prepare changelog for v5.3.1-rc.2 2020-04-16 18:39:06 +01:00
Bruno Windels d6fe650c4c Merge pull request #1321 from matrix-org/bwindels/untrustedqrcodereciprocate-rc
Implement QR code reciprocate for self-verification with untrusted MSK
2020-04-16 12:38:04 +00:00
Bruno Windels 919189db1a Merge pull request #1320 from matrix-org/bwindels/untrustedqrcodereciprocate
Implement QR code reciprocate for self-verification with untrusted MSK
2020-04-16 12:34:46 +00:00
Bruno Windels 951191d99a update comments 2020-04-16 14:31:44 +02:00
Bruno Windels f814a96eac update comments 2020-04-16 14:30:51 +02:00
Bruno Windels 2654f4bf0f implement qr code reciprocate for self-verif with untrusted MSK 2020-04-16 13:26:56 +02:00
Bruno Windels 218aa423c3 implement qr code reciprocate for self-verif with untrusted MSK 2020-04-16 13:01:46 +02:00
RiotRobot 195f3a7550 v5.3.1-rc.1 2020-04-15 19:05:12 +01:00
RiotRobot 1c9343ed8f Prepare changelog for v5.3.1-rc.1 2020-04-15 19:05:11 +01:00
RiotRobot 5c7d9981f8 Merge branch 'release-v5.3.0' into release-v5.3.1 2020-04-15 19:02:40 +01:00
J. Ryan Stinnett 63bc17a6b4 Merge pull request #1319 from matrix-org/jryans/release-script
Adapt release script for riot-desktop
2020-04-15 18:26:04 +01:00
J. Ryan Stinnett 629490c4ae Adapt release script for riot-desktop
This allows the bulk of the release script to be used for riot-desktop, while
skipping the NPM and develop branch parts that do not apply.

Part of https://github.com/vector-im/riot-web/issues/13176
2020-04-15 18:10:46 +01:00
Bruno Windels d59d62f96a Merge pull request #1318 from matrix-org/bwindels/fixindexernotifs
Fix: prevent spurious notifications from indexer
2020-04-15 16:50:18 +00:00
Bruno Windels 8a460c2368 fix jsdoc 2020-04-15 18:33:55 +02:00
Bruno Windels 6f60183316 allow creating event mappers that map events whose events are not reemitted 2020-04-15 17:08:25 +02:00
J. Ryan Stinnett a21c62519b Merge pull request #1317 from matrix-org/jryans/gen-user-obj
Always create our own user object
2020-04-14 16:19:45 +01:00
J. Ryan Stinnett 1b8f6d1739 Always create our own user object
This ensures our own user object is always available for tasks like
verification, even if you aren't in any rooms.

Fixes https://github.com/vector-im/riot-web/issues/13165
2020-04-14 16:02:00 +01:00
Hubert Chathi e087bce61a Merge pull request #1311 from matrix-org/uhoreg/fix_backup_key_format
Fix incorrect backup key format in SSSS
2020-04-13 17:38:45 -04:00
Hubert Chathi d2f24c3e87 cut long line to appease lint 2020-04-13 17:03:17 -04:00
Hubert Chathi 5d606bba66 add test for passthrough on backups 2020-04-13 16:54:02 -04:00
Hubert Chathi 864fe459b7 improve readability of tests 2020-04-13 16:27:46 -04:00
Marcel ea8362ed63 Keep the null in authData if this is the first stage. 2020-04-10 19:32:50 +02:00
Marcel 3a1508c2ab Fix "should make a request if no authdata is provided" test 2020-04-10 19:15:21 +02:00
Marcel f914391aaf Revert hack to set auth = null 2020-04-10 18:31:35 +02:00
Bruno Windels 8992438aa6 Merge pull request #1315 from matrix-org/bwindels/fixe2eecrash
Fix e2ee crash after refreshing after having received a cross-singing key reset
2020-04-10 10:20:20 +00:00
Bruno Windels 197d5ebb44 fix e2ee crash after refreshing after cross-singing key reset 2020-04-10 10:37:12 +02:00
Hubert Chathi 4039498eee use cached backup key if available 2020-04-09 17:05:01 -04:00
Bruno Windels a686231a5b Merge pull request #1314 from matrix-org/bwindels/catchverifysenderrors
Fix: catch send errors in SAS verifier
2020-04-09 16:32:09 +00:00
Bruno Windels 97cf4bff1f fix tests 2020-04-09 18:21:20 +02:00
Bruno Windels cc8e8434ec Update src/crypto/SecretStorage.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-04-09 15:58:43 +00:00
Bruno Windels 11727833a2 mark request as cancelled immediately after verifier is cancelled
in case send errors prevent us from receiving remote echo
2020-04-09 17:54:45 +02:00
Hubert Chathi df38fde336 apply changes from code review 2020-04-09 11:48:58 -04:00
Bruno Windels 00233d610b pass on send errors so request gets cancelled 2020-04-09 17:23:44 +02:00
Bruno Windels 1b94b3c4de throw something more informative when we cant find a key to decrypt with 2020-04-09 16:00:08 +02:00
Bruno Windels d1c9030fac Merge pull request #1312 from matrix-org/bwindels/fixresetkeys
Clear cross-signing keys when detecting the keys have changed
2020-04-09 13:34:26 +00:00
Bruno Windels 70071eef41 also emit user trust has changed 2020-04-09 14:09:51 +02:00
Bruno Windels 65dd56f53a remove obsolete comment 2020-04-09 13:27:13 +02:00
Bruno Windels c8fb4af369 fix comment style 2020-04-09 13:13:17 +02:00
Bruno Windels 9e1ba992e3 Clear cross-signing keys when detecting the keys have changed 2020-04-09 12:24:29 +02:00
J. Ryan Stinnett bad09fed44 Merge pull request #1310 from matrix-org/jryans/upgrade-2020-04-08
Upgrade deps
2020-04-09 10:19:09 +01:00
Hubert Chathi 5bd146bb85 lint 2020-04-09 00:21:31 -04:00
Hubert Chathi 75703f273f fix unit tests 2020-04-09 00:18:21 -04:00
Hubert Chathi ca7b49d209 fix incorrect backup key format in SSSS 2020-04-09 00:08:17 -04:00
Marcel 379322db0d Revert setting authDict = null by default
Remove console.log
Revert null checks on this._data
2020-04-08 20:50:59 +02:00
J. Ryan Stinnett 30bce8a682 yarn upgrade 2020-04-08 14:41:36 +01:00
RiotRobot f413f0ee5f v5.3.0-rc.1 2020-04-08 13:46:11 +01:00
RiotRobot be6778a931 Prepare changelog for v5.3.0-rc.1 2020-04-08 13:46:10 +01:00
J. Ryan Stinnett 84637c6ebd Merge pull request #1308 from matrix-org/jryans/backup-key-cache-format
Store key backup key in cache as Uint8Array
2020-04-08 11:32:18 +01:00
J. Ryan Stinnett 2ed6e9ba2f Convert recovery key to Uint8Array explicitly 2020-04-08 00:43:52 +01:00
J. Ryan Stinnett 2e3cb54d38 Restore redundant conversion for tests 2020-04-07 19:21:25 +01:00
J. Ryan Stinnett 0efac7eae0 Store key backup key in cache as Uint8Array
Bootstrap was incorrectly storing the key backup key as base64 encoded, when
instead it should be a uint8Array.

Fixes https://github.com/vector-im/riot-web/issues/13057
2020-04-07 18:28:48 +01:00
J. Ryan Stinnett dc56f05d03 Throw error when trying to cache keys in the wrong format 2020-04-07 18:06:57 +01:00
Hubert Chathi 4d2625278c Merge pull request #1307 from MTRNord/fix-keys_query_endpoint
Use the correct request body for the /keys/query endpoint.
2020-04-07 12:01:06 -04:00
Marcel bff8a947a1 Fix expectBobQueryKeys test 2020-04-07 16:53:28 +02:00
Marcel 9cb7406ebd Fix downloadKeys test 2020-04-07 16:44:30 +02:00
Marcel 38681ca6ca Fix expectAliQueryKeys test 2020-04-07 16:31:15 +02:00
Marcel 916696c906 Fix expectKeyQuery test 2020-04-07 16:19:46 +02:00
Marcel 1b3c4c935e Use the correct request body for the /keys/query endpoint.
https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-keys-query
2020-04-07 14:08:49 +02:00
Marcel bf6d1f6555 Fix interactive-auth.js lint 2020-04-07 13:26:27 +02:00
Marcel 9faeac4c83 Merge branch 'develop' into fix-register-auth-with-new-spec 2020-04-07 13:15:51 +02:00
Marcel a863cf3d72 Fix auth linter error and tests 2020-04-07 13:13:49 +02:00
David Baker 66417e6742 Merge pull request #1305 from matrix-org/dbkr/uia_ignore_poll_after_success
Avoid creating two devices on registration
2020-04-07 10:32:08 +01:00
David Baker 6ad4d6dd62 also fix typo 2020-04-07 10:21:57 +01:00
David Baker 59607f8dbf Clear resolve / reject function after rejecting too 2020-04-07 10:20:58 +01:00
David Baker 62380a48d5 Merge pull request #1306 from matrix-org/dbkr/max_warnings_81
Lower max-warnings to 81
2020-04-07 10:19:45 +01:00
David Baker 4245372495 Lower max-warnings to 81
We apparently fixed 11 warnings at some point. Let's keep it that way.
2020-04-07 10:06:23 +01:00
David Baker 964a448470 Avoid creating two devices on registration
Ignore poll requests after a UIA session has finished. Sometimes
the app calls poll() even after the registration has succeeded
which causes the API call to happen again which, in the case of
registration, creates a phantom device.

Fixes https://github.com/vector-im/riot-web/issues/13040
2020-04-06 19:45:18 +01:00
J. Ryan Stinnett 2151f28d4c Merge pull request #1303 from matrix-org/jryans/backup-key-not-cached
Move key backup key creation before caching
2020-04-06 18:41:58 +01:00
Marcel 45a92bcf9c Fix auth check as auth == {} not works 2020-04-06 17:30:32 +02:00
Marcel 0a0441756b Fix auth check 2020-04-06 17:23:24 +02:00
Marcel c1de2ebbf9 Fix authdata to only be null if nothing had been added. 2020-04-06 17:14:43 +02:00
J. Ryan Stinnett 060549656e Move key backup key creation before caching
For some reason, we were trying to cache the key backup key before it was
created, and as a result we wouldn't cache the key until perhaps a second time
through bootstrapping.

Fixes https://github.com/vector-im/riot-web/issues/13023
2020-04-06 16:03:24 +01:00
Marcel 557c2db955 Handle Error during user-interactive auth: TypeError: "this._data is null" 2020-04-06 15:37:52 +02:00
Marcel ed8d064a13 Replace {} with null to be compliant with newer specs. While older also accepted null for the auth part of the registration request object 2020-04-06 15:22:04 +02:00
Zoe 7dabf507a2 Merge pull request #1298 from matrix-org/foldleft/12843-resend-requests
Expose function to force-reset outgoing room key requests
2020-04-06 13:43:06 +01:00
Zoe d69afa47a1 review feedback 2020-04-06 13:30:36 +01:00
Bruno Windels 275a8175aa Merge pull request #1302 from matrix-org/bwindels/selfverifux
Add isSelfVerification property to VerificationRequest
2020-04-03 16:02:05 +00:00
Bruno Windels 40bebf4cbd Merge pull request #1297 from matrix-org/bwindels/qr-reciprocate
QR code reciprocation
2020-04-03 12:28:34 +00:00
Zoe b8bc323c03 add a crypto-level function for resending key requests 2020-04-03 11:47:13 +01:00
Zoe c6d32ea2b0 Expose function to force-reset outgoing room key requests 2020-04-02 16:25:50 +01:00
Bruno Windels 2ace35ad6b add isSelfVerification helper 2020-04-02 17:16:06 +02:00
Bruno Windels b5ea8d3a78 Merge branch 'bwindels/qr-reciprocate' into bwindels/selfverifux 2020-04-02 17:15:32 +02:00
Bruno Windels f3a05f6ed0 fix jsdoc 2020-04-02 13:37:15 +02:00
Bruno Windels 43eae4929b remove ts annotations as I don't want to convert the whole file now 2020-04-02 13:31:17 +02:00
Bruno Windels 6144962c24 cancel with m.user code when user doesn't reciprocate 2020-04-02 12:53:52 +02:00
Bruno Windels 544cc36006 explain a bit why we're doing things here 2020-04-02 11:31:44 +02:00
Hubert Chathi 1834e6688f Merge pull request #1294 from matrix-org/uhoreg/check_sssss_passphrase
Add ability to check symmetric SSSS key before we try to use it
2020-04-01 16:01:34 -04:00
David Baker e1ee56aa43 Merge pull request #1296 from matrix-org/dbkr/debug_stuck_events
Add some debug logging for events stuck to bottom of timeline
2020-04-01 18:54:52 +01:00
David Baker a1779719be Add some debug logging for events stuck to bottom of timeline 2020-04-01 18:48:59 +01:00
Bruno Windels 0e464af627 Merge pull request #1295 from matrix-org/bwindels/fixdoublerequestexplosion
Fix: spontanous verification request cancellation under some circumstances
2020-04-01 17:17:24 +00:00
Bruno Windels fd7f0c3b5a Ignore phase while checking if other party supports scanning
(as the phase hasn't been set yet)

Also, set the qrCodeData in handleEvent as we need it to be async
2020-04-01 19:01:53 +02:00
Bruno Windels ed210a4fb1 Use static constructor that can be async (as looking up device is async)
Also fix static methods not to use this
2020-04-01 19:00:57 +02:00
Bruno Windels c2a0504980 update comments 2020-04-01 18:23:34 +02:00
Bruno Windels b6708871d3 fix olmlib import 2020-04-01 18:23:34 +02:00
Bruno Windels 275ea6aacb spec recommends 11 random bytes for shared secret 2020-04-01 18:23:34 +02:00
Bruno Windels f97d413603 expose chosen method after request has started
so we can show a different ux now that it's not just SAS anymore,
but also reciprocate
2020-04-01 18:23:34 +02:00
Bruno Windels 2c7ea0606b move shared secret to QRCodeData 2020-04-01 18:23:34 +02:00
Bruno Windels d7a7100912 reuse keys from qr code data to sign
so we don't sign the wrong keys if a malicious HS admin
forged a cross-sign identity reset
2020-04-01 18:23:34 +02:00
Bruno Windels 2c54b8d77e ask the user if the other user scanned the QR code 2020-04-01 18:23:34 +02:00
Bruno Windels b30f278e03 checking the userid isn't a thing anymore AFAIK 2020-04-01 18:23:34 +02:00
Bruno Windels b642030a34 keep a copy of the master/device key and mode, for use in reciprocate 2020-04-01 18:23:34 +02:00
Bruno Windels 725976d472 Add QR code buffer generation to VerificationRequest (from react-sdk)
Doing this so later we can keep the other user master signing key on it
so we can make sure we cross-sign that one once we decide to sign.
2020-04-01 18:23:34 +02:00
Bruno Windels 80d87e1bf1 prevent the same event being handled twice in the verification request, result in cancellation 2020-04-01 17:13:24 +02:00
Hubert Chathi d01f527e71 only try to set keyInfo if it exists 2020-03-31 16:54:06 -04:00
Hubert Chathi 3e68c82171 apply changes from code review 2020-03-31 16:34:41 -04:00
Hubert Chathi 51bde23207 Merge branch 'develop' into uhoreg/check_sssss_passphrase 2020-03-31 10:41:39 -04:00
J. Ryan Stinnett 934ed37fdc Merge pull request #1293 from matrix-org/jryans/4s-prompt-overload
Receive private key for caching from the app layer
2020-03-31 11:04:51 +01:00
J. Ryan Stinnett 4a965f5408 Convert secret storage key creation to object 2020-03-31 10:45:02 +01:00
J. Ryan Stinnett 6343da33c3 Merge pull request #1292 from matrix-org/jryans/id-change-red
Track whether we have verified a user before
2020-03-31 10:07:20 +01:00
Hubert Chathi e6edee863f add ability to check symmetric SSSS key before we try to use it 2020-03-30 17:25:03 -04:00
J. Ryan Stinnett 9a1d62438d Receive private key for caching from the app layer
This passes through the new secret storage key as well as the key info so that
bootstrap can use it immediately without triggering an immediate prompt.

Part of https://github.com/vector-im/riot-web/issues/12867
2020-03-30 21:46:22 +01:00
J. Ryan Stinnett d2ba3039c7 Add missing awaits in bootstrap path 2020-03-30 21:46:22 +01:00
J. Ryan Stinnett b6ad4c10bc Track whether we have verified a user before
This tracks whether we have ever cross-signing verified a user before (at least
as far as the current device has ever observed). This info helps to present an
alert in case the user subsequently becomes unverified.

Part of https://github.com/vector-im/riot-web/issues/12808
2020-03-30 14:51:49 +01:00
J. Ryan Stinnett 93954d314e Remove unused variable in device list 2020-03-30 14:51:49 +01:00
J. Ryan Stinnett 282f85f1dd Add missing await when changing key IDs 2020-03-30 14:51:49 +01:00
RiotRobot 223d37ffce Merge branch 'master' into develop 2020-03-30 13:23:17 +01:00
RiotRobot 3d20388ca0 v5.2.0 2020-03-30 13:20:20 +01:00
RiotRobot 198c9d934e Prepare changelog for v5.2.0 2020-03-30 13:20:20 +01:00
J. Ryan Stinnett d43005d91e Merge pull request #1290 from matrix-org/dbkr/send_is_verified_rel
Fix isVerified returning false
2020-03-30 10:28:51 +01:00
Bruno Windels 02264b4572 Merge pull request #1222 from matrix-org/bwindels/fixtestfailure
Fix: error during tests
2020-03-30 08:36:15 +00:00
Bruno Windels add652f18e Merge pull request #1288 from matrix-org/bwindels/alwayssenddone
Send .done event for to_device verification
2020-03-27 16:30:28 +00:00
David Baker 1b9146b9e7 Merge pull request #1291 from matrix-org/dbkr/request_key_backup_key
Request the key backup key & restore backup
2020-03-27 15:43:16 +00:00
J. Ryan Stinnett 5178819b51 Merge pull request #1276 from mjattiot/hotfix/screen_sharing
Make screen sharing works on Chrome using getDisplayMedia()
2020-03-27 15:40:53 +00:00
David Baker f57c25ec27 Make test pass again 2020-03-27 15:13:46 +00:00
David Baker 794429b68b Request the key backup key & restore backup
After a successful verification with ourselves, request the key
backup key too and restore a key backup if we get it.

Also cache the key backup key when we cache the SSK & USK so we have
it available to share.

Fixes https://github.com/vector-im/riot-web/issues/12704
2020-03-27 14:40:15 +00:00
David Baker 983a04bb00 Merge pull request #1289 from matrix-org/dbkr/send_is_verified
Fix isVerified returning false
2020-03-27 14:28:47 +00:00
David Baker adbef16b9d Also pass the parameter in 2020-03-27 14:26:58 +00:00
David Baker 157ea49328 Fix isVerified returning false
which would cause key backups uploads to be missing is_verified
because it was set to `undefined` which would cause the backup to
fail

Fixes https://github.com/vector-im/riot-web/issues/12901
2020-03-27 14:26:53 +00:00
David Baker 17386e7aae Also pass the parameter in 2020-03-27 14:14:45 +00:00
David Baker cb19cd673f Fix isVerified returning false
which would cause key backups uploads to be missing is_verified
because it was set to `undefined` which would cause the backup to
fail

Fixes https://github.com/vector-im/riot-web/issues/12901
2020-03-27 14:11:32 +00:00
Bruno Windels 4f0a297cf3 remove obsolete comment 2020-03-27 12:08:43 +01:00
Bruno Windels 6553e331cd also send .done event for to_device verification 2020-03-27 12:04:48 +01:00
Bruno Windels 21908aea6c Merge pull request #1286 from matrix-org/bwindels/verifignoredupes
Fix: verification gets cancelled when event gets duplicated
2020-03-26 17:07:09 +00:00
David Baker 7c40798ee0 Merge pull request #1287 from matrix-org/dbkr/secret_request_front_door
Use requestSecret on the client to request secrets
2020-03-26 16:16:02 +00:00
David Baker 8cdc635cad lint and aldo indent in a more traditional way 2020-03-26 16:12:11 +00:00
David Baker 7f5ac072e6 Use requestSecret on the client to request secrets
Rather than accessing private method to get the secret storage
object (this was a bit confusing when I grepped for 'requestSecret'
and didn't find anything).
2020-03-26 16:01:35 +00:00
Travis Ralston d69af72c7a Merge pull request #1277 from Awesome-Technologies/develop
Allow guests to fetch TURN servers
2020-03-26 09:33:41 -06:00
Bruno Windels ece1e202de indenting 2020-03-26 15:04:28 +01:00
Bruno Windels 91f38a362d ignore duplicate verification events 2020-03-26 15:04:13 +01:00
RiotRobot 5a3cc314be v5.2.0-rc.1 2020-03-26 12:55:17 +00:00
RiotRobot 3dfaafd177 Prepare changelog for v5.2.0-rc.1 2020-03-26 12:55:16 +00:00
David Baker bdba61975b Merge pull request #1285 from matrix-org/dbkr/trust_cross_signing_flag
Add a flag for whether cross signing signatures are trusted
2020-03-26 12:19:08 +00:00
David Baker 3b9023ec2b add comment 2020-03-26 12:04:16 +00:00
David Baker 4dfc7958b6 lint 2020-03-26 10:07:17 +00:00
David Baker 2fad318726 Make the flag only affect trust of other people's devices 2020-03-26 09:58:05 +00:00
David Baker 480b0e64a6 lint 2020-03-25 18:44:55 +00:00
David Baker 6ec7b5d404 Add a flag for whether cross signing signatures are trusted 2020-03-25 18:36:08 +00:00
J. Ryan Stinnett 0781d78da8 Merge pull request #1282 from matrix-org/jryans/robust-secret-share
Cache user and self signing keys during bootstrap
2020-03-25 17:50:58 +00:00
Zoe 513a256ec1 Merge pull request #1283 from matrix-org/foldleft/remove-extra-promise
remove unnecessary promise
2020-03-25 12:52:42 +00:00
Zoe 9372790666 remove unnecessary promise 2020-03-25 11:47:59 +00:00
J. Ryan Stinnett a6532b7881 Fix logging lints 2020-03-24 18:34:05 +00:00
J. Ryan Stinnett cea3582ed1 Always attempt caching via bootstrap 2020-03-24 18:28:31 +00:00
J. Ryan Stinnett 6bd22a3e9c Add logging to secret request side 2020-03-24 17:44:44 +00:00
J. Ryan Stinnett 7b93b99054 Cache USK and SSK private key during bootstrap 2020-03-24 17:35:59 +00:00
Michael Albert c6eb1525b5 Allow guests to fetch TURN servers
Signed-off-by: Michael Albert <michael.albert@awesome-technologies.de>
2020-03-24 18:04:26 +01:00
J. Ryan Stinnett a4b8ba0bb3 Add logging when replying to secret requests 2020-03-24 15:51:35 +00:00
Zoe 02216b15e5 Merge pull request #1281 from matrix-org/foldleft/12704-key-requests
Functions to cache session backups key automatically
2020-03-24 15:32:09 +00:00
David Baker 42efdf1e0a Merge pull request #1279 from matrix-org/dbkr/unify_cross_signing_checks
Add function for checking cross-signing is ready
2020-03-24 13:34:19 +00:00
David Baker 465f9e634e Merge pull request #1272 from matrix-org/dbkr/symmetric-ssss-migrate
Migration to symmetric SSSS
2020-03-24 13:12:17 +00:00
David Baker 7e92f0e5c8 OK, that really is all the comment formatting 2020-03-24 13:08:49 +00:00
David Baker 859a0d8db2 More comment formatting 2020-03-24 13:08:12 +00:00
David Baker 71740cabb5 comment formatting 2020-03-24 13:06:08 +00:00
David Baker 8f77680750 Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-03-24 13:05:15 +00:00
David Baker 509e4b337d Update for new name 2020-03-24 13:01:46 +00:00
David Baker 942ff0c9fd Better name
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-03-24 13:00:53 +00:00
David Baker 24c3dd1f1a Merge pull request #1280 from matrix-org/uhoreg/reduce_olm_creation
reduce number of one-time-key requests
2020-03-24 10:30:23 +00:00
Hubert Chathi 4f58e9945b factor out failed device notif to a function, and record all failed devices
instead of filtering out already-notified devices
2020-03-24 00:15:04 -04:00
Hubert Chathi 547ded9155 handle failed devices that we aren't going to retry 2020-03-23 23:14:36 -04:00
Hubert Chathi 4f112e8379 only re-try creating olm sessions for servers that failed to respond
If the server responded, then retrying likely won't help.  Retrying is mainly
to help with slow servers.
2020-03-23 22:36:10 -04:00
Hubert Chathi 4d63f8ed04 don't always do second phase of olm creation
don't need to do the shorter timeout when doing preparation to encrypt, and
skip the second phase if the first phase already took longer than a normal
otk claim
2020-03-23 21:26:56 -04:00
Hubert Chathi 944d39c836 add some comments 2020-03-23 16:51:44 -04:00
Bruno Windels 433977b918 Merge pull request #1275 from matrix-org/bwindels/assumemethodswhentodevice
Fix: assume the requested method is supported by other party with to_device
2020-03-23 19:39:27 +00:00
David Baker d9796e3bec Fix indenting 2020-03-23 19:00:02 +00:00
David Baker 0a7b9109f0 Move aes functions to their own file 2020-03-23 18:56:32 +00:00
David Baker 89bf9ff65b doc style fix 2020-03-23 18:40:53 +00:00
David Baker 7f6e223c0c Add function for checking cross-signing is ready
Aggregate function that checks the various things are in place for
cross-signing to work.
2020-03-23 18:34:16 +00:00
David Baker c696e5238b Merge pull request #1278 from matrix-org/dbkr/blacklist_use_device_trust
Use checkDeviceTrust when computing untrusted devices
2020-03-23 14:58:06 +00:00
David Baker d303fd0c7c Fix test 2020-03-23 14:53:55 +00:00
David Baker e1ad2f8a21 Use checkDeviceTrust when computing untrusted devices
Apparently we missed using cross-signing trust in the js-sdk itself
2020-03-23 14:28:10 +00:00
Zoe 7053cf0182 Functions to cache session backups key automatically 2020-03-23 14:24:35 +00:00
mjattiot e25158975b formatting
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
mjattiot 7e028a82fc improved compatibility
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
mjattiot 17fe3e4dc1 init
Signed-off-by: mjattiot <mjattiot@opensense.fr>
2020-03-20 15:22:56 +01:00
Bruno Windels 4bd09c45a0 assume the requested method is supported by other party during to_device verification 2020-03-20 13:29:29 +01:00
Zoe 6a7a255081 Merge pull request #1271 from matrix-org/foldleft/12704-key-storage
Rename ssss cache functions to be more general
2020-03-20 11:17:21 +00:00
Zoe 6701fdd486 Rename ssss cache functions to be more general 2020-03-20 10:18:06 +00:00
David Baker ddce14b20b Use the typeof test to avoid undefined 2020-03-19 21:12:57 +00:00
David Baker f1317e824b Don't assume subtleCrypto exists if there's a window
Jest has a window object but doesn't have subtleCrypto
2020-03-19 21:04:36 +00:00
David Baker db285af0b5 Add callback to get the user's current key backup passphrase
And also add a null check
2020-03-19 20:36:00 +00:00
David Baker 0434bf5a48 Add functions to get the raw key backup key 2020-03-19 20:34:57 +00:00
Zoe 78d9111646 Add a store for backup keys 2020-03-19 15:30:28 +00:00
J. Ryan Stinnett 0f28a89c52 Merge pull request #1268 from matrix-org/jryans/send-only-new-key-sigs
Upload only new device signature of master key
2020-03-19 14:56:29 +00:00
Hubert Chathi 92db6599d8 Merge pull request #1270 from matrix-org/uhoreg/expose_prepare_to_encrypt
expose prepareToEncrypt in the client API
2020-03-19 10:56:16 -04:00
Hubert Chathi 70fb5dcaa4 Merge pull request #1269 from matrix-org/uhoreg/device_list_no_dying
don't kill the whole device download if one device gives an error
2020-03-19 10:56:00 -04:00
David Baker a265574da1 Merge remote-tracking branch 'origin/develop' into dbkr/symmetric-ssss-migrate 2020-03-19 14:27:25 +00:00
Hubert Chathi 9911766435 expose prepareToEncrypt in the client API 2020-03-18 18:53:26 -04:00
Hubert Chathi fb08ef9a9b don't kill the whole device download if one device gives an error 2020-03-18 15:28:54 -04:00
J. Ryan Stinnett 2fab06111c Upload only new device signature of master key
This changes bootstrap to only upload the new device signature of the master
key. We were previously _adding_ the new signature, but then uploading both old
and new device key signatures of the master key.

This was particularly bad when re-uploading signatures from deleted devices, as
that would cause the homeserver to reject the entire upload.

Fixes https://github.com/vector-im/riot-web/issues/12752
2020-03-18 18:35:37 +00:00
Bruno Windels 11e3b1ab53 Merge pull request #1267 from matrix-org/bwindels/handleselfverifstartrace
handle racing .start event during self verification
2020-03-18 14:06:45 +00:00
Zoe 3c78f7dbe1 Merge pull request #1266 from matrix-org/foldleft/fix-label-error
A crypto.keySignatureUploadFailure event reported the wrong source
2020-03-18 11:21:16 +00:00
Bruno Windels 999cebc304 handle racing .start event during self verification
by comparing the device id rather than the user id, as defined in the MSC
2020-03-17 17:51:32 +01:00
RiotRobot b2e154377a Merge branch 'master' into develop 2020-03-17 14:09:49 +00:00
RiotRobot d5c68139c0 v5.1.1 2020-03-17 14:07:01 +00:00
RiotRobot cbde77a5cd Prepare changelog for v5.1.1 2020-03-17 14:07:00 +00:00
David Baker 8120041ba7 Merge branch 'symmetric-ssss-migrate' of git://github.com/uhoreg/matrix-js-sdk into uhoreg-symmetric-ssss-migrate 2020-03-17 13:11:01 +00:00
Michael Telatynski 68bc8edaae Merge pull request #1263 from matrix-org/t3chguy/fix_editing
Fix editing of unsent messages by waiting for actual event id
2020-03-17 13:00:55 +00:00
Zoe 7ec339985a a crypto.keySignatureUploadFailure event reported the wrong source 2020-03-17 11:42:03 +00:00
Bruno Windels 70c0abaef8 Merge pull request #1265 from matrix-org/bwindels/fixolmapierror-release
Fix: ensureOlmSessionsForDevices parameter format
2020-03-17 11:25:59 +00:00
Bruno Windels d4dcac93b1 devicesByUser should be userId => array of devices 2020-03-17 12:21:56 +01:00
Michael Telatynski 43889cfb31 use async/await instead
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-17 11:14:25 +00:00
Bruno Windels 9e4e14802d Merge pull request #1264 from matrix-org/bwindels/fixolmapierror
Fix: ensureOlmSessionsForDevices parameter format
2020-03-17 11:08:23 +00:00
Bruno Windels 9bebb22746 devicesByUser should be userId => array of devices 2020-03-17 09:51:28 +01:00
Hubert Chathi 3b06b0ffc1 fix lint 2020-03-16 17:22:12 -04:00
Hubert Chathi 1b24d55b24 misc fixes and cleanups 2020-03-16 17:20:54 -04:00
Hubert Chathi c8c6444f6a migrate backup key from asymmetric SSSS to symmetric SSSS 2020-03-16 11:05:07 -04:00
Hubert Chathi 45a88f0517 add function to check that secret storage needs upgrading 2020-03-16 11:00:11 -04:00
Michael Telatynski 53cb3ca79b return the additional promise to simplify the rejection chain
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 12:23:13 +00:00
Michael Telatynski 68526284f1 fix rejection handling
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:34:39 +00:00
Zoe 68cebc7ff9 If a key upload fails, throw an error and emit an event (#1254) 2020-03-16 10:24:31 +00:00
Michael Telatynski 38286b74e3 tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:10:22 +00:00
Michael Telatynski 86f56082f0 Make use of scheduler instead of an additional promise
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 10:09:17 +00:00
Michael Telatynski e87bbfc535 Fix editing of unsent messages by waiting for actual event id
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-03-16 09:29:37 +00:00
Travis Ralston 758e12d6dd Merge pull request #1261 from matrix-org/travis/yarn-cleanup
Remove stuff that yarn install doesn't think we need
2020-03-13 09:25:23 -06:00
Bruno Windels bff461081a Merge pull request #1262 from matrix-org/bwindels/nullcheckonreceipts-release
Fix: prevent error being thrown during sync in some cases
2020-03-13 12:47:52 +00:00
Bruno Windels 33d36395aa check if push actions has a tweaks object 2020-03-13 13:41:32 +01:00
Hubert Chathi e373508211 some fixes in SSSS migration 2020-03-12 18:08:54 -04:00
Bruno Windels 9051edad37 Merge pull request #1258 from matrix-org/bwindels/nullcheckonreceipts
Fix: prevent error being thrown during sync in some cases
2020-03-12 17:09:10 +00:00
Travis Ralston 678b268008 Remove stuff that yarn install doesn't think we need 2020-03-12 10:44:52 -06:00
J. Ryan Stinnett 0361bcf94f Merge pull request #1260 from matrix-org/jryans/verified-to-bool-release
Force `is_verified` for key backups to bool and fix computation
2020-03-12 15:48:07 +00:00
J. Ryan Stinnett b1f02d30c1 Check key backup trust for the right user ID
This corrects the key backup trust computation so that we use the user ID for
the device we're checking inside of always using the client's main user ID,
which would always resulted in false for other people.

Fixes https://github.com/vector-im/riot-web/issues/12693
2020-03-12 15:42:14 +00:00
J. Ryan Stinnett 2af0e5b176 Convert trustedLocally to a bool in all cases
This ensure we always have a boolean value, even when device is null.

Part of https://github.com/vector-im/riot-web/issues/12693
2020-03-12 15:42:14 +00:00
J. Ryan Stinnett c204812d9c Merge pull request #1259 from matrix-org/jryans/verified-to-bool
Force `is_verified` for key backups to bool and fix computation
2020-03-12 15:39:26 +00:00
J. Ryan Stinnett 3b7def880f Check key backup trust for the right user ID
This corrects the key backup trust computation so that we use the user ID for
the device we're checking inside of always using the client's main user ID,
which would always resulted in false for other people.

Fixes https://github.com/vector-im/riot-web/issues/12693
2020-03-12 14:47:28 +00:00
J. Ryan Stinnett e5ec2f03c2 Convert trustedLocally to a bool in all cases
This ensure we always have a boolean value, even when device is null.

Part of https://github.com/vector-im/riot-web/issues/12693
2020-03-12 14:21:46 +00:00
Bruno Windels a1b3e8055f check if push actions has a tweaks object 2020-03-12 12:59:43 +01:00
Bruno Windels 1e503261f2 Merge pull request #1257 from matrix-org/bwindels/devicelegacyverif
Add a method for legacy single device verification, returning a verification request
2020-03-12 11:30:08 +00:00
David Baker 9107a3e569 Merge pull request #1256 from matrix-org/dbkr/yarn_upgrade_20200311
yarn upgrade
2020-03-12 09:44:51 +00:00
RiotRobot c6070519ed v5.1.1-rc.1 2020-03-11 15:05:49 +00:00
RiotRobot 30ece1be70 Prepare changelog for v5.1.1-rc.1 2020-03-11 15:05:48 +00:00
Bruno Windels b66a1d30a0 method for legacy single device verification, returning a verification request rather than a verifier 2020-03-11 15:53:38 +01:00
David Baker 51e1f56873 yarn upgrade 2020-03-11 14:47:48 +00:00
Hubert Chathi 86304fd037 Merge pull request #1252 from matrix-org/uhoreg/megolm_speed
refactor megolm encryption to improve perceived speed
2020-03-10 20:09:41 -04:00
Hubert Chathi 04387e78cc some cleanups 2020-03-10 15:56:33 -04:00
Travis Ralston 2bfc44b947 Merge pull request #1253 from matrix-org/travis/remove-v1-identity
Remove v1 identity server fallbacks
2020-03-10 09:30:22 -06:00
Bruno Windels 33941eb37b Merge pull request #1251 from matrix-org/bwindels/altaliasesforname
Use alt_aliases instead of local ones for room names
2020-03-10 12:42:50 +00:00
J. Ryan Stinnett 0a45559276 Merge pull request #1250 from matrix-org/jryans/xsign-slow-login
Upload cross-signing key signatures in the background
2020-03-10 11:07:45 +00:00
Travis Ralston 800441e0ed Appease the linter 2020-03-09 17:10:37 -06:00
Travis Ralston 95164d08d5 Remove v1 identity server fallbacks
Fixes https://github.com/vector-im/riot-web/issues/10443

**Review with https://github.com/matrix-org/matrix-react-sdk/pull/4191**
2020-03-09 17:06:10 -06:00
Hubert Chathi 98d955ef1f refactor megolm encryption to improve perceived speed
- allow applications to pre-send decryption keys before the message is sent
- establish new olm sessions with a shorter timeout first, and then re-try in
  the background with a longer timeout without blocking message sending
2020-03-09 18:38:18 -04:00
Bruno Windels 950dadc14e fix tests 2020-03-09 18:33:20 +01:00
Bruno Windels 31d2f0135b use alt aliases instead of local ones for room names 2020-03-09 17:13:50 +01:00
J. Ryan Stinnett c02928f294 Upload cross-signing key signatures in the background
At the moment, uploading cross-signing key signatures is a slow process that can
potentially take many minutes (!) for large accounts / slow servers. This
changes to do the bootstrapping related versions of this in the background.

Note that key signature uploads for interactive flows like verification are
still blocking for now.

Fixes https://github.com/vector-im/riot-web/issues/12223
2020-03-09 15:08:14 +00:00
J. Ryan Stinnett 951fff45e6 Skip device verif upgrades when callback not present
This skips the upgrade when the upgrade callback is not present (which is
expected as no one sets it currently). This adds logging for around the upgrade
process.
2020-03-09 15:03:02 +00:00
J. Ryan Stinnett 4fdd817ff5 Add logging around key change post-processing 2020-03-09 14:46:10 +00:00
J. Ryan Stinnett acba31bd6d Merge pull request #1249 from matrix-org/jryans/sharing-names
Fix secret sharing names to match spec
2020-03-09 13:48:06 +00:00
J. Ryan Stinnett b5eea01848 Fix secret sharing names to match spec
When sharing keys, we should use `m.cross_signing` prefix.

Part of https://github.com/vector-im/riot-web/issues/12661
2020-03-09 13:40:02 +00:00
Bruno Windels 074e02ccf2 Merge pull request #1248 from matrix-org/bwindels/removecryptoverifstartevent
Cleanup: remove crypto.verification.start event
2020-03-06 16:47:34 +00:00
Bruno Windels 4b9bc67cb6 remove crypto.verification.start event
as it is not used anymore by the react-sdk
2020-03-06 16:48:44 +01:00
Zoe 936ef4116b For self-verifications, also request keys from the other device (#1245)
* For self-verifications, also request keys from the other device
* removed some XXX's so the editor doesn't think it's three issues
* add methods to access key cache callbacks
2020-03-06 09:56:56 +00:00
J. Ryan Stinnett 9883d6851a Merge pull request #1246 from matrix-org/jryans/xsign-trust-bool
Fix regression in key backup request params
2020-03-05 14:16:16 +00:00
J. Ryan Stinnett 4c08e126ca Fix regression in key backup request params
This converts the cross-signing trust to a boolean as required by the
homeserver.

Regressed by https://github.com/vector-im/riot-web/issues/12599
Fixes https://github.com/vector-im/riot-web/issues/12618
2020-03-05 12:17:42 +00:00
J. Ryan Stinnett bc53f8fdec Merge pull request #1244 from matrix-org/jryans/xsign-key-backup-verif
Use cross-signing trust to mark backups verified
2020-03-03 18:03:46 +00:00
J. Ryan Stinnett 0b76d3d7bd Merge pull request #1243 from matrix-org/jryans/xsign-auto-share
Check both cross-signing and local trust for key sharing
2020-03-03 18:03:38 +00:00
J. Ryan Stinnett abaf71418e Use cross-signing trust to mark backups verified
This changes to cross-signing trust as well as local trust when we decide
whether to tell the homeserver a session of room keys is verified.

Fixes https://github.com/vector-im/riot-web/issues/12599
2020-03-03 15:52:38 +00:00
J. Ryan Stinnett c96a906b39 Check both cross-signing and local trust for key sharing
When sharing room keys with our own devices, this ensure we check both
cross-signing and local trust.

Fixes https://github.com/vector-im/riot-web/issues/12596
2020-03-03 15:12:40 +00:00
RiotRobot da96765020 Merge branch 'master' into develop 2020-03-02 16:55:55 +00:00
RiotRobot f654c8a892 v5.1.0 2020-03-02 16:53:10 +00:00
RiotRobot 336fce55df Prepare changelog for v5.1.0 2020-03-02 16:53:10 +00:00
Zoe d11946d86b Merge pull request #1242 from matrix-org/foldleft/fix-bad-merge
Fixed up tests to match new way that crypto stores are created
2020-03-02 15:01:27 +00:00
Zoe 3a4c72ac08 actually, returning is unnecessary 2020-03-02 14:46:26 +00:00
Zoe 6d3f0f653b there's some days that the linter and i, we just really don't see eye-to-eye 2020-03-02 14:38:24 +00:00
Zoe 81d3534569 added return back 2020-03-02 13:06:13 +00:00
Zoe c54922dba3 Fixed up tests to match new way that crypto stores are created 2020-03-02 12:51:47 +00:00
Michael Telatynski 9da1f7b8d5 Fix unhomoglyph import to make browser-matrix.js happy once more 2020-03-02 10:54:14 +00:00
Zoe a4ed3d97fc Merge pull request #1235 from matrix-org/foldleft/12299-local-ssk
Store USK and SSK locally
2020-03-02 09:52:44 +00:00
Zoe 656694ee00 proper spacing for test output text 2020-03-02 09:45:55 +00:00
Hubert Chathi c6b5936f8a use the right operator 2020-02-28 16:09:24 -05:00
Travis Ralston 03752ab60c Merge pull request #1236 from matrix-org/travis/unpadded-qr-codes
Use unpadded base64 for QR code secrets
2020-02-28 10:20:57 -07:00
Bruno Windels 7203542cfd Merge pull request #1239 from matrix-org/bwindels/dontrequiredoneforselfverif
Don't require .done event for finishing self-verification
2020-02-28 15:16:09 +00:00
Bruno Windels 4b36bbc122 Merge pull request #1237 from matrix-org/bwindels/dontcancelas3rdparty
Don't cancel as 3rd party in verification request
2020-02-28 15:15:49 +00:00
Bruno Windels ecaf21ceb0 Don't require .done event for finishing self-verification
Instead, call onVerifierFinished from the verifier on the request
so we can internally mark it as done. This flag is not persisted,
but we don't have historical (persisted) to-device requests anyway.
2020-02-28 14:56:38 +01:00
Zoe 67fe4e1460 lint & only cache valid keys 2020-02-28 11:04:28 +00:00
Zoe a94503ad03 address PR feedback 2020-02-28 10:43:57 +00:00
Bruno Windels ce6dd8688c Merge pull request #1234 from matrix-org/bwindels/evenmoreloggingforverif
Verification: log when switching start event
2020-02-28 10:24:09 +00:00
Hubert Chathi 1151bdc6db initial work in migrating ssss to symmetric 2020-02-27 22:56:34 -05:00
Hubert Chathi ed223d1d76 remove unnecessary awaits 2020-02-27 22:54:43 -05:00
Bruno Windels 650eee7705 dont cancel as 3rd party in verification request 2020-02-27 18:38:16 +01:00
Travis Ralston 4510eb6540 Match all the equals
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-02-27 10:10:24 -07:00
Travis Ralston 9a236f317d Use unpadded base64 for QR code secrets 2020-02-27 10:00:56 -07:00
Zoe 25c467d608 Wire cache through to matrix client 2020-02-27 16:53:26 +00:00
Zoe c2daf0d74e Store data in cryptostore 2020-02-27 16:53:26 +00:00
J. Ryan Stinnett fa19616ad1 Merge pull request #1233 from matrix-org/jryans/safari-e2e-idb
Perform crypto store operations directly after transaction
2020-02-27 16:48:09 +00:00
Zoe 02cbd33284 Added cache callbacks to CrossSigningInfo 2020-02-27 16:37:25 +00:00
Zoe 941ae18d74 Added tests for CrossSigningInfo.getCrossSigningKey 2020-02-27 16:37:25 +00:00
Bruno Windels 90f400abe1 log when switching start event 2020-02-27 17:35:58 +01:00
J. Ryan Stinnett ff2d93d421 Perform crypto store operations directly after transaction
At least on Safari but perhaps other browsers as well, you must perform
IndexedDB operations in the same JS task as you start the transaction. As a
concrete example, you cannot open the transaction and await some promise before
actually using it.

This fixes the crypto store to meet this requirement.

Fixes https://github.com/vector-im/riot-web/issues/12207
2020-02-27 14:57:07 +00:00
Bruno Windels 8d26bd9a17 Merge pull request #1232 from matrix-org/bwindels/logeventidinverifreq
More verification request logging
2020-02-27 13:26:53 +00:00
J. Ryan Stinnett a9fa0484ff Add exception handling to crypto store paths
A few of the crypto store backend paths were missing try / catch wrappers to
abort the transaction if the inner callback throws.
2020-02-27 12:26:18 +00:00
J. Ryan Stinnett d3d12ab62f Merge pull request #1231 from matrix-org/jryans/upgrade-deps-2020-02-26
Upgrade deps
2020-02-27 11:24:33 +00:00
Bruno Windels 1e29b1a31d log event id in verif request to differentiate between double processing vs double sending 2020-02-26 18:49:18 +01:00
J. Ryan Stinnett 9318bf5f2f Upgrade deps 2020-02-26 15:00:43 +00:00
RiotRobot 6b35302442 v5.1.0-rc.1 2020-02-26 14:16:57 +00:00
RiotRobot 2937e58215 Prepare changelog for v5.1.0-rc.1 2020-02-26 14:16:57 +00:00
J. Ryan Stinnett d42589b6cc Merge pull request #1230 from matrix-org/jryans/dist-tags
Add latest dist-tag for releases
2020-02-26 14:14:09 +00:00
J. Ryan Stinnett 26e9dfb4fb Add latest dist-tag for a release 2020-02-26 14:07:20 +00:00
J. Ryan Stinnett f27d03a6bc Always publish to next tag
This ensures that anyone who wants the latest version (pre-release or final
release) can always use the `next` tag.
2020-02-26 13:55:46 +00:00
J. Ryan Stinnett b1e3150a81 Reset device list dirty flag only after writing
This ensures we wait until after the device list writes to the crypto store
before marking thing as clean. This is particularly important for the error
path, as the write to the crypto store can fail.

Part of https://github.com/vector-im/riot-web/issues/12207
2020-02-25 17:56:47 +00:00
Hubert Chathi 5d52053caa use symmetric encryption for SSSS 2020-02-24 17:38:53 -05:00
Bruno Windels ce668d051c Merge pull request #1225 from matrix-org/bwindels/aliasautocomplete
Add room method for alt_aliases
2020-02-24 12:17:45 +00:00
David Baker e06579ecf5 Merge pull request #1227 from matrix-org/dbkr/move_bk_pipelines
Remove buildkite pipeline
2020-02-21 17:34:09 +00:00
David Baker 6c30af245c Remove buildkite pipeline
Now moved to the pipelines repo
2020-02-21 17:21:42 +00:00
Bruno Windels c9c40a6dde Merge pull request #1226 from matrix-org/bwindels/dontfailonhistoricalcancelafterstart
don't assume verify has been called when receiving a cancellation in verifier
2020-02-21 17:19:03 +00:00
Travis Ralston e748ac3d00 Merge pull request #1221 from matrix-org/travis/qr-binary
Reduce secret size for new binary packing
2020-02-21 10:05:03 -07:00
Bruno Windels aec79f3a79 don't assume verify has been called when receiving a cancellation in verifier 2020-02-21 17:26:29 +01:00
Hubert Chathi bf92cb1522 try to re-fetch devices before giving up on trying to heal a broken olm (#1224) 2020-02-21 10:20:46 -05:00
Bruno Windels 14e1920ff5 fix docs parser error 2020-02-21 13:43:08 +01:00
Bruno Windels c95cdf5a11 add room method for alt_aliases 2020-02-21 13:37:14 +01:00
Bruno Windels c14d0616ea always return null if there is no canonical alias 2020-02-21 13:36:52 +01:00
Hubert Chathi 0112701145 Merge pull request #1223 from matrix-org/uhoreg/misc_rageshake_fixes
misc rageshake fixes
2020-02-20 16:28:49 -05:00
Hubert Chathi cb69515be9 add some logging when sender could not establish an olm session 2020-02-20 14:49:32 -05:00
Hubert Chathi 3cd791e08f add function for getting the user's curve25519 key 2020-02-20 14:44:28 -05:00
Hubert Chathi 6e233e860e remove leftover debugging messages 2020-02-20 14:43:59 -05:00
Hubert Chathi b4f0ea441b remove obsolete comment 2020-02-20 14:43:24 -05:00
Bruno Windels 39974d3a61 Merge pull request #1220 from matrix-org/bwindels/fixhistoricalcancelledrequests
Fix cancelled historical requests not appearing as cancelled
2020-02-20 17:07:35 +00:00
Bruno Windels a998006842 Merge pull request #1217 from matrix-org/bwindels/fixqrcode
Fix renaming error that broke QR code verification
2020-02-20 11:00:39 +00:00
Bruno Windels c4e449fc45 add null check for when there is no response 2020-02-20 11:42:33 +01:00
Travis Ralston 765fbe2182 Reduce secret size for new binary packing
See https://github.com/matrix-org/matrix-react-sdk/pull/4091
2020-02-19 17:21:56 -07:00
Bruno Windels 08dfa73b57 pending excludes observeOnly now, still allow observeOnly requests to get cancelled 2020-02-19 17:51:53 +01:00
RiotRobot a58e7a34e7 v5.0.1 2020-02-19 15:03:04 +00:00
RiotRobot 7a481beec6 Prepare changelog for v5.0.1 2020-02-19 15:03:03 +00:00
Bruno Windels d51fad2de4 Merge pull request #1219 from matrix-org/bwindels/fixaliases
add method for new /aliases endpoint
2020-02-19 10:02:32 +00:00
Bruno Windels c66755a756 jsdoc 2020-02-19 10:13:32 +01:00
Bruno Windels 886ad03505 add method to check server feature flag 2020-02-19 10:08:05 +01:00
Bruno Windels ba33ef0a68 use unstable prefix 2020-02-19 10:07:52 +01:00
Bruno Windels fe97dc3ece add method for new /aliases endpoint 2020-02-18 15:33:41 +01:00
Bruno Windels 76c4875088 fix targetDevice renaming 2020-02-18 11:23:04 +01:00
Bruno Windels 04a3aaee35 Merge pull request #1213 from matrix-org/bwindels/filterverifmethods
method for checking if other party supports verification method
2020-02-18 10:15:49 +00:00
Bruno Windels fef03cda9b Update src/crypto/verification/request/VerificationRequest.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-18 10:03:02 +00:00
Bruno Windels 3292fde41b Merge pull request #1210 from matrix-org/bwindels/localecho2
add local echo state for accepting or declining a verif req
2020-02-18 09:55:09 +00:00
RiotRobot 38cf25ac5a Merge branch 'master' into develop 2020-02-17 11:58:01 +00:00
RiotRobot 13d5d2f958 v5.0.0 2020-02-17 11:55:26 +00:00
RiotRobot 7f6b66c824 Prepare changelog for v5.0.0 2020-02-17 11:55:25 +00:00
Bruno Windels 62c344b633 Merge pull request #1214 from matrix-org/bwindels/workswithrageshakes
make logging compatible with rageshakes
2020-02-14 16:39:05 +00:00
Bruno Windels 75ce2729f9 comment typo 2020-02-14 17:35:07 +01:00
Bruno Windels 6669554867 make logging compatible with rageshakes 2020-02-14 17:31:40 +01:00
Bruno Windels d3294da37c Merge pull request #1209 from matrix-org/bwindels/oneverifrequest
Find existing requests when starting a new verification request
2020-02-14 15:33:06 +00:00
Bruno Windels 9b56bf25cf Update src/crypto/verification/request/InRoomChannel.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-14 14:43:50 +00:00
Bruno Windels e1a33d8a7b Update src/crypto/verification/request/ToDeviceChannel.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-02-14 13:41:38 +00:00
Bruno Windels 47a1224c13 Merge pull request #1211 from matrix-org/bwindels/logsasmac
log MAC calculation during SAS
2020-02-14 12:54:40 +00:00
Bruno Windels 5c57d81e94 method for checking if other party supports verification method 2020-02-14 13:47:24 +01:00
Bruno Windels edefd3ec88 log MAC calculation 2020-02-14 12:20:02 +01:00
Bruno Windels f15098efde add local echo state for accepting or declining a verif req 2020-02-13 17:27:18 +01:00
RiotRobot 8ee99a0616 v5.0.0-rc.1 2020-02-13 15:41:46 +00:00
RiotRobot 3ace1d04cd Prepare changelog for v5.0.0-rc.1 2020-02-13 15:41:45 +00:00
Bruno Windels 365bb772bc also find existing request for to-device verification 2020-02-13 15:37:21 +01:00
Bruno Windels 5ee6ada973 use pending instead of individual checks 2020-02-13 15:37:04 +01:00
Bruno Windels ee0fa0e687 fix lint 2020-02-13 14:47:35 +01:00
Bruno Windels 0d41f6aafc remove commented out logging 2020-02-13 14:36:18 +01:00
Bruno Windels 91b6499815 more consistent naming 2020-02-13 14:36:09 +01:00
Bruno Windels 7cd1166a47 allow finding existing verif req without starting a new one 2020-02-13 14:31:33 +01:00
Bruno Windels f76cb677ff store sasEvent on verifier so we can get it if we missed show_sas event 2020-02-13 14:31:03 +01:00
Bruno Windels 05e7f4e6f7 look for existing verification request when trying to start a new one 2020-02-13 14:30:38 +01:00
Bruno Windels 6684574bdf Merge pull request #1206 from matrix-org/bwindels/dontpassmethodstoverify
Remove methods argument to verification
2020-02-13 08:51:27 +00:00
Hubert Chathi 36a945f8e2 Merge pull request #1207 from matrix-org/uhoreg/fix_opts_request
don't do a dynamic import of request
2020-02-11 13:54:12 -05:00
Hubert Chathi 6a3d322033 don't do a dynamic import of request 2020-02-11 13:02:34 -05:00
Bruno Windels 00c003ec65 remove methods arg to requestVerification(DM)
as it's easy to have this argument be out of sync from all
the places this is called from the js-sdk. There is also little point,
as you can already specify the methods a consumer of the js-sdk
wants to provide through the verificationMethods option when creating
the client object.
2020-02-11 17:42:49 +01:00
Bruno Windels f4d335c161 use default methods if none are provided to the client 2020-02-11 17:42:17 +01:00
Bruno Windels 659f42139b Merge pull request #1201 from matrix-org/travis/wip/qr
QR self-verification fixes
2020-02-11 15:17:02 +00:00
Bruno Windels 0e791ed022 Merge pull request #1204 from matrix-org/bwindels/logverif
Log every verification event
2020-02-11 13:17:23 +00:00
Bruno Windels 48655aa1a3 log every verification event 2020-02-11 10:08:17 +01:00
Bruno Windels 83fa80cfda Merge pull request #1203 from matrix-org/bwindels/dontrequiredoneconfirmation
dont require .done event from other party
2020-02-11 08:18:46 +00:00
Bruno Windels cf5b5ee085 dont require .done event from other party 2020-02-10 18:00:24 +01:00
Bruno Windels 429a4e3526 fix lint 2020-02-10 17:21:22 +01:00
Zoe d66d4c1cd9 Merge pull request #1202 from matrix-org/foldleft/12221-reset-cross-signing
New option to fully reset Secret Storage keys in boostrapSecretStorage
2020-02-10 09:59:28 +00:00
Zoe 7a1bbdf2dd oops 2020-02-07 15:51:27 +00:00
Travis Ralston 29c1459568 Merge pull request #1190 from matrix-org/travis/qr-code-request-based
Add function to estimate target device for a VerificationRequest
2020-02-07 15:37:49 +00:00
Travis Ralston efad46a8a4 Rename target device prop 2020-02-07 15:37:34 +00:00
Zoe a69c621305 New option to fully reset Secret Storage keys in boostrapSecretStorage 2020-02-07 14:45:10 +00:00
Bruno Windels ad6dde6f26 Merge pull request #1200 from matrix-org/bwindels/4sunlockpurpose
pass ssss item name to callback so we can differentiate UI on it
2020-02-07 08:58:43 +00:00
Bruno Windels 2627e46723 add jsdoc for new param 2020-02-06 18:43:46 +01:00
Bruno Windels 408d70b55e pass ssss item name to callback so we can differentiate UI on it 2020-02-06 16:54:12 +01:00
Hubert Chathi 3f369e528b Merge pull request #1167 from cedricvanrompay/1-olm-device-export-import
add export/import of Olm devices
2020-02-05 20:09:01 -05:00
Zoe 312976294b Merge pull request #1199 from matrix-org/foldleft/types-for-utils
Convert utils.js -> utils.ts
2020-02-05 12:41:17 +00:00
Zoe 77f42c479b Update src/utils.ts
Co-Authored-By: Travis Ralston <travpc@gmail.com>
2020-02-05 11:50:39 +00:00
Zoe d60bd22674 actually let's not get into the business of writing types for our deps 2020-02-05 11:43:11 +00:00
Zoe 2e67f77d3e compiler flags 2020-02-05 11:17:55 +00:00
Zoe 6d8e8e6bd7 fix tests 2020-02-05 11:07:55 +00:00
Zoe 9c01945a05 copyright notice *sigh* 2020-02-05 10:23:24 +00:00
Zoe 7ce5ddd380 lint 2020-02-05 10:14:26 +00:00
Zoe 2b5de914f5 review feedback 2020-02-05 09:57:46 +00:00
Zoe 18a2426707 Convert utils.js -> utils.ts 2020-02-04 19:09:48 +00:00
David Baker 367fac6d54 Merge pull request #1197 from matrix-org/dbkr/stop_signing_yourself
Don't sign ourselves as a user
2020-02-04 14:31:08 +00:00
David Baker 157cc9e5eb Merge remote-tracking branch 'origin/develop' into dbkr/stop_signing_yourself 2020-02-04 14:26:58 +00:00
David Baker 81daf12598 Merge pull request #1196 from matrix-org/dbkr/verfication_logging
Add a bunch of logging to verification
2020-02-04 14:21:08 +00:00
Bruno Windels 9249b0652f Merge pull request #1198 from matrix-org/bwindels/fixverifroomeventtype
Fix: always return a valid string from InRoomChannel.getEventType
2020-02-04 14:16:14 +00:00
Bruno Windels ee4c6b6265 Merge pull request #1195 from matrix-org/bwindels/logoncancel
add logging when a request is being cancelled
2020-02-04 13:08:13 +00:00
David Baker 68deab4a68 We still need to mark our master key locally verified 2020-02-04 12:27:53 +00:00
Bruno Windels c9c765b5b8 fix getEventType 2020-02-04 13:12:38 +01:00
David Baker 616f73d8c6 forgive me, o great linter 2020-02-04 12:12:02 +00:00
Bruno Windels 208c371afb add failing test for getEventType 2020-02-04 13:10:06 +01:00
David Baker 3a59cfa9c0 Don't sign ourselves as a user 2020-02-04 12:09:42 +00:00
David Baker cf94527bd5 Add a bunch of logging to verification
So we have a better idea of what's going on
2020-02-04 12:04:50 +00:00
Travis Ralston fa93479863 Merge pull request #1194 from matrix-org/travis/fix-type
Don't explode verification validation if we don't have an event type
2020-02-04 11:48:25 +00:00
Bruno Windels 8bc0ef8c27 add logging when a request is being cancelled
so we can more easily see (especially for to_device requests)
why something was cancelled
2020-02-04 12:48:02 +01:00
Travis Ralston bd403b6d87 Don't explode verification validation if we don't have an event type
I don't know why this is undefined at this point, or why membership events are ending up here, but this fixes develop for people.

See https://github.com/vector-im/riot-web/issues/12231
2020-02-04 11:46:31 +00:00
Bruno Windels 57a7328065 Merge pull request #1193 from matrix-org/bwindels/dontshowverifrequestnotforme
Fix: verification request appearing for users that are not the receiver or sender if they are in room
2020-02-04 09:39:45 +00:00
Bruno Windels 4945463beb fix lint 2020-02-03 20:12:21 +01:00
Bruno Windels dfafa791f2 fix getOtherPartyUserId 2020-02-03 19:17:40 +01:00
Bruno Windels 5f2cb6b3a4 only an m.room.message with msgtype can be a .request 2020-02-03 19:17:18 +01:00
Bruno Windels 5398fac348 add (failing) tests for getEventType and getOtherPartyUserId 2020-02-03 19:16:48 +01:00
Cédric Van Rompay b217f6aa81 minor doc update (with sign-off)
Signed-off-by: Cédric Van Rompay <cedric.vanrompay@gmail.com>
2020-02-03 10:32:32 +01:00
Cédric Van Rompay ec597bea93 fix new way of calling OlmDevice.init 2020-02-03 10:27:10 +01:00
Cédric Van Rompay 7a5c54fef7 set pickle key through OlmDevice.init 2020-02-03 09:58:18 +01:00
David Baker 4064f18de2 Merge pull request #1192 from matrix-org/dbkr/fix_passthrough_key_get
Fix getting secrets encoded with passthrough keys
2020-02-02 19:12:39 +00:00
David Baker 6d13457172 Fix getting secrets encoded with passthrough keys 2020-02-01 17:29:08 +00:00
Travis Ralston f39518ef93 Unreviewed crypto verification for self 2020-02-01 10:49:32 +00:00
Bruno Windels 4b1cecd246 also set the deviceId on .ready so we know who to send .start to 2020-01-31 14:50:48 +01:00
Cédric Van Rompay 352509fd3a Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:53:20 +01:00
Cédric Van Rompay d0f08f8839 Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:53:09 +01:00
Cédric Van Rompay efd38a3471 Update src/crypto/OlmDevice.js
Co-Authored-By: Hubert Chathi <hubert@uhoreg.ca>
2020-01-31 11:52:54 +01:00
Cédric Van Rompay a4e74fea94 fix linting errors 2020-01-31 11:51:17 +01:00
Travis Ralston fdb33b6189 Merge remote-tracking branch 'origin/bwindels/todevicereadystartdone' into travis/wip 2020-01-30 18:06:31 +00:00
Bruno Windels dcbb67838b for the right panel to work, the verifier should send .done events 2020-01-30 18:46:22 +01:00
Bruno Windels 1727d636a3 don't assume both parties have a different userId in verif ping-pong 2020-01-30 18:45:54 +01:00
Travis Ralston 9eadc7f868 Add function to estimate target device for a VerificationRequest
For https://github.com/matrix-org/matrix-react-sdk/pull/4001
2020-01-30 16:57:01 +00:00
Travis Ralston 620118af5f Merge pull request #1175 from matrix-org/travis/update-qr-code
Update QR code handling for new spec
2020-01-30 11:25:57 +00:00
Travis Ralston 3645764f9a Appease the linter 2020-01-30 11:15:25 +00:00
Travis Ralston 769bfeb10f Verify all the things 2020-01-30 11:10:25 +00:00
Travis Ralston 5fbaa9cfa7 Fix verification of the master key 2020-01-29 18:06:25 +00:00
Travis Ralston 007508ba12 Merge branch 'develop' into travis/update-qr-code 2020-01-29 16:57:57 +00:00
David Baker 0f1f18b232 Merge pull request #1188 from matrix-org/dbkr/dont_add_epemeral_events_to_timeline_when_peeking
Don't add ephemeral events to timeline when peeking
2020-01-29 15:17:27 +00:00
David Baker d6b754b133 Merge pull request #1189 from matrix-org/dbkr/be_prepaed
Fix typo
2020-01-29 15:16:56 +00:00
Travis Ralston 1b80c83676 Merge branch 'develop' into travis/update-qr-code 2020-01-29 15:11:06 +00:00
Travis Ralston ec4dc582b6 Remove tests for old QR code stuff 2020-01-29 15:10:35 +00:00
David Baker 65646ff9e2 Fix typo
This would probably just cause apps to wait until the first live
sync had finished rather than the one from the store, so slowing
them down / breaking offline support.
2020-01-29 15:06:19 +00:00
Travis Ralston 92f6ec918b Appease the linter 2020-01-29 15:06:13 +00:00
David Baker 62bd41d2e6 Don't add ephemeral events to timeline when peeking
As hopefully explained by comment.

Fixes https://github.com/vector-im/riot-web/issues/11120
2020-01-29 15:04:09 +00:00
Bruno Windels 9d864ffd60 Merge pull request #1187 from matrix-org/bwindels/fixstartrace-rebased
Verification: resolve race between .start events from both parties
2020-01-29 15:04:00 +00:00
Travis Ralston c45b38cece Actually do the verification 2020-01-29 14:56:28 +00:00
Travis Ralston 0d7aee2c36 Misc cleanup 2020-01-29 14:52:04 +00:00
Travis Ralston be345a523f Fix verification flow 2020-01-29 14:43:37 +00:00
Bruno Windels 470bdf8741 fix tests 2020-01-29 15:19:18 +01:00
Bruno Windels 59319fb55b use logger instead of console 2020-01-29 15:19:07 +01:00
Bruno Windels fb7695fdbc fix unrelated issue: errorFactory returns function, so call it 2020-01-29 15:18:48 +01:00
Bruno Windels 25b7552683 startEvent can always be passed to verifier
as we'll check the sender there to see on which side we are
2020-01-29 15:18:18 +01:00
Bruno Windels 21d520378f apply same algo to pick .start event initially when changing phase
smallest sender userid wins
2020-01-29 15:17:36 +01:00
Bruno Windels 9cd6607520 attempt to switch start event if we already have a verifier 2020-01-29 15:16:54 +01:00
Bruno Windels efd3550f53 support switching startEvent while waiting for .accept on initiator side
if we get a .start event from the other party and we've also sent one,
the .start event with the sender that is first in sorting order should
be taken, and the other one ignored.

At the point where we will receive it, the verifier has already
been returned from beginKeyVerification, so we'll need to switch
start event internally, and retry the verification, now on the
receiver (sending .accept) side instead of initiator side
(sending .start).
2020-01-29 15:13:59 +01:00
Travis Ralston 76402ec8d7 Lie to the verification handling 2020-01-29 13:45:02 +00:00
Travis Ralston f689142806 Define NAME as a property higher up 2020-01-29 10:52:26 +00:00
Travis Ralston fd563bda6a Remove irrelevant verification flows for QR codes
You can't actually get at these through our verification framework - they scan/show steps are pre-verification framework.
2020-01-29 09:26:29 +00:00
Travis Ralston 09a8f7122c Merge branch 'develop' into travis/update-qr-code 2020-01-29 00:18:15 +00:00
David Baker 608fb00844 Merge pull request #1184 from matrix-org/dbkr/new_keybackup_in_bootstrap
Add option to bootstrap to start new key backup
2020-01-28 22:02:10 +00:00
David Baker 5c45e9c306 Add option to bootstrap to start new key backup
The key backup needs to be signed by the cross-signing key so
doing it here allows us to do it before we blow the private part
out of memory.
2020-01-28 19:36:00 +00:00
Travis Ralston 950221dc13 Merge branch 'develop' into travis/update-qr-code 2020-01-28 17:27:38 +00:00
Travis Ralston f816679596 Merge pull request #1182 from matrix-org/travis/null-guards
Add a bunch of null guards to feature checks
2020-01-28 14:24:53 +00:00
Travis Ralston 80ccf18b16 Merge pull request #1183 from salzig/docs/fix_matrix_client_reference
docs: fix MatrixClient reference
2020-01-28 14:13:59 +00:00
Ben Rexin c7abd9062a docs: fix MatrixClient reference 2020-01-28 15:05:13 +01:00
Travis Ralston 4287f2229b Add a bunch of null guards to feature checks 2020-01-28 13:21:01 +00:00
Michael Telatynski 8408055137 Merge pull request #1180 from matrix-org/t3chguy/cs_verification_decoration
Add helper to obtain the cancellation code for a verification request
2020-01-28 11:23:24 +00:00
Michael Telatynski cc0965d703 s/^t/T/ 2020-01-28 11:19:05 +00:00
Michael Telatynski 94b3d9d3e1 Add helper to obtain the cancellation code for a verification request 2020-01-28 11:15:07 +00:00
J. Ryan Stinnett 772bf7d6ff Merge pull request #1178 from matrix-org/jryans/tag-prerelease-next
Publish pre-releases as a separate tag on npm
2020-01-27 22:32:56 +00:00
J. Ryan Stinnett 15c2e4bb07 Publish pre-releases as a separate tag on npm
npm will install the newest version a package has published to the `latest` tag,
including pre-releases, which is not ideal since those may not be ready for
production use yet.

This uses an alternate tag (`next` is a common convention, but it can be
anything) for pre-releases so the default installs only get stable versions.

Fixes https://github.com/vector-im/riot-web/issues/12029
2020-01-27 20:40:35 +00:00
Travis Ralston 419693023f Add untested reciprocate function 2020-01-27 11:41:52 -07:00
Travis Ralston 2d081f2c19 Merge branch 'develop' into travis/update-qr-code 2020-01-27 11:41:05 -07:00
David Baker c76ce1fd85 Merge pull request #1177 from matrix-org/dbkr/fix_passthrough_keys
Fix support for passthrough keys
2020-01-27 16:55:27 +00:00
David Baker f38b4d37e6 Check for the whole thing being null 2020-01-27 16:25:08 +00:00
David Baker 73c92dfc57 Merge pull request #1174 from matrix-org/dbkr/trust_cross_signing_on_verify
Trust our own cross-signing keys if we verify them with another device
2020-01-27 16:11:08 +00:00
David Baker 61c5430deb Fix support for passthrough keys
and add code to fix up ones mis-stored by the old code
2020-01-27 15:50:01 +00:00
J. Ryan Stinnett 21e4c597d9 Merge pull request #1176 from matrix-org/jryans/await-device-list
Ensure cross-signing keys are downloaded when checking trust
2020-01-27 15:34:19 +00:00
J. Ryan Stinnett 4dbeee8cb3 Ignore downloading for tests 2020-01-27 15:28:36 +00:00
J. Ryan Stinnett adc76c636e Merge pull request #1172 from matrix-org/bwindels/reduceveriflogging
Don't log verification validation errors for normal messages
2020-01-27 15:04:00 +00:00
J. Ryan Stinnett 0dbf89b2b4 Ensure cross-signing keys are downloaded when checking trust
When checking cross-signing trust during login, we may not have downloaded keys
yet. This ensures we make an attempt first if needed.

Fixes https://github.com/vector-im/riot-web/issues/12068
2020-01-27 14:55:20 +00:00
Travis Ralston 83241ac17d Update QR code handling for new URL
This doesn't have any meaningful change on the process, just makes it more in line with what we do.
2020-01-27 06:59:04 -07:00
Cédric Van Rompay 6aa5d39357 move new example to own directory 2020-01-27 14:28:50 +01:00
Cédric Van Rompay 1304ecbe03 factor out _initializeFromExportedDevice 2020-01-27 14:12:43 +01:00
RiotRobot aafc027812 Merge branch 'master' into develop 2020-01-27 11:31:09 +00:00
RiotRobot d84e0b166b v4.0.0 2020-01-27 11:28:17 +00:00
RiotRobot d1d46009cd Prepare changelog for v4.0.0 2020-01-27 11:28:17 +00:00
Cédric Van Rompay 3a4b6f0ea0 rename "kwargs" to "opts" 2020-01-27 11:48:28 +01:00
Cédric Van Rompay b3d10ace21 mention export method in import 2020-01-27 11:45:17 +01:00
Cédric Van Rompay c17df7a6f7 fix typo in comments 2020-01-27 11:42:15 +01:00
David Baker 1c13f5026e Merge pull request #1173 from matrix-org/dbkr/fix_bootstrap_cleanup
Fix bootstrap cleanup
2020-01-27 10:18:37 +00:00
David Baker b9cfede888 Trust our own cross-signing keys if we verify them with another device 2020-01-25 20:38:11 +00:00
David Baker 49fd9e90a0 this can be const now 2020-01-25 19:48:36 +00:00
David Baker e09038232e Fix bootstrap cleanup
As hopefully explained in the comment. The symptom of this was that
bootstrapping would work just fine the first time you called it
in any run of the app, but then if called a second time (eg. if you
cancelled by dismissing the password prompt) it would create keys and
upload the public parts but not store the private parts in SSSS,
leaving you with cross signing keys you don't have the private parts
of.

Also use object.assign in the save keys callback just in case we
ever reset a subset of the keys (and also because it makes it a
bit simpler to reason about what objects are where).
2020-01-25 19:42:02 +00:00
Travis Ralston 2cfe310e89 Merge pull request #1155 from matrix-org/travis/qr-verif-rp
QR code verification
2020-01-24 08:55:42 -07:00
Bruno Windels 973c7467e8 Merge pull request #1171 from matrix-org/bwindels/fixverifyowndevice
expose deviceId prop on device channel
2020-01-24 11:24:23 +00:00
Bruno Windels 583df7ed7d don't log verification validation errors for normal messages 2020-01-24 12:23:18 +01:00
Bruno Windels 6d05376f04 expose deviceId prop on device channel
used to check if a verification came through to_device in the toast
2020-01-24 12:01:20 +01:00
Cédric Van Rompay e1f832bfa7 fix linting errors 2020-01-24 09:20:43 +01:00
Travis Ralston b8092cd00b Make the tests pass 2020-01-23 20:41:52 -07:00
Travis Ralston 3c1dca6cef Generate a shared secret if we don't have one 2020-01-23 20:15:02 -07:00
Travis Ralston c0f7dd6fe9 Fix secret size 2020-01-23 20:06:04 -07:00
Travis Ralston 6af6e99480 Expose the request event more readily for consumers 2020-01-23 20:05:56 -07:00
Travis Ralston c5cbe48668 Remove docs too 2020-01-23 19:29:42 -07:00
Travis Ralston 15707956ef Remove private key accessors for cross-signing 2020-01-23 19:29:42 -07:00
Travis Ralston 4668fc87a1 Add cross-signing accessors and QR code stuff 2020-01-23 19:29:42 -07:00
Jack Works 468fb2cc41 chore: remove custom promise, use es6 standard
Signed-off-by: Jack Works <jackworks@protonmail.com>
2020-01-23 19:23:08 -07:00
Jack Works 7c79e7e836 fix: typos
Signed-off-by: Jack Works <jackworks@protonmail.com>
2020-01-23 19:21:19 -07:00
Travis Ralston 925c6ffc3e Merge pull request #1170 from matrix-org/travis/fix-build-release
Move & upgrade babel runtime into dependencies (like it wants)
2020-01-23 15:46:30 -07:00
Travis Ralston 0bf1f48623 Merge pull request #1169 from matrix-org/travis/fix-build
Move & upgrade babel runtime into dependencies (like it wants)
2020-01-23 15:45:53 -07:00
Travis Ralston ffcb1c2513 Move & upgrade babel runtime into dependencies (like it wants)
https://babeljs.io/docs/en/babel-runtime
2020-01-23 15:44:59 -07:00
Travis Ralston f286eb4d11 Move & upgrade babel runtime into dependencies (like it wants)
https://babeljs.io/docs/en/babel-runtime
2020-01-23 15:44:36 -07:00
Cédric Van Rompay 9346c83dc1 fix destructuration of potentially nil value 2020-01-23 18:53:42 +01:00
Bruno Windels a76267f5b0 Merge pull request #1166 from matrix-org/bwindels/verifyowndevicechecks
Add unit tests for verifying your own device, remove .event property on verification request
2020-01-23 17:01:43 +00:00
Cédric Van Rompay 1d3a7b3d52 add example for export/import in browser 2020-01-23 16:55:16 +01:00
Cédric Van Rompay f78f04d553 userId must be included in exported data 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 7b6dabbe9c add high-level export/import methods
not sure how to test these high-level methods though
2020-01-23 16:55:16 +01:00
Cédric Van Rompay ed01b3b8cf stop checking structure of exported data
it should suffice that the exported data
allows to recreate a device that can do crypto
2020-01-23 16:55:16 +01:00
Cédric Van Rompay 7880a30e57 add importing in OlmDevice.init() 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 3a3ff93450 improve export doc 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 3a1cdd37a3 move export test with other Olm tests to have active sessions 2020-01-23 16:55:16 +01:00
Cédric Van Rompay 8db38f8e75 fix output of getAllEndToEndSessions 2020-01-23 16:55:16 +01:00
Cédric Van Rompay ff24ef4ee5 add OlmDevice.prototype.export
- only exporting account and P2P sessions
- test is halfway done:
  - it only prints the export result instead of running assertions on it
  - there are no sessions to export

Note: to run only the added test:

    node_modules/.bin/jest spec/unit/crypto/algorithms/olm.spec.js --testEnvironment node --testNamePattern OlmDevice
2020-01-23 16:55:16 +01:00
Bruno Windels 3faeec4add fix lint 2020-01-23 15:59:47 +01:00
Bruno Windels 7d56ee5084 with the change in the linked react-sdk PR, event isn't used anymore 2020-01-23 15:52:23 +01:00
Bruno Windels b2afaabb8c add unit tests for verifying your own device over to_device messages 2020-01-23 15:52:07 +01:00
Bruno Windels 3efaf90bc8 Merge pull request #1163 from matrix-org/bwindels/verificationaccceptedbyotherdevice
For dm-verification, also consider events sent by other devices of same user as "our" events
2020-01-23 13:27:15 +00:00
Bruno Windels 0c52887688 copyright year
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-23 10:51:19 +00:00
David Baker 8aa1c1545e Merge pull request #1164 from matrix-org/dbkr/prepublish_rel
Add a prepare script
2020-01-22 20:59:40 +00:00
David Baker 7c84f421c5 Turns out prepublish is deprecated and should be prepare 2020-01-22 20:58:25 +00:00
David Baker 42a1dea7ad Add a prepublish script
So we actually build the lib directory before publishing it
2020-01-22 20:58:18 +00:00
David Baker d5e9155a33 Merge pull request #1161 from matrix-org/dbkr/prepublish
Add a prepare script
2020-01-22 20:56:59 +00:00
Michael Telatynski 5def5ab074 Merge pull request #1162 from matrix-org/t3chguy/crypto/keys/upload/deviceId
Remove :deviceId from /keys/upload/:deviceId as not spec-compliant
2020-01-22 17:13:21 +00:00
Bruno Windels 1b242e636b remove obsolete comment 2020-01-22 17:39:21 +01:00
Bruno Windels 05f05c889a don't verify in observeOnly mode 2020-01-22 17:39:21 +01:00
Bruno Windels 1367e285c8 have channel decide what is considered "sent by us"
for in room verification, if another client accepts the request,
we still want to observe so those events should still be
considered ours, so looking at from_device doesn't work there.
2020-01-22 17:39:21 +01:00
Bruno Windels 45ec3e0bb9 also emit if the phase didn't change but observeOnly did 2020-01-22 17:39:21 +01:00
Bruno Windels dc38f78da2 add unit tests for verification request 2020-01-22 17:39:21 +01:00
Michael Telatynski 1b6a74fd93 Remove :deviceId from /keys/upload/:deviceId as not spec-compliant 2020-01-22 15:20:13 +00:00
David Baker 9d8a1494aa Turns out prepublish is deprecated and should be prepare 2020-01-22 14:14:24 +00:00
David Baker 08465cf236 Add a prepublish script
So we actually build the lib directory before publishing it
2020-01-22 14:09:01 +00:00
Damir Jelić 7016848401 Merge branch 'poljar/timeline-window-refactor' into develop 2020-01-21 17:01:16 +01:00
poljar bdd2a9e7e8 timeline-window: Small docfix.
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-21 16:55:12 +01:00
David Baker 80256e6782 Merge pull request #1158 from matrix-org/dbkr/upload_device_keys_empty_auth
Allow a device key upload request without auth
2020-01-21 15:03:58 +00:00
Damir Jelić 7907ef44f8 timeline-window: Refactor out and expose the logic to extend the window. 2020-01-21 15:42:55 +01:00
Damir Jelić 3a97a24686 timeline-window: Refactor out the TimelineIndex getting logic. 2020-01-21 15:21:10 +01:00
David Baker 7f208ed44e Allow a device key upload request without auth
This is useful for querying the supported auth methods.
2020-01-21 11:35:49 +00:00
Bruno Windels 22e6cfaebb Merge pull request #1140 from matrix-org/bwindels/verification-right-panel
Support for .ready verification event (MSC2366) & other things
2020-01-20 17:17:51 +00:00
Bruno Windels 9d6f873048 remove obsolete and now broken method
a request should be accepted by calling accept() on the request.
2020-01-20 18:13:18 +01:00
Bruno Windels d526229a0f update jsdoc of requestVerificationDM
which now returns a Promise of VerificationRequest instead of verifier
2020-01-20 18:12:52 +01:00
Bruno Windels aac68290ac remove obsolete comment 2020-01-20 17:56:28 +01:00
Bruno Windels bd9a2c13eb implement API change in sas test for requestVerificationDM 2020-01-20 17:55:48 +01:00
Bruno Windels e5c65d53f8 set transaction_id for remote echos in TestClient
as InRoomChannel looks at this to decide whether an event is
a remote echo (and to pass it to the verifier or not)
2020-01-20 17:54:26 +01:00
Bruno Windels 121e9d0225 don't overwrite a request when the remote echo arrives before event_id 2020-01-20 17:39:18 +01:00
Bruno Windels c12a3b6610 more fixup: make sure remote echo doesn't arrive earlier for TestClient 2020-01-20 17:35:44 +01:00
RiotRobot 43fee73924 v4.0.0-rc.1 2020-01-20 14:24:36 +00:00
RiotRobot b72e9cb36c Prepare changelog for v4.0.0-rc.1 2020-01-20 14:24:35 +00:00
Bruno Windels 77d0a76186 fixup: another timeout 2020-01-20 14:52:34 +01:00
Bruno Windels e89528315d enable fake timers for consistency
although it doesn't make or break the test
2020-01-20 14:04:32 +01:00
Bruno Windels c34ccc9d53 adjust test: requestVerification returns the request instead of verifier 2020-01-20 14:03:43 +01:00
Bruno Windels e51ba795f3 to make this work while using fake timers, don't use setTimeout
but instead use Promise.resolved() as then always runs in the next tick.
2020-01-20 13:56:39 +01:00
J. Ryan Stinnett 737dcc1d29 Merge pull request #1154 from matrix-org/jryans/complete-sec-confused
Convert secret storage to new account data API
2020-01-20 11:30:55 +00:00
Travis Ralston dba08d230e Merge pull request #1157 from aaronraimist/v5-safe
Add v5 as a safe room version
2020-01-18 19:29:44 -07:00
Aaron Raimist 15fb363874 Add v5 as a safe room version
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2020-01-18 16:37:19 -06:00
Bruno Windels cbe2965849 mention reason in cancellation error 2020-01-17 19:01:30 +01:00
Bruno Windels 59bfc45856 use setTimeout of setInterval 2020-01-17 19:01:08 +01:00
J. Ryan Stinnett ceb4581f91 Convert secret storage to new account data API
This converts all secret storage to use a newer account data API which uses
cached data in stored when available, but also knows how to ask the homeserver
in case it's invoked during early client startup before the initial sync.

As a consequence, it means most secret storage APIs are now async.

Part of https://github.com/vector-im/riot-web/issues/11901
2020-01-17 17:56:05 +00:00
Bruno Windels 07cc93cca2 fix lint 2020-01-17 16:58:19 +01:00
Travis Ralston 1205178e26 Merge branch 'develop' into bwindels/verification-right-panel 2020-01-16 13:13:00 -07:00
J. Ryan Stinnett 8217c0f05f Merge pull request #1153 from matrix-org/jryans/cross-signing-setup
Add API to get account data from server
2020-01-16 16:42:12 +00:00
J. Ryan Stinnett c5c27b3cb0 Add API to get account data from server
This adds an API account data getter that bypasses the local store and goes
directly to the homeserver.

Part of https://github.com/vector-im/riot-web/issues/11214
2020-01-15 21:17:53 +00:00
Travis Ralston 04bbfae08e Merge pull request #1151 from matrix-org/travis/sourcemaps
Fix sourcemaps by refactoring the build system
2020-01-15 10:11:25 -07:00
Travis Ralston b3efa73eda Fix conflict in megolm.js 2020-01-15 09:06:30 -07:00
Travis Ralston f3efac059c Merge branch 'develop' into travis/sourcemaps 2020-01-15 09:03:21 -07:00
Hubert Chathi 9fb4ed2ec0 Merge pull request #1146 from uhoreg/reporting_olm_error
record, report, and notify about olm errors
2020-01-15 09:37:43 -05:00
Hubert Chathi f19013143a fix indexedDB storage and retry decryption when we get an olm error 2020-01-14 23:47:05 -05:00
Hubert Chathi ea3ee9bea5 Merge pull request #1148 from matrix-org/erikj/per_user_device_messages
Send device messages for the same user in same API call.
2020-01-14 21:33:46 -05:00
Travis Ralston ccca6f4b6d Re-add dist script usage to js-sdk and release script 2020-01-14 14:44:06 -07:00
David Baker 6a583d2ba6 Merge pull request #1150 from matrix-org/dbkr/dont_error_on_unknown_devices
Add an option to ignore unverified devices
2020-01-14 17:11:47 +00:00
Travis Ralston 4049a32871 Fix imports in crypto index post-merge 2020-01-14 10:08:26 -07:00
Travis Ralston 331c9ce1ff [CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps 2020-01-14 10:04:36 -07:00
David Baker 81ab2aca37 spelling
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2020-01-14 14:10:17 +00:00
David Baker 564b8276bf Merge pull request #1144 from matrix-org/dbkr/key_backup_resign
Sign key backup with cross-signing key on upgrade
2020-01-14 11:30:06 +00:00
Erik Johnston b4a93d2dc3 Also apply cahnge to '_splitBlockedDevices' 2020-01-14 10:09:19 +00:00
Erik Johnston 260040b919 Rename var to match new function 2020-01-14 10:07:54 +00:00
J. Ryan Stinnett 8dbef8b68e Merge pull request #1145 from aaronraimist/lock
Emoji verification: Change name of 🔒 to lock
2020-01-13 21:32:49 +00:00
Travis Ralston 458b2d422d Merge branch 'develop' into travis/sourcemaps 2020-01-13 11:17:27 -07:00
David Baker ee51357dbc Add an option to ignore unverified devices
Hopefully all necessary information is on the docstring.

Default behaviour remains unchanged.
2020-01-13 17:37:38 +00:00
Hubert Chathi fa679e873d Merge pull request #1147 from uhoreg/separate_encrypted_content
use a separate object for each encrypted content
2020-01-13 10:17:32 -05:00
Erik Johnston ed3fded8e8 Send device messages for the same user in same API call.
Currently we split the device messages up to limit the number per call,
but that can end up splitting messages to a given users device over
separate API calls. This is fine, but means that the server can't e.g.
bundle them into a single EDU for remote users or sanity check that the
client is sending to the right set of devices (i.e. its device list
cache isn't wrong).
2020-01-13 13:43:00 +00:00
RiotRobot 92df82bfa9 Merge branch 'master' into develop 2020-01-13 12:55:18 +00:00
RiotRobot 0dc9c27651 v3.0.0 2020-01-13 12:52:23 +00:00
RiotRobot f6f54c35a3 Prepare changelog for v3.0.0 2020-01-13 12:52:22 +00:00
Hubert Chathi 0a9959bffb use a separate object for each encrypted content
so that we don't duplicate the ciphertext for everyone
2020-01-11 15:49:01 -05:00
Hubert Chathi b3a16cb852 lint (and add a comment) 2020-01-10 13:36:11 -05:00
Aaron Raimist 9beb259333 Emoji verification: Change name of 🔒 to lock
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2020-01-09 21:55:43 -06:00
Hubert Chathi 63c57e8e02 record, report, and notify about olm errors 2020-01-09 22:19:35 -05:00
David Baker 0448a7ea68 Sign key backup with cross-signing key on upgrade
Add a signature from the cross-signing master key to the key
backup when upgrading the key backup into cross-signing.

For https://github.com/vector-im/riot-web/issues/11747
2020-01-09 20:46:36 +00:00
Travis Ralston 5bd005b28a Merge pull request #1143 from matrix-org/travis/sourcemaps-dev
Sourcemaps: develop -> feature branch
2020-01-07 15:30:56 -07:00
Travis Ralston 3aec6367d1 Fix OlmDevice import for "algorithms" to fix tests 2020-01-07 15:09:46 -07:00
Travis Ralston cea3831c20 Fix merge conflicts
This is done outside of the merge to highlight the changes, hopefully.
2020-01-07 14:43:36 -07:00
Travis Ralston 18ccceca2d [CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps-dev 2020-01-07 14:37:17 -07:00
Travis Ralston fffcdcb514 Merge pull request #1134 from matrix-org/travis/babel7-wp-media
Use a safer import/export scheme for the ContentRepo utilities
2020-01-07 14:27:36 -07:00
Hubert Chathi efadf374d6 Merge pull request #1142 from uhoreg/reporting_no_key_fix
Fix error handling in decryptGroupMessage
2020-01-07 11:36:53 -05:00
Hubert Chathi 55ecb40190 don't keep processing if we have an error 2020-01-07 11:25:30 -05:00
Hubert Chathi 01f6b3dfc6 notify devices when we don't send them keys (#1135)
and handle incoming notifications
2020-01-06 17:47:22 -05:00
RiotRobot 786590eadc v3.0.0-rc.1 2020-01-06 13:54:48 +00:00
RiotRobot c9174188ba Prepare changelog for v3.0.0-rc.1 2020-01-06 13:54:47 +00:00
Michael Telatynski 64fb79e0be Merge pull request #1141 from matrix-org/t3chguy/fuzzier_disambiguate
Make displayName disambiguation more fuzzy especially against RTL/LTR content
2020-01-06 13:12:39 +00:00
Michael Telatynski 088ff5d0aa Merge pull request #1129 from matrix-org/t3chguy/m_too_large
stop trying to resend event if we get M_TOO_LARGE
2020-01-06 11:42:27 +00:00
Michael Telatynski 99e58b0297 Make displayName disambiguation more fuzzy especially against RTL/LTR content
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-01-05 19:40:04 +00:00
Travis Ralston f4d1c5c006 Switch back to plain export functions instead of class 2020-01-03 12:16:54 -07:00
Bruno Windels 72fd1e4e7c add note to fix bug later 2020-01-03 18:21:33 +01:00
Bruno Windels f44e0a8e12 parenthesis in wrong place broke logic 2020-01-03 18:21:18 +01:00
Bruno Windels 9338d9c2a6 commit logging 2020-01-03 18:20:59 +01:00
Bruno Windels 75fc25feb5 fix method names 2020-01-03 18:20:50 +01:00
Bruno Windels 5919874f6f check !unsent instead of requested for emitting the crypto.request event 2020-01-03 18:20:16 +01:00
Bruno Windels 213bb9dba2 allow to move straight from UNSENT to STARTED
this was one of the things breaking to_device verification
2020-01-03 18:19:49 +01:00
Bruno Windels 3a9dc37d02 new state machine relies on having remote echos, so fake for to_device 2020-01-03 18:18:39 +01:00
Bruno Windels 423c8a886d use isRemoteEcho to determine if the event is theirs or not
rather than the sender and from_device (which is not always set)

as this was one of the things breaking to_device verification
of ones own devices.
2020-01-03 18:16:25 +01:00
David Baker f8a1e98de1 Merge pull request #1139 from matrix-org/dbkr/fix_create_key_backup_crosssigning_disabled
Fix creating a key backup with cross signing diabled
2020-01-03 14:52:16 +00:00
David Baker 5487cf2070 Fix callback check
We need to check for getCrossSisgningKey but that was added
unconditionally elsewhere - only add it if we actually have a
getSecretStorageKey callback to use.
2020-01-03 14:36:04 +00:00
J. Ryan Stinnett e998be3a9b Fix typos in comments 2020-01-03 14:21:10 +00:00
David Baker d70767ef3a Merge pull request #1138 from matrix-org/dbkr/key_backup_checkdevicetrust
Use checkDeviceTrust with key backup
2020-01-03 14:03:50 +00:00
David Baker fbb355c5c9 Thank you once again, o great linter, for saving our lines from being too long 2020-01-03 14:02:38 +00:00
David Baker 20bc8071fc Fix creating a key backup with cross signing diabled
It broke if no scret key callback was supplied but a cross-signing
identity did exist (as hopefully explained in comment).

Fixes https://github.com/vector-im/riot-web/issues/11763
2020-01-03 13:52:36 +00:00
David Baker 0438c6c51c Oh great linter, your wisdom knows no bounds. 2020-01-03 13:37:55 +00:00
David Baker b39abba41e Use checkDeviceTrust with key backup
We did check if it was signed with a cross signing key which should
be the norm going forward, but for completeness, use the proper
cross-signing ernabled check for sigs from individual devices too.

Also adds a deviceTrust member to the signature so the app can see
the cross-signing trust status ofthe device without having to
recalculate it.
2020-01-03 13:32:31 +00:00
Bruno Windels 3ec8233a2d fixes & implement timeout 2020-01-03 13:42:06 +01:00
Bruno Windels 8ed51c806e don't cancel or timeout when verify isn't called 2020-01-03 13:42:06 +01:00
Bruno Windels 57135a898f don't mark events loaded from cache as live events
this makes the verifier want to interact with the other party
when just reloading the session.
2020-01-03 13:42:06 +01:00
Bruno Windels 0d3d27a519 fixes and cleanup for historical 2020-01-03 13:42:06 +01:00
Bruno Windels cf42ad83da WIP historical 2020-01-03 13:42:06 +01:00
Bruno Windels e7bcb61a3b attempt at only creating verifier for live events
but doesn't work yet? data where liveEvent is fished out is undefined
2020-01-03 13:42:06 +01:00
Bruno Windels 883b83f1da move blocking non-participating users back to InRoomChannel
as it doesn't need to happen for ToDeviceChannel
2020-01-03 13:42:06 +01:00
Bruno Windels 48977e6eaa get other party user id by inspecting initial event sender/to fields
also fail validation with any event not sent by or directed to us
2020-01-03 13:42:06 +01:00
Bruno Windels efe2488155 get other user id from channel
next up is inspecting the .request event to
determine it reliably in InRoomChannel
2020-01-03 13:42:06 +01:00
Bruno Windels 29c04b6f9c only move to PHASE_DONE when both .done events are received
as once in done, the request is removed from the request map
and the second .done event that comes in will not find the request
anymore, so the request wouldn't be attached to the event anymore,
breaking rendering it in the timeline.
2020-01-03 13:42:06 +01:00
Bruno Windels 984b6234d2 don't block remote echos to VerificationRequests
also put logic to block non-participating senders in VerificationRequest
so it is shared between both channels.

Remote echo's should not be passed to the verifier though.
2020-01-03 13:42:06 +01:00
Bruno Windels dac4a5452d make this a public prop 2020-01-03 13:42:06 +01:00
Bruno Windels 5f9e82204a more ready and remote echo support 2020-01-03 13:42:06 +01:00
Bruno Windels c4142d93c3 store in-room verification requests by roomId, txnId
as it's harder to determine the other side of a request, given
the in-room code also processes remote echos for own events.
2020-01-03 13:42:06 +01:00
Bruno Windels b34a2c7ee2 WIP 2020-01-03 13:42:06 +01:00
Bruno Windels cd7cc1b71f set verification request on event 2020-01-03 13:42:06 +01:00
Bruno Windels 4c6dd564a4 filter verification methods from argument 2020-01-03 13:42:06 +01:00
Bruno Windels 28e46a82ea expose common phases as properties
so we don't need to import the PHASE_ constants where we need to check
2020-01-03 13:42:06 +01:00
Bruno Windels 10e294784e waitForVerifier is unused now, make it more broadly useful with callback 2020-01-03 13:42:06 +01:00
Bruno Windels 2da725340c return request instead of verifier from verification methods
as MSC2366 adds an extra interactive step to the verification process,
we can't wait for the verifier after sending the request.

This is a breaking change in the js-sdk as it changes the return type
of an existing method.
2020-01-03 13:42:06 +01:00
Bruno Windels 882d3a765d support .ready event in VerificationRequest 2020-01-03 13:42:06 +01:00
Travis Ralston e52e2f10bf Merge pull request #1131 from matrix-org/travis/babel7-wp-main
Add additional properties to package.json for riot-web's webpack
2020-01-02 10:51:52 -07:00
Travis Ralston dfc19e79f1 Merge pull request #1133 from matrix-org/travis/babel7-wp-idb
Fix import for indexeddb crypto store
2019-12-27 12:04:11 -07:00
Travis Ralston f59bd3da7a Merge pull request #1132 from matrix-org/travis/babel7-wp-request
Use the right request when creating clients
2019-12-27 11:56:52 -07:00
Travis Ralston 50791e3aa7 Make ContentRepo a class for easier importing
Exporting it the way we were was causing problems for webpack way down
the line, so we export it differently here to get around that. We also
have to fix all the import references so we import the right thing.
2019-12-22 20:51:30 -07:00
Travis Ralston 8211b2358f Fix import for indexeddb crypto store 2019-12-22 20:48:24 -07:00
Travis Ralston f2e1f3393d Add additional properties to package.json for riot-web's webpack
See https://github.com/vector-im/riot-web/pull/11679/commits/a1c9551bc8a1a6d61afed7e87ff7cebb3042a5ac
2019-12-22 20:47:45 -07:00
Travis Ralston 0ffec0a32d Use the right request when creating clients 2019-12-22 20:45:04 -07:00
Travis Ralston 1e5e705458 Regen lockfile 2019-12-19 17:10:32 -07:00
Travis Ralston f2af6ea60d Merge pull request #1127 from matrix-org/travis/babel7-btargets
Target NodeJS 10, minified browser bundle, and other publishing/package things
2019-12-19 17:08:57 -07:00
Travis Ralston de9187fee2 Merge branch 'travis/sourcemaps' into travis/babel7-btargets 2019-12-19 17:08:50 -07:00
Travis Ralston 5eed091185 Merge pull request #1126 from matrix-org/travis/babel7-updated-sourcemaps
Re-focus sourcemap generation
2019-12-19 17:08:03 -07:00
Travis Ralston 06644b5748 Merge pull request #1125 from matrix-org/travis/babel7-cleanup
Remove ancient polyfill for prototype inheritance
2019-12-19 17:07:56 -07:00
Travis Ralston bb853f65e0 Merge pull request #1124 from matrix-org/travis/babel7-test-sourcemaps
Remove "source-map-support" from tests because it makes sourcemaps worse
2019-12-19 17:07:49 -07:00
Travis Ralston eb830dd014 Merge pull request #1123 from matrix-org/travis/babel7-strict
Remove ancient "use strict" annotations
2019-12-19 17:07:41 -07:00
Travis Ralston de82d1e90c Merge pull request #1122 from matrix-org/travis/babel7-src-es6
Use ES6 imports/exports instead of older CommonJS ones
2019-12-19 17:07:29 -07:00
Travis Ralston 53e838083c Fix terser for new sourcemap handling 2019-12-19 14:04:01 -07:00
Travis Ralston 975368de8f Externalize our sourcemaps for the browser bundle 2019-12-19 13:58:08 -07:00
Travis Ralston 89173be055 Update README to describe build targets better 2019-12-19 13:42:47 -07:00
David Baker fe2bdd027e Merge pull request #1128 from matrix-org/dbkr/keybackup_migrate
Add support for passthrough SSSS secrets
2019-12-19 19:55:39 +00:00
Travis Ralston b376a7c399 Improve minification
uglifify was being applied inline, which only resulted in one asset. With this we've gone back to using `terser` to generate the minified version of the browser bundle, which also exports sourcemaps interpretted from browserify. 

The babelify options have changed to ensure that browser-safe code gets published so terser can more effectively parse it. It doesn't like things like classes it seems, but is fine with let/const and such. The preset-env preset automatically knows it is targeting a browser.
2019-12-19 12:25:28 -07:00
Travis Ralston 2df262d877 Reword concerns about default exports 2019-12-19 11:56:47 -07:00
David Baker 320ab050fe Stray p 2019-12-19 17:28:07 +00:00
David Baker 1816d7aa4c comment 2019-12-19 17:27:15 +00:00
David Baker 41b763f331 Just get the private key from the decryption object 2019-12-19 17:25:28 +00:00
Michael Telatynski 36db57615d stop retrying to send event if we get M_TOO_LARGE 2019-12-19 13:21:05 +00:00
David Baker 8f7ed1dc15 Lint 2019-12-19 11:50:25 +00:00
David Baker 83a8a0cf21 Add support for passthrough SSSS secrets
So we can migrate key backup keys

Adding a passthrough secret itself isn't exposed outside of the
js-sdk: hopefully this should only ever be necessary for this
bootstrap process which the js-sdk handles.
2019-12-19 11:23:57 +00:00
Travis Ralston ffb0e27efa We don't need no dist 2019-12-18 11:37:21 -07:00
Travis Ralston e71c4b3bc4 Publish src to npm as well
So downstream projects can use it via /src if they really want to.
2019-12-17 19:35:00 -07:00
Travis Ralston 85a0adb004 Reorder babel arguments to leave more of it to the config 2019-12-17 19:31:53 -07:00
Travis Ralston f1475cd3d7 Target NodeJS 10 and minified browser bundle
We release with Node 10 currently, so we should use that. The browser bundle is minfied because we want to keep it, so we might as well shave about 1mb off of it.
2019-12-17 16:18:11 -07:00
Travis Ralston 8c14812537 Re-focus sourcemap generation
We'll let babel decide where best to put the sourcemaps. We previously needed inline sourcemaps for browserify to work, though `babelify` takes care of this now that we use `src/` (without sourcemaps in `lib/` being inline, the transform wouldn't work).

Typescript sourcemaps have also been enabled as a mental reminder that they will be exported. Babel is the only thing that uses the tsconfig for generation right now, and it appears to ignore the sourcemaps field.
2019-12-17 16:14:14 -07:00
Travis Ralston 27aedf0563 Remove ancient polyfill for prototype inheritance
None of our targets care about this.
2019-12-17 16:07:56 -07:00
Travis Ralston 95c2c1643e Remove "source-map-support" from tests because it makes sourcemaps worse
Now that we're pointing at `src/` for tests, we can stop trying to load source maps from random places. With this dependency used, source maps are off by a few lines.
2019-12-17 15:45:15 -07:00
Travis Ralston f952f6742f Remove ancient "use strict" annotations
We don't need these anymore. Theoretically this commit could go to develop, but for safety it's going to `travis/sourcemaps` first.
2019-12-17 15:43:02 -07:00
Travis Ralston f3a10a8166 Appease the linter's line length limit 2019-12-17 15:27:17 -07:00
Travis Ralston 0790201cca Add tsify so the browser bundle can use src/ 2019-12-17 15:21:22 -07:00
Travis Ralston 5938c49453 Move index files for outputs and update pipeline
Having them in `src/` helps IDEs do autocomplete a bit more nicely, and helps us not get confused about which one is referencing which. They have also been converted to TypeScript for typings to be generated.
2019-12-17 15:16:37 -07:00
Travis Ralston 14fb080f80 Document ES6 changes 2019-12-17 15:16:37 -07:00
Travis Ralston 034b8db070 Convert tests to ES6
The earlier commit, d3ce0cb82f, has most of the juicy details on this. In addition to d3ce's changes, we also:
* Use `TestClient` in many integration tests due to subtle behaviour changes in imports when switching to ES6. Namely the behaviour where setting the request function is less reliable in the way we did it, but `TestClient` is very reliable.
* We now use the Olm loader more often to avoid having to maintain so much duplicate code. This makes the imports slightly easier to read.
2019-12-17 15:16:37 -07:00
Travis Ralston d3ce0cb82f Convert src to ES6
The bulk of this is just export/import changes, though there's a couple pieces to highlight:
* We no longer use default exports. This is because it's discouraged by the JS community, though not in any official capacity.
* We now use `polyfillSuper` for some prototype inheritance because the tests, and sometimes webpack, break on "cannot call EncryptionAlgorithm without 'new'". It's very much a workaround, and definitely not needed when we use real classes.

There is some import shuffling to help keep the imports clean - this was done by my IDE.
2019-12-17 15:14:22 -07:00
Travis Ralston 4dbda8dffd Merge pull request #1113 from matrix-org/travis/babel7-watcher
[BREAKING] Refactor the entire build process
2019-12-12 16:09:18 -07:00
Travis Ralston 01f32e0f45 Undo change to utils import 2019-12-12 14:14:19 -07:00
J. Ryan Stinnett 9a0de545b8 Merge pull request #1118 from matrix-org/jryans/4s-new-key-backup
Add support for key backups using secret storage
2019-12-12 17:33:05 +00:00
Travis Ralston 86c530e967 Leave the description alone 2019-12-12 10:27:02 -07:00
J. Ryan Stinnett 049b769f68 Add docs 2019-12-12 17:27:01 +00:00
Travis Ralston dcd6626fe6 Fix readme for new lack of minification 2019-12-12 10:26:44 -07:00
Travis Ralston 601cefe975 Fix the release script for new build process
It doesn't seem to care what the version is, so just build the SDK normally.
2019-12-12 10:25:19 -07:00
J. Ryan Stinnett 1fc2ab7f7d Fix backup tests 2019-12-12 16:06:46 +00:00
J. Ryan Stinnett f2c5b2bd49 Emit user trust via the client 2019-12-12 15:41:56 +00:00
J. Ryan Stinnett f31f88ce31 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-12 15:38:32 +00:00
J. Ryan Stinnett d35f5152a9 Restore key backup from stored key 2019-12-12 15:11:48 +00:00
J. Ryan Stinnett d8e19db8bf Merge pull request #1117 from matrix-org/jryans/rm-user-verif-event
Remove unused user verification event
2019-12-12 15:06:13 +00:00
J. Ryan Stinnett 376e56d5fd Guard free calls 2019-12-12 14:49:49 +00:00
J. Ryan Stinnett 72f856eca4 Add util to check whether backup key is stored 2019-12-12 14:46:40 +00:00
J. Ryan Stinnett dbab75eae7 Report cross-signing sig as JS style boolean 2019-12-12 13:25:03 +00:00
J. Ryan Stinnett 7457da80e9 Clean up backup trust checks
There were several inaccurate comments and redundant code paths around backup
trust checks.
2019-12-12 13:18:34 +00:00
J. Ryan Stinnett 443e01d38c Always check backup validity, even during enabling
This ensure we run the full backup validity check even when enabling (rather
than assuming we've signed things correctly) to ensure any problem are reported
right away.
2019-12-12 13:15:32 +00:00
J. Ryan Stinnett 880438c5c1 Remove unused user verification event
This was added with cross-signing work, but nothing actually emits it. Let's
remove it until we find a need.
2019-12-12 12:13:40 +00:00
Travis Ralston 1984cf02cf Merge branch 'develop' into travis/babel7-watcher 2019-12-11 17:22:42 -07:00
J. Ryan Stinnett 5423d3ca61 Merge pull request #1116 from matrix-org/jryans/4s-new-key-backup
Fix check for private keys
2019-12-11 17:54:19 +00:00
J. Ryan Stinnett 3f448df1d3 Create key backup with secret storage
When secret storage is enable, create a random key for encrypting key backups
and store it in SSSS.
2019-12-11 16:29:02 +00:00
J. Ryan Stinnett a626b44bbe Fix check for private keys
This check for new keys was always true, instead of checking whether something
was added.
2019-12-11 14:58:57 +00:00
J. Ryan Stinnett 4c6e2fca91 Merge pull request #1115 from matrix-org/jryans/restore-watch
Restore watching mode for `start:watch`
2019-12-11 14:58:19 +00:00
J. Ryan Stinnett ab4d9ae4bc Restore watching mode for start:watch
Regressed by #1112
2019-12-11 14:50:28 +00:00
J. Ryan Stinnett fb3d075da2 Merge pull request #1079 from matrix-org/jryans/4s-new-key-backup
Add secret storage bootstrap flow
2019-12-11 11:04:22 +00:00
J. Ryan Stinnett 657e48de7e Fix grammar 2019-12-11 10:51:12 +00:00
J. Ryan Stinnett 1b63cb1406 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-11 10:09:29 +00:00
Travis Ralston 4bdabbfbe9 [BREAKING] Refactor the entire build process
For https://github.com/vector-im/riot-web/issues/8880

Features:
* Export modern JS
* Export typings
* Export source maps that actually mean something
* No longer supporting minified builds

This is a step towards being a boring SDK and not anticipating an install location. 

This commit requires a major version bump of the SDK.
2019-12-10 13:25:07 -07:00
Travis Ralston 01f0dd4498 Merge pull request #1112 from matrix-org/travis/babel-7
Part 1 of many: Upgrade to babel@7 and TypeScript
2019-12-10 12:14:24 -07:00
Travis Ralston f59650d8a6 Use better-docs jsdoc template for TypeScript+JS documentation
See description of https://github.com/matrix-org/matrix-js-sdk/pull/1112#issue-351540830 for more info
2019-12-10 11:59:46 -07:00
Travis Ralston 0e444fd925 Re-add babel-eslint
This was accidentally removed.
2019-12-10 11:53:02 -07:00
Travis Ralston 9b8b57d186 Convert randomstring to typescript as a proof of concept 2019-12-10 11:50:01 -07:00
Travis Ralston ca6a52727c Fix logger imports in tests 2019-12-10 11:39:56 -07:00
Travis Ralston 3dfde6bf6a Setup for babel@7 + typescript
Refs:
* https://github.com/matrix-org/matrix-js-sdk/pull/1012
* https://github.com/matrix-org/matrix-js-sdk/pull/1106
2019-12-10 11:31:23 -07:00
J. Ryan Stinnett 780394b051 Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-10 17:54:02 +00:00
J. Ryan Stinnett 6942e3467b Rework to hold cross-signing keys in JS SDK as needed 2019-12-10 17:36:35 +00:00
Michael Telatynski 70eb8a7300 Merge pull request #1100 from matrix-org/t3chguy/remove_bluebird_13
Remove Bluebird: phase 2.5
2019-12-10 16:58:22 +00:00
Michael Telatynski 15a8c23cd0 Merge pull request #1088 from matrix-org/t3chguy/remove_bluebird_12
Remove Bluebird: phase 3
2019-12-10 16:55:23 +00:00
Hubert Chathi 49f0e368d0 Merge pull request #1104 from matrix-org/uhoreg/ignore_verification_done
ignore m.key.verification.done messages when we don't expect any more messages
2019-12-10 09:27:22 -05:00
Bruno Windels 590608a215 Merge pull request #1111 from matrix-org/bwindels/fix-verif-remote-echo-cancel
dont cancel on remote echo of own .request event
2019-12-10 14:09:47 +00:00
Bruno Windels 202fec2a35 dont cancel on remote echo of own .request event 2019-12-10 14:35:17 +01:00
Bruno Windels 817bfa35e5 Merge pull request #1109 from matrix-org/bwindels/accept-verif-request-rebased
Refactor verification request code
2019-12-10 11:10:01 +00:00
Michael Telatynski 110c9800f0 Merge branches 't3chguy/remove_bluebird_12' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_12 2019-12-10 00:19:03 +00:00
Michael Telatynski 1a6dc973bb Merge branches 'develop' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-10 00:18:32 +00:00
J. Ryan Stinnett 44dd674dab Note about static potential 2019-12-09 17:57:24 +00:00
J. Ryan Stinnett 4a3ce640d7 Document verification methods accepts cross-signing key ID as well 2019-12-09 17:20:54 +00:00
Bruno Windels df6ebf83b4 fix tests 2019-12-09 17:45:01 +01:00
Bruno Windels e5dcc5a407 use verification request with channel from crypto 2019-12-09 17:45:01 +01:00
Bruno Windels 1ee8abb0e6 wrap channel passed to verifier to nofity request
so request is notified when verifier sends an event
2019-12-09 17:45:01 +01:00
Bruno Windels dd40435425 make verifier use channel instead of client straight away
so it is agnostic of the channel used
2019-12-09 17:45:01 +01:00
Bruno Windels 74cb57c761 extract DM verification specific things into InRoomChannel 2019-12-09 17:45:01 +01:00
Bruno Windels 86123f28f7 extract to_device verification specific things into ToDeviceChannel 2019-12-09 17:45:01 +01:00
Bruno Windels f97ab32e7c extract common logic between normal & DM verif into single request class 2019-12-09 17:45:01 +01:00
RiotRobot b0e2544e4b Merge branch 'master' into develop 2019-12-09 11:25:47 +00:00
RiotRobot 0d59963b53 v2.4.6 2019-12-09 11:23:37 +00:00
RiotRobot c669aafedb Prepare changelog for v2.4.6 2019-12-09 11:23:37 +00:00
J. Ryan Stinnett 2a2a40af7a Add separate check for secret storage keys
Decryption vs. signing keys are calculated differently and so require separate
check functions.
2019-12-06 17:51:22 +00:00
J. Ryan Stinnett 1df12d1677 Fix type docs for checkPrivateKey 2019-12-06 14:41:29 +00:00
J. Ryan Stinnett 14a2d7e860 Add docs for more exported cross-signing APIs 2019-12-06 13:20:57 +00:00
J. Ryan Stinnett 3f2c05664f More unstable notices 2019-12-06 13:13:20 +00:00
J. Ryan Stinnett 9b05d1d68e Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-12-06 12:08:33 +00:00
J. Ryan Stinnett 772d668389 Merge pull request #1105 from matrix-org/jryans/xs-dl-storage
Fix device list's cross-signing storage path
2019-12-06 12:06:49 +00:00
J. Ryan Stinnett 03360a663e Fix device list's cross-signing storage path
Some variables were changed during the course of the initial cross-signing PR
(https://github.com/matrix-org/matrix-js-sdk/pull/832) without updating the
storage path to match, so we weren't storing / loading cross-signing info for
devices in the end.

This updates storage and loading to match where the data now lives in memory.
2019-12-06 11:59:17 +00:00
Hubert Chathi e1e9f690c9 ignore m.key.verification.done messages when we don't expect any more messages 2019-12-05 12:53:59 -05:00
J. Ryan Stinnett 934e81d16c Clarify the key backup integration is unfinished 2019-12-05 16:36:17 +00:00
J. Ryan Stinnett 88bb31d3e6 Expose deriveKey from passphrase 2019-12-05 16:30:10 +00:00
J. Ryan Stinnett 33f5894547 Adjust secret key adding to consume instead of create
This changes `addKey` for secret storage to consume info about a pre-generated
key, rather than creating the key in middle of the method. This eases UI work
that want to have the public and private keys earlier on in the flow.
2019-12-05 16:30:10 +00:00
David Baker fa46d2bef8 Merge pull request #1103 from matrix-org/dbkr/yarn_upgrade_dec19
yarn upgrade
2019-12-05 13:58:50 +00:00
J. Ryan Stinnett 65f8556ee9 Include KDF params in recovery key info
This adjusts the metadata from `createRecoveryKeyFromPassphrase` to include KDF
info formatted in the way secret storage expects. Since
`prepareKeyBackupVersion` did something similar, we adjust it to use the new
function and reshape the objects.
2019-12-05 10:25:24 +00:00
Michael Telatynski ebe174fbef lets *not* get rid of pointless waits :D
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:47:53 +00:00
Michael Telatynski eaaeedbb37 Merge branches 'develop' and 't3chguy/remove_bluebird_12' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_12 2019-12-04 23:44:04 +00:00
Michael Telatynski bf45c176a7 get rid of bunch of seemingly pointless waits
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:42:16 +00:00
Michael Telatynski 87a8e4c216 Apply uhoreg's patch (with jesty stuff changed out)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:37:49 +00:00
Michael Telatynski 30cc7d4f0f Fix one of the crypto.spec.js failures
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-04 23:31:20 +00:00
Michael Telatynski 4a47867e49 Down to two test failures 2019-12-04 19:17:58 +00:00
J. Ryan Stinnett 5fced642fa Allow getSecretStorageKey to be async 2019-12-04 17:30:27 +00:00
J. Ryan Stinnett 9fb559307b Add recovery key generation path 2019-12-04 15:32:22 +00:00
J. Ryan Stinnett 96c8c2b9c3 Test for cross-signing private key as well as public 2019-12-04 15:16:38 +00:00
J. Ryan Stinnett 145cdf6985 Remove extra space 2019-12-04 14:59:39 +00:00
J. Ryan Stinnett 5910fd95ff Treat secret as not stored if its key info is missing 2019-12-04 14:23:47 +00:00
J. Ryan Stinnett c0dbf2df7f Publicise method testing for secret storage key existence 2019-12-04 14:23:47 +00:00
David Baker cfaadef669 yarn upgrade 2019-12-04 14:19:38 +00:00
J. Ryan Stinnett eeffe208ec Clarify client-level method for adding secret storage keys 2019-12-04 13:45:56 +00:00
RiotRobot 358f13500b v2.4.6-rc.1 2019-12-04 11:57:08 +00:00
RiotRobot 016f16954a Prepare changelog for v2.4.6-rc.1 2019-12-04 11:57:07 +00:00
J. Ryan Stinnett 9dc61faa6f Add bootstrap option to specify storage key 2019-12-04 11:36:25 +00:00
J. Ryan Stinnett 2173ab3437 Add test for bootstrapping from scratch 2019-12-04 10:59:28 +00:00
Travis Ralston c1543545d2 Merge pull request #1102 from matrix-org/travis/aliases
Update alias handling
2019-12-03 13:54:27 -07:00
Travis Ralston 5da936d96a Fix tests 2019-12-03 13:38:40 -07:00
Travis Ralston 0dead73837 Update alias handling
Fixes https://github.com/vector-im/riot-web/issues/11551
2019-12-03 12:29:50 -07:00
J. Ryan Stinnett 66a6dd1f0c Switch to Node compatible base64 handling 2019-12-03 18:01:45 +00:00
Michael Telatynski 8a8109272a fix undef3
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:45:12 +00:00
Michael Telatynski 7ea30c449e Merge branch 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-02 23:43:32 +00:00
Michael Telatynski a6e4096773 fix
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:43:06 +00:00
Michael Telatynski c1e2d646b6 undo remove
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:42:10 +00:00
Michael Telatynski 710ac6847d fix undef2
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:39:47 +00:00
Michael Telatynski f0267eae36 fix undef
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:38:55 +00:00
Michael Telatynski 1632ee3537 fix order
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:18:26 +00:00
Michael Telatynski a16cdb948c Fix cross-signing.spec by waiting for right emit
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 23:12:45 +00:00
Michael Telatynski c4ae27dae6 Merge branches 'develop' and 't3chguy/remove_bluebird_13' of github.com:matrix-org/matrix-js-sdk into t3chguy/remove_bluebird_13 2019-12-02 22:56:13 +00:00
Michael Telatynski 053bc49738 simplify promiseTry
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-12-02 22:55:05 +00:00
Michael Telatynski 3a1de9fbdc Rip out more bluebirds AGAIN 2019-12-02 22:34:46 +00:00
Michael Telatynski efcaadd0b4 Rip out more bluebirds 2019-12-02 22:34:46 +00:00
Michael Telatynski 0170cb066d add another clean-up to sas.spec.js 2019-12-02 22:34:46 +00:00
Michael Telatynski 6bba5ca25a Rip out some more bluebird 2019-12-02 22:34:46 +00:00
Michael Telatynski edcdeb31ea Rip out bluebird of a bunch more places, not as much to go :| 2019-12-02 22:34:46 +00:00
Michael Telatynski 1286007b2e Rip out bluebird of a bunch of places, lots to go :( 2019-12-02 22:34:46 +00:00
Michael Telatynski 9faab093f7 delint 2019-12-02 22:34:46 +00:00
Michael Telatynski 64bf145e4b Replace rest of defers 2019-12-02 22:34:46 +00:00
Michael Telatynski 733008cfc4 delint and replace more defers 2019-12-02 22:34:46 +00:00
Michael Telatynski bab4582139 Replace more Bluebirdy stuffs 2019-12-02 22:34:46 +00:00
Michael Telatynski fddf2843b4 Replace Bluebird.try 2019-12-02 22:34:45 +00:00
Michael Telatynski f8d83f8273 Stop using Bluebird::mapSeries 2019-12-02 22:34:45 +00:00
J. Ryan Stinnett cfeaf188ed Encode cross-signing keys via base64 2019-12-02 14:39:21 +00:00
J. Ryan Stinnett 58ad1ecbfe Sign secret storage keys when cross-signing keys change 2019-12-02 13:50:43 +00:00
J. Ryan Stinnett 463538178d Clarify purpose of the after local key change helper 2019-12-02 13:02:21 +00:00
J. Ryan Stinnett 14907065d7 Rename device signing auth param 2019-11-29 17:50:59 +00:00
J. Ryan Stinnett ce2059a4b9 Add follow-up steps when restoring from secret storage
If we restore cross-signing keys from secret storage, we also need to run many
of the same follow-up steps from resetting the keys, such as saving to the
crypto store, upgrading device verifications, etc.
2019-11-29 15:20:59 +00:00
J. Ryan Stinnett 2bfc157e64 Clarify cross-signing reset variable 2019-11-29 15:15:18 +00:00
J. Ryan Stinnett fda7a2cf13 Add issue link for key verification 2019-11-29 13:59:09 +00:00
J. Ryan Stinnett e69de8c26f Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup 2019-11-29 11:23:48 +00:00
J. Ryan Stinnett f404c80714 Get cross-signing private keys from secret storage
If you've already set up cross-signing elsewhere and start using a new device,
this loads the private keys from secret storage and regenerates the public keys
to match.

We may also want to download the public keys from the homeserver's key sharing
and verify that they match the private keys, but for now that's left as future
work.
2019-11-29 11:11:45 +00:00
Hubert Chathi 92ca2386ea Merge pull request #1096 from uhoreg/fix_sas_unit_test
increase timeout on flush to fix failing unit test
2019-11-28 17:19:41 -05:00
Hubert Chathi 59b25d6837 increase timeout on flush to fix failing unit test
also remove unused requests
2019-11-28 16:53:21 -05:00
Travis Ralston a6f7936311 Merge pull request #1095 from matrix-org/travis/fix-tests-18
Disable broken cross-signing test
2019-11-28 09:11:05 -07:00
J. Ryan Stinnett e2b680c223 Document CrossSigningInfo#resetKeys 2019-11-28 14:31:53 +00:00
J. Ryan Stinnett bdaf2e3b4f Reflow comment 2019-11-28 12:13:48 +00:00
J. Ryan Stinnett 2190022e64 Add return type 2019-11-28 11:54:20 +00:00
J. Ryan Stinnett e000e2b9fd Move cross-signing storage to 4S into class 2019-11-28 11:54:20 +00:00
Travis Ralston 7392b4de17 xit instead of comment 2019-11-27 19:23:59 -07:00
Travis Ralston 79b0a5fada Add issue to comment 2019-11-27 19:21:57 -07:00
Travis Ralston aee9442e52 Disable broken cross-signing test
I don't know why it's broken, but the two errors I can get out of it are "unknown device for verification" and "user_signing key does not match". Someone who knows a bit more about cross-signing will probably need to take a look at this one.

Fixes https://github.com/vector-im/riot-web/issues/11520 (technically)
Opened https://github.com/vector-im/riot-web/issues/11545 to fix this correctly.
2019-11-27 19:20:09 -07:00
J. Ryan Stinnett d5000820fd Fix comment typo 2019-11-27 17:19:41 +00:00
Travis Ralston 569d5d1fce Merge pull request #1094 from matrix-org/travis/fix-tests-13
Fix a couple SAS tests
2019-11-27 09:24:27 -07:00
J. Ryan Stinnett 9d91d197e4 Revert to previous cross-signing keys on error 2019-11-27 16:11:06 +00:00
J. Ryan Stinnett 5b767ae948 More comment tweaks to cross-signing keys 2019-11-27 15:51:00 +00:00
RiotRobot 6ea8003df2 Merge branch 'master' into develop 2019-11-27 10:28:14 +00:00
RiotRobot c8ab82010a v2.4.5 2019-11-27 10:17:54 +00:00
RiotRobot bf1bec9c6c Prepare changelog for v2.4.5 2019-11-27 10:17:54 +00:00
Travis Ralston e0c90ec9e3 Fix test flakes in SAS verification with old MAC
This has similar fixes to 7ad5021147

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:29:06 -07:00
Travis Ralston 7ad5021147 Fix SAS verification test
There's 3 things going on in this commit:
1. `this` is maintained in the tests. Some binds are added instead of the `.call(this, ...)` syntax.
2. We use the right `origSendToDevice`
3. We ensure `downloadKeys` is actually on the client

The combination of these 3 fixes makes the test pass.

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:24:37 -07:00
Matthew Hodgson fd73c3fb3a fix bogus logline 2019-11-27 01:19:17 +00:00
Travis Ralston e3dbf7cc41 Merge pull request #1093 from matrix-org/travis/fix-tests-3
Fix Olm unwedging test
2019-11-26 15:32:50 -07:00
Travis Ralston 18749c580e Fix Olm unwedging test
Deep within the crypto layers we call `getId()`, and when we don't have that function the async call on the emitter fails but doesn't fail the test. This manifests as a timeout because the code path that would call the thing blew up.
2019-11-26 15:06:56 -07:00
J. Ryan Stinnett 396db30fbf Update tests 2019-11-26 12:04:14 -07:00
J. Ryan Stinnett 6b38868de6 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-26 12:04:07 -07:00
Matthew Hodgson 01a46ad880 log outbound to_device msgs for tracking keyshares 2019-11-26 12:03:34 -07:00
Matthew Hodgson 46f8251e94 s/console/logger/ as per review 2019-11-26 12:03:28 -07:00
Matthew Hodgson 77f882f45a log keyshare ID 2019-11-26 12:03:24 -07:00
Matthew Hodgson 8c72fd104e lint 2019-11-26 12:03:20 -07:00
Matthew Hodgson 549656884b expand e2ee logging to better debug UISIs 2019-11-26 12:03:11 -07:00
Matthew Hodgson 5b8b0a8aa3 log outbound to_device msgs for tracking keyshares 2019-11-26 18:35:25 +00:00
Travis Ralston b1924d4db6 Merge pull request #1089 from matrix-org/travis/upgrade-notifications
Fix empty string handling in push notifications
2019-11-26 11:00:24 -07:00
Travis Ralston 1b877118ef Only do one type check 2019-11-26 10:31:04 -07:00
Travis Ralston 682a5daf1c Merge branch 'develop' into travis/upgrade-notifications 2019-11-26 10:29:40 -07:00
Matthew Hodgson fcbfaac1fd Merge pull request #1090 from matrix-org/matthew/more_e2ee_logging
expand e2ee logging to better debug UISIs
2019-11-26 10:18:28 +00:00
Matthew Hodgson 3787b6f1c7 s/console/logger/ as per review 2019-11-26 09:07:23 +00:00
Matthew Hodgson 6e08835496 log keyshare ID 2019-11-26 01:58:04 +00:00
Matthew Hodgson 191695da5a lint 2019-11-26 01:41:59 +00:00
Matthew Hodgson 2215087f96 expand e2ee logging to better debug UISIs 2019-11-26 01:17:12 +00:00
Michael Telatynski 32234ee7fc Merge pull request #1087 from matrix-org/t3chguy/remove_bluebird_11
Remove Bluebird: phase 2
2019-11-26 00:03:22 +00:00
Travis Ralston aa37f697bf Fix empty string handling in push notifications
Fixes https://github.com/vector-im/riot-web/issues/11460

Empty strings are falsey, and the state key match for a tombstone event is an empty string. Ergo, nothing happens because all the conditions fail.
2019-11-25 16:35:27 -07:00
David Baker 49448fafaa Correct comment (it's not just a cache) 2019-11-25 15:05:10 +00:00
Michael Telatynski 057303d57c s/beforeEach/beforeAll/ for Olm.init() and cleanup sas.spec.js 2019-11-25 13:26:10 +00:00
RiotRobot ccc85d98e2 Merge branch 'master' into develop 2019-11-25 13:24:08 +00:00
RiotRobot c30a8b5a29 v2.4.4 2019-11-25 13:10:04 +00:00
RiotRobot 295010893d Prepare changelog for v2.4.4 2019-11-25 13:10:04 +00:00
Michael Telatynski 7fb807919c Stop using bluebird .returns and .spread 2019-11-25 12:31:46 +00:00
Michael Telatynski bd8f8ef28d Replace yet more deferreds 2019-11-25 11:28:09 +00:00
Michael Telatynski 3901a381cc replace another couple of deferreds 2019-11-25 11:18:32 +00:00
J. Ryan Stinnett 12f6e51ef6 Merge pull request #1062 from matrix-org/jryans/identity-disco-opt
Relax identity server discovery checks to FAIL_PROMPT
2019-11-25 10:48:53 +00:00
Travis Ralston aa8454e30a Merge pull request #1061 from beaclnd92/develop
Fix incorrect return value of MatrixClient.prototype.uploadKeys
2019-11-24 22:19:09 -07:00
Travis Ralston 6b70230e0d Merge branch 'develop' into develop2-test 2019-11-24 22:13:53 -07:00
Michael Telatynski 5e0ba9971c nothing works anymore :(( 2019-11-23 12:18:39 +00:00
David Baker fa577c9475 Merge pull request #1086 from matrix-org/dbkr/fix_calls_in_e2e_rooms
Fix calls in e2e rooms
2019-11-22 17:48:33 +00:00
Bruno Windels 11a958b8ca Merge pull request #1085 from matrix-org/bwindels/verif-toasts
Monitor verification request over DM as well
2019-11-22 16:39:07 +00:00
Bruno Windels 6952db6762 no need to filter here anymore when listening for timeline, also remove obsolete docs 2019-11-22 17:32:37 +01:00
Bruno Windels 51898cffe8 add comments for timeout constants 2019-11-22 17:31:48 +01:00
David Baker d8337d703d Use the right variable name 2019-11-22 15:59:36 +00:00
David Baker adac0c353c Fix calls in e2e rooms
Events will be decrypted after the sync event, so we were having
to wait until the next sync event before they got processed.
2019-11-22 15:56:06 +00:00
Michael Telatynski 04fca16420 Stop using Bluebird promise::value 2019-11-22 15:36:42 +00:00
Bruno Windels ca89b6e7a8 use adapter for to_device requests to have same api as for verif over DM
Riot doesn't fully implement to_device verifications, e.g.
it doesn't send a `request` but immediately sends a `start` event.

Because of this, `crypto.verification.request` doesn't get fired,
as that code path doesn't get triggered. This is why MatrixChat
in the react-sdk was listening for `crypto.verification.start`.

Verification over DM *does* send a `request` event first, so
to have the same API for both methods, we fake the request and
wrap the verifier in it.
2019-11-22 16:12:19 +01:00
Bruno Windels ac1173c628 also emit crypto.verification.request for verification over DM 2019-11-22 16:11:49 +01:00
Michael Telatynski 0a0ae111f6 replace Bluebird::map 2019-11-22 15:03:03 +00:00
David Baker 71a6e015f4 Merge pull request #1084 from matrix-org/dbkr/remove_check
Remove 'check' npm script
2019-11-22 10:53:48 +00:00
David Baker e8bbb8a1cc Remove 'check' npm script
...whose only purpose was to run the tests without coverage because
the coverage tool was awful and ruined all the line numbers (moreso).
2019-11-21 19:30:46 +00:00
David Baker 04764998cb Merge pull request #1083 from matrix-org/dbkr/advanced_anti_chirp
Always process call events in batches
2019-11-21 19:22:24 +00:00
David Baker 5262d716e4 Lint 2019-11-21 19:10:27 +00:00
David Baker 7addacba38 Always process call events in batches
We had a bunch of logic in place to suppress calls if the answer
or hangup had already arrived, but we only used it on startup.
This extends this logic to happen all the time, which means we'll
also do the same suppression if a call happenned while we were
offline.
2019-11-21 18:56:37 +00:00
David Baker 8f8c9c8ec0 Merge pull request #1082 from matrix-org/dbkr/death_to_the_reload_chirp
Fix ringing chirp on loading
2019-11-21 18:20:20 +00:00
David Baker 3a9832a8c6 Fix ringing chirp on loading
We have a heap of logic to do the right thing when a call event
arrives, eg. wait until the client is ready so we can see if there's
already been a hangup event before saying there's an incoming call.

Unfortunately it only waited until the client was prepared, not
until it was syncing, so any events that arrived from the server
in the catchup sync bypassed this logic altogether.

This was probably broken back when we introduced the sync accumulator,
since before then, "PREPARED" meant, "done initialsync" rather than
"loaded fake initialsync from storage".

Fixes https://github.com/vector-im/riot-web/issues/3572
2019-11-21 17:56:04 +00:00
Bruno Windels 4a40c10d4c add helper method on event for current age according to local clock 2019-11-21 17:13:43 +01:00
Michael Telatynski 58f8ca7d66 Merge pull request #1081 from matrix-org/t3chguy/remove_bluebird_2
Remove *most* bluebird specific things
2019-11-21 11:34:19 +00:00
Michael Telatynski 4d950fec66 fixxy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:59:18 +00:00
Michael Telatynski b4f68f4fc6 Stop using Bluebird promise::nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:43:58 +00:00
Michael Telatynski ac742aad70 use Bluebird in promise utils sleep so it has .done and .nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:14:06 +00:00
Michael Telatynski 53d225a1d1 fix stub timelineSet for timeline-window.spec
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:09:36 +00:00
Michael Telatynski 549b0f9313 Stop using Bluebird.delay and Bluebird promise::delay
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:05:59 +00:00
Michael Telatynski 2ce106382a Stop using Bluebird promise::isFulfilled()
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:01:16 +00:00
Michael Telatynski b44f43e5db fix import in relations.js
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 09:45:00 +00:00
Michael Telatynski 2321b9a04e Merge pull request #1080 from matrix-org/t3chguy/jest
Switch to Jest
2019-11-20 22:03:25 +00:00
Michael Telatynski 3bd518cf7f update buildkite pipeline name
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:59:50 +00:00
Michael Telatynski c57109c2f3 tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:16:11 +00:00
Michael Telatynski 522640edd9 rip out lolex also
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:05:21 +00:00
Michael Telatynski 5fc0629201 fix expect calls
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 20:19:54 +00:00
Michael Telatynski 26edd7431a fix yarn scripts test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:56:50 +00:00
Michael Telatynski fd58957b06 migrate to jest from mocha+expect+istanbul
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:52:50 +00:00
RiotRobot 12bb0b86dd v2.4.4-rc.1 2019-11-20 18:19:30 +00:00
RiotRobot 165c1fc0b6 Prepare changelog for v2.4.4-rc.1 2019-11-20 18:19:30 +00:00
J. Ryan Stinnett 4116d89d5f Store cross-signing keys in secret storage 2019-11-20 17:48:36 +00:00
J. Ryan Stinnett cc192efe45 Create the SSSS default key when needed 2019-11-20 17:27:57 +00:00
J. Ryan Stinnett feef1a35b9 Add first pass at bootstrapping Secure Secret Storage
This adds a first chunk of bootstrapping Secure Secret Storage by creating
cross-signing keys and supporting interactive auth to upload them.

Part of https://github.com/vector-im/riot-web/issues/11212
2019-11-20 17:23:21 +00:00
J. Ryan Stinnett 55a2f46604 Remove doesCrossSigningHaveKeys, use getId instead 2019-11-20 14:42:46 +00:00
J. Ryan Stinnett ed8b303400 Simplify public key access for cross signing
This simplifies reading the code by removing the semi-magical `[1]` bit to
access the unprefixed version of the key.
2019-11-19 17:47:18 +00:00
Bruno Windels c785b10603 Merge pull request #1077 from matrix-org/bwindels/dm-verif-in-e2ee-rooms
Fix SAS verification in encrypted DMs
2019-11-19 14:32:01 +00:00
Bruno Windels 90512bdd5f also listen for non-encrypted events when verifying over DM 2019-11-19 15:23:05 +01:00
J. Ryan Stinnett 4acd06eaba Fix typo in CrossSigning#resetKeys 2019-11-19 13:52:26 +00:00
J. Ryan Stinnett 10751e9a6d Merge pull request #1078 from matrix-org/jryans/4s-new-key-backup
Cross-signing / secret storage tweaks
2019-11-19 12:54:38 +00:00
J. Ryan Stinnett d2ebc58c3c Use secret storage alg const in tests 2019-11-19 11:46:08 +00:00
J. Ryan Stinnett d51c5a2d68 Rename secret storage file to match the default class 2019-11-19 11:21:20 +00:00
J. Ryan Stinnett 1f24845431 Standardise naming of key ID variables in secret storage
Keys in secret storage have both an ID and an optional name, but most `keyName`
variables were actually storing the ID value. This renames and standardises to
avoid confusion.
2019-11-19 10:48:47 +00:00
Bruno Windels 3b02b62ba5 add m.relates_to back to the content on the requesting side for e2e room
as it needs to be added to the commitment hash
as before, getContent() in an e2ee room doesn't return the relation
2019-11-18 18:34:05 +01:00
Bruno Windels 24ae787736 make it explicit that the transaction id is added for the start event
as it should included in the commitment hash
2019-11-18 18:33:11 +01:00
Bruno Windels cd735ef459 use getRelation as getContent()[m.relates_to] doesn't work in e2ee rooms 2019-11-18 18:31:39 +01:00
Bruno Windels 180fea8ace only send decrypted events to Verifier in e2ee rooms 2019-11-18 18:30:43 +01:00
J. Ryan Stinnett 5f02c4b5ad Namespace default secret storage key methods 2019-11-18 15:19:18 +00:00
David Baker 41680f6089 Merge pull request #1075 from matrix-org/dbkr/fix_key_backup_local_trust
Fix local trust for key backups
2019-11-18 14:57:35 +00:00
J. Ryan Stinnett 730f7d3dff Extract secret storage algorithm to constant 2019-11-18 14:38:02 +00:00
David Baker d32033f105 Merge remote-tracking branch 'origin/develop' into dbkr/fix_key_backup_local_trust 2019-11-18 14:10:04 +00:00
David Baker 440274d639 Fix local trust for key backups
https://github.com/matrix-org/matrix-js-sdk/pull/832 added
cross-signing checks for backup trust but we failed to merge in the
check for the the backup being trusted locally.

Fixes https://github.com/vector-im/riot-web/issues/11404
2019-11-18 13:30:00 +00:00
J. Ryan Stinnett f93130a8a7 Add method to check whether cross-signing has keys 2019-11-18 12:32:39 +00:00
Bruno Windels 3d9bddfb9f Merge pull request #1072 from matrix-org/bwindels/userinfomakeover
Add method to get last active timestamp in room
2019-11-15 16:39:31 +00:00
Travis Ralston 439abbcce9 Merge pull request #1071 from matrix-org/travis/new-deactivate
Check the right Synapse endpoint for determining admin capabilities
2019-11-15 09:17:12 -07:00
David Baker ac91367801 Merge pull request #832 from matrix-org/dbkr/cross_signing
Cross Signing Support
2019-11-15 16:09:06 +00:00
David Baker 2a63cc474c Update import 2019-11-15 15:57:25 +00:00
David Baker 56261263f5 Rename backup_password & functions
Not Just For Backups Anymore
2019-11-15 15:54:43 +00:00
David Baker 04b57bbe9d Remove ghost of some old code 2019-11-15 15:27:02 +00:00
David Baker c550f83a04 update jsdoc 2019-11-15 14:57:29 +00:00
David Baker 5224ef4b1f This is now implemented
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:56:05 +00:00
David Baker 2ab033e76e is now implemented 2019-11-15 14:45:43 +00:00
David Baker fa2e669eda More jsdoc updates
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:44:08 +00:00
David Baker f0ba1f2ac0 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:43:41 +00:00
David Baker 6d0237ec71 This now returns DeviceTrustLevel too 2019-11-15 14:42:26 +00:00
David Baker 97dff4640a Capitalise jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:41:12 +00:00
David Baker 00b571a429 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:57 +00:00
David Baker 86e0f49231 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:16 +00:00
David Baker f2f205f9bd Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:44 +00:00
David Baker f84ec090cb backticks in jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:27 +00:00
David Baker d37ed9ff6f lint 2019-11-15 12:39:14 +00:00
David Baker f5a5f5e51a Update yarn.lock 2019-11-15 12:31:22 +00:00
David Baker fe010242d9 Why is 'cross-signing' so hard to type?
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:30:05 +00:00
David Baker 545ebf81bf Move Crypto.prototype.init back to its rightful place 2019-11-15 12:29:03 +00:00
David Baker 408934932a copy jsdoc to internal methods 2019-11-15 12:27:14 +00:00
David Baker 6f42824c35 Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:26:24 +00:00
David Baker c3215d51bd Switch the CroosSigningLevel constants
we check in resetKeys and set all if it's & 4 anyway, so may as well
make the constants a normal bitmask and then we can use the MASTER
constant below.
2019-11-15 12:23:37 +00:00
David Baker e541b96a71 Change check{User|Device}Trust interfaces
...to return objects with functions rather than a bitmask
2019-11-15 12:15:13 +00:00
Travis Ralston 904a2f466e Maybe use the right user ID too 2019-11-14 11:54:13 -07:00
Travis Ralston bad48da11a Check the right Synapse endpoint for determining admin capabilities 2019-11-14 11:50:06 -07:00
David Baker ce2d1d6e2b Don't emit event here, as per comment 2019-11-14 17:41:58 +00:00
Bruno Windels 2820071db1 add method to get last active timestamp in room 2019-11-14 17:00:28 +01:00
David Baker 5937185ce9 Assert usage of setDeviceVerification for cross-signing keys
We can't mark a cross-signing key as blocked/unblocked, known/unknown
or unverified, so throw an exception instead of doing nothing.

Also comment what's going on in this function.
2019-11-14 14:24:41 +00:00
David Baker be9b7a0d24 Remove getPublicKey
which was the same as getId
2019-11-14 14:10:13 +00:00
David Baker 7ca09ad749 tariling space 2019-11-14 12:18:07 +00:00
David Baker 686a7a40f9 Remove outdated comment
uhoreg says this is fine now...
2019-11-14 12:11:07 +00:00
David Baker 2a7b2835b6 remove unused function 2019-11-14 12:11:07 +00:00
David Baker 69ecf3b145 jsdoc formatting 2019-11-14 12:11:07 +00:00
David Baker 2cd748b50c Add matrix foundation copyright
The creation of this file just predates matrix.org foundation so it
should have both
2019-11-14 12:11:07 +00:00
David Baker 291133beb9 Fix comment 2019-11-14 12:11:07 +00:00
David Baker e10c17c866 Use official name for SSSS
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 12:10:56 +00:00
David Baker 0048cbef08 Mark cross siging / SSSS APIs as unstable
also add missing jsdoc
2019-11-14 11:51:44 +00:00
David Baker d9d65309b3 More s/cross-signing/crossSigning/ 2019-11-14 11:29:08 +00:00
David Baker d5d8032b5b Camelcase event names
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 11:04:37 +00:00
David Baker 693c749da0 lint 2019-11-13 17:59:25 +00:00
David Baker 7218e31a9c Sign & verify SSSS keys 2019-11-13 17:52:24 +00:00
David Baker 1798f3921f Make setDeafultKeyId wait for event 2019-11-13 14:42:08 +00:00
David Baker d12c56a623 lint 2019-11-13 14:11:50 +00:00
David Baker 26aa3d3ce7 Support default keys 2019-11-13 14:09:40 +00:00
David Baker c97a87d1f6 Throw if an unknown key is specified
It's probably important that the app knows if a secret isn't going
to be stored under one or more of the keys it thought it was going
to be stored under.

Also add a test to assert it.
2019-11-12 14:07:05 +00:00
David Baker 9bc185d459 Fix what was probablyt a c+p fail 2019-11-12 13:21:37 +00:00
David Baker 4c651c15ea Convert secrets events to callbacks too 2019-11-11 20:01:11 +00:00
David Baker a98e6964ef Missed bits of callback renaming 2019-11-11 16:51:49 +00:00
David Baker 6f8d9c4693 Rename getPrivateKeys to getCrossSigningKeys 2019-11-11 16:45:01 +00:00
David Baker fbc4bd0c96 Merge pull request #1067 from matrix-org/bwindels/verification-over-dm
Support for verification requests in the timeline
2019-11-08 16:19:50 +00:00
J. Ryan Stinnett 03c9241783 Merge pull request #1066 from matrix-org/jryans/privacy-prefix-r0
Use stable API prefix for 3PID APIs when supported
2019-11-07 20:00:57 +02:00
David Baker 3a983271d6 add comments 2019-11-07 16:21:53 +00:00
David Baker 03fe4afe32 lint 2019-11-07 15:20:07 +00:00
David Baker 12627022d1 Convert sas verification test to callbacks 2019-11-07 15:18:16 +00:00
David Baker fabfe16d45 lint 2019-11-07 12:35:39 +00:00
David Baker a34758f938 Convert event interface to callbacks
Use options.cryptoCallbacks for things that require information
from the app rather than events, since events can have zero, one
or many listeners and the emitter doesn't know how many, so if
nobody's listening then we would have just waited forever for a
response.

Also a collection of other changes like renaming 'fu' to 'firstUse'
2019-11-07 12:31:44 +00:00
J. Ryan Stinnett 20f5c3ea28 Use stable API prefix for 3PID APIs when supported
If the server advertises spec version r0.6.0, it must have the 3PID APIs
available under the stable API prefix.

Fixes https://github.com/vector-im/riot-web/issues/11246
2019-11-06 18:02:10 +00:00
Bruno Windels 62e490cfe4 add FIXME note for (expected) uncaught rejection 2019-11-06 12:36:50 +01:00
Bruno Windels a9dba39623 include redacted event so has same signature as other Relations events 2019-11-04 15:56:15 +01:00
Bruno Windels f1d417597c only emit Event.relationsCreated once event has been added
so if we ask for the relations in the handler, we don't get
an empty result
2019-11-04 15:55:29 +01:00
RiotRobot 549f679bf1 Merge branch 'master' into develop 2019-11-04 13:59:24 +00:00
RiotRobot 6ba052dcc4 v2.4.3 2019-11-04 13:51:50 +00:00
RiotRobot de873b84f5 Prepare changelog for v2.4.3 2019-11-04 13:51:49 +00:00
Bruno Windels 37558ac1b4 detect other end cancelling and reject main promise 2019-11-04 14:43:44 +01:00
Bruno Windels 9140d5a091 don't clear expected type before including it in error 2019-11-04 14:37:12 +01:00
J. Ryan Stinnett 7827af0d90 Merge pull request #1063 from matrix-org/jryans/rm-jenkins
Remove Jenkins scripts
2019-11-01 18:23:16 +02:00
J. Ryan Stinnett 1af8d20adf Remove Jenkins scripts
We haven't used Jenkins for a while, so it seems safe to remove the scripts that
supported it.
2019-11-01 16:08:29 +00:00
J. Ryan Stinnett 91df096698 Update tests 2019-11-01 14:10:16 +00:00
J. Ryan Stinnett e8fd0498a7 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-01 11:24:51 +00:00
David Baker f3073e120d Space 2019-11-01 10:51:49 +00:00
David Baker a571624e13 Typo 2019-11-01 10:46:43 +00:00
David Baker 74b649c04c Typo 2019-11-01 10:45:41 +00:00
ZengJing 7973b99f50 Fix incorrect return value of MatrixClient.prototype.uploadKeys
Signed-off-by: Zeng Jing <beaclnd92@gmail.com>
2019-11-01 09:25:50 +08:00
RiotRobot e8f5a8b89d v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
RiotRobot 2d0bda933c Prepare changelog for v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
David Baker 49588da73d Fix more tests 2019-10-29 19:39:31 +00:00
David Baker 3e2d845342 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-10-28 16:47:16 +00:00
David Baker e92d2bd70a Fix test again
That one was part of the protocol - don't camelcase that
2019-10-28 16:07:26 +00:00
David Baker de1b545df1 lint 2019-10-28 15:42:42 +00:00
David Baker 3bec28b2ff make other tests pass 2019-10-28 15:23:58 +00:00
David Baker 8cad116dd7 Make tests pass
* Pass the http backend out of makeTestClients so we can tell it
   to expect queries and flush requests out
 * Change colons to dots in the key events
2019-10-28 14:56:35 +00:00
Hubert Chathi 35adb75d80 Merge pull request #1056 from uhoreg/fix_logger_path
fix the path in references to logger.js
2019-10-26 13:45:24 -04:00
Hubert Chathi e9908b1d97 fix the path in references to logger.js 2019-10-25 23:24:30 -04:00
Hubert Chathi fffd2eb70a Merge pull request #1050 from uhoreg/verification_in_dms
verification in DMs
2019-10-23 12:09:52 -04:00
Hubert Chathi 136b9c0f50 remove unnecessary async 2019-10-23 12:03:17 -04:00
Hubert Chathi 0f1206b4ee apply suggestions from review 2019-10-22 13:29:24 -04:00
RiotRobot 46d7e4c707 Merge branch 'master' into develop 2019-10-18 14:38:58 +01:00
RiotRobot c874783742 v2.4.2 2019-10-18 14:36:57 +01:00
RiotRobot bb296f50d9 Prepare changelog for v2.4.2 2019-10-18 14:36:57 +01:00
Michael Telatynski da68b53ff9 Merge pull request #1054 from rcsm/develop
Properly document the function possible returns for getRelationsForEvent
2019-10-17 15:35:33 +01:00
Rafael Cascalho bbe141d44e chore: updated throws docs in function getRelationsForEvent 2019-10-17 11:30:41 -03:00
Rafael Cascalho 8a03e41a7c chore: corrected the docs the getRelationsForEvent function 2019-10-17 10:51:34 -03:00
Rafael Cascalho a79e1bc976 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-17 10:15:10 -03:00
J. Ryan Stinnett 056bfbf7a3 Merge pull request #1055 from matrix-org/jryans/bluebird-3.5
Downgrade to Bluebird 3.5.5 to fix Firefox
2019-10-17 00:10:54 +02:00
J. Ryan Stinnett e0b64a487d Downgrade to Bluebird 3.5.5 to fix Firefox
Bluebird 3.6.0+ currently breaks in at least Firefox with errors in their
rejection tracking approach. For now, this retreats back to the version we had
used for a long time.

Regressed by https://github.com/matrix-org/matrix-js-sdk/pull/1053
Fixes https://github.com/vector-im/riot-web/issues/11148
2019-10-17 00:05:47 +02:00
Rafael Cascalho d47d1d8f26 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-15 11:12:00 -03:00
Rafael Cascalho 42a07de9a7 chore: corrected getRelationForEvent return docs 2019-10-15 10:28:23 -03:00
J. Ryan Stinnett aead855470 Merge pull request #1053 from matrix-org/jryans/major-deps-2
Upgrade safe deps to latest major version
2019-10-11 13:53:58 +01:00
J. Ryan Stinnett 335b2314f1 Upgrade safe deps to latest major version 2019-10-11 12:44:36 +01:00
Travis Ralston 89bab24c14 Merge pull request #1052 from clokep/no-js-import
Don't include .js in the import string.
2019-10-10 00:14:52 +01:00
Patrick Cloke 3a439dcdad Don't include .js in the import string.
Signed-off-by: Patrick Cloke <clokep@patrick.cloke.us>
2019-10-09 18:44:41 -04:00
RiotRobot 20d82eb92f v2.4.2-rc.1 2019-10-09 16:49:43 +01:00
RiotRobot 319e1d1191 Prepare changelog for v2.4.2-rc.1 2019-10-09 16:49:42 +01:00
Hubert Chathi 5f3492dbf8 send the m.key.verification.done message when done 2019-10-09 11:13:32 -04:00
David Baker 107c8c0b1f Merge pull request #1047 from matrix-org/dbkr/olm_session_describe
Log state of Olm sessions
2019-10-09 15:48:54 +01:00
David Baker 8c6d9586bf Update docs to reflect minimum olm version 2019-10-09 15:41:34 +01:00
David Baker 1271fc6bf3 Actually bump olm 2019-10-09 15:38:47 +01:00
David Baker c9df03c40c Bump to olm 3.1.4 which has olm_session_describe 2019-10-09 10:19:13 +01:00
Hubert Chathi d8e8dddd25 initial implementation of verification in DMs 2019-10-08 15:44:51 -04:00
Bruno Windels 27f6745123 Merge pull request #1048 from matrix-org/bwindels/redact-all-more-robust
Add method to get access to all timelines
2019-10-08 06:58:13 +00:00
Bruno Windels 964f448334 moar doc fix 2019-10-02 15:30:28 +02:00
Bruno Windels 20ee03bb44 fix docs 2019-10-02 15:25:19 +02:00
Bruno Windels 77bd677182 add method to get access to all timelines 2019-10-02 10:00:11 +02:00
RiotRobot e024d047e3 v2.4.1 2019-10-01 11:30:13 +01:00
RiotRobot 40943edc06 Prepare changelog for v2.4.1 2019-10-01 11:30:12 +01:00
David Baker e6699c5424 Log state of Olm sessions
...whenever we encrypt or decrypt a message on them. This adds
another line of logging for every device in the room, so will
be reasonably verbose if you're in large encrypted rooms, but
the information ought to be valuable.

Requires https://gitlab.matrix.org/matrix-org/olm/merge_requests/9

Don't merge before a new version of Olm is released with this merge
request (it won't work).
2019-10-01 11:19:52 +01:00
J. Ryan Stinnett bd8a307e50 Merge pull request #1046 from matrix-org/jryans/deps-2019-09-27
Upgrade deps
2019-09-27 16:01:50 +01:00
J. Ryan Stinnett f71301cafc Upgrade deps 2019-09-27 15:04:21 +01:00
RiotRobot 562bf9331b Merge branch 'master' into develop 2019-09-27 11:44:14 +01:00
RiotRobot 11e6eb94b5 v2.4.0 2019-09-27 11:42:17 +01:00
RiotRobot cee3aa2a7a Prepare changelog for v2.4.0 2019-09-27 11:42:17 +01:00
David Baker 81e3783488 Merge pull request #1043 from matrix-org/dbkr/ignore_empty_crypto_event
Ignore crypto events with no content
2019-09-27 10:44:17 +01:00
J. Ryan Stinnett fc7f9786f8 Merge pull request #1045 from matrix-org/jryans/yarn-cache-clean
Clean Yarn cache during release
2019-09-26 11:37:18 +01:00
J. Ryan Stinnett 0808c0edf1 Clean Yarn cache during release
Always run `yarn cache clean` during the `dist` step to workaround a Yarn bug
with Git commit package dependencies.
2019-09-26 11:18:20 +01:00
RiotRobot 8de6746efd v2.4.0-rc.1 2019-09-25 17:14:17 +01:00
RiotRobot eb9b8ef7c6 Prepare changelog for v2.4.0-rc.1 2019-09-25 17:14:16 +01:00
J. Ryan Stinnett b09621b915 Merge pull request #1044 from matrix-org/jryans/rm-id-server-creds
Remove id_server from creds for interactive auth
2019-09-25 16:02:41 +01:00
J. Ryan Stinnett 8d667f9367 Remove id_server from creds for interactive auth
For HSes that do not require an IS, we can remove `id_server` from the auth
params.

Fixes https://github.com/vector-im/riot-web/issues/10959
2019-09-25 14:59:32 +01:00
David Baker 56dfe6630f Ignore crypto events with no content 2019-09-25 12:05:52 +01:00
J. Ryan Stinnett 8b3b181a48 Merge pull request #1041 from matrix-org/jryans/rm-id-params-request-token
Remove IS details from requestToken to HS
2019-09-23 14:43:03 +01:00
J. Ryan Stinnett c952768542 Remove IS details from requestToken to HS
This removes the IS details (server and access token) from `requestToken` calls
to the HS, as long as the HS supports the new separate add and bind mode. In
this mode, all of the 3PID validation is handled by the HS, so the IS details
are not used.

Fixes https://github.com/vector-im/riot-web/issues/10933
2019-09-23 13:36:07 +01:00
J. Ryan Stinnett 1a368aa996 Merge pull request #1040 from matrix-org/jryans/msisdn-submit-url
Add support for sending MSISDN tokens to alternate URLs
2019-09-23 13:21:28 +01:00
J. Ryan Stinnett 61449458cf Add support for sending MSISDN tokens to alternate URLs
Part of https://github.com/vector-im/riot-web/issues/10923
2019-09-23 12:20:28 +01:00
J. Ryan Stinnett 4eb547e535 Merge pull request #1038 from matrix-org/jryans/msc2290
Add separate 3PID add and bind APIs
2019-09-20 14:37:00 +01:00
J. Ryan Stinnett b54acffaef Tweak unbind 3PID params 2019-09-19 17:28:50 +01:00
J. Ryan Stinnett 65a1833e1f Add 3PID unbind API 2019-09-19 15:28:58 +01:00
J. Ryan Stinnett 1ce4f25811 Use unstable prefix for add and bind 2019-09-19 15:28:35 +01:00
J. Ryan Stinnett 3127105516 Merge pull request #1037 from matrix-org/dependabot/npm_and_yarn/eslint-utils-1.4.2
Bump eslint-utils from 1.4.0 to 1.4.2
2019-09-18 12:52:28 +01:00
dependabot[bot] d59ea4be78 Bump eslint-utils from 1.4.0 to 1.4.2
Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.4.0...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-18 11:48:29 +00:00
J. Ryan Stinnett f256f04440 Add MSISDN validation API on the IS
This API has existed for quite a while, but historically we've instead proxied
this request via the homeserver. As part of MSC2290 work, we are changing
approaches such that we will request tokens directly from the IS when binding
for discovery.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:47:37 +01:00
J. Ryan Stinnett b444aaa67e Add separate add and bind HS APIs
Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:16:23 +01:00
J. Ryan Stinnett 745185e689 Fix function doc syntax 2019-09-18 10:38:52 +01:00
J. Ryan Stinnett 2bfa891f0a Add function to check for separate 3PID add and bind
This adds a way to test for MSC2290 support on the homeserver with separate add
and bind functions. It checks the unstable feature flag as well as the upcoming
r0.6.0 spec version.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 10:36:41 +01:00
Travis Ralston 147167bed3 Merge pull request #1036 from matrix-org/travis/wrap-ff
Handle WebRTC security errors as non-fatal
2019-09-17 13:52:19 -06:00
Travis Ralston 565e18e8a3 Handle WebRTC security errors as non-fatal
Fixes https://github.com/vector-im/riot-web/issues/10898

In some restricted modes of Firefox, the WebRTC classes aren't super available: accessing them can cause SecurityErrors to be raised. In these conditions, we should just disable WebRTC support instead of falling apart.
2019-09-17 13:49:50 -06:00
Travis Ralston 55b4595bbf Merge pull request #1035 from matrix-org/travis/r0.6
Check for r0.6.0 support in addition to unstable feature flags
2019-09-16 14:43:00 -06:00
Travis Ralston eeb2c463dc Check for r0.6.0 support in addition to unstable feature flags
To avoid the same problem that happened with lazy-loading (see https://github.com/matrix-org/synapse/issues/5528).

Note that as of writing r0.6.0 is not yet released, however it is the next scheduled release of the client-server API.
2019-09-16 14:30:18 -06:00
RiotRobot d9bb0e9a52 Merge branch 'master' into develop 2019-09-16 17:43:01 +01:00
RiotRobot 8cae00407a Merge branch 'master' into develop 2019-09-12 12:51:29 +01:00
David Baker aaabebe7f5 Merge pull request #1030 from matrix-org/dbkr/update_profile_on_redact
Update room members on member event redaction
2019-09-11 18:07:49 +01:00
David Baker 80a92dcdc2 Update room members on member event redaction
If a member event was redacted, we weren't updating the current
state.
2019-09-11 16:07:34 +01:00
Travis Ralston dc9081e9d4 Merge pull request #1028 from matrix-org/travis/hidden_rr
Support hidden read receipts
2019-09-10 10:55:58 -06:00
Travis Ralston 3c299637b6 Merge pull request #1029 from matrix-org/travis/lowercase-lookups
Do 3pid lookups in lowercase
2019-09-09 15:19:48 -06:00
Travis Ralston 07af333943 clarify comment 2019-09-09 14:44:51 -06:00
Travis Ralston 0bbc781d0c Do 3pid lookups in lowercase
Fixes https://github.com/vector-im/riot-web/issues/10754
2019-09-07 14:04:30 -06:00
Travis Ralston 79bf64f079 Appease the linter 2019-09-05 20:40:16 -06:00
Travis Ralston ed67d39456 Support hidden read receipts 2019-09-05 19:38:49 -06:00
Travis Ralston 2f8cc75432 Merge pull request #1027 from matrix-org/travis/synapse_admin
Add Synapse admin functions for deactivating a user
2019-09-02 11:08:39 -06:00
Travis Ralston 03cccef805 Update src/client.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-09-02 11:06:54 -06:00
Travis Ralston 6d5a0c2718 Add Synapse admin functions for deactivating a user
For https://github.com/matrix-org/matrix-react-sdk/pull/3371
2019-09-01 18:05:12 -06:00
David Baker 42b359eb5c Merge pull request #1026 from matrix-org/dbkr/fix_add_pending_events_chronological
Fix addPendingEvent with pending event order == chronological
2019-08-28 17:02:20 -04:00
David Baker 3071587f11 Fix addPendingEvent with pending event order == chronological
When the pending event order setting was set to 'chronological'
(the default) `addPendingEvent` would NPE because Room no longer
has a `this._filter` property. It should get the filter from the
event timeline set instead, as it does in the previous line when
checking or the presence of a filter.

We should strongly consider changing the default pending event order
to 'detached' and probably removing 'chronological' or comitting to
support it properly: it's not really tested and is prone to breakage
like this.

Applies flumpt's fix from https://github.com/matrix-org/matrix-js-sdk/issues/599
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/599
2019-08-28 16:34:09 -04:00
Hubert Chathi f3ec9768bc update to follow latest MSC 2019-08-27 16:53:36 -07:00
Travis Ralston 23159807b0 Merge pull request #1024 from matrix-org/travis/wk-raw
Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
2019-08-27 08:22:55 -06:00
Travis Ralston b1ba9f76b8 Merge pull request #1025 from matrix-org/travis/fix-identity-json-error
Don't convert errors to JSON if they are JSON already
2019-08-23 12:14:58 -06:00
Travis Ralston 0e51dfed46 Don't convert errors to JSON if they are JSON already
For example, if the identity server throws a 401 on `/account`, we end up here with a JSON object. Don't convert the string `object Object` to JSON because it'll fail: just use the object.
2019-08-23 12:00:07 -06:00
J. Ryan Stinnett 09b00335f8 Merge pull request #1022 from matrix-org/jryans/is-token-to-hs
Send id_access_token to HS for use in proxied IS requests
2019-08-23 17:13:41 +01:00
J. Ryan Stinnett 3d274815d9 Change to provider object 2019-08-23 11:17:39 +01:00
Travis Ralston 70d60b905d Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
Useful in cases where you don't need to validate the homeserver information.
2019-08-22 14:45:42 -06:00
Travis Ralston 3e2ffb25a6 Merge pull request #1023 from matrix-org/travis/cleanup-identity-http
Clean up JSON handling in identity server requests
2019-08-22 10:55:08 -06:00
Travis Ralston 8b9bef5cb3 Clean up JSON handling in identity server requests 2019-08-22 08:32:20 -06:00
J. Ryan Stinnett 31e72efc91 Send id_access_token to HS for use in proxied IS requests
This passes along the `id_access_token` to the HS, which it will need when
speaking v2 IS APIs to the IS.

Unfortunately, some HSes seem to explode when given this new parameter, so we
only pass it along for the moment if an unstable feature `m.id_access_token` is
also set.

Part of https://github.com/vector-im/riot-web/issues/10525
Defined in MSC2140
2019-08-22 14:31:41 +01:00
Travis Ralston 60b7252597 Merge pull request #1021 from matrix-org/travis/v2-identity-lookups
Use the v2 (hashed) lookup for identity server queries
2019-08-22 07:05:16 -06:00
Travis Ralston 3980b62df2 js-doc syntax is unknown to our js-doc 2019-08-21 14:32:38 -06:00
Travis Ralston b306df726a Lookups are URL safe 2019-08-21 14:30:24 -06:00
Travis Ralston 3d5a79be3b Hashes need tokens too 2019-08-21 14:30:16 -06:00
Travis Ralston ba78d1a9ae JSON is JSON 2019-08-21 14:30:04 -06:00
Travis Ralston 241811298f Appease the js-doc 2019-08-21 14:17:55 -06:00
Travis Ralston 8a0ddc43ab Use the v2 (hashed) lookup for identity server queries
Fixes https://github.com/vector-im/riot-web/issues/10556
Implements [MSC2134](https://github.com/matrix-org/matrix-doc/pull/2134) with assistance from [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) for auth.

Note: this also changes all identity server requests to use JSON as a request body. URL encoded forms were allowed in v1 but deprecated in favour of JSON. v2 APIs do not allow URL encoded forms.
2019-08-21 14:08:46 -06:00
David Baker 898fa0e41b Merge pull request #1018 from matrix-org/dbkr/getversions
Add getIdServer() & doesServerRequireIdServerParam()
2019-08-19 11:23:53 +01:00
David Baker 081ff4dec0 Merge pull request #1019 from matrix-org/dbkr/requesttoken_optional_id_server
Make requestToken endpoints work without ID Server
2019-08-19 11:23:05 +01:00
David Baker 3c69b8511d cache should expire TODO 2019-08-19 11:21:32 +01:00
David Baker 6843d86ecf Truthiness not null check
Co-Authored-By: Travis Ralston <travpc@gmail.com>
2019-08-16 19:41:37 +01:00
David Baker 2e91200136 lint
also WE FIXED SOME WARNINGS! Go us.
2019-08-16 18:13:42 +01:00
David Baker 852304c417 Make requestToken endpoints work without ID Server
Hopefully with doc in appropriate functions
2019-08-16 18:09:29 +01:00
David Baker ee752e3885 Add getIdServer() & doesServerRequireIdServerParam()
Remove individual cache for lazy loading and just cache the whole
versions response, then we can cache both of these flags
2019-08-16 15:02:49 +01:00
David Baker b9480e4302 Merge pull request #1016 from matrix-org/dbkr/fix_setidentityserver
Fix setIdentityServer
2019-08-15 15:11:54 +01:00
David Baker 2ae4d07971 Fix setIdentityServer 2019-08-15 12:01:40 +01:00
J. Ryan Stinnett 90cac8a118 Merge pull request #1015 from matrix-org/jryans/stun-turn-fallback
Change ICE fallback server and make fallback opt-in
2019-08-15 10:50:56 +01:00
J. Ryan Stinnett db18274f6e Revert no TURN servers event
The intended flow has changed so we prompt about this case in context of making
a call, so this event is no longer needed.
2019-08-14 14:09:05 +01:00
J. Ryan Stinnett 172bad8b55 Support configuring ICE fallback at client init time
This adds a new client init option to configure whether an ICE fallback server
is allowed to be used.
2019-08-14 13:44:50 +01:00
J. Ryan Stinnett dfe454e18f Change ICE server fallback to opt-in
This changes the ICE server fallback to be disabled by default. The SDK consumer
will receive a new event in case the homeserver has no ICE servers of its own,
and can prompt the user to agree to the fallback if desired.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 15:55:31 +01:00
J. Ryan Stinnett 3d8dd29b4c Change STUN fallback to turn.matrix.org
This changes the STUN fallback server from a Google server to one hosted at
`turn.matrix.org`.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 13:31:43 +01:00
J. Ryan Stinnett c3ff213ec9 Improve WebRTC object logging 2019-08-13 13:29:59 +01:00
J. Ryan Stinnett e80e5e1f8c Firefox supports urls on RTCIceServer
There's no longer a need to translate ICE server objects for Firefox, as it
supports the `urls` array since version 37.
2019-08-13 13:15:56 +01:00
David Baker bba249d5ce Merge pull request #1014 from matrix-org/dbkr/throw_if_no_id_url
Throw an exception if trying to do an ID server request with no ID server
2019-08-13 10:41:04 +01:00
David Baker f57df2bee5 Catch no ID server on other method 2019-08-12 16:06:37 +01:00
David Baker b930638156 Be more verbose
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-08-12 16:00:40 +01:00
David Baker 39c1de19fc Throw an exception if trying to do an ID server request with no ID server
Will help with https://github.com/vector-im/riot-web/issues/10540
2019-08-12 15:39:50 +01:00
David Baker 17724fc8d3 Merge pull request #1013 from matrix-org/dbkr/set_is
Add setIdentityServerUrl
2019-08-09 18:11:45 +01:00
David Baker 4c6d11d9ed Add setIdentityServerUrl
Hopefully fairly self explanatory. The ID server URL can be changed
fairly readily, whereas the HS URL would require a different access
token etc.
2019-08-09 18:05:37 +01:00
Michael Telatynski 05d77a85c9 Merge pull request #1011 from matrix-org/t3chguy/add-rooms-roomid-report-eventid
Add matrix base API to report an event
2019-08-08 15:21:20 +01:00
J. Ryan Stinnett e95a133cdd Merge pull request #1010 from matrix-org/jryans/user-settings-toggle-3pid
Fix POST body for v2 IS requests
2019-08-08 15:17:53 +01:00
Michael Telatynski c21382d721 Add matrix base API to report an event
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-08-08 15:16:11 +01:00
J. Ryan Stinnett 8c15125e23 Fix POST body for v2 IS requests
POST bodies for v2 IS requests must be sent as JSON.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-08 10:29:14 +01:00
J. Ryan Stinnett 64ddbd97dd Merge pull request #1009 from matrix-org/jryans/user-settings-toggle-3pid
Add API for bulk lookup on the Identity Server
2019-08-07 18:05:58 +01:00
J. Ryan Stinnett 9c24bcb7a9 Tweak the type 2019-08-07 18:05:32 +01:00
J. Ryan Stinnett 8f016726f0 Add API for bulk lookup on the Identity Server
This adds support for querying `/bulk_lookup` on the IS to check several 3PIDs
at the same time.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-07 17:45:13 +01:00
J. Ryan Stinnett 649fe7a490 Merge pull request #1000 from matrix-org/t3chguy/remove_authedRequestWithPrefix
Remove deprecated authedRequestWithPrefix and requestWithPrefix
2019-08-06 18:58:24 +01:00
RiotRobot 35f1cdf89c Merge branch 'master' into develop 2019-08-05 11:48:35 +01:00
J. Ryan Stinnett f05bf3f845 Merge pull request #1007 from matrix-org/jryans/is-account-info
Add API for checking IS account info
2019-08-01 17:28:08 +01:00
Travis Ralston a40d691159 Merge pull request #1006 from matrix-org/travis/tombstone-push
Support rewriting push rules when our internal defaults change
2019-08-01 08:22:47 -06:00
J. Ryan Stinnett 4ebe60b2ad Add API for checking IS account info
Part of https://github.com/vector-im/riot-web/issues/10452
2019-08-01 12:02:56 +01:00
J. Ryan Stinnett 5a70859593 Merge pull request #1005 from matrix-org/jryans/upgrade-deps-2019-07-31
Upgrade dependencies
2019-07-31 18:51:14 +01:00
Travis Ralston c7be810e65 Appease the tests 2019-07-31 11:00:44 -06:00
Travis Ralston 101217cfb6 Appease the linter 2019-07-31 11:00:38 -06:00
Travis Ralston 5c2aa4677f Support rewriting push rules when our internal defaults change
and change our internal default for tombstones
2019-07-31 10:52:44 -06:00
J. Ryan Stinnett ab9bfa68ae Upgrade dependencies 2019-07-31 17:36:35 +01:00
Michael Telatynski b004d1602d Remove deprecated authedRequestWithPrefix and requestWithPrefix
replacing as documented with authedRequest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-07-29 12:25:38 +01:00
Hubert Chathi 7f8b9de560 offer to upgrade device verifications to cross-signing 2019-07-08 12:26:00 -04:00
Hubert Chathi 761f22b63d minor cleanups 2019-07-08 12:25:28 -04:00
Hubert Chathi b00804102d obsolete todo 2019-07-03 21:37:18 -04:00
Hubert Chathi 8d1d657c44 add unit test for backups signed by cross-signing key 2019-07-03 19:16:26 -04:00
Hubert Chathi 6cd09c6af2 pksign was moved to olmlib 2019-07-03 16:00:44 -04:00
Hubert Chathi 46a8486245 rename m.secrets.share to m.secrets.send to agree with latest MSC 2019-07-03 15:15:56 -04:00
Hubert Chathi c5caf8f8f4 sign backups with master key 2019-07-03 15:15:41 -04:00
Hubert Chathi 4356603665 save public part of cross-signing keys 2019-06-27 23:37:57 -04:00
Hubert Chathi 1cae5e8b97 fix unit tests to match event name changes 2019-06-27 23:33:07 -04:00
Hubert Chathi 07c2e34d87 Merge branch 'develop' into dbkr/cross_signing 2019-06-14 22:57:02 -04:00
Hubert Chathi 5bcbe76f2c cleanups and a lot more docs 2019-06-14 22:50:29 -04:00
Hubert Chathi 4c6fa89053 various cross-signing fixes and improvements 2019-06-12 11:47:12 -04:00
Hubert Chathi 98815ffdf6 allow http request stub to ignore unhandled syncs 2019-06-12 11:41:26 -04:00
Hubert Chathi 6f6e7ea921 verify cross-signing key with SAS 2019-06-05 15:27:31 -04:00
Hubert Chathi 0c714ba4a1 some cleanups 2019-06-05 15:24:03 -04:00
Hubert Chathi 5f539aacd9 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 15:21:17 -04:00
Hubert Chathi 6a77df7b41 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 12:48:17 -04:00
Hubert Chathi 4a9a1b40e9 initial implementation of secret storage and sharing 2019-06-04 15:04:45 -04:00
Hubert Chathi dc971b9a59 add missing semicolon 2019-06-04 14:58:46 -04:00
Hubert Chathi 95131c7658 add test for syncing trust on another user 2019-05-29 17:01:25 -04:00
Hubert Chathi 936eef194a minor fixes to tests 2019-05-29 17:01:13 -04:00
Hubert Chathi 941d871daf fix check for empty cross-signing repsonse 2019-05-29 16:59:51 -04:00
Hubert Chathi 609ee663fa use the right path for logger 2019-05-29 16:58:49 -04:00
Hubert Chathi 53804cac5c save cross-signing keys from sync and verify new keys for user 2019-05-28 22:28:54 -04:00
Hubert Chathi 193ad9e09d use 3 keys for cross-signing 2019-05-23 18:18:21 -04:00
Hubert Chathi 405451d783 complete some more unit tests 2019-05-03 23:23:08 -04:00
Hubert Chathi b0275afac2 remove some debugging lines 2019-05-03 23:22:51 -04:00
Hubert Chathi ae71f41138 add missing files 2019-05-03 18:12:17 -04:00
Hubert Chathi ec2f07e1aa add methods for signing and checking users and devices with cross-signing 2019-05-03 18:05:36 -04:00
Hubert Chathi 32814d1833 Merge branch 'develop' into dbkr/cross_signing 2019-04-03 19:28:51 -04:00
David Baker e54f71718f Olm pre2 for cross-signing 2019-02-05 13:41:14 +00:00
David Baker 7f5584e4f5 All the linting 2019-02-05 13:03:27 +00:00
David Baker b3513dc8f8 Make linting rules more consistent
* Put back babel-eslint for class-properties
 * Allow arrow functions without params

This makes the style more consistent with react-sdk.

NB. The line lengths are still inconsistent but it's not clear which
way to go on that yet.
2019-02-05 11:56:08 +00:00
David Baker 1b82dffcb4 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-02-01 22:40:14 +00:00
David Baker 5500f0d794 Re-track own device list
Sp we don't stop tracking our own
2019-02-01 22:39:12 +00:00
David Baker c8082535de Always track your own devices
This was causing all the cross-signing stuff to fail and was almost
certainly the cause of https://github.com/vector-im/riot-web/issues/8213
2019-02-01 19:19:00 +00:00
David Baker 7dedcb82b2 Lint
or at least the rules that are consistent with the rest of our
codebase
2019-02-01 18:12:27 +00:00
David Baker 7195365188 Update package-lock.json
because Travis and npm now have a thing where they combust if your
package-lock is out of sync
2019-02-01 15:59:53 +00:00
David Baker 910d0ec9c1 Sign & trust the key backup from the SSK 2019-02-01 15:49:20 +00:00
David Baker 1d58a64ee1 Track SSKs for users
and verify our own against our locally stored private part
2019-02-01 13:04:21 +00:00
David Baker 1f77cc6d1a Cross sign the current device with the SSK
whenever we get the SSK, ie. when creating or restoring a backup
2019-01-31 21:13:01 +00:00
David Baker 02d4dcb128 Store SSK & USK in crypto store
and restore them from the key backup.

NB. This has an interface change to restoreKeyBackup where I've
changed it to take a backupInfo rather than a version (this also
saves us re-fetching the backup metadata in the case of a passphrase
restore).
2019-01-31 15:48:05 +00:00
David Baker 2b54f442d1 Add cross signing key creation into key backup
Start of cross-signing impl
2019-01-30 18:10:40 +00:00
156 changed files with 26063 additions and 8650 deletions
+16 -12
View File
@@ -1,15 +1,19 @@
{
"presets": ["es2015"],
"plugins": [
"transform-class-properties",
// this transforms async functions into generator functions, which
// are then made to use the regenerator module by babel's
// transform-regnerator plugin (which is enabled by es2015).
"transform-async-to-bluebird",
// This makes sure that the regenerator runtime is available to
// the transpiled code.
"transform-runtime",
"sourceMaps": true,
"presets": [
["@babel/preset-env", {
"targets": {
"node": 10
},
"modules": "commonjs"
}],
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime"
]
}
-34
View File
@@ -1,34 +0,0 @@
steps:
- label: ":eslint: Lint"
command:
- "yarn install"
- "yarn lint"
plugins:
- docker#v3.0.1:
image: "node:10"
- label: ":karma: Tests"
command:
- "yarn install"
- "yarn test"
plugins:
- docker#v3.0.1:
image: "node:10"
- label: "📃 Docs"
command:
- "yarn install"
- "yarn gendoc"
plugins:
- docker#v3.0.1:
image: "node:10"
- wait
- label: "🐴 Trigger matrix-react-sdk"
trigger: "matrix-react-sdk"
branches: "develop"
build:
branch: "develop"
message: "[js-sdk] ${BUILDKITE_MESSAGE}"
async: true
+24 -67
View File
@@ -1,69 +1,15 @@
module.exports = {
parser: "babel-eslint", // now needed for class properties
parserOptions: {
sourceType: "module",
ecmaFeatures: {
}
},
env: {
browser: true,
node: true,
// babel's transform-runtime converts references to ES6 globals such as
// Promise and Map to core-js polyfills, so we can use ES6 globals.
es6: true,
},
extends: ["eslint:recommended", "google"],
extends: ["matrix-org"],
plugins: [
"babel",
],
env: {
browser: true,
node: true,
},
rules: {
// rules we've always adhered to or now do
"max-len": ["error", {
code: 90,
ignoreComments: true,
}],
curly: ["error", "multi-line"],
"prefer-const": ["error"],
"comma-dangle": ["error", {
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "always-multiline",
}],
// loosen jsdoc requirements a little
"require-jsdoc": ["error", {
require: {
FunctionDeclaration: false,
}
}],
"valid-jsdoc": ["error", {
requireParamDescription: false,
requireReturn: false,
requireReturnDescription: false,
}],
// rules we do not want from eslint-recommended
"no-console": ["off"],
"no-constant-condition": ["off"],
"no-empty": ["error", { "allowEmptyCatch": true }],
// rules we do not want from the google styleguide
"object-curly-spacing": ["off"],
"spaced-comment": ["off"],
"guard-for-in": ["off"],
// in principle we prefer single quotes, but life is too short
quotes: ["off"],
// rules we'd ideally like to adhere to, but the current
// code does not (in most cases because it's still ES5)
// we set these to warnings, and assert that the number
// of warnings doesn't exceed a given threshold
"no-var": ["warn"],
"brace-style": ["warn", "1tbs", {"allowSingleLine": true}],
"prefer-rest-params": ["warn"],
"prefer-spread": ["warn"],
"one-var": ["warn"],
@@ -77,10 +23,21 @@ module.exports = {
"asyncArrow": "always",
}],
"arrow-parens": "off",
// eslint's built in no-invalid-this rule breaks with class properties
"no-invalid-this": "off",
// so we replace it with a version that is class property aware
"babel/no-invalid-this": "error",
}
}
"prefer-promise-reject-errors": "off",
"quotes": "off",
"indent": "off",
"no-constant-condition": "off",
"no-async-promise-executor": "off",
// We use a `logger` intermediary module
"no-console": "error",
},
overrides: [{
"files": ["src/**/*.ts"],
"extends": ["matrix-org/ts"],
"rules": {
// While we're converting to ts we make heavy use of this
"@typescript-eslint/no-explicit-any": "off",
"quotes": "off",
},
}],
};
-2
View File
@@ -10,10 +10,8 @@ build/Release
coverage
lib-cov
out
reports
/dist
/lib
/specbuild
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
+1239
View File
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -28,16 +28,23 @@ use GitHub's pull request workflow to review the contribution, and either ask
you to make any refinements needed or merge it and make them ourselves. The
changes will then land on master when we next do a release.
We use Travis for continuous integration, and all pull requests get
automatically tested by Travis: if your change breaks the build, then the PR
will show that there are failed checks, so please check back after a few
minutes.
We use continuous integration, and all pull requests get automatically tested:
if your change breaks the build, then the PR will show that there are failed
checks, so please check back after a few minutes.
Code style
~~~~~~~~~~
The code-style for matrix-js-sdk is not formally documented, but contributors
are encouraged to read the code style document for matrix-react-sdk
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the code style document for matrix-react-sdk
(`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_)
and follow the principles set out there.
+28 -26
View File
@@ -9,12 +9,16 @@ Quickstart
In a browser
------------
Download either the full or minified version from
Download the browser version from
https://github.com/matrix-org/matrix-js-sdk/releases/latest and add that as a
``<script>`` to your page. There will be a global variable ``matrixcs``
attached to ``window`` through which you can access the SDK. See below for how to
include libolm to enable end-to-end-encryption.
The browser bundle supports recent versions of browsers. Typically this is ES2015
or `> 0.5%, last 2 versions, Firefox ESR, not dead` if using
[browserlists](https://github.com/browserslist/browserslist).
Please check [the working browser example](examples/browser) for more information.
In Node.js
@@ -22,13 +26,18 @@ In Node.js
Ensure you have the latest LTS version of Node.js installed.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/) if you do not have it already.
This SDK targets Node 10 for compatibility, which translates to ES6. If you're using
a bundler like webpack you'll likely have to transpile dependencies, including this
SDK, to match your target browsers.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://classic.yarnpkg.com/en/docs/install)
if you do not have it already.
``yarn add matrix-js-sdk``
```javascript
var sdk = require("matrix-js-sdk");
var client = sdk.createClient("https://matrix.org");
import * as sdk from "matrix-js-sdk";
const client = sdk.createClient("https://matrix.org");
client.publicRooms(function(err, data) {
console.log("Public Rooms: %s", JSON.stringify(data));
});
@@ -59,7 +68,7 @@ client.once('sync', function(state, prevState, res) {
To send a message:
```javascript
var content = {
const content = {
"body": "message text",
"msgtype": "m.text"
};
@@ -161,7 +170,7 @@ which will be fulfilled in the future.
The typical usage is something like:
```javascript
matrixClient.someMethod(arg1, arg2).done(function(result) {
matrixClient.someMethod(arg1, arg2).then(function(result) {
...
});
```
@@ -173,10 +182,8 @@ you can pass the result of the promise into it with something like:
matrixClient.someMethod(arg1, arg2).nodeify(callback);
```
The main thing to note is that it is an error to discard the result of a
promise-returning function, as that will cause exceptions to go unobserved. If
you have nothing better to do with the result, just call ``.done()`` on it. See
http://documentup.com/kriskowal/q/#the-end for more information.
The main thing to note is that it is problematic to discard the result of a
promise-returning function, as that will cause exceptions to go unobserved.
Methods which return a promise show this in their documentation.
@@ -191,10 +198,10 @@ This section provides some useful code snippets which demonstrate the
core functionality of the SDK. These examples assume the SDK is setup like this:
```javascript
var sdk = require("matrix-js-sdk");
var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var matrixClient = sdk.createClient({
import * as sdk from "matrix-js-sdk";
const myUserId = "@example:localhost";
const myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId
@@ -206,7 +213,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
```javascript
matrixClient.on("RoomMember.membership", function(event, member) {
if (member.membership === "invite" && member.userId === myUserId) {
matrixClient.joinRoom(member.roomId).done(function() {
matrixClient.joinRoom(member.roomId).then(function() {
console.log("Auto-joined %s", member.roomId);
});
}
@@ -247,11 +254,11 @@ Output:
```javascript
matrixClient.on("RoomState.members", function(event, state, member) {
var room = matrixClient.getRoom(state.roomId);
const room = matrixClient.getRoom(state.roomId);
if (!room) {
return;
}
var memberList = state.getMembers();
const memberList = state.getMembers();
console.log(room.name);
console.log(Array(room.name.length + 1).join("=")); // underline
for (var i = 0; i < memberList.length; i++) {
@@ -297,7 +304,7 @@ End-to-end encryption support
=============================
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
application to make libolm available, via the ``Olm`` global.
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
@@ -319,16 +326,16 @@ To provide the Olm library in a browser application:
* download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
To provide the Olm library in a node.js application:
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.0.0.tgz``
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
(replace the URL with the latest version you want to use from
https://packages.matrix.org/npm/olm/)
* ``global.Olm = require('olm');`` *before* loading ``matrix-js-sdk``.
If you want to package Olm as dependency for your node.js application, you can
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.0.0.tgz``. If your
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``. If your
application also works without e2e crypto enabled, add ``--optional`` to mark it
as an optional dependency.
@@ -351,11 +358,6 @@ To build a browser version from scratch when developing::
$ yarn build
```
To constantly do builds when files are modified (using ``watchify``)::
```
$ yarn watch
```
To run tests (Jasmine)::
```
$ yarn test
-35
View File
@@ -1,35 +0,0 @@
var matrixcs = require("./lib/matrix");
const request = require('browser-request');
const queryString = require('qs');
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
// we mimic `request`'s query string interface to make it all work
// as expected.
// browser-request will happily take the constructed string as the
// query string without trying to modify it further.
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
return request(opts, fn);
});
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
var indexedDB;
try {
indexedDB = global.indexedDB;
} catch(e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto"
);
}
);
}
module.exports = matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;
-1
View File
@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk");
var client = matrixcs.createClient("http://matrix.org");
+2
View File
@@ -0,0 +1,2 @@
olm.js
olm.wasm
+1
View File
@@ -0,0 +1 @@
../../../dist/browser-matrix.js
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test Crypto in Browser</title>
<script src="lib/olm.js"></script>
<script src="lib/matrix.js"></script>
</head>
<body>
<h1>Testing export/import of Olm devices in the browser</h1>
<ul>
<li>
Make sure you built the current version of the Matrix JS SDK
(<code>yarn build</code>)
</li>
<li>
copy <code>olm.js</code> and <code>olm.wasm</code>
from a recent release of Olm (was tested with version 3.1.4)
in directory <code>lib/</code>
</li>
<li>start a local Matrix homeserver (on port 8008, or change the port in the code)</li>
<li>Serve this HTML file (e.g. <code>python3 -m http.server</code>) and go to it through your browser</li>
<li>
in the JS console, do:
<pre>
aliceMatrixClient = await newMatrixClient("alice-"+randomHex());
await aliceMatrixClient.exportDevice();
await aliceMatrixClient.getAccessToken();
</pre>
</li>
<li>
copy the result of <code>exportDevice</code> and <code>getAccessToken</code> somewhere
(<strong>not</strong> in a JS variable as it will be destroyed when you refresh the page)
</li>
<li><strong>refresh the page (F5)</strong> to make sure the client is destroyed</li>
<li>
Do the following, replacing <code>ALICE_ID</code>
with the user ID of Alice (you can find it in the exported data)
<pre>
bobMatrixClient = await newMatrixClient("bob-"+randomHex());
roomId = await bobMatrixClient.createEncryptedRoom([ALICE_ID]);
await bobMatrixClient.sendTextMessage('Hi Alice!', roomId);
</pre>
</li>
<li>Again, <strong>refresh the page (F5)</strong>. You may want to clear your console as well.</li>
<li>
Now do the following, using the exported data and the access token you saved previously:
<pre>
aliceMatrixClient = await importMatrixClient(EXPORTED_DATA, ACCESS_TOKEN);
</pre>
</li>
<li>You should see the message sent by Bob printed in the console.</li>
</ul>
<script src="olm-device-export-import.js"></script>
</body>
</html>
@@ -0,0 +1,122 @@
if (!Olm) {
console.error(
"global.Olm does not seem to be present."
+ " Did you forget to add olm in the lib/ directory?"
);
}
const BASE_URL = 'http://localhost:8008';
const ROOM_CRYPTO_CONFIG = { algorithm: 'm.megolm.v1.aes-sha2' };
const PASSWORD = 'password';
// useful to create new usernames
window.randomHex = () => Math.floor(Math.random() * (10**6)).toString(16);
window.newMatrixClient = async function (username) {
const registrationClient = matrixcs.createClient(BASE_URL);
const userRegisterResult = await registrationClient.register(
username,
PASSWORD,
null,
{ type: 'm.login.dummy' }
);
const matrixClient = matrixcs.createClient({
baseUrl: BASE_URL,
userId: userRegisterResult.user_id,
accessToken: userRegisterResult.access_token,
deviceId: userRegisterResult.device_id,
sessionStore: new matrixcs.WebStorageSessionStore(window.localStorage),
cryptoStore: new matrixcs.MemoryCryptoStore(),
});
extendMatrixClient(matrixClient);
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
window.importMatrixClient = async function (exportedDevice, accessToken) {
const matrixClient = matrixcs.createClient({
baseUrl: BASE_URL,
deviceToImport: exportedDevice,
accessToken,
sessionStore: new matrixcs.WebStorageSessionStore(window.localStorage),
cryptoStore: new matrixcs.MemoryCryptoStore(),
});
extendMatrixClient(matrixClient);
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
function extendMatrixClient(matrixClient) {
// automatic join
matrixClient.on('RoomMember.membership', async (event, member) => {
if (member.membership === 'invite' && member.userId === matrixClient.getUserId()) {
await matrixClient.joinRoom(member.roomId);
// setting up of room encryption seems to be triggered automatically
// but if we don't wait for it the first messages we send are unencrypted
await matrixClient.setRoomEncryption(member.roomId, { algorithm: 'm.megolm.v1.aes-sha2' })
}
});
matrixClient.onDecryptedMessage = message => {
console.log('Got encrypted message: ', message);
}
matrixClient.on('Event.decrypted', (event) => {
if (event.getType() === 'm.room.message'){
matrixClient.onDecryptedMessage(event.getContent().body);
} else {
console.log('decrypted an event of type', event.getType());
console.log(event);
}
});
matrixClient.createEncryptedRoom = async function(usersToInvite) {
const {
room_id: roomId,
} = await this.createRoom({
visibility: 'private',
invite: usersToInvite,
});
// matrixClient.setRoomEncryption() only updates local state
// but does not send anything to the server
// (see https://github.com/matrix-org/matrix-js-sdk/issues/905)
// so we do it ourselves with 'sendStateEvent'
await this.sendStateEvent(
roomId, 'm.room.encryption', ROOM_CRYPTO_CONFIG,
);
await this.setRoomEncryption(
roomId, ROOM_CRYPTO_CONFIG,
);
// Marking all devices as verified
let room = this.getRoom(roomId);
let members = (await room.getEncryptionTargetMembers()).map(x => x["userId"])
let memberkeys = await this.downloadKeys(members);
for (const userId in memberkeys) {
for (const deviceId in memberkeys[userId]) {
await this.setDeviceVerified(userId, deviceId);
}
}
return roomId;
}
matrixClient.sendTextMessage = async function(message, roomId) {
return matrixClient.sendMessage(
roomId,
{
body: message,
msgtype: 'm.text',
}
)
}
}
+14 -15
View File
@@ -1,5 +1,3 @@
"use strict";
var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var sdk = require("matrix-js-sdk");
@@ -56,7 +54,7 @@ rl.on('line', function(line) {
}
}
if (notSentEvent) {
matrixClient.resendEvent(notSentEvent, viewingRoom).done(function() {
matrixClient.resendEvent(notSentEvent, viewingRoom).then(function() {
printMessages();
rl.prompt();
}, function(err) {
@@ -70,7 +68,7 @@ rl.on('line', function(line) {
}
else if (line.indexOf("/more ") === 0) {
var amount = parseInt(line.split(" ")[1]) || 20;
matrixClient.scrollback(viewingRoom, amount).done(function(room) {
matrixClient.scrollback(viewingRoom, amount).then(function(room) {
printMessages();
rl.prompt();
}, function(err) {
@@ -79,7 +77,7 @@ rl.on('line', function(line) {
}
else if (line.indexOf("/invite ") === 0) {
var userId = line.split(" ")[1].trim();
matrixClient.invite(viewingRoom.roomId, userId).done(function() {
matrixClient.invite(viewingRoom.roomId, userId).then(function() {
printMessages();
rl.prompt();
}, function(err) {
@@ -92,7 +90,7 @@ rl.on('line', function(line) {
matrixClient.uploadContent({
stream: stream,
name: filename
}).done(function(url) {
}).then(function(url) {
var content = {
msgtype: "m.file",
body: filename,
@@ -116,7 +114,7 @@ rl.on('line', function(line) {
viewingRoom = roomList[roomIndex];
if (viewingRoom.getMember(myUserId).membership === "invite") {
// join the room first
matrixClient.joinRoom(viewingRoom.roomId).done(function(room) {
matrixClient.joinRoom(viewingRoom.roomId).then(function(room) {
setRoomList();
viewingRoom = room;
printMessages();
@@ -128,7 +126,7 @@ rl.on('line', function(line) {
else {
printMessages();
}
}
}
}
rl.prompt();
});
@@ -281,8 +279,8 @@ function printMemberList(room) {
member.membership + new Array(10 - member.membership.length).join(" ")
);
print(
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
membershipWithPadding, member.name,
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
membershipWithPadding, member.name,
(member.userId === myUserId ? "Me" : member.userId),
fmt
);
@@ -290,26 +288,27 @@ function printMemberList(room) {
}
function printRoomInfo(room) {
var eventDict = room.currentState.events;
var eventMap = room.currentState.events;
var eTypeHeader = " Event Type(state_key) ";
var sendHeader = " Sender ";
// pad content to 100
var restCount = (
100 - "Content".length - " | ".length - " | ".length -
100 - "Content".length - " | ".length - " | ".length -
eTypeHeader.length - sendHeader.length
);
var padSide = new Array(Math.floor(restCount/2)).join(" ");
var contentHeader = padSide + "Content" + padSide;
print(eTypeHeader+sendHeader+contentHeader);
print(new Array(100).join("-"));
Object.keys(eventDict).forEach(function(eventType) {
eventMap.keys().forEach(function(eventType) {
if (eventType === "m.room.member") { return; } // use /members instead.
Object.keys(eventDict[eventType]).forEach(function(stateKey) {
var eventEventMap = eventMap.get(eventType);
eventEventMap.keys().forEach(function(stateKey) {
var typeAndKey = eventType + (
stateKey.length > 0 ? "("+stateKey+")" : ""
);
var typeStr = fixWidth(typeAndKey, eTypeHeader.length);
var event = eventDict[eventType][stateKey];
var event = eventEventMap.get(stateKey);
var sendStr = fixWidth(event.getSender(), sendHeader.length);
var contentStr = fixWidth(
JSON.stringify(event.getContent()), contentHeader.length
-1
View File
@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk");
var BASE_URL = "https://matrix.org";
var TOKEN = "accesstokengoeshere";
-6
View File
@@ -1,6 +0,0 @@
var matrixcs = require("./lib/matrix");
matrixcs.request(require("request"));
module.exports = matrixcs;
var utils = require("./lib/utils");
utils.runPolyfills();
-36
View File
@@ -1,36 +0,0 @@
#!/bin/bash -l
set -x
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 10 || exit $?
yarn install || exit $?
RC=0
function fail {
echo $@ >&2
RC=1
}
# don't use last time's test reports
rm -rf reports coverage || exit $?
yarn test || fail "yarn test finished with return code $?"
yarn -s lint -f checkstyle > eslint.xml ||
fail "eslint finished with return code $?"
# delete the old tarball, if it exists
rm -f matrix-js-sdk-*.tgz
# `yarn pack` doesn't seem to run scripts, however that seems okay here as we
# just built as part of `install` above.
yarn pack ||
fail "yarn pack finished with return code $?"
yarn gendoc || fail "JSDoc failed with code $?"
exit $RC
+30
View File
@@ -0,0 +1,30 @@
{
"tags": {
"allowUnknownTags": true
},
"plugins": [
"node_modules/better-docs/category",
"node_modules/better-docs/typescript"
],
"source": {
"include": [
"src"
],
"includePattern": ".(ts|js)$"
},
"opts": {
"encoding": "utf8",
"destination": ".jsdoc",
"readme": "README.md",
"recurse": true,
"verbose": true,
"template": "node_modules/docdash"
},
"docdash": {
"static": true,
"private": false,
"search": true,
"collapse": true,
"typedefs": true
}
}
+65 -66
View File
@@ -1,24 +1,23 @@
{
"name": "matrix-js-sdk",
"version": "2.3.2",
"version": "9.2.0",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
"test:build": "babel -s -d specbuild spec",
"test:run": "istanbul cover --report text --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" node_modules/mocha/bin/_mocha -- --recursive specbuild --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_path=reports/test-results.xml",
"test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors",
"test": "yarn test:build && yarn test:run",
"check": "yarn test:build && _mocha --recursive specbuild --colors",
"gendoc": "babel --no-babelrc --plugins transform-class-properties -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
"start": "yarn start:init && yarn start:watch",
"start:watch": "babel -s -w --skip-initial-build -d lib src",
"start:init": "babel -s -d lib src",
"prepare": "yarn build",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"clean": "rimraf lib dist",
"build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && terser -c -m -o dist/browser-matrix.min.js --source-map \"content='dist/browser-matrix.js.map'\" dist/browser-matrix.js",
"dist": "yarn build",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
"lint": "eslint --max-warnings 101 src spec",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt"
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:js",
"lint:js": "eslint --max-warnings 73 src spec",
"lint:types": "tsc --noEmit",
"test": "jest spec/ --coverage --testEnvironment node",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
},
"repository": {
"type": "git",
@@ -27,72 +26,72 @@
"keywords": [
"matrix-org"
],
"browser": "browser-index.js",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"author": "matrix.org",
"license": "Apache-2.0",
"files": [
".babelrc",
".eslintrc.js",
"spec/.eslintrc.js",
"dist",
"lib",
"src",
"git-revision.txt",
"CHANGELOG.md",
"CONTRIBUTING.rst",
"LICENSE",
"README.md",
"RELEASING.md",
"examples",
"git-hooks",
"git-revision.txt",
"index.js",
"browser-index.js",
"jenkins.sh",
"lib",
"package.json",
"release.sh",
"spec",
"src"
"release.sh"
],
"dependencies": {
"@babel/runtime": "^7.11.2",
"another-json": "^0.2.0",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.0",
"browser-request": "^0.3.3",
"bs58": "^4.0.1",
"content-type": "^1.0.2",
"loglevel": "1.6.1",
"qs": "^6.5.2",
"request": "^2.88.0",
"unhomoglyph": "^1.0.2"
"content-type": "^1.0.4",
"loglevel": "^1.7.0",
"qs": "^6.9.4",
"request": "^2.88.2",
"unhomoglyph": "^1.0.6"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^10.0.1",
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.18.0",
"browserify": "^16.2.3",
"browserify-shim": "^3.8.13",
"eslint": "^5.12.0",
"eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.3.0",
"exorcist": "^0.4.0",
"expect": "^1.20.2",
"istanbul": "^0.4.5",
"jsdoc": "^3.5.5",
"lolex": "^1.5.2",
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4",
"@babel/register": "^7.11.5",
"@types/jest": "^26.0.14",
"@types/node": "12",
"@types/request": "^2.48.5",
"babel-eslint": "^10.1.0",
"babel-jest": "^24.9.0",
"babelify": "^10.0.0",
"better-docs": "^2.3.2",
"browserify": "^16.5.2",
"docdash": "^1.2.0",
"eslint": "7.9.0",
"eslint-config-matrix-org": "^0.1.2",
"eslint-plugin-babel": "^5.3.1",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.1.2",
"jest": "^24.9.0",
"jest-localstorage-mock": "^2.4.3",
"jsdoc": "^3.6.6",
"matrix-mock-request": "^1.2.3",
"mocha": "^5.2.0",
"mocha-jenkins-reporter": "^0.4.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.0.tgz",
"rimraf": "^2.5.4",
"source-map-support": "^0.4.11",
"sourceify": "^0.1.0",
"terser": "^4.0.0",
"watchify": "^3.11.1"
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"rimraf": "^3.0.2",
"terser": "^4.8.0",
"tsify": "^4.0.2",
"typescript": "^3.9.7"
},
"browserify": {
"transform": [
"sourceify"
]
"jest": {
"testEnvironment": "node"
}
}
+43 -17
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#
# Script to perform a release of matrix-js-sdk.
# Script to perform a release of matrix-js-sdk and downstream projects.
#
# Requires:
# github-changelog-generator; install via:
@@ -9,6 +9,8 @@
# hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
# npm; typically installed by Node.js
# yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/)
#
# Note: this script is also used to release matrix-react-sdk and element-web.
set -e
@@ -36,6 +38,7 @@ $USAGE
-c changelog_file: specify name of file containing changelog
-x: skip updating the changelog
-z: skip generating the jsdoc
-n: skip publish to NPM
EOF
}
@@ -58,9 +61,10 @@ fi
skip_changelog=
skip_jsdoc=
skip_npm=
changelog_file="CHANGELOG.md"
expected_npm_user="matrixdotorg"
while getopts hc:u:xz f; do
while getopts hc:u:xzn f; do
case $f in
h)
help
@@ -75,6 +79,9 @@ while getopts hc:u:xz f; do
z)
skip_jsdoc=1
;;
n)
skip_npm=1
;;
u)
expected_npm_user="$OPTARG"
;;
@@ -87,6 +94,14 @@ if [ $# -ne 1 ]; then
exit 1
fi
# We use Git branch / commit dependencies for some packages, and Yarn seems
# to have a hard time getting that right. See also
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
# global cache here to ensure we get the right thing.
yarn cache clean
# Ensure all dependencies are updated
yarn install --ignore-scripts
if [ -z "$skip_changelog" ]; then
# update_changelog doesn't have a --version flag
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
@@ -94,10 +109,12 @@ fi
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
actual_npm_user=`npm whoami`;
if [ $expected_npm_user != $actual_npm_user ]; then
echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2
exit 1
if [ -z "$skip_npm" ]; then
actual_npm_user=`npm whoami`;
if [ $expected_npm_user != $actual_npm_user ]; then
echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2
exit 1
fi
fi
# ignore leading v on release
@@ -289,7 +306,16 @@ rm "${latest_changes}"
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
npm publish
# Tag both releases and prereleases as `next` so the last stable release remains
# the default.
if [ -z "$skip_npm" ]; then
npm publish --tag next
if [ $prerelease -eq 0 ]; then
# For a release, also add the default `latest` tag.
package=$(cat package.json | jq -er .name)
npm dist-tag add "$package@$release" latest
fi
fi
if [ -z "$skip_jsdoc" ]; then
echo "generating jsdocs"
@@ -304,6 +330,7 @@ if [ -z "$skip_jsdoc" ]; then
$release index.html
git add "$release"
git commit --no-verify -m "Add jsdoc for $release" index.html "$release"
git push origin gh-pages
fi
# if it is a pre-release, leave it on the release branch for now.
@@ -316,16 +343,15 @@ fi
echo "updating master branch"
git checkout master
git pull
git merge "$rel_branch"
git merge "$rel_branch" --no-edit
# push master and docs (if generated) to github
# push master to github
git push origin master
if [ -z "$skip_jsdoc" ]; then
git push origin gh-pages
fi
# finally, merge master back onto develop
git checkout develop
git pull
git merge master
git push origin develop
# finally, merge master back onto develop (if it exists)
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
git checkout develop
git pull
git merge master --no-edit
git push origin develop
fi
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
env: {
mocha: true,
},
}
+2 -3
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,7 +19,7 @@ limitations under the License.
* A mock implementation of the webstorage api
* @constructor
*/
function MockStorageApi() {
export function MockStorageApi() {
this.data = {};
this.keys = [];
this.length = 0;
@@ -52,5 +53,3 @@ MockStorageApi.prototype = {
},
};
/** */
module.exports = MockStorageApi;
+21 -16
View File
@@ -16,18 +16,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
// load olm before the sdk if possible
import './olm-loader';
import sdk from '..';
import testUtils from './test-utils';
import MockHttpBackend from 'matrix-mock-request';
import expect from 'expect';
import Promise from 'bluebird';
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
import logger from '../src/logger';
import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
import {logger} from '../src/logger';
import {WebStorageSessionStore} from "../src/store/session/webstorage";
import {syncPromise} from "./test-utils";
import {createClient} from "../src/matrix";
import {MockStorageApi} from "./MockStorageApi";
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@@ -41,16 +39,16 @@ import logger from '../src/logger';
* session store. If undefined, we will create a MockStorageApi.
* @param {object} options additional options to pass to the client
*/
export default function TestClient(
export function TestClient(
userId, deviceId, accessToken, sessionStoreBackend, options,
) {
this.userId = userId;
this.deviceId = deviceId;
if (sessionStoreBackend === undefined) {
sessionStoreBackend = new testUtils.MockStorageApi();
sessionStoreBackend = new MockStorageApi();
}
const sessionStore = new sdk.WebStorageSessionStore(sessionStoreBackend);
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
this.httpBackend = new MockHttpBackend();
@@ -67,10 +65,13 @@ export default function TestClient(
this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend);
options.cryptoStore = this.cryptoStore;
}
this.client = sdk.createClient(options);
this.client = createClient(options);
this.deviceKeys = null;
this.oneTimeKeys = {};
this._callEventHandler = {
calls: new Map(),
};
}
TestClient.prototype.toString = function() {
@@ -99,7 +100,7 @@ TestClient.prototype.start = function() {
return Promise.all([
this.httpBackend.flushAllExpected(),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
logger.log(this + ': started');
});
@@ -159,7 +160,7 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
.respond(200, (path, content) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
expect(content.one_time_keys).not.toEqual({});
logger.log('%s: received %i one-time keys', this,
Object.keys(content.one_time_keys).length);
this.oneTimeKeys = content.one_time_keys;
@@ -187,7 +188,7 @@ TestClient.prototype.expectKeyQuery = function(response) {
200, (path, content) => {
Object.keys(response.device_keys).forEach((userId) => {
expect(content.device_keys[userId]).toEqual(
{},
[],
"Expected key query for " + userId + ", got " +
Object.keys(content.device_keys),
);
@@ -227,8 +228,12 @@ TestClient.prototype.flushSync = function() {
logger.log(`${this}: flushSync`);
return Promise.all([
this.httpBackend.flush('/sync', 1),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
logger.log(`${this}: flushSync completed`);
});
};
TestClient.prototype.isFallbackICEServerAllowed = function() {
return true;
};
+23
View File
@@ -0,0 +1,23 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// stub for browser-matrix browserify tests
global.XMLHttpRequest = jest.fn();
afterAll(() => {
// clean up XMLHttpRequest mock
global.XMLHttpRequest = undefined;
});
+103
View File
@@ -0,0 +1,103 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// load XmlHttpRequest mock
import "./setupTests";
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
import {MockStorageApi} from "../MockStorageApi";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import MockHttpBackend from "matrix-mock-request";
import {LocalStorageCryptoStore} from "../../src/crypto/store/localStorage-crypto-store";
import * as utils from "../test-utils";
const USER_ID = "@user:test.server";
const DEVICE_ID = "device_id";
const ACCESS_TOKEN = "access_token";
const ROOM_ID = "!room_id:server.test";
/* global matrixcs */
describe("Browserify Test", function() {
let client;
let httpBackend;
async function createTestClient() {
const sessionStoreBackend = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
const httpBackend = new MockHttpBackend();
const options = {
baseUrl: "http://" + USER_ID + ".test.server",
userId: USER_ID,
accessToken: ACCESS_TOKEN,
deviceId: DEVICE_ID,
sessionStore: sessionStore,
request: httpBackend.requestFn,
cryptoStore: new LocalStorageCryptoStore(sessionStoreBackend),
};
const client = matrixcs.createClient(options);
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
return { client, httpBackend };
}
beforeEach(async () => {
({client, httpBackend} = await createTestClient());
await client.startClient();
});
afterEach(async () => {
client.stopClient();
await httpBackend.stop();
});
it("Sync", async function() {
const event = utils.mkMembership({
room: ROOM_ID,
mship: "join",
user: "@other_user:server.test",
name: "Displayname",
});
const syncData = {
next_batch: "batch1",
rooms: {
join: {},
},
};
syncData.rooms.join[ROOM_ID] = {
timeline: {
events: [
event,
],
limited: false,
},
};
httpBackend.when("GET", "/sync").respond(200, syncData);
await Promise.race([
Promise.all([
httpBackend.flushAllExpected(),
]),
new Promise((_, reject) => {
client.once("sync.unexpectedError", reject);
}),
]);
}, 20000); // additional timeout as this test can take quite a while
});
+6 -10
View File
@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,12 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import expect from 'expect';
import Promise from 'bluebird';
import TestClient from '../TestClient';
import testUtils from '../test-utils';
import logger from '../../src/logger';
import {TestClient} from '../TestClient';
import * as testUtils from '../test-utils';
import {logger} from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -88,8 +86,6 @@ describe("DeviceList management:", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// we create our own sessionStoreBackend so that we can use it for
// another TestClient.
sessionStoreBackend = new testUtils.MockStorageApi();
@@ -144,7 +140,7 @@ describe("DeviceList management:", function() {
it("We should not get confused by out-of-order device query responses",
() => {
// https://github.com/vector-im/riot-web/issues/3126
// https://github.com/vector-im/element-web/issues/3126
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
return aliceTestClient.start().then(() => {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
@@ -275,7 +271,7 @@ describe("DeviceList management:", function() {
});
}).timeout(3000);
// https://github.com/vector-im/riot-web/issues/4983
// https://github.com/vector-im/element-web/issues/4983
describe("Alice should know she has stale device lists", () => {
beforeEach(async function() {
await aliceTestClient.start();
+46 -55
View File
@@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -24,19 +25,14 @@ limitations under the License.
* See also `megolm.spec.js`.
*/
"use strict";
import 'source-map-support/register';
// load olm before the sdk if possible
import '../olm-loader';
import expect from 'expect';
const sdk = require("../..");
import Promise from 'bluebird';
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
const TestClient = require('../TestClient').default;
import logger from '../../src/logger';
import {logger} from '../../src/logger';
import * as testUtils from "../test-utils";
import * as utils from "../../src/utils";
import {TestClient} from "../TestClient";
import {CRYPTO_ENABLED} from "../../src/client";
let aliTestClient;
const roomId = "!room:localhost";
@@ -56,7 +52,7 @@ function bobUploadsDeviceKeys() {
bobTestClient.client.uploadKeys(),
bobTestClient.httpBackend.flush(),
]).then(() => {
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
}
@@ -74,7 +70,7 @@ function expectAliQueryKeys() {
aliTestClient.httpBackend.when("POST", "/keys/query")
.respond(200, function(path, content) {
expect(content.device_keys[bobUserId]).toEqual(
{},
[],
"Expected Alice to key query for " + bobUserId + ", got " +
Object.keys(content.device_keys),
);
@@ -102,7 +98,7 @@ function expectBobQueryKeys() {
"POST", "/keys/query",
).respond(200, function(path, content) {
expect(content.device_keys[aliUserId]).toEqual(
{},
[],
"Expected Bob to key query for " + aliUserId + ", got " +
Object.keys(content.device_keys),
);
@@ -204,7 +200,7 @@ function aliSendsFirstMessage() {
expectAliQueryKeys()
.then(expectAliClaimKeys)
.then(expectAliSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -219,7 +215,7 @@ function aliSendsMessage() {
return Promise.all([
sendMessage(aliTestClient.client),
expectAliSendMessageRequest(),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -235,7 +231,7 @@ function bobSendsReplyMessage() {
sendMessage(bobTestClient.client),
expectBobQueryKeys()
.then(expectBobSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -280,16 +276,17 @@ function sendMessage(client) {
function expectSendMessageRequest(httpBackend) {
const path = "/send/m.room.encrypted/";
const deferred = Promise.defer();
httpBackend.when("PUT", path).respond(200, function(path, content) {
deferred.resolve(content);
return {
event_id: "asdfgh",
};
const prom = new Promise((resolve) => {
httpBackend.when("PUT", path).respond(200, function(path, content) {
resolve(content);
return {
event_id: "asdfgh",
};
});
});
// it can take a while to process the key query
return httpBackend.flush(path, 1).then(() => deferred.promise);
return httpBackend.flush(path, 1).then(() => prom);
}
function aliRecvMessage() {
@@ -401,13 +398,11 @@ function firstSync(testClient) {
describe("MatrixClient crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken);
await aliTestClient.client.initCrypto();
@@ -430,15 +425,14 @@ describe("MatrixClient crypto", function() {
.then(bobUploadsDeviceKeys);
});
it("Ali downloads Bobs device keys", function(done) {
Promise.resolve()
it("Ali downloads Bobs device keys", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(aliDownloadsKeys)
.nodeify(done);
.then(aliDownloadsKeys);
});
it("Ali gets keys with an invalid signature", function(done) {
Promise.resolve()
it("Ali gets keys with an invalid signature", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(function() {
// tamper bob's keys
@@ -455,11 +449,10 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
})
.nodeify(done);
});
});
it("Ali gets keys with an incorrect userId", function(done) {
it("Ali gets keys with an incorrect userId", function() {
const eveUserId = "@eve:localhost";
const bobDeviceKeys = {
@@ -488,7 +481,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId, eveUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -496,14 +489,14 @@ describe("MatrixClient crypto", function() {
aliTestClient.client.getStoredDevicesForUser(bobUserId),
aliTestClient.client.getStoredDevicesForUser(eveUserId),
]);
}).spread((bobDevices, eveDevices) => {
}).then(([bobDevices, eveDevices]) => {
// should get an empty list
expect(bobDevices).toEqual([]);
expect(eveDevices).toEqual([]);
}).nodeify(done);
});
});
it("Ali gets keys with an incorrect deviceId", function(done) {
it("Ali gets keys with an incorrect deviceId", function() {
const bobDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'bad_device',
@@ -530,7 +523,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -538,7 +531,7 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
}).nodeify(done);
});
});
@@ -548,19 +541,18 @@ describe("MatrixClient crypto", function() {
.then(() => bobTestClient.awaitOneTimeKeyUpload())
.then((keys) => {
expect(Object.keys(keys).length).toEqual(5);
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
});
it("Ali sends a message", function(done) {
it("Ali sends a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
.then(aliEnablesEncryption)
.then(aliSendsFirstMessage)
.nodeify(done);
.then(aliSendsFirstMessage);
});
it("Bob receives a message", function() {
@@ -628,9 +620,9 @@ describe("MatrixClient crypto", function() {
});
});
it("Ali blocks Bob's device", function(done) {
it("Ali blocks Bob's device", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -645,12 +637,12 @@ describe("MatrixClient crypto", function() {
expect(sentContent.ciphertext).toEqual({});
});
return Promise.all([p1, p2]);
}).nodeify(done);
});
});
it("Bob receives two pre-key messages", function(done) {
it("Bob receives two pre-key messages", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -658,8 +650,7 @@ describe("MatrixClient crypto", function() {
.then(aliSendsFirstMessage)
.then(bobRecvMessage)
.then(aliSendsMessage)
.then(bobRecvMessage)
.nodeify(done);
.then(bobRecvMessage);
});
it("Bob replies to the message", function() {
@@ -753,9 +744,9 @@ describe("MatrixClient crypto", function() {
.then(() => httpBackend.when("POST", "/keys/upload")
.respond(200, (path, content) => {
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
expect(content.one_time_keys).not.toEqual({});
expect(Object.keys(content.one_time_keys).length)
.toBeGreaterThanOrEqualTo(1);
.toBeGreaterThanOrEqual(1);
logger.log('received %i one-time keys',
Object.keys(content.one_time_keys).length);
// cancel futher calls by telling the client
+7 -19
View File
@@ -1,28 +1,16 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
describe("MatrixClient events", function() {
const baseUrl = "http://localhost.or.something";
let client;
let httpBackend;
const selfUserId = "@alice:localhost";
const selfAccessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
client = testClient.client;
httpBackend = testClient.httpBackend;
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
@@ -164,7 +152,7 @@ describe("MatrixClient events", function() {
});
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
expect(fired).toBe(true, "User.presence didn't fire.");
done();
});
@@ -219,7 +207,7 @@ describe("MatrixClient events", function() {
client.on("RoomState.events", function(event, state) {
eventsInvokeCount++;
const index = roomStateEventTypes.indexOf(event.getType());
expect(index).toNotEqual(
expect(index).not.toEqual(
-1, "Unexpected room state event type: " + event.getType(),
);
if (index >= 0) {
+60 -76
View File
@@ -1,13 +1,8 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const EventTimeline = sdk.EventTimeline;
import logger from '../../src/logger';
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/matrix";
import {logger} from "../../src/logger";
import {TestClient} from "../TestClient";
const baseUrl = "http://localhost.or.something";
const userId = "@alice:localhost";
const userName = "Alice";
const accessToken = "aseukfgwef";
@@ -83,18 +78,19 @@ function startClient(httpBackend, client) {
client.startClient();
// set up a promise which will resolve once the client is initialised
const deferred = Promise.defer();
client.on("sync", function(state) {
logger.log("sync", state);
if (state != "SYNCING") {
return;
}
deferred.resolve();
const prom = new Promise((resolve) => {
client.on("sync", function(state) {
logger.log("sync", state);
if (state != "SYNCING") {
return;
}
resolve();
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
}
@@ -103,9 +99,9 @@ describe("getEventTimeline support", function() {
let client;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
const testClient = new TestClient(userId, "DEVICE", accessToken);
client = testClient.client;
httpBackend = testClient.httpBackend;
});
afterEach(function() {
@@ -115,53 +111,44 @@ describe("getEventTimeline support", function() {
return httpBackend.stop();
});
it("timeline support must be enabled to work", function(done) {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
startClient(httpBackend, client,
).then(function() {
it("timeline support must be enabled to work", function() {
return startClient(httpBackend, client).then(function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toThrow();
}).nodeify(done);
});
});
it("timeline support works when enabled", function() {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
timelineSupport: true,
});
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
client = testClient.client;
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toNotThrow();
}).not.toThrow();
});
});
it("scrollback should be able to scroll back to before a gappy /sync",
function(done) {
function() {
// need a client with timelineSupport disabled to make this work
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
let room;
startClient(httpBackend, client,
).then(function() {
return startClient(httpBackend, client).then(function() {
room = client.getRoom(roomId);
httpBackend.when("GET", "/sync").respond(200, {
@@ -217,27 +204,24 @@ describe("getEventTimeline support", function() {
expect(room.timeline[0].event).toEqual(EVENTS[0]);
expect(room.timeline[1].event).toEqual(EVENTS[1]);
expect(room.oldState.paginationToken).toEqual("pagin_end");
}).nodeify(done);
});
});
});
import expect from 'expect';
describe("MatrixClient event timelines", function() {
let client = null;
let httpBackend = null;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
timelineSupport: true,
});
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
client = testClient.client;
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client);
});
@@ -349,25 +333,25 @@ describe("MatrixClient event timelines", function() {
};
});
const deferred = Promise.defer();
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).done(() => deferred.resolve(),
(e) => deferred.reject(e));
const prom = new Promise((resolve, reject) => {
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).then(resolve, reject);
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
});
@@ -697,7 +681,7 @@ describe("MatrixClient event timelines", function() {
});
it("should handle gappy syncs after redactions", function(done) {
it("should handle gappy syncs after redactions", function() {
// https://github.com/vector-im/vector-web/issues/1389
// a state event, followed by a redaction thereof
@@ -729,7 +713,7 @@ describe("MatrixClient event timelines", function() {
};
httpBackend.when("GET", "/sync").respond(200, syncData);
Promise.all([
return Promise.all([
httpBackend.flushAllExpected(),
utils.syncPromise(client),
]).then(function() {
@@ -765,6 +749,6 @@ describe("MatrixClient event timelines", function() {
const room = client.getRoom(roomId);
const tl = room.getLiveTimeline();
expect(tl.getEvents().length).toEqual(1);
}).nodeify(done);
});
});
});
+38 -53
View File
@@ -1,42 +1,23 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const publicGlobals = require("../../lib/matrix");
const Room = publicGlobals.Room;
const MemoryStore = publicGlobals.MemoryStore;
const Filter = publicGlobals.Filter;
const utils = require("../test-utils");
const MockStorageApi = require("../MockStorageApi");
import expect from 'expect';
import * as utils from "../test-utils";
import {CRYPTO_ENABLED} from "../../src/client";
import {Filter, MemoryStore, Room} from "../../src/matrix";
import {TestClient} from "../TestClient";
describe("MatrixClient", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
let store = null;
let sessionStore = null;
const userId = "@alice:localhost";
const accessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
store = new MemoryStore();
const mockStorage = new MockStorageApi();
sessionStore = new sdk.WebStorageSessionStore(mockStorage);
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
deviceId: "aliceDevice",
accessToken: accessToken,
const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, {
store: store,
sessionStore: sessionStore,
});
httpBackend = testClient.httpBackend;
client = testClient.client;
});
afterEach(function() {
@@ -46,7 +27,7 @@ describe("MatrixClient", function() {
describe("uploadContent", function() {
const buf = new Buffer('hello world');
it("should upload the file", function(done) {
it("should upload the file", function() {
httpBackend.when(
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
@@ -74,25 +55,26 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
// for backwards compatibility, we return the raw JSON
expect(response).toEqual("content");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
httpBackend.flush();
return prom2;
});
it("should parse the response if rawResponse=false", function(done) {
it("should parse the response if rawResponse=false", function() {
httpBackend.when(
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.opts.json).toBeFalsy();
}).respond(200, { "content_uri": "uri" });
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -100,12 +82,13 @@ describe("MatrixClient", function() {
rawResponse: false,
}).then(function(response) {
expect(response.content_uri).toEqual("uri");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should parse errors into a MatrixError", function(done) {
it("should parse errors into a MatrixError", function() {
httpBackend.when(
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
@@ -116,7 +99,7 @@ describe("MatrixClient", function() {
"error": "broken",
});
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -126,12 +109,13 @@ describe("MatrixClient", function() {
expect(error.httpStatus).toEqual(400);
expect(error.errcode).toEqual("M_SNAFU");
expect(error.message).toEqual("broken");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should return a promise which can be cancelled", function(done) {
it("should return a promise which can be cancelled", function() {
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
@@ -143,17 +127,18 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
throw Error("request not aborted");
}, function(error) {
expect(error).toEqual("aborted");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
const r = client.cancelUpload(prom);
expect(r).toBe(true);
return prom2;
});
});
@@ -180,7 +165,7 @@ describe("MatrixClient", function() {
event_format: "client",
});
store.storeFilter(filter);
client.getFilter(userId, filterId, true).done(function(gotFilter) {
client.getFilter(userId, filterId, true).then(function(gotFilter) {
expect(gotFilter).toEqual(filter);
done();
});
@@ -201,7 +186,7 @@ describe("MatrixClient", function() {
event_format: "client",
});
store.storeFilter(storeFilter);
client.getFilter(userId, filterId, false).done(function(gotFilter) {
client.getFilter(userId, filterId, false).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
done();
});
@@ -219,7 +204,7 @@ describe("MatrixClient", function() {
httpBackend.when(
"GET", "/user/" + encodeURIComponent(userId) + "/filter/" + filterId,
).respond(200, httpFilterDefinition);
client.getFilter(userId, filterId, true).done(function(gotFilter) {
client.getFilter(userId, filterId, true).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
expect(store.getFilter(userId, filterId)).toBeTruthy();
done();
@@ -247,7 +232,7 @@ describe("MatrixClient", function() {
filter_id: filterId,
});
client.createFilter(filterDefinition).done(function(gotFilter) {
client.createFilter(filterDefinition).then(function(gotFilter) {
expect(gotFilter.getDefinition()).toEqual(filterDefinition);
expect(store.getFilter(userId, filterId)).toEqual(gotFilter);
done();
@@ -294,7 +279,7 @@ describe("MatrixClient", function() {
});
}).respond(200, response);
httpBackend.flush().done(function() {
httpBackend.flush().then(function() {
done();
});
});
@@ -302,7 +287,7 @@ describe("MatrixClient", function() {
describe("downloadKeys", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
@@ -310,7 +295,7 @@ describe("MatrixClient", function() {
return client.initCrypto();
});
it("should do an HTTP request and then store the keys", function(done) {
it("should do an HTTP request and then store the keys", function() {
const ed25519key = "7wG2lzAqbjcyEkOP7O4gU7ItYcn+chKzh5sT/5r2l78";
// ed25519key = client.getDeviceEd25519Key();
const borisKeys = {
@@ -362,8 +347,8 @@ describe("MatrixClient", function() {
httpBackend.when("POST", "/keys/query").check(function(req) {
expect(req.data).toEqual({device_keys: {
'boris': {},
'chaz': {},
'boris': [],
'chaz': [],
}});
}).respond(200, {
device_keys: {
@@ -372,7 +357,7 @@ describe("MatrixClient", function() {
},
});
client.downloadKeys(["boris", "chaz"]).then(function(res) {
const prom = client.downloadKeys(["boris", "chaz"]).then(function(res) {
assertObjectContains(res.boris.dev1, {
verified: 0, // DeviceVerification.UNVERIFIED
keys: { "ed25519:dev1": ed25519key },
@@ -386,26 +371,26 @@ describe("MatrixClient", function() {
algorithms: ["2"],
unsigned: { "ghi": "def" },
});
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
});
describe("deleteDevice", function() {
const auth = {a: 1};
it("should pass through an auth dict", function(done) {
it("should pass through an auth dict", function() {
httpBackend.when(
"DELETE", "/_matrix/client/r0/devices/my_device",
).check(function(req) {
expect(req.data).toEqual({auth: auth});
}).respond(200);
client.deleteDevice(
"my_device", auth,
).nodeify(done);
const prom = client.deleteDevice("my_device", auth);
httpBackend.flush();
return prom;
});
});
});
+18 -22
View File
@@ -1,12 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const MatrixClient = sdk.MatrixClient;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
import * as utils from "../test-utils";
import HttpBackend from "matrix-mock-request";
import {MatrixClient} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {MemoryStore} from "../../src/store/memory";
import {MatrixError} from "../../src/http-api";
describe("MatrixClient opts", function() {
const baseUrl = "http://localhost.or.something";
@@ -58,7 +55,6 @@ describe("MatrixClient opts", function() {
};
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
});
@@ -75,7 +71,7 @@ describe("MatrixClient opts", function() {
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
scheduler: new sdk.MatrixScheduler(),
scheduler: new MatrixScheduler(),
});
});
@@ -88,7 +84,7 @@ describe("MatrixClient opts", function() {
httpBackend.when("PUT", "/txn1").respond(200, {
event_id: eventId,
});
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
expect(res.event_id).toEqual(eventId);
done();
});
@@ -101,7 +97,7 @@ describe("MatrixClient opts", function() {
"m.room.create",
];
client.on("event", function(event) {
expect(expectedEventTypes.indexOf(event.getType())).toNotEqual(
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
-1, "Recv unexpected event type: " + event.getType(),
);
expectedEventTypes.splice(
@@ -128,7 +124,7 @@ describe("MatrixClient opts", function() {
beforeEach(function() {
client = new MatrixClient({
request: httpBackend.requestFn,
store: new sdk.MemoryStore(),
store: new MemoryStore(),
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
@@ -137,11 +133,11 @@ describe("MatrixClient opts", function() {
});
it("shouldn't retry sending events", function(done) {
httpBackend.when("PUT", "/txn1").fail(500, {
httpBackend.when("PUT", "/txn1").fail(500, new MatrixError({
errcode: "M_SOMETHING",
error: "Ruh roh",
});
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
}));
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
expect(false).toBe(true, "sendTextMessage resolved but shouldn't");
}, function(err) {
expect(err.errcode).toEqual("M_SOMETHING");
@@ -159,16 +155,16 @@ describe("MatrixClient opts", function() {
});
let sentA = false;
let sentB = false;
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
sentA = true;
expect(sentB).toBe(true);
});
client.sendTextMessage("!foo:bar", "b body", "txn2").done(function(res) {
client.sendTextMessage("!foo:bar", "b body", "txn2").then(function(res) {
sentB = true;
expect(sentA).toBe(false);
});
httpBackend.flush("/txn2", 1).done(function() {
httpBackend.flush("/txn1", 1).done(function() {
httpBackend.flush("/txn2", 1).then(function() {
httpBackend.flush("/txn1", 1).then(function() {
done();
});
});
@@ -178,7 +174,7 @@ describe("MatrixClient opts", function() {
httpBackend.when("PUT", "/txn1").respond(200, {
event_id: "foo",
});
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
expect(res.event_id).toEqual("foo");
done();
});
+16 -23
View File
@@ -1,16 +1,9 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const EventStatus = sdk.EventStatus;
import expect from 'expect';
import {EventStatus} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {Room} from "../../src/models/room";
import {TestClient} from "../TestClient";
describe("MatrixClient retrying", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
let scheduler;
@@ -20,17 +13,17 @@ describe("MatrixClient retrying", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
scheduler = new sdk.MatrixScheduler();
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
scheduler: scheduler,
});
room = new sdk.Room(roomId);
scheduler = new MatrixScheduler();
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{scheduler},
);
httpBackend = testClient.httpBackend;
client = testClient.client;
room = new Room(roomId);
client.store.storeRoom(room);
});
@@ -97,7 +90,7 @@ describe("MatrixClient retrying", function() {
// wait for the localecho of ev1 to be updated
const p3 = new Promise((resolve, reject) => {
room.on("Room.localEchoUpdated", (ev0) => {
if(ev0 === ev1) {
if (ev0 === ev1) {
resolve();
}
});
+27 -32
View File
@@ -1,15 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const EventStatus = sdk.EventStatus;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import * as utils from "../test-utils";
import {EventStatus} from "../../src/models/event";
import {TestClient} from "../TestClient";
import Promise from 'bluebird';
import expect from 'expect';
describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
const userId = "@alice:localhost";
@@ -103,17 +97,18 @@ describe("MatrixClient room timelines", function() {
});
}
beforeEach(function(done) {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
// these tests should work with or without timelineSupport
timelineSupport: true,
});
beforeEach(function() {
// these tests should work with or without timelineSupport
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
);
httpBackend = testClient.httpBackend;
client = testClient.client;
setNextSyncData();
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
@@ -122,9 +117,9 @@ describe("MatrixClient room timelines", function() {
return NEXT_SYNC_DATA;
});
client.startClient();
httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/filter");
}).nodeify(done);
});
});
afterEach(function() {
@@ -153,7 +148,7 @@ describe("MatrixClient room timelines", function() {
expect(member.userId).toEqual(userId);
expect(member.name).toEqual(userName);
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
done();
});
});
@@ -179,10 +174,10 @@ describe("MatrixClient room timelines", function() {
return;
}
const room = client.getRoom(roomId);
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
client.sendTextMessage(roomId, "I am a fish", "txn1").then(
function() {
expect(room.timeline[1].getId()).toEqual(eventId);
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
expect(room.timeline[1].getId()).toEqual(eventId);
done();
});
@@ -212,10 +207,10 @@ describe("MatrixClient room timelines", function() {
}
const room = client.getRoom(roomId);
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
httpBackend.flush("/sync", 1).done(function() {
httpBackend.flush("/sync", 1).then(function() {
expect(room.timeline.length).toEqual(2);
httpBackend.flush("/txn1", 1);
promise.done(function() {
promise.then(function() {
expect(room.timeline.length).toEqual(2);
expect(room.timeline[1].getId()).toEqual(eventId);
done();
@@ -250,7 +245,7 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(1);
expect(room.oldState.paginationToken).toBe(null);
@@ -314,7 +309,7 @@ describe("MatrixClient room timelines", function() {
// sync response
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(5);
const joinMsg = room.timeline[0];
expect(joinMsg.sender.name).toEqual("Old Alice");
@@ -352,7 +347,7 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
client.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(3);
expect(room.timeline[0].event).toEqual(sbEvents[1]);
expect(room.timeline[1].event).toEqual(sbEvents[0]);
@@ -383,11 +378,11 @@ describe("MatrixClient room timelines", function() {
const room = client.getRoom(roomId);
expect(room.oldState.paginationToken).toBeTruthy();
client.scrollback(room, 1).done(function() {
client.scrollback(room, 1).then(function() {
expect(room.oldState.paginationToken).toEqual(sbEndTok);
});
httpBackend.flush("/messages", 1).done(function() {
httpBackend.flush("/messages", 1).then(function() {
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
+17 -29
View File
@@ -1,16 +1,9 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const MatrixEvent = sdk.MatrixEvent;
const EventTimeline = sdk.EventTimeline;
import expect from 'expect';
import Promise from 'bluebird';
import {MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
describe("MatrixClient syncing", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
const selfUserId = "@alice:localhost";
@@ -23,14 +16,9 @@ describe("MatrixClient syncing", function() {
const roomTwo = "!bar:localhost";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
httpBackend = testClient.httpBackend;
client = testClient.client;
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
@@ -53,7 +41,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
@@ -67,7 +55,7 @@ describe("MatrixClient syncing", function() {
client.startClient();
httpBackend.flushAllExpected().done(function() {
httpBackend.flushAllExpected().then(function() {
done();
});
});
@@ -528,7 +516,7 @@ describe("MatrixClient syncing", function() {
awaitSyncEvent(),
]).then(function() {
const room = client.getRoom(roomTwo);
expect(room).toExist();
expect(room).toBeDefined();
const tok = room.getLiveTimeline()
.getPaginationToken(EventTimeline.BACKWARDS);
expect(tok).toEqual("roomtwotok");
@@ -693,12 +681,12 @@ describe("MatrixClient syncing", function() {
include_leave: true }});
}).respond(200, { filter_id: "another_id" });
const defer = Promise.defer();
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
defer.resolve();
}).respond(200, {});
const prom = new Promise((resolve) => {
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
resolve();
}).respond(200, {});
});
client.syncLeftRooms();
@@ -709,7 +697,7 @@ describe("MatrixClient syncing", function() {
// flush the syncs
return httpBackend.flushAllExpected();
}),
defer.promise,
prom,
]);
});
+15 -15
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,16 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
const anotherjson = require('another-json');
import Promise from 'bluebird';
import expect from 'expect';
const utils = require('../../lib/utils');
const testUtils = require('../test-utils');
const TestClient = require('../TestClient').default;
import logger from '../../src/logger';
import anotherjson from "another-json";
import * as utils from "../../src/utils";
import * as testUtils from "../test-utils";
import {TestClient} from "../TestClient";
import {logger} from "../../src/logger";
const ROOM_ID = "!room:id";
@@ -283,8 +279,6 @@ describe("megolm", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs",
);
@@ -352,7 +346,7 @@ describe("megolm", function() {
});
it("Alice receives a megolm message before the session keys", function() {
// https://github.com/vector-im/riot-web/issues/2273
// https://github.com/vector-im/element-web/issues/2273
let roomKeyEncrypted;
return aliceTestClient.start().then(() => {
@@ -621,6 +615,9 @@ describe("megolm", function() {
).respond(200, {
event_id: '$event_id',
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
@@ -713,11 +710,14 @@ describe("megolm", function() {
'PUT', '/send/',
).respond(200, function(path, content) {
logger.log('/send:', content);
expect(content.session_id).toNotEqual(megolmSessionId);
expect(content.session_id).not.toEqual(megolmSessionId);
return {
event_id: '$event_id',
};
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
@@ -726,7 +726,7 @@ describe("megolm", function() {
});
});
// https://github.com/vector-im/riot-web/issues/2676
// https://github.com/vector-im/element-web/issues/2676
it("Alice should send to her other devices", function() {
// for this test, we make the testOlmAccount be another of Alice's devices.
// it ought to get included in messages Alice sends.
+11 -1
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../src/logger';
import {logger} from '../src/logger';
import * as utils from "../src/utils";
// try to load the olm library.
try {
@@ -23,3 +25,11 @@ try {
} catch (e) {
logger.warn("unable to run crypto tests: libolm not available");
}
// also try to set node crypto
try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
logger.log('nodejs was compiled without crypto support: some tests will fail');
}
+164 -40
View File
@@ -1,13 +1,8 @@
"use strict";
import expect from 'expect';
import Promise from 'bluebird';
// load olm before the sdk if possible
import './olm-loader';
import logger from '../src/logger';
import sdk from '..';
const MatrixEvent = sdk.MatrixEvent;
import {logger} from '../src/logger';
import {MatrixEvent} from "../src/models/event";
/**
* Return a promise that is resolved when the client next emits a
@@ -16,7 +11,7 @@ const MatrixEvent = sdk.MatrixEvent;
* @param {Number=} count Number of syncs to wait for (default 1)
* @return {Promise} Resolves once the client has emitted a SYNCING event
*/
module.exports.syncPromise = function(client, count) {
export function syncPromise(client, count) {
if (count === undefined) {
count = 1;
}
@@ -27,7 +22,7 @@ module.exports.syncPromise = function(client, count) {
const p = new Promise((resolve, reject) => {
const cb = (state) => {
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state == 'SYNCING') {
if (state === 'SYNCING') {
resolve();
} else {
client.once('sync', cb);
@@ -37,21 +32,9 @@ module.exports.syncPromise = function(client, count) {
});
return p.then(() => {
return module.exports.syncPromise(client, count-1);
return syncPromise(client, count-1);
});
};
/**
* Perform common actions before each test case, e.g. printing the test case
* name to stdout.
* @param {Mocha.Context} context The test context
*/
module.exports.beforeEach = function(context) {
const desc = context.currentTest.fullTitle();
logger.log(desc);
logger.log(new Array(1 + desc.length).join("="));
};
}
/**
* Create a spy for an object and automatically spy its methods.
@@ -59,7 +42,7 @@ module.exports.beforeEach = function(context) {
* @param {string} name The name of the class
* @return {Object} An instantiated object with spied methods/properties.
*/
module.exports.mock = function(constr, name) {
export function mock(constr, name) {
// Based on
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
const HelperConstr = new Function(); // jshint ignore:line
@@ -71,7 +54,7 @@ module.exports.mock = function(constr, name) {
for (const key in constr.prototype) { // eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = expect.createSpy();
result[key] = jest.fn();
}
} catch (ex) {
// Direct access to some non-function fields of DOM prototypes may
@@ -80,7 +63,7 @@ module.exports.mock = function(constr, name) {
}
}
return result;
};
}
/**
* Create an Event.
@@ -93,7 +76,7 @@ module.exports.mock = function(constr, name) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object} a JSON object representing this event.
*/
module.exports.mkEvent = function(opts) {
export function mkEvent(opts) {
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
@@ -112,14 +95,14 @@ module.exports.mkEvent = function(opts) {
event.state_key = "";
}
return opts.event ? new MatrixEvent(event) : event;
};
}
/**
* Create an m.presence event.
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkPresence = function(opts) {
export function mkPresence(opts) {
if (!opts.user) {
throw new Error("Missing user");
}
@@ -135,7 +118,7 @@ module.exports.mkPresence = function(opts) {
},
};
return opts.event ? new MatrixEvent(event) : event;
};
}
/**
* Create an m.room.member event.
@@ -150,7 +133,7 @@ module.exports.mkPresence = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkMembership = function(opts) {
export function mkMembership(opts) {
opts.type = "m.room.member";
if (!opts.skey) {
opts.skey = opts.sender || opts.user;
@@ -167,8 +150,8 @@ module.exports.mkMembership = function(opts) {
if (opts.url) {
opts.content.avatar_url = opts.url;
}
return module.exports.mkEvent(opts);
};
return mkEvent(opts);
}
/**
* Create an m.room.message event.
@@ -179,7 +162,7 @@ module.exports.mkMembership = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
module.exports.mkMessage = function(opts) {
export function mkMessage(opts) {
opts.type = "m.room.message";
if (!opts.msg) {
opts.msg = "Random->" + Math.random();
@@ -191,8 +174,8 @@ module.exports.mkMessage = function(opts) {
msgtype: "m.text",
body: opts.msg,
};
return module.exports.mkEvent(opts);
};
return mkEvent(opts);
}
/**
@@ -200,10 +183,10 @@ module.exports.mkMessage = function(opts) {
*
* @constructor
*/
module.exports.MockStorageApi = function() {
export function MockStorageApi() {
this.data = {};
};
module.exports.MockStorageApi.prototype = {
}
MockStorageApi.prototype = {
get length() {
return Object.keys(this.data).length;
},
@@ -228,7 +211,7 @@ module.exports.MockStorageApi.prototype = {
* @param {MatrixEvent} event
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/
module.exports.awaitDecryption = function(event) {
export function awaitDecryption(event) {
if (!event.isBeingDecrypted()) {
return Promise.resolve(event);
}
@@ -241,4 +224,145 @@ module.exports.awaitDecryption = function(event) {
resolve(ev);
});
});
}
export function HttpResponse(
httpLookups, acceptKeepalives, ignoreUnhandledSync,
) {
this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.ignoreUnhandledSync = ignoreUnhandledSync;
this.pendingLookup = null;
}
HttpResponse.prototype.request = function(
cb, method, path, qp, data, prefix,
) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
return Promise.resolve();
}
const next = this.httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
logger.log(logLine);
if (!next) { // no more things to return
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
return new Promise(() => {});
}
if (this.pendingLookup) {
if (this.pendingLookup.method === method
&& this.pendingLookup.path === path) {
return this.pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
method + " " + path,
);
}
this.pendingLookup = {
promise: new Promise(() => {}),
method: method,
path: path,
};
return this.pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
if (next.expectBody) {
expect(next.expectBody).toEqual(data);
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
});
}
if (next.thenCall) {
process.nextTick(next.thenCall, 0); // next tick so we return first.
}
if (next.error) {
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
message: "Expected testing error",
data: next.error,
});
}
return Promise.resolve(next.data);
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
this.httpLookups.unshift(next);
return new Promise(() => {});
}
expect(true).toBe(false, "Expected different request. " + logLine);
return new Promise(() => {});
};
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
HttpResponse.PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
HttpResponse.USER_ID = "@alice:bar";
HttpResponse.filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
HttpResponse.SYNC_DATA = {
next_batch: "s_5_3",
presence: { events: [] },
rooms: {},
};
HttpResponse.SYNC_RESPONSE = {
method: "GET",
path: "/sync",
data: HttpResponse.SYNC_DATA,
};
HttpResponse.defaultResponses = function(userId) {
return [
HttpResponse.PUSH_RULES_RESPONSE,
HttpResponse.filterResponse(userId),
HttpResponse.SYNC_RESPONSE,
];
};
export function setHttpResponses(
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
) {
const httpResponseObj = new HttpResponse(
responses, acceptKeepalives, ignoreUnhandledSyncs,
);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.authedRequestWithPrefix.mockImplementation(httpReq);
client._http.requestWithPrefix.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
}
+23 -31
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,24 +14,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const utils = require("../test-utils");
const AutoDiscovery = sdk.AutoDiscovery;
import expect from 'expect';
import MockHttpBackend from "matrix-mock-request";
import * as sdk from "../../src";
import {AutoDiscovery} from "../../src/autodiscovery";
describe("AutoDiscovery", function() {
let httpBackend = null;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new MockHttpBackend();
sdk.request(httpBackend.requestFn);
});
@@ -416,8 +408,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(missing base_url)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (missing base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -438,14 +430,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
@@ -456,8 +448,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(empty base_url)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (empty base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -478,14 +470,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
@@ -496,8 +488,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(validation error: 404)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 404)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -519,14 +511,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: "https://identity.example.org",
},
@@ -537,8 +529,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(validation error: 500)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 500)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -560,14 +552,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: "https://identity.example.org",
},
+8 -44
View File
@@ -1,22 +1,13 @@
"use strict";
import 'source-map-support/register';
const ContentRepo = require("../../lib/content-repo");
const testUtils = require("../test-utils");
import expect from 'expect';
import {getHttpUriForMxc} from "../../src/content-repo";
describe("ContentRepo", function() {
const baseUrl = "https://my.home.server";
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("getHttpUriForMxc", function() {
it("should do nothing to HTTP URLs when allowing direct links", function() {
const httpUrl = "http://example.com/image.jpeg";
expect(
ContentRepo.getHttpUriForMxc(
getHttpUriForMxc(
baseUrl, httpUrl, undefined, undefined, undefined, true,
),
).toEqual(httpUrl);
@@ -24,25 +15,25 @@ describe("ContentRepo", function() {
it("should return the empty string HTTP URLs by default", function() {
const httpUrl = "http://example.com/image.jpeg";
expect(ContentRepo.getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
expect(getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
});
it("should return a download URL if no width/height/resize are specified",
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
);
});
it("should return the empty string for null input", function() {
expect(ContentRepo.getHttpUriForMxc(null)).toEqual("");
expect(getHttpUriForMxc(null)).toEqual("");
});
it("should return a thumbnail URL if a width/height/resize is specified",
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32&height=64&method=crop",
);
@@ -51,7 +42,7 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs after any query parameters",
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
expect(getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32#automade",
);
@@ -60,36 +51,9 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs at the end of the HTTP URI",
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",
);
});
});
describe("getIdenticonUri", function() {
it("should do nothing for null input", function() {
expect(ContentRepo.getIdenticonUri(null)).toEqual(null);
});
it("should set w/h by default to 96", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=96&height=96",
);
});
it("should be able to set custom w/h", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=32&height=64",
);
});
it("should URL encode the identicon string", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" +
"?width=32&height=64",
);
});
});
});
+117 -82
View File
@@ -1,38 +1,92 @@
import 'source-map-support/register';
import '../olm-loader';
import Crypto from '../../lib/crypto';
import expect from 'expect';
import WebStorageSessionStore from '../../lib/store/session/webstorage';
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../MockStorageApi';
import TestClient from '../TestClient';
import {MatrixEvent} from '../../lib/models/event';
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';
import lolex from 'lolex';
const EventEmitter = require("events").EventEmitter;
const sdk = require("../..");
import {Crypto} from "../../src/crypto";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../MockStorageApi";
import {TestClient} from "../TestClient";
import {MatrixEvent} from "../../src/models/event";
import {Room} from "../../src/models/room";
import * as olmlib from "../../src/crypto/olmlib";
import {sleep} from "../../src/utils";
import {EventEmitter} from "events";
import {CRYPTO_ENABLED} from "../../src/client";
import {DeviceInfo} from "../../src/crypto/deviceinfo";
const Olm = global.Olm;
describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}
beforeEach(function(done) {
Olm.init().then(done);
beforeAll(function() {
return Olm.init();
});
it("Crypto exposes the correct olm library version", function() {
expect(Crypto.getOlmVersion()[0]).toEqual(3);
});
describe("encrypted events", function() {
it("provides encryption information", async function() {
const client = (new TestClient(
"@alice:example.com", "deviceid",
)).client;
await client.initCrypto();
// unencrypted event
const event = {
getId: () => "$event_id",
getSenderKey: () => null,
getWireContent: () => {return {};},
};
let encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeFalsy();
// unknown sender (e.g. deleted device), forwarded megolm key (untrusted)
event.getSenderKey = () => 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
event.getWireContent = () => {return {algorithm: olmlib.MEGOLM_ALGORITHM};};
event.getForwardingCurve25519KeyChain = () => ["not empty"];
event.isKeySourceUntrusted = () => false;
event.getClaimedEd25519Key =
() => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
expect(encryptionInfo.authenticated).toBeFalsy();
expect(encryptionInfo.sender).toBeFalsy();
// known sender, megolm key from backup
event.getForwardingCurve25519KeyChain = () => [];
event.isKeySourceUntrusted = () => true;
const device = new DeviceInfo("FLIBBLE");
device.keys["curve25519:FLIBBLE"] =
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
device.keys["ed25519:FLIBBLE"] =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
client._crypto._deviceList.getDeviceByIdentityKey = () => device;
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
expect(encryptionInfo.authenticated).toBeFalsy();
expect(encryptionInfo.sender).toBeTruthy();
expect(encryptionInfo.mismatchedSender).toBeFalsy();
// known sender, trusted megolm key, but bad ed25519key
event.isKeySourceUntrusted = () => false;
device.keys["ed25519:FLIBBLE"] =
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
expect(encryptionInfo.authenticated).toBeTruthy();
expect(encryptionInfo.sender).toBeTruthy();
expect(encryptionInfo.mismatchedSender).toBeTruthy();
client.stopClient();
});
});
describe('Session management', function() {
const otkResponse = {
@@ -76,9 +130,9 @@ describe("Crypto", function() {
});
mockBaseApis = {
sendToDevice: expect.createSpy(),
getKeyBackupVersion: expect.createSpy(),
isGuest: expect.createSpy(),
sendToDevice: jest.fn(),
getKeyBackupVersion: jest.fn(),
isGuest: jest.fn(),
};
mockRoomList = {};
@@ -110,15 +164,16 @@ describe("Crypto", function() {
});
fakeEmitter.emit('toDeviceEvent', {
getType: expect.createSpy().andReturn('m.room.message'),
getContent: expect.createSpy().andReturn({
getId: jest.fn().mockReturnValue("$wedged"),
getType: jest.fn().mockReturnValue('m.room.message'),
getContent: jest.fn().mockReturnValue({
msgtype: 'm.bad.encrypted',
}),
getWireContent: expect.createSpy().andReturn({
getWireContent: jest.fn().mockReturnValue({
algorithm: 'm.olm.v1.curve25519-aes-sha2',
sender_key: 'this is a key',
}),
getSender: expect.createSpy().andReturn('@bob:home.server'),
getSender: jest.fn().mockReturnValue('@bob:home.server'),
});
await prom;
@@ -245,7 +300,7 @@ describe("Crypto", function() {
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).toNotBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
const cryptoStore = bobClient._cryptoStore;
const eventContent = events[0].getWireContent();
@@ -260,7 +315,7 @@ describe("Crypto", function() {
// the room key request should still be there, since we haven't
// decrypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toExist();
.toBeDefined();
// keyshare the session key starting at the first message, so
// that it can now be decrypted
@@ -268,10 +323,11 @@ describe("Crypto", function() {
ksEvent = await keyshareEventForEvent(events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).toNotBe("m.bad.encrypted");
// the room key request should be gone since we've now decypted everything
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
await sleep(1);
// the room key request should be gone since we've now decrypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toNotExist();
.toBeFalsy();
},
);
@@ -296,10 +352,12 @@ describe("Crypto", function() {
sender_key: "senderkey",
};
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toExist();
.toBeDefined();
});
it("uses a new txnid for re-requesting keys", async function() {
jest.useFakeTimers();
const event = new MatrixEvent({
sender: "@bob:example.com",
room_id: "!someroom",
@@ -309,58 +367,35 @@ describe("Crypto", function() {
sender_key: "senderkey",
},
});
/* return a promise and a function. When the function is called,
* the promise will be resolved.
*/
function awaitFunctionCall() {
let func;
const promise = new Promise((resolve, reject) => {
func = function(...args) {
resolve(args);
return new Promise((resolve, reject) => {
// give us some time to process the result before
// continuing
global.setTimeout(resolve, 1);
});
};
});
return {func, promise};
}
// replace Alice's sendToDevice function with a mock
aliceClient.sendToDevice = jest.fn().mockResolvedValue(undefined);
aliceClient.startClient();
const clock = lolex.install();
// make a room key request, and record the transaction ID for the
// sendToDevice call
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
// key requests get queued until the sync has finished, but we don't
// let the client set up enough for that to happen, so gut-wrench a bit
// to force it to send now.
aliceClient._crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
jest.runAllTimers();
await Promise.resolve();
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
const txnId = aliceClient.sendToDevice.mock.calls[0][2];
try {
let promise;
// make a room key request, and record the transaction ID for the
// sendToDevice call
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
clock.runToLast();
let args = await promise;
const txnId = args[2];
clock.runToLast();
// give the room key request manager time to update the state
// of the request
await Promise.resolve();
// give the room key request manager time to update the state
// of the request
await Promise.resolve();
// cancel and resend the room key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
clock.runToLast();
// the first call to sendToDevice will be the cancellation
args = await promise;
// the second call to sendToDevice will be the key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
clock.runToLast();
args = await promise;
clock.runToLast();
expect(args[2]).toNotBe(txnId);
} finally {
clock.uninstall();
}
// cancel and resend the room key request
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
jest.runAllTimers();
await Promise.resolve();
// cancelAndResend will call sendToDevice twice:
// the first call to sendToDevice will be the cancellation
// the second call to sendToDevice will be the key request
expect(aliceClient.sendToDevice).toBeCalledTimes(3);
expect(aliceClient.sendToDevice.mock.calls[2][2]).not.toBe(txnId);
});
});
});
+258
View File
@@ -0,0 +1,258 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import {
CrossSigningInfo,
createCryptoStoreCacheCallbacks,
} from '../../../src/crypto/CrossSigning';
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {logger} from '../../../src/logger';
const userId = "@alice:example.com";
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const types = [
{ type: "master", shouldCache: true },
{ type: "self_signing", shouldCache: true },
{ type: "user_signing", shouldCache: true },
{ type: "invalid", shouldCache: false },
];
const badKey = Uint8Array.from(testKey);
badKey[0] ^= 1;
const masterKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("CrossSigningInfo.getCrossSigningKey", function() {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should throw if no callback is provided", async () => {
const info = new CrossSigningInfo(userId);
await expect(info.getCrossSigningKey("master")).rejects.toThrow();
});
it.each(types)("should throw if the callback returns falsey",
async ({type, shouldCache}) => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => false,
});
await expect(info.getCrossSigningKey(type)).rejects.toThrow("falsey");
});
it("should throw if the expected key doesn't come back", async () => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => masterKeyPub,
});
await expect(info.getCrossSigningKey("master", "")).rejects.toThrow();
});
it("should return a key from its callback", async () => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => testKey,
});
const [pubKey, pkSigning] = await info.getCrossSigningKey("master", masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
// check that the pkSigning object corresponds to the pubKey
const signature = pkSigning.sign("message");
const util = new global.Olm.Utility();
try {
util.ed25519_verify(pubKey, "message", signature);
} finally {
util.free();
}
});
it.each(types)("should request a key from the cache callback (if set)" +
" and does not call app if one is found" +
" %o",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
return Promise.reject(new Error("Regular callback called"));
} else {
return Promise.resolve(testKey);
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
if (shouldCache) {
expect(getCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
}
});
it.each(types)("should store a key with the cache callback (if set)",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(shouldCache ? 1 : 0);
if (shouldCache) {
expect(storeCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
expect(storeCrossSigningKeyCache.mock.calls[0][1]).toBe(testKey);
}
});
it.each(types)("does not store a bad key to the cache",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
await expect(info.getCrossSigningKey(type, masterKeyPub)).rejects.toThrow();
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(0);
});
it.each(types)("does not store a value to the cache if it came from the cache",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
return Promise.reject(new Error("Regular callback called"));
} else {
return Promise.resolve(testKey);
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockRejectedValue(
new Error("Tried to store a value from cache"),
);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(0);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
});
it.each(types)("requests a key from the cache callback (if set) and then calls app" +
" if one is not found", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const storeCrossSigningKeyCache = jest.fn();
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKey.mock.calls.length).toBe(1);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
it.each(types)("requests a key from the cache callback (if set) and then" +
" calls app if that key doesn't match", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn();
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
);
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKey.mock.calls.length).toBe(1);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
});
/*
* Note that MemoryStore is weird. It's only used for testing - as far as I can tell,
* it's not possible to get one in normal execution unless you hack as we do here.
*/
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
store._backend = new MemoryCryptoStore();
store._backendPromise = Promise.resolve(store._backend);
return store;
}],
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
let store;
beforeAll(() => {
store = dbFactory();
});
beforeEach(async () => {
await store.deleteAllData();
});
it("should cache data to the store and retrieve it", async () => {
await store.startup();
const olmDevice = new OlmDevice(store);
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
createCryptoStoreCacheCallbacks(store, olmDevice);
await storeCrossSigningKeyCache("self_signing", testKey);
// If we've not saved anything, don't expect anything
// Definitely don't accidentally return the wrong key for the type
const nokey = await getCrossSigningKeyCache("self", "");
expect(nokey).toBeNull();
const key = await getCrossSigningKeyCache("self_signing", "");
expect(new Uint8Array(key)).toEqual(testKey);
});
});
+18 -21
View File
@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,14 +16,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import DeviceList from '../../../lib/crypto/DeviceList';
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
import testUtils from '../../test-utils';
import utils from '../../../lib/utils';
import logger from '../../../src/logger';
import expect from 'expect';
import Promise from 'bluebird';
import {logger} from "../../../src/logger";
import * as utils from "../../../src/utils";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {DeviceList} from "../../../src/crypto/DeviceList";
const signedDeviceList = {
"failures": {},
@@ -60,11 +57,9 @@ describe('DeviceList', function() {
let deviceLists = [];
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
deviceLists = [];
downloadSpy = expect.createSpy();
downloadSpy = jest.fn();
cryptoStore = new MemoryCryptoStore();
});
@@ -77,6 +72,8 @@ describe('DeviceList', function() {
function createTestDeviceList() {
const baseApis = {
downloadKeysForUsers: downloadSpy,
getUserId: () => '@test1:sw1v.org',
deviceId: 'HGKAWHRVJQ',
};
const mockOlm = {
verifySignature: function(key, message, signature) {},
@@ -91,8 +88,8 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
@@ -110,16 +107,16 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
downloadSpy.reset();
downloadSpy.mockReset();
// outdated notif arrives while the request is in flight.
const queryDefer2 = Promise.defer();
downloadSpy.andReturn(queryDefer2.promise);
const queryDefer2 = utils.defer();
downloadSpy.mockReturnValue(queryDefer2.promise);
dl.invalidateUserDeviceList('@test1:sw1v.org');
dl.refreshOutdatedDeviceLists();
@@ -136,10 +133,10 @@ describe('DeviceList', function() {
// uh-oh; user restarts before second request completes. The new instance
// should know we never got a complete device list.
logger.log("Creating new devicelist to simulate app reload");
downloadSpy.reset();
downloadSpy.mockReset();
const dl2 = createTestDeviceList();
const queryDefer3 = Promise.defer();
downloadSpy.andReturn(queryDefer3.promise);
const queryDefer3 = utils.defer();
downloadSpy.mockReturnValue(queryDefer3.promise);
const prom3 = dl2.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
+400 -56
View File
@@ -1,18 +1,16 @@
import '../../../olm-loader';
import * as algorithms from "../../../../src/crypto/algorithms";
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import * as testUtils from "../../../test-utils";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import {Crypto} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {MatrixEvent} from "../../../../src/models/event";
import {TestClient} from "../../../TestClient";
import {Room} from "../../../../src/models/room";
import * as olmlib from "../../../../src/crypto/olmlib";
import expect from 'expect';
import Promise from 'bluebird';
import sdk from '../../../..';
import algorithms from '../../../../lib/crypto/algorithms';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import Crypto from '../../../../lib/crypto';
import logger from '../../../../src/logger';
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -26,16 +24,16 @@ describe("MegolmDecryption", function() {
return;
}
beforeAll(function() {
return Olm.init();
});
let megolmDecryption;
let mockOlmLib;
let mockCrypto;
let mockBaseApis;
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await Olm.init();
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockBaseApis = {};
@@ -55,9 +53,9 @@ describe("MegolmDecryption", function() {
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
expect.createSpy().andReturn(Promise.resolve());
jest.fn().mockResolvedValue(undefined);
megolmDecryption.olmlib = mockOlmLib;
});
@@ -135,22 +133,22 @@ describe("MegolmDecryption", function() {
// set up some pre-conditions for the share call
const deviceInfo = {};
mockCrypto.getStoredDevice.andReturn(deviceInfo);
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
mockOlmLib.ensureOlmSessionsForDevices.andReturn(
Promise.resolve({'@alice:foo': {'alidevice': {
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
'@alice:foo': {'alidevice': {
sessionId: 'alisession',
}}}),
);
}},
});
const awaitEncryptForDevice = new Promise((res, rej) => {
mockOlmLib.encryptMessageForDevice.andCall(() => {
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
res();
return Promise.resolve();
});
});
mockBaseApis.sendToDevice = expect.createSpy();
mockBaseApis.sendToDevice = jest.fn();
// do the share
megolmDecryption.shareKeysWithDevice(keyRequest);
@@ -160,21 +158,20 @@ describe("MegolmDecryption", function() {
}).then(() => {
// check that it called encryptMessageForDevice with
// appropriate args.
expect(mockOlmLib.encryptMessageForDevice.calls.length)
.toEqual(1);
expect(mockOlmLib.encryptMessageForDevice).toBeCalledTimes(1);
const call = mockOlmLib.encryptMessageForDevice.calls[0];
const payload = call.arguments[6];
const call = mockOlmLib.encryptMessageForDevice.mock.calls[0];
const payload = call[6];
expect(payload.type).toEqual("m.forwarded_room_key");
expect(payload.content).toInclude({
expect(payload.content).toMatchObject({
sender_key: "SENDER_CURVE25519",
sender_claimed_ed25519_key: "SENDER_ED25519",
session_id: groupSession.session_id(),
chain_index: 0,
forwarding_curve25519_key_chain: [],
});
expect(payload.content.session_key).toExist();
expect(payload.content.session_key).toBeDefined();
});
});
@@ -201,13 +198,12 @@ describe("MegolmDecryption", function() {
origin_server_ts: 1507753886000,
});
const successHandler = expect.createSpy();
const failureHandler = expect.createSpy()
.andCall((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
const successHandler = jest.fn();
const failureHandler = jest.fn((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
return megolmDecryption.decryptEvent(event1).then((res) => {
const event2 = new MatrixEvent({
@@ -228,7 +224,7 @@ describe("MegolmDecryption", function() {
successHandler,
failureHandler,
).then(() => {
expect(successHandler).toNotHaveBeenCalled();
expect(successHandler).not.toHaveBeenCalled();
expect(failureHandler).toHaveBeenCalled();
});
});
@@ -266,10 +262,10 @@ describe("MegolmDecryption", function() {
const cryptoStore = new MemoryCryptoStore(mockStorage);
const olmDevice = new OlmDevice(cryptoStore);
olmDevice.verifySignature = expect.createSpy();
olmDevice.verifySignature = jest.fn();
await olmDevice.init();
mockBaseApis.claimOneTimeKeys = expect.createSpy().andReturn(Promise.resolve({
mockBaseApis.claimOneTimeKeys = jest.fn().mockReturnValue(Promise.resolve({
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
@@ -285,22 +281,26 @@ describe("MegolmDecryption", function() {
},
},
}));
mockBaseApis.sendToDevice = expect.createSpy().andReturn(Promise.resolve());
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
mockCrypto.downloadKeys.andReturn(Promise.resolve({
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
aliceDevice: {
deviceId: 'aliceDevice',
isBlocked: expect.createSpy().andReturn(false),
isUnverified: expect.createSpy().andReturn(false),
getIdentityKey: expect.createSpy().andReturn(
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: expect.createSpy().andReturn(''),
getFingerprint: jest.fn().mockReturnValue(''),
},
},
}));
mockCrypto.checkDeviceTrust.mockReturnValue({
isVerified: () => false,
});
const megolmEncryption = new MegolmEncryption({
userId: '@user:id',
crypto: mockCrypto,
@@ -312,10 +312,10 @@ describe("MegolmDecryption", function() {
},
});
const mockRoom = {
getEncryptionTargetMembers: expect.createSpy().andReturn(
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{userId: "@alice:home.server"}],
),
getBlacklistUnverifiedDevices: expect.createSpy().andReturn(false),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
@@ -323,28 +323,372 @@ describe("MegolmDecryption", function() {
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
mockBaseApis.claimOneTimeKeys.reset();
mockBaseApis.claimOneTimeKeys.mockReset();
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some more text",
});
// this should *not* have claimed a key as it should be using the same session
expect(mockBaseApis.claimOneTimeKeys).toNotHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).not.toHaveBeenCalled();
// likewise they should show the same session ID
expect(ct2.session_id).toEqual(ct1.session_id);
});
});
it("notifies devices that have been blocked", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient1 = (new TestClient(
"@bob:example.com", "bobdevice1",
)).client;
const bobClient2 = (new TestClient(
"@bob:example.com", "bobdevice2",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient1.initCrypto(),
bobClient2.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice1 = bobClient1._crypto._olmDevice;
const bobDevice2 = bobClient2._crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
room.getEncryptionTargetMembers = async function() {
return [{userId: "@bob:example.com"}];
};
room.setBlacklistUnverifiedDevices(true);
aliceClient.store.storeRoom(room);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
const BOB_DEVICES = {
bobdevice1: {
user_id: "@bob:example.com",
device_id: "bobdevice1",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
},
verified: 0,
},
bobdevice2: {
user_id: "@bob:example.com",
device_id: "bobdevice2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
},
verified: -1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
let run = false;
aliceClient.sendToDevice = async (msgtype, contentMap) => {
run = true;
expect(msgtype).toBe("org.matrix.room_key.withheld");
delete contentMap["@bob:example.com"].bobdevice1.session_id;
delete contentMap["@bob:example.com"].bobdevice2.session_id;
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice1: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.unverified',
reason:
'The sender has disabled encrypting to unverified devices.',
sender_key: aliceDevice.deviceCurve25519Key,
},
bobdevice2: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.blacklisted',
reason: 'The sender has blocked you.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
};
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {
msgtype: "m.text",
body: "secret",
},
});
await aliceClient._crypto.encryptEvent(event, room);
expect(run).toBe(true);
aliceClient.stopClient();
bobClient1.stopClient();
bobClient2.stopClient();
});
it("notifies devices when unable to create olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice = bobClient._crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
await bobClient.setRoomEncryption(roomId, encryptionCfg);
aliceRoom.getEncryptionTargetMembers = async () => {
return [
{
userId: "@alice:example.com",
membership: "join",
},
{
userId: "@bob:example.com",
membership: "join",
},
];
};
const BOB_DEVICES = {
bobdevice: {
user_id: "@bob:example.com",
device_id: "bobdevice",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
},
known: true,
verified: 1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
aliceClient.claimOneTimeKeys = async () => {
// Bob has no one-time keys
return {
one_time_keys: {},
};
};
const sendPromise = new Promise((resolve, reject) => {
aliceClient.sendToDevice = async (msgtype, contentMap) => {
expect(msgtype).toBe("org.matrix.room_key.withheld");
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice: {
algorithm: "m.megolm.v1.aes-sha2",
code: 'm.no_olm',
reason: 'Unable to establish a secure channel.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
resolve();
};
});
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {},
});
await aliceClient._crypto.encryptEvent(event, aliceRoom);
await sendPromise;
});
it("throws an error describing why it doesn't have a key", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
}))).rejects.toThrow("The sender has blocked you.");
});
it("throws an error describing the lack of an olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
aliceClient._crypto.downloadKeys = async () => {};
const bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
const now = Date.now();
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
});
it("throws an error to indicate a wedged olm session", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
aliceClient._crypto.downloadKeys = async () => {};
const roomId = "!someroom";
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
msgtype: "m.bad.encrypted",
algorithm: "m.megolm.v1.aes-sha2",
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
});
});
+66 -15
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018,2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,16 +16,12 @@ limitations under the License.
*/
import '../../../olm-loader';
import expect from 'expect';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
import logger from '../../../../src/logger';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import olmlib from '../../../../lib/crypto/olmlib';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import {logger} from "../../../../src/logger";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
function makeOlmDevice() {
const mockStorage = new MockStorageApi();
@@ -44,20 +41,20 @@ async function setupSession(initiator, opponent) {
return sid;
}
describe("OlmDecryption", function() {
describe("OlmDevice", function() {
if (!global.Olm) {
logger.warn('Not running megolm unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
let aliceOlmDevice;
let bobOlmDevice;
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await global.Olm.init();
aliceOlmDevice = makeOlmDevice();
bobOlmDevice = makeOlmDevice();
await aliceOlmDevice.init();
@@ -84,6 +81,60 @@ describe("OlmDecryption", function() {
);
});
it('exports picked account and olm sessions', async function() {
const sessionId = await setupSession(aliceOlmDevice, bobOlmDevice);
const exported = await bobOlmDevice.export();
// At this moment only Alice (the “initiator” in setupSession) has a session
expect(exported.sessions).toEqual([]);
const MESSAGE = (
"The olm or proteus is an aquatic salamander"
+ " in the family Proteidae"
);
const ciphertext = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
sessionId,
MESSAGE,
);
const bobRecreatedOlmDevice = makeOlmDevice();
bobRecreatedOlmDevice.init({ fromExportedDevice: exported });
const decrypted = await bobRecreatedOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key,
ciphertext.type,
ciphertext.body,
);
expect(decrypted.payload).toEqual(MESSAGE);
const exportedAgain = await bobRecreatedOlmDevice.export();
// this time we expect Bob to have a session to export
expect(exportedAgain.sessions).toHaveLength(1);
const MESSAGE_2 = (
"In contrast to most amphibians,"
+ " the olm is entirely aquatic"
);
const ciphertext2 = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
sessionId,
MESSAGE_2,
);
const bobRecreatedAgainOlmDevice = makeOlmDevice();
bobRecreatedAgainOlmDevice.init({ fromExportedDevice: exportedAgain });
// Note: "decrypted_2" does not have the same structure as "decrypted"
const decrypted2 = await bobRecreatedAgainOlmDevice.decryptMessage(
aliceOlmDevice.deviceCurve25519Key,
decrypted.session_id,
ciphertext2.type,
ciphertext2.body,
);
expect(decrypted2).toEqual(MESSAGE_2);
});
it("creates only one session at a time", async function() {
// if we call ensureOlmSessionsForDevices multiple times, it should
// only try to create one session at a time, even if the server is
+139 -38
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,25 +16,21 @@ limitations under the License.
*/
import '../../olm-loader';
import expect from 'expect';
import Promise from 'bluebird';
import sdk from '../../..';
import algorithms from '../../../lib/crypto/algorithms';
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../MockStorageApi';
import testUtils from '../../test-utils';
import OlmDevice from '../../../lib/crypto/OlmDevice';
import Crypto from '../../../lib/crypto';
import logger from '../../../src/logger';
import {logger} from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
import {MatrixClient} from "../../../src/client";
import {MatrixEvent} from "../../../src/models/event";
import * as algorithms from "../../../src/crypto/algorithms";
import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../MockStorageApi";
import * as testUtils from "../../test-utils";
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {Crypto} from "../../../src/crypto";
import {resetCrossSigningKeys} from "./crypto-utils";
const Olm = global.Olm;
const MatrixClient = sdk.MatrixClient;
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const ROOM_ID = '!ROOM:ID';
@@ -83,20 +80,30 @@ const BACKUP_INFO = {
},
};
const keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
function makeTestClient(sessionStore, cryptoStore) {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
return new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
@@ -108,6 +115,7 @@ function makeTestClient(sessionStore, cryptoStore) {
deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore,
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
});
}
@@ -117,6 +125,10 @@ describe("MegolmBackup", function() {
return;
}
beforeAll(function() {
return Olm.init();
});
let olmDevice;
let mockOlmLib;
let mockCrypto;
@@ -125,9 +137,6 @@ describe("MegolmBackup", function() {
let cryptoStore;
let megolmDecryption;
beforeEach(async function() {
await Olm.init();
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
@@ -143,9 +152,9 @@ describe("MegolmBackup", function() {
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
expect.createSpy().andReturn(Promise.resolve());
jest.fn().mockResolvedValue(undefined);
});
describe("backup", function() {
@@ -206,7 +215,7 @@ describe("MegolmBackup", function() {
};
mockCrypto.cancelRoomKeyRequest = function() {};
mockCrypto.backupGroupSession = expect.createSpy();
mockCrypto.backupGroupSession = jest.fn();
return event.attemptDecryption(mockCrypto).then(() => {
return megolmDecryption.onRoomKeyEvent(event);
@@ -267,7 +276,7 @@ describe("MegolmBackup", function() {
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqualTo(1);
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
@@ -276,8 +285,8 @@ describe("MegolmBackup", function() {
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toExist();
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
@@ -296,6 +305,71 @@ describe("MegolmBackup", function() {
});
});
it('signs backups with the cross-signing master key', async function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
let privateKeys;
client.uploadDeviceSigningKeys = async function(e) {return;};
client.uploadKeySignatures = async function(e) {return;};
client.on("crossSigning.saveCrossSigningKeys", function(e) {
privateKeys = e;
});
client.on("crossSigning.getKey", function(e) {
e.done(privateKeys[e.type]);
});
await resetCrossSigningKeys(client);
let numCalls = 0;
await new Promise((resolve, reject) => {
client._http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("POST");
expect(path).toBe("/room_keys/version");
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
data.auth_data, client.getCrossSigningId(), "@alice:bar",
);
} catch (e) {
reject(e);
return Promise.resolve({});
}
resolve();
return Promise.resolve({});
};
client.createKeyBackupVersion({
algorithm: "m.megolm_backup.v1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
});
expect(numCalls).toBe(1);
});
it('retries when a backup fails', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
@@ -305,16 +379,16 @@ describe("MegolmBackup", function() {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
const client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
@@ -372,7 +446,7 @@ describe("MegolmBackup", function() {
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqualTo(2);
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls >= 3) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
@@ -381,8 +455,8 @@ describe("MegolmBackup", function() {
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toExist();
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
if (numCalls > 1) {
@@ -444,6 +518,7 @@ describe("MegolmBackup", function() {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
expect(res.untrusted).toBeTruthy(); // keys from backup are untrusted
});
});
@@ -468,5 +543,31 @@ describe("MegolmBackup", function() {
expect(res.clearEvent.content).toEqual('testytest');
});
});
it('has working cache functions', async function() {
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client._crypto.storeSessionBackupPrivateKey(key);
const result = await client._crypto.getSessionBackupPrivateKey();
expect(new Uint8Array(result)).toEqual(key);
});
it('caches session backup keys as it encounters them', async function() {
const cachedNull = await client._crypto.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client._http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
await new Promise((resolve) => {
client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BACKUP_INFO,
{ cacheCompleteCallback: resolve },
);
});
const cachedKey = await client._crypto.getSessionBackupPrivateKey();
expect(cachedKey).not.toBeNull();
});
});
});
+855
View File
@@ -0,0 +1,855 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import anotherjson from 'another-json';
import * as olmlib from "../../../src/crypto/olmlib";
import {TestClient} from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
import { resetCrossSigningKeys } from "./crypto-utils";
import { MatrixError } from '../../../src/http-api';
import {logger} from '../../../src/logger';
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
if (!options) options = {};
options.cryptoCallbacks = Object.assign(
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
);
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
await client.initCrypto();
return client;
}
describe("Cross Signing", function() {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should sign the master key with the device key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
await olmlib.verifySignature(
alice._crypto._olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
);
});
alice.uploadKeySignatures = async () => {};
alice.setAccountData = async () => {};
alice.getAccountDataFromServer = async () => {};
// set Alice's cross-signing key
await alice.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
});
expect(alice.uploadDeviceSigningKeys).toHaveBeenCalled();
});
it("should abort bootstrap if device signing auth fails", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async (auth, keys) => {
const errorResponse = {
session: "sessionId",
flows: [
{
stages: [
"m.login.password",
],
},
],
params: {},
};
// If we're not just polling for flows, add on error rejecting the
// auth attempt.
if (auth) {
Object.assign(errorResponse, {
completed: [],
error: "Invalid password",
errcode: "M_FORBIDDEN",
});
}
const error = new MatrixError(errorResponse);
error.httpStatus == 401;
throw error;
};
alice.uploadKeySignatures = async () => {};
alice.setAccountData = async () => {};
alice.getAccountDataFromServer = async () => { };
const authUploadDeviceSigningKeys = async func => await func({});
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
// through failure, stopping before actually applying changes.
let bootstrapDidThrow = false;
try {
await alice.bootstrapCrossSigning({
authUploadDeviceSigningKeys,
});
} catch (e) {
if (e.errcode === "M_FORBIDDEN") {
bootstrapDidThrow = true;
}
}
expect(bootstrapDidThrow).toBeTruthy();
});
it("should upload a signature when a user is verified", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's device key
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:bobs+master+pubkey": "bobs+master+pubkey",
},
},
},
});
// Alice verifies Bob's key
const promise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = (...args) => {
resolve(...args);
};
});
await alice.setDeviceVerified("@bob:example.com", "bobs+master+pubkey", true);
// Alice should send a signature of Bob's key to the server
await promise;
});
it("should get cross-signing keys from sync", async function() {
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
// will be called to sign our own device
getCrossSigningKey: type => {
if (type === 'master') {
return masterKey;
} else {
return selfSigningKey;
}
},
},
},
);
const keyChangePromise = new Promise((resolve, reject) => {
alice.once("crossSigning.keysChanged", async (e) => {
resolve(e);
await alice.checkOwnCrossSigningTrust();
});
});
const uploadSigsPromise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = jest.fn(async (content) => {
await olmlib.verifySignature(
alice._crypto._olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
"@alice:example.com",
);
resolve();
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes master key, ssk, device key
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@alice:example.com",
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
},
"master_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
},
},
"self_signing_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@alice:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs"
+ "Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses, true, true);
await alice.startClient();
// once ssk is confirmed, device key should be trusted
await keyChangePromise;
await uploadSigsPromise;
const aliceTrust = alice.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
expect(aliceTrust.isVerified()).toBeTruthy();
const aliceDeviceTrust = alice.checkDeviceTrust("@alice:example.com", "Osborne2");
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isTofu()).toBeTruthy();
expect(aliceDeviceTrust.isVerified()).toBeTruthy();
});
it("should use trust chain to determine device verification", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's ssk and device key
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const sig = bobSigning.sign(anotherjson.stringify(bobDevice));
bobDevice.signatures = {
"@bob:example.com": {
["ed25519:" + bobPubkey]: sig,
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust2.isTofu()).toBeTruthy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust2.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeTruthy();
});
it("should trust signatures received from other devices", async function() {
const aliceKeys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
null,
aliceKeys,
);
alice._crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice._crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice._crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
const bobKeys = JSON.parse(bobOlmAccount.identity_keys());
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobKeys.ed25519,
"curve25519:Dynabook": bobKeys.curve25519,
},
};
const deviceStr = anotherjson.stringify(bobDevice);
bobDevice.signatures = {
"@bob:example.com": {
"ed25519:Dynabook": bobOlmAccount.sign(deviceStr),
},
};
olmlib.pkSign(bobDevice, selfSigningKey, "@bob:example.com");
const bobMaster = {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
};
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com");
// Alice downloads Bob's keys
// - device key
// - ssk
// - master key signed by her usk (pretend that it was signed by another
// of Alice's devices)
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
"@bob:example.com": {
"Dynabook": bobDevice,
},
},
"master_keys": {
"@bob:example.com": bobMaster,
},
"self_signing_keys": {
"@bob:example.com": {
user_id: "@bob:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@bob:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB"
+ "LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses);
await alice.startClient();
await keyChangePromise;
// Bob's device key should be trusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
});
it("should dis-trust an unsigned device", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's ssk and device key
// (NOTE: device key is not signed by ssk)
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be untrusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be untrusted
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
});
it("should dis-trust a user when their ssk changes", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
await resetCrossSigningKeys(alice);
// Alice downloads Bob's keys
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const bobDeviceString = anotherjson.stringify(bobDevice);
const sig = bobSigning.sign(bobDeviceString);
bobDevice.signatures = {};
bobDevice.signatures["@bob:example.com"] = {};
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeTruthy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice downloads new SSK for Bob
const bobMasterSigning2 = new global.Olm.PkSigning();
const bobMasterPrivkey2 = bobMasterSigning2.generate_seed();
const bobMasterPubkey2 = bobMasterSigning2.init_with_seed(bobMasterPrivkey2);
const bobSigning2 = new global.Olm.PkSigning();
const bobPrivkey2 = bobSigning2.generate_seed();
const bobPubkey2 = bobSigning2.init_with_seed(bobPrivkey2);
const bobSSK2 = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey2]: bobPubkey2,
},
};
const sskSig2 = bobMasterSigning2.sign(anotherjson.stringify(bobSSK2));
bobSSK2.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey2]: bobMasterPubkey2,
},
},
self_signing: bobSSK2,
},
firstUse: 0,
unsigned: {},
});
// Bob's and his device should be untrusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeFalsy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey2, true);
// Bob should be trusted but not his device
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isVerified()).toBeTruthy();
const bobDeviceTrust3 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust3.isVerified()).toBeFalsy();
// Alice gets new signature for device
const sig2 = bobSigning2.sign(bobDeviceString);
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device should be trusted again (but not TOFU)
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust3.isVerified()).toBeTruthy();
const bobDeviceTrust4 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust4.isCrossSigningVerified()).toBeTruthy();
});
it("should offer to upgrade device verifications to cross-signing", async function() {
let upgradeResolveFunc;
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
shouldUpgradeDeviceVerifications: (verifs) => {
expect(verifs.users["@bob:example.com"]).toBeDefined();
upgradeResolveFunc();
return ["@bob:example.com"];
},
},
},
);
const bob = await makeTestClient(
{userId: "@bob:example.com", deviceId: "Dynabook"},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
// set Bob's cross-signing key
await resetCrossSigningKeys(bob);
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: {
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": bob._crypto._olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob._crypto._olmDevice.deviceEd25519Key,
},
verified: 1,
known: true,
},
});
alice._crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob._crypto._crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// when alice sets up cross-signing, she should notice that bob's
// cross-signing key is signed by his Dynabook, which alice has
// verified, and ask if the device verification should be upgraded to a
// cross-signing verification
let upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
await resetCrossSigningKeys(alice);
await upgradePromise;
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice._crypto._deviceList._crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
expect(bobTrust2.isTofu()).toBeTruthy();
upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
await new Promise((resolve) => {
alice._crypto.on("userTrustStatusChanged", resolve);
});
await upgradePromise;
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust3.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust3.isTofu()).toBeTruthy();
});
});
+44
View File
@@ -0,0 +1,44 @@
import {IndexedDBCryptoStore} from '../../../src/crypto/store/indexeddb-crypto-store';
// needs to be phased out and replaced with bootstrapSecretStorage,
// but that is doing too much extra stuff for it to be an easy transition.
export async function resetCrossSigningKeys(client, {
level,
authUploadDeviceSigningKeys = async func => await func(),
} = {}) {
const crypto = client._crypto;
const oldKeys = Object.assign({}, crypto._crossSigningInfo.keys);
try {
await crypto._crossSigningInfo.resetKeys(level);
await crypto._signObject(crypto._crossSigningInfo.keys.master);
// write a copy locally so we know these are trusted keys
await crypto._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto._cryptoStore.storeCrossSigningKeys(
txn, crypto._crossSigningInfo.keys);
},
);
} catch (e) {
// If anything failed here, revert the keys so we know to try again from the start
// next time.
crypto._crossSigningInfo.keys = oldKeys;
throw e;
}
crypto._baseApis.emit("crossSigning.keysChanged", {});
await crypto._afterCrossSigningLocalKeyChange();
}
export async function createSecretStorageKey() {
const decryption = new global.Olm.PkDecryption();
const storagePublicKey = decryption.generate_key();
const storagePrivateKey = decryption.get_private_key();
decryption.free();
return {
// `pubkey` not used anymore with symmetric 4S
keyInfo: { pubkey: storagePublicKey },
privateKey: storagePrivateKey,
};
}
@@ -0,0 +1,87 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {
ROOM_KEY_REQUEST_STATES,
} from '../../../src/crypto/OutgoingRoomKeyRequestManager';
const requests = [
{
requestId: "A",
requestBody: { session_id: "A", room_id: "A" },
state: ROOM_KEY_REQUEST_STATES.SENT,
},
{
requestId: "B",
requestBody: { session_id: "B", room_id: "B" },
state: ROOM_KEY_REQUEST_STATES.SENT,
},
{
requestId: "C",
requestBody: { session_id: "C", room_id: "C" },
state: ROOM_KEY_REQUEST_STATES.UNSENT,
},
];
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
store._backend = new MemoryCryptoStore();
store._backendPromise = Promise.resolve(store._backend);
return store;
}],
])("Outgoing room key requests [%s]", function(name, dbFactory) {
let store;
beforeAll(async () => {
store = dbFactory();
await store.startup();
await Promise.all(requests.map((request) =>
store.getOrAddOutgoingRoomKeyRequest(request),
));
});
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state",
async () => {
const r = await
store.getAllOutgoingRoomKeyRequestsByState(ROOM_KEY_REQUEST_STATES.SENT);
expect(r).toHaveLength(2);
requests.filter((e) => e.state == ROOM_KEY_REQUEST_STATES.SENT).forEach((e) => {
expect(r).toContainEqual(e);
});
});
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => {
const r =
await store.getOutgoingRoomKeyRequestByState([ROOM_KEY_REQUEST_STATES.SENT]);
expect(r).not.toBeNull();
expect(r).not.toBeUndefined();
expect(r.state).toEqual(ROOM_KEY_REQUEST_STATES.SENT);
expect(requests).toContainEqual(r);
});
});
+674
View File
@@ -0,0 +1,674 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import * as olmlib from "../../../src/crypto/olmlib";
import {SECRET_STORAGE_ALGORITHM_V1_AES} from "../../../src/crypto/SecretStorage";
import {MatrixEvent} from "../../../src/models/event";
import {TestClient} from '../../TestClient';
import {makeTestClients} from './verification/util';
import {encryptAES} from "../../../src/crypto/aes";
import {resetCrossSigningKeys, createSecretStorageKey} from "./crypto-utils";
import {logger} from '../../../src/logger';
import * as utils from "../../../src/utils";
try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
logger.log('nodejs was compiled without crypto support');
}
async function makeTestClient(userInfo, options) {
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
// Make it seem as if we've synced and thus the store can be trusted to
// contain valid account data.
client.isInitialSyncComplete = function() {
return true;
};
await client.initCrypto();
// No need to download keys for these tests
client._crypto.downloadKeys = async function() {};
return client;
}
// Wrapper around pkSign to return a signed object. pkSign returns the
// signature, rather than the signed object.
function sign(obj, key, userId) {
olmlib.pkSign(obj, key, userId);
return obj;
}
describe("Secrets", function() {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should store and retrieve a secret", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const signing = new global.Olm.PkSigning();
const signingKey = signing.generate_seed();
const signingPubKey = signing.init_with_seed(signingKey);
const signingkeyInfo = {
user_id: "@alice:example.com",
usage: ['master'],
keys: {
['ed25519:' + signingPubKey]: signingPubKey,
},
};
const getKey = jest.fn(e => {
expect(Object.keys(e.keys)).toEqual(["abc"]);
return ['abc', key];
});
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => signingKey,
getSecretStorageKey: getKey,
},
},
);
alice._crypto._crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice._crypto._secretStorage;
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
if (callback) {
callback();
}
};
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice._crypto._crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.key.abc",
content: keyAccountData,
}),
]);
expect(await secretStorage.isStored("foo")).toBeFalsy();
await secretStorage.store("foo", "bar", ["abc"]);
expect(await secretStorage.isStored("foo")).toBeTruthy();
expect(await secretStorage.get("foo")).toBe("bar");
expect(getKey).toHaveBeenCalled();
});
it("should throw if given a key that doesn't exist", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", ["this secret does not exist"]);
// should be able to use expect(...).toThrow() but mocha still fails
// the test even when it throws for reasons I have no inclination to debug
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should refuse to encrypt with zero keys", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", []);
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should encrypt with default key if keys is null", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn(e => {
expect(Object.keys(e.keys)).toEqual([newKeyId]);
return [newKeyId, key];
});
let keys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => keys[t],
saveCrossSigningKeys: k => keys = k,
getSecretStorageKey: getKey,
},
},
);
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
};
resetCrossSigningKeys(alice);
const { keyId: newKeyId } = await alice.addSecretStorageKey(
SECRET_STORAGE_ALGORITHM_V1_AES,
);
// we don't await on this because it waits for the event to come down the sync
// which won't happen in the test setup
alice.setDefaultSecretStorageKeyId(newKeyId);
await alice.storeSecret("foo", "bar");
const accountData = alice.getAccountData('foo');
expect(accountData.getContent().encrypted).toBeTruthy();
});
it("should refuse to encrypt if no keys given and no default key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar");
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should request secrets from other clients", async function() {
const [osborne2, vax] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@alice:example.com", deviceId: "VAX"},
],
{
cryptoCallbacks: {
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
expect(secretName).toBe("foo");
return "bar";
},
},
},
);
const vaxDevice = vax.client._crypto._olmDevice;
const osborne2Device = osborne2.client._crypto._olmDevice;
const secretStorage = osborne2.client._crypto._secretStorage;
osborne2.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
user_id: "@alice:example.com",
device_id: "VAX",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:VAX": vaxDevice.deviceEd25519Key,
"curve25519:VAX": vaxDevice.deviceCurve25519Key,
},
},
});
vax.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": osborne2Device.deviceEd25519Key,
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key,
},
},
});
await osborne2Device.generateOneTimeKeys(1);
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client._crypto._olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
Object.values(otks)[0],
);
const request = await secretStorage.request("foo", ["VAX"]);
const secret = await request.promise;
expect(secret).toBe("bar");
});
describe("bootstrap", function() {
// keys used in some of the tests
const XSK = new Uint8Array(
olmlib.decodeBase64("3lo2YdJugHjfE+Or7KJ47NuKbhE7AAGLgQ/dc19913Q="),
);
const XSPubKey = "DRb8pFVJyEJ9OWvXeUoM0jq/C2Wt+NxzBZVuk2nRb+0";
const USK = new Uint8Array(
olmlib.decodeBase64("lKWi3hJGUie5xxHgySoz8PHFnZv6wvNaud/p2shN9VU="),
);
const USPubKey = "CUpoiTtHiyXpUmd+3ohb7JVxAlUaOG1NYs9Jlx8soQU";
const SSK = new Uint8Array(
olmlib.decodeBase64("1R6JVlXX99UcfUZzKuCDGQgJTw8ur1/ofgPD8pp+96M="),
);
const SSPubKey = "0DfNsRDzEvkCLA0gD3m7VAGJ5VClhjEsewI35xq873Q";
const SSSSKey = new Uint8Array(
olmlib.decodeBase64(
"XrmITOOdBhw6yY5Bh7trb/bgp1FRdIGyCUxxMP873R0=",
),
);
it("bootstraps when no storage or cross-signing keys locally", async function() {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn(e => {
return [Object.keys(e.keys)[0], key];
});
const bob = await makeTestClient(
{
userId: "@bob:example.com",
deviceId: "bob1",
},
{
cryptoCallbacks: {
getSecretStorageKey: getKey,
},
},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
bob.setAccountData = async function(eventType, contents, callback) {
const event = new MatrixEvent({
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
};
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
});
await bob.bootstrapSecretStorage({
createSecretStorageKey,
});
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
});
it("bootstraps when cross-signing keys in secret storage", async function() {
const decryption = new global.Olm.PkDecryption();
const storagePublicKey = decryption.generate_key();
const storagePrivateKey = decryption.get_private_key();
const bob = await makeTestClient(
{
userId: "@bob:example.com",
deviceId: "bob1",
},
{
cryptoCallbacks: {
getSecretStorageKey: async request => {
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
return [defaultKeyId, storagePrivateKey];
},
},
},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
bob.setAccountData = async function(eventType, contents, callback) {
const event = new MatrixEvent({
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
};
bob._crypto.checkKeyBackup = async () => {};
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
});
await bob.bootstrapSecretStorage({
createSecretStorageKey: async () => ({
// `pubkey` not used anymore with symmetric 4S
keyInfo: { pubkey: storagePublicKey },
privateKey: storagePrivateKey,
}),
});
// Clear local cross-signing keys and read from secret storage
bob._crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
crossSigning.keys = {};
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
});
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
});
it("adds passphrase checking if it's lacking", async function() {
let crossSigningKeys = {
master: XSK,
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
}
}
},
},
},
);
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.default_key",
content: {
key: "key_id",
},
}),
new MatrixEvent({
type: "m.secret_storage.key.key_id",
content: {
algorithm: "m.secret_storage.v1.aes-hmac-sha2",
passphrase: {
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
},
},
}),
// we never use these values, other than checking that they
// exist, so just use dummy values
new MatrixEvent({
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
};
};
alice.setAccountData = async function(name, data) {
const event = new MatrixEvent({
type: name,
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
};
await alice.bootstrapSecretStorage();
expect(alice.getAccountData("m.secret_storage.default_key").getContent())
.toEqual({key: "key_id"});
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")
.getContent();
expect(keyInfo.algorithm)
.toEqual("m.secret_storage.v1.aes-hmac-sha2");
expect(keyInfo.passphrase).toEqual({
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
});
expect(keyInfo).toHaveProperty("iv");
expect(keyInfo).toHaveProperty("mac");
expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo))
.toBeTruthy();
});
it("fixes backup keys in the wrong format", async function() {
let crossSigningKeys = {
master: XSK,
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
}
}
},
},
},
);
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.default_key",
content: {
key: "key_id",
},
}),
new MatrixEvent({
type: "m.secret_storage.key.key_id",
content: {
algorithm: "m.secret_storage.v1.aes-hmac-sha2",
passphrase: {
algorithm: "m.pbkdf2",
iterations: 500000,
salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
},
},
}),
new MatrixEvent({
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
},
},
}),
new MatrixEvent({
type: "m.megolm_backup.v1",
content: {
encrypted: {
key_id: await encryptAES(
"123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
secretStorageKeys.key_id, "m.megolm_backup.v1",
),
},
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
};
};
alice.setAccountData = async function(name, data) {
const event = new MatrixEvent({
type: name,
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
};
await alice.bootstrapSecretStorage();
const backupKey = alice.getAccountData("m.megolm_backup.v1")
.getContent();
expect(backupKey.encrypted).toHaveProperty("key_id");
expect(await alice.getSecret("m.megolm_backup.v1"))
.toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
});
});
});
@@ -0,0 +1,98 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
describe("InRoomChannel tests", function() {
const ALICE = "@alice:hs.tld";
const BOB = "@bob:hs.tld";
const MALORY = "@malory:hs.tld";
const client = {
getUserId() { return ALICE; },
};
it("getEventType only returns .request for a message with a msgtype", function() {
const invalidEvent = new MatrixEvent({
type: "m.key.verification.request",
});
expect(InRoomChannel.getEventType(invalidEvent)).toStrictEqual("");
const validEvent = new MatrixEvent({
type: "m.room.message",
content: { msgtype: "m.key.verification.request" },
});
expect(InRoomChannel.getEventType(validEvent)).
toStrictEqual("m.key.verification.request");
const validFooEvent = new MatrixEvent({ type: "m.foo" });
expect(InRoomChannel.getEventType(validFooEvent)).
toStrictEqual("m.foo");
});
it("getEventType should return m.room.message for messages", function() {
const messageEvent = new MatrixEvent({
type: "m.room.message",
content: { msgtype: "m.text" },
});
// XXX: The event type doesn't matter too much, just as long as it's not a verification event
expect(InRoomChannel.getEventType(messageEvent)).
toStrictEqual("m.room.message");
});
it("getEventType should return actual type for non-message events", function() {
const event = new MatrixEvent({
type: "m.room.member",
content: { },
});
expect(InRoomChannel.getEventType(event)).
toStrictEqual("m.room.member");
});
it("getOtherPartyUserId should not return anything for a request not " +
"directed at me", function() {
const event = new MatrixEvent({
sender: BOB,
type: "m.room.message",
content: { msgtype: "m.key.verification.request", to: MALORY },
});
expect(InRoomChannel.getOtherPartyUserId(event, client)).toStrictEqual(undefined);
});
it("getOtherPartyUserId should not return anything an event that is not of a valid " +
"request type", function() {
// invalid because this should be a room message with msgtype
const invalidRequest = new MatrixEvent({
sender: BOB,
type: "m.key.verification.request",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(invalidRequest, client))
.toStrictEqual(undefined);
const startEvent = new MatrixEvent({
sender: BOB,
type: "m.key.verification.start",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(startEvent, client))
.toStrictEqual(undefined);
const fooEvent = new MatrixEvent({
sender: BOB,
type: "m.foo",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(fooEvent, client))
.toStrictEqual(undefined);
});
});
+12 -117
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,18 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import {ShowQRCode, ScanQRCode} from '../../../../lib/crypto/verification/QRCode';
import "../../../olm-loader";
import {logger} from "../../../../src/logger";
const Olm = global.Olm;
@@ -34,113 +25,17 @@ describe("QR code verification", function() {
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
return Olm.init();
});
describe("showing", function() {
it("should emit an event to show a QR code", async function() {
const qrCode = new ShowQRCode({
getUserId: () => "@alice:example.com",
deviceId: "ABCDEFG",
getDeviceEd25519Key: function() {
return "device+ed25519+key";
},
});
const spy = expect.createSpy().andCall((e) => {
qrCode.done();
});
qrCode.on("show_qr_code", spy);
await qrCode.verify();
expect(spy).toHaveBeenCalledWith({
url: "https://matrix.to/#/@alice:example.com?device=ABCDEFG"
+ "&action=verify&key_ed25519%3AABCDEFG=device%2Bed25519%2Bkey",
});
});
});
describe("scanning", function() {
const QR_CODE_URL = "https://matrix.to/#/@alice:example.com?device=ABCDEFG"
+ "&action=verify&key_ed25519%3AABCDEFG=device%2Bed25519%2Bkey";
it("should verify when a QR code is sent", async function() {
const device = DeviceInfo.fromStorage(
{
algorithms: [],
keys: {
"curve25519:ABCDEFG": "device+curve25519+key",
"ed25519:ABCDEFG": "device+ed25519+key",
},
verified: false,
known: false,
unsigned: {},
},
"ABCDEFG",
);
const client = {
getStoredDevice: expect.createSpy().andReturn(device),
setDeviceVerified: expect.createSpy(),
};
const qrCode = new ScanQRCode(client);
qrCode.on("confirm_user_id", ({userId, confirm}) => {
if (userId === "@alice:example.com") {
confirm();
} else {
qrCode.cancel(new Error("Incorrect user"));
}
});
qrCode.on("scan", ({done}) => {
done(QR_CODE_URL);
});
await qrCode.verify();
expect(client.getStoredDevice)
.toHaveBeenCalledWith("@alice:example.com", "ABCDEFG");
expect(client.setDeviceVerified)
.toHaveBeenCalledWith("@alice:example.com", "ABCDEFG");
});
it("should error when the user ID doesn't match", async function() {
const client = {
getStoredDevice: expect.createSpy(),
setDeviceVerified: expect.createSpy(),
};
const qrCode = new ScanQRCode(client, "@bob:example.com", "ABCDEFG");
qrCode.on("scan", ({done}) => {
done(QR_CODE_URL);
});
const spy = expect.createSpy();
await qrCode.verify().catch(spy);
expect(spy).toHaveBeenCalled();
expect(client.getStoredDevice).toNotHaveBeenCalled();
expect(client.setDeviceVerified).toNotHaveBeenCalled();
});
it("should error if the key doesn't match", async function() {
const device = DeviceInfo.fromStorage(
{
algorithms: [],
keys: {
"curve25519:ABCDEFG": "device+curve25519+key",
"ed25519:ABCDEFG": "a+different+device+ed25519+key",
},
verified: false,
known: false,
unsigned: {},
},
"ABCDEFG",
);
const client = {
getStoredDevice: expect.createSpy().andReturn(device),
setDeviceVerified: expect.createSpy(),
};
const qrCode = new ScanQRCode(client, "@alice:example.com", "ABCDEFG");
qrCode.on("scan", ({done}) => {
done(QR_CODE_URL);
});
const spy = expect.createSpy();
await qrCode.verify().catch(spy);
expect(spy).toHaveBeenCalled();
expect(client.getStoredDevice).toHaveBeenCalled();
expect(client.setDeviceVerified).toNotHaveBeenCalled();
describe("reciprocate", () => {
it("should verify the secret", () => {
// TODO: Actually write a test for this.
// Tests are hard because we are running before the verification
// process actually begins, and are largely UI-driven rather than
// logic-driven (compared to something like SAS). In the interest
// of time, tests are currently excluded.
});
});
});
+23 -23
View File
@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,32 +14,29 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import {verificationMethods} from '../../../../lib/crypto';
import SAS from '../../../../lib/crypto/verification/SAS';
import "../../../olm-loader";
import {verificationMethods} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
const Olm = global.Olm;
import {makeTestClients} from './util';
jest.useFakeTimers();
describe("verification request", function() {
describe("verification request integration tests with crypto layer", function() {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
setupWebcrypto();
return Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("should request and accept a verification", async function() {
@@ -51,7 +49,7 @@ describe("verification request", function() {
verificationMethods: [verificationMethods.SAS],
},
);
alice._crypto._deviceList.getRawStoredDevicesForUser = function() {
alice.client._crypto._deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
keys: {
@@ -60,21 +58,23 @@ describe("verification request", function() {
},
};
};
alice.downloadKeys = () => {
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.downloadKeys = () => {
bob.client.downloadKeys = () => {
return Promise.resolve();
};
bob.on("crypto.verification.request", (request) => {
bob.client.on("crypto.verification.request", (request) => {
const bobVerifier = request.beginKeyVerification(verificationMethods.SAS);
bobVerifier.verify();
// XXX: Private function access (but it's a test, so we're okay)
bobVerifier._endTimer();
});
const aliceVerifier = await alice.requestVerification("@bob:example.com");
expect(aliceVerifier).toBeAn(SAS);
const aliceRequest = await alice.client.requestVerification("@bob:example.com");
await aliceRequest.waitFor(r => r.started);
const aliceVerifier = aliceRequest.verifier;
expect(aliceVerifier).toBeInstanceOf(SAS);
// XXX: Private function access (but it's a test, so we're okay)
aliceVerifier._endTimer();
+326 -96
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,28 +14,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import sdk from '../../../..';
import {verificationMethods} from '../../../../lib/crypto';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import SAS from '../../../../lib/crypto/verification/SAS';
import "../../../olm-loader";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
import {MatrixEvent} from "../../../../src/models/event";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {verificationMethods} from "../../../../src/crypto";
import * as olmlib from "../../../../src/crypto/olmlib";
import {logger} from "../../../../src/logger";
import {resetCrossSigningKeys} from "../crypto-utils";
const Olm = global.Olm;
const MatrixEvent = sdk.MatrixEvent;
import {makeTestClients} from './util';
let ALICE_DEVICES;
let BOB_DEVICES;
describe("SAS verification", function() {
if (!global.Olm) {
@@ -42,27 +35,40 @@ describe("SAS verification", function() {
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
setupWebcrypto();
return Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("should error on an unexpected event", async function() {
const sas = new SAS({}, "@alice:example.com", "ABCDEFG");
//channel, baseApis, userId, deviceId, startEvent, request
const request = {
onVerifierCancelled: function() {},
};
const channel = {
send: function() {
return Promise.resolve();
},
};
const sas = new SAS(channel, {}, "@alice:example.com", "ABCDEFG", null, request);
sas.handleEvent(new MatrixEvent({
sender: "@alice:example.com",
type: "es.inquisition",
content: {},
}));
const spy = expect.createSpy();
await sas.verify()
.catch(spy);
const spy = jest.fn();
await sas.verify().catch(spy);
expect(spy).toHaveBeenCalled();
// Cancel the SAS for cleanup (we started a verification, so abort)
sas.cancel();
});
describe("verification", function() {
describe("verification", () => {
let alice;
let bob;
let aliceSasEvent;
@@ -70,7 +76,7 @@ describe("SAS verification", function() {
let aliceVerifier;
let bobPromise;
beforeEach(async function() {
beforeEach(async () => {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
@@ -81,39 +87,44 @@ describe("SAS verification", function() {
},
);
alice.setDeviceVerified = expect.createSpy();
alice.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
alice.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
const aliceDevice = alice.client._crypto._olmDevice;
const bobDevice = bob.client._crypto._olmDevice;
ALICE_DEVICES = {
Osborne2: {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": aliceDevice.deviceEd25519Key,
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key,
},
"Dynabook",
);
},
};
alice.downloadKeys = () => {
BOB_DEVICES = {
Dynabook: {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice.deviceEd25519Key,
"curve25519:Dynabook": bobDevice.deviceCurve25519Key,
},
},
};
alice.client._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.setDeviceVerified = expect.createSpy();
bob.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Osborne2": "alice+base64+ed25519+key",
},
},
"Osborne2",
);
};
bob.getDeviceEd25519Key = () => {
return "bob+base64+ed25519+key";
};
bob.downloadKeys = () => {
bob.client._crypto._deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
return Promise.resolve();
};
@@ -121,8 +132,8 @@ describe("SAS verification", function() {
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.on("crypto.verification.start", (verifier) => {
verifier.on("show_sas", (e) => {
bob.client.on("crypto.verification.request", request => {
request.verifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
@@ -138,12 +149,12 @@ describe("SAS verification", function() {
}
}
});
resolve(verifier);
resolve(request.verifier);
});
});
aliceVerifier = alice.beginKeyVerification(
verificationMethods.SAS, bob.getUserId(), bob.deviceId,
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.deviceId,
);
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
@@ -162,69 +173,163 @@ describe("SAS verification", function() {
}
});
});
afterEach(async () => {
await Promise.all([
alice.stop(),
bob.stop(),
]);
});
it("should verify a key", async function() {
it("should verify a key", async () => {
let macMethod;
const origSendToDevice = alice.sendToDevice;
bob.sendToDevice = function(type, map) {
let keyAgreement;
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.getUserId()][alice.deviceId]
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
keyAgreement = map[alice.client.getUserId()][alice.client.deviceId]
.key_agreement_protocol;
}
return origSendToDevice.call(this, type, map);
return origSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
// make sure that it uses the preferred method
expect(macMethod).toBe("hkdf-hmac-sha256");
expect(keyAgreement).toBe("curve25519-hkdf-sha256");
// make sure Alice and Bob verified each other
expect(alice.setDeviceVerified)
.toHaveBeenCalledWith(bob.getUserId(), bob.deviceId);
expect(bob.setDeviceVerified)
.toHaveBeenCalledWith(alice.getUserId(), alice.deviceId);
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should be able to verify using the old MAC", async function() {
it("should be able to verify using the old MAC", async () => {
// pretend that Alice can only understand the old (incorrect) MAC,
// and make sure that she can still verify with Bob
let macMethod;
const origSendToDevice = alice.sendToDevice;
alice.sendToDevice = function(type, map) {
const aliceOrigSendToDevice = alice.client.sendToDevice.bind(alice.client);
alice.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.start") {
// Note: this modifies not only the message that Bob
// receives, but also the copy of the message that Alice
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.getUserId()][bob.deviceId]
map[bob.client.getUserId()][bob.client.deviceId]
.message_authentication_codes = ['hmac-sha256'];
}
return origSendToDevice.call(this, type, map);
return aliceOrigSendToDevice(type, map);
};
bob.sendToDevice = function(type, map) {
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.getUserId()][alice.deviceId]
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
}
return origSendToDevice.call(this, type, map);
return bobOrigSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
expect(macMethod).toBe("hmac-sha256");
expect(alice.setDeviceVerified)
.toHaveBeenCalledWith(bob.getUserId(), bob.deviceId);
expect(bob.setDeviceVerified)
.toHaveBeenCalledWith(alice.getUserId(), alice.deviceId);
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should verify a cross-signing key", async () => {
alice.httpBackend.when('POST', '/keys/device_signing/upload').respond(
200, {},
);
alice.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
alice.httpBackend.flush(undefined, 2);
await resetCrossSigningKeys(alice.client);
bob.httpBackend.when('POST', '/keys/device_signing/upload').respond(200, {});
bob.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
bob.httpBackend.flush(undefined, 2);
await resetCrossSigningKeys(bob.client);
bob.client._crypto._deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client._crypto._crossSigningInfo.keys,
},
);
const verifyProm = Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => {
bob.httpBackend.when(
'POST', '/keys/signatures/upload',
).respond(200, {});
bob.httpBackend.flush(undefined, 1, 2000);
return verifier.verify();
}),
]);
await verifyProm;
const bobDeviceTrust = alice.client.checkDeviceTrust(
"@bob:example.com", "Dynabook",
);
expect(bobDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(bobDeviceTrust.isCrossSigningVerified()).toBeFalsy();
const aliceTrust = bob.client.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
const aliceDeviceTrust = bob.client.checkDeviceTrust(
"@alice:example.com", "Osborne2",
);
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeFalsy();
});
});
@@ -238,39 +343,164 @@ describe("SAS verification", function() {
verificationMethods: [verificationMethods.SAS],
},
);
alice.setDeviceVerified = expect.createSpy();
alice.downloadKeys = () => {
alice.client.setDeviceVerified = jest.fn();
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.setDeviceVerified = expect.createSpy();
bob.downloadKeys = () => {
bob.client.setDeviceVerified = jest.fn();
bob.client.downloadKeys = () => {
return Promise.resolve();
};
const bobPromise = new Promise((resolve, reject) => {
bob.on("crypto.verification.start", (verifier) => {
verifier.on("show_sas", (e) => {
bob.client.on("crypto.verification.request", request => {
request.verifier.on("show_sas", (e) => {
e.mismatch();
});
resolve(verifier);
resolve(request.verifier);
});
});
const aliceVerifier = alice.beginKeyVerification(
verificationMethods.SAS, bob.getUserId(), bob.deviceId,
const aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.client.deviceId,
);
const aliceSpy = expect.createSpy();
const bobSpy = expect.createSpy();
const aliceSpy = jest.fn();
const bobSpy = jest.fn();
await Promise.all([
aliceVerifier.verify().catch(aliceSpy),
bobPromise.then((verifier) => verifier.verify()).catch(bobSpy),
]);
expect(aliceSpy).toHaveBeenCalled();
expect(bobSpy).toHaveBeenCalled();
expect(alice.setDeviceVerified)
.toNotHaveBeenCalled();
expect(bob.setDeviceVerified)
.toNotHaveBeenCalled();
expect(alice.client.setDeviceVerified)
.not.toHaveBeenCalled();
expect(bob.client.setDeviceVerified)
.not.toHaveBeenCalled();
});
describe("verification in DM", function() {
let alice;
let bob;
let aliceSasEvent;
let bobSasEvent;
let aliceVerifier;
let bobPromise;
beforeEach(async function() {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.setDeviceVerified = jest.fn();
alice.client.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
alice.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
},
"Dynabook",
);
};
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.setDeviceVerified = jest.fn();
bob.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Osborne2": "alice+base64+ed25519+key",
},
},
"Osborne2",
);
};
bob.client.getDeviceEd25519Key = () => {
return "bob+base64+ed25519+key";
};
bob.client.downloadKeys = () => {
return Promise.resolve();
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.client.on("crypto.verification.request", async (request) => {
const verifier = request.beginKeyVerification(SAS.NAME);
verifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
bobSasEvent = e;
} else {
try {
expect(e.sas).toEqual(aliceSasEvent.sas);
e.confirm();
aliceSasEvent.confirm();
} catch (error) {
e.mismatch();
aliceSasEvent.mismatch();
}
}
});
await verifier.verify();
resolve();
});
});
const aliceRequest = await alice.client.requestVerificationDM(
bob.client.getUserId(), "!room_id",
);
await aliceRequest.waitFor(r => r.started);
aliceVerifier = aliceRequest.verifier;
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {
aliceSasEvent = e;
} else {
try {
expect(e.sas).toEqual(bobSasEvent.sas);
e.confirm();
bobSasEvent.confirm();
} catch (error) {
e.mismatch();
bobSasEvent.mismatch();
}
}
});
});
afterEach(async function() {
await Promise.all([
alice.stop(),
bob.stop(),
]);
});
it("should verify a key", async function() {
await Promise.all([
aliceVerifier.verify(),
bobPromise,
]);
// make sure Alice and Bob verified each other
expect(alice.client.setDeviceVerified)
.toHaveBeenCalledWith(bob.client.getUserId(), bob.client.deviceId);
expect(bob.client.setDeviceVerified)
.toHaveBeenCalledWith(alice.client.getUserId(), alice.client.deviceId);
});
});
});
@@ -0,0 +1,118 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {VerificationBase} from '../../../../src/crypto/verification/Base';
import {CrossSigningInfo} from '../../../../src/crypto/CrossSigning';
import {encodeBase64} from "../../../../src/crypto/olmlib";
import {setupWebcrypto, teardownWebcrypto} from './util';
jest.useFakeTimers();
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const testKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("self-verifications", () => {
beforeAll(function() {
setupWebcrypto();
return global.Olm.init();
});
afterAll(() => {
teardownWebcrypto();
});
it("triggers a request for key sharing upon completion", async () => {
const userId = "@test:localhost";
const cacheCallbacks = {
getCrossSigningKeyCache: jest.fn().mockReturnValue(null),
storeCrossSigningKeyCache: jest.fn(),
};
const _crossSigningInfo = new CrossSigningInfo(
userId,
{},
cacheCallbacks,
);
_crossSigningInfo.keys = {
master: { keys: { X: testKeyPub } },
self_signing: { keys: { X: testKeyPub } },
user_signing: { keys: { X: testKeyPub } },
};
const _secretStorage = {
request: jest.fn().mockReturnValue({
promise: Promise.resolve(encodeBase64(testKey)),
}),
};
const storeSessionBackupPrivateKey = jest.fn();
const restoreKeyBackupWithCache = jest.fn(() => Promise.resolve());
const client = {
_crypto: {
_crossSigningInfo,
_secretStorage,
storeSessionBackupPrivateKey,
getSessionBackupPrivateKey: () => null,
},
requestSecret: _secretStorage.request.bind(_secretStorage),
getUserId: () => userId,
getKeyBackupVersion: () => Promise.resolve({}),
restoreKeyBackupWithCache,
};
const request = {
onVerifierFinished: () => undefined,
};
const verification = new VerificationBase(
undefined, // channel
client, // baseApis
userId,
"ABC", // deviceId
undefined, // startEvent
request,
);
verification._resolve = () => undefined;
const result = await verification.done();
/* We should request, and store, 3 cross signing keys and the key backup key */
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls.length).toBe(3);
expect(_secretStorage.request.mock.calls.length).toBe(4);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[0][1])
.toEqual(testKey);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[1][1])
.toEqual(testKey);
expect(storeSessionBackupPrivateKey.mock.calls[0][0])
.toEqual(testKey);
expect(restoreKeyBackupWithCache).toHaveBeenCalled();
expect(result).toBeInstanceOf(Array);
expect(result[0][0]).toBe(testKeyPub);
expect(result[1][0]).toBe(testKeyPub);
});
});
+70 -15
View File
@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,16 +15,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import TestClient from '../../../TestClient';
import sdk from '../../../..';
const MatrixEvent = sdk.MatrixEvent;
import {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
import nodeCrypto from "crypto";
import {logger} from '../../../../src/logger';
export async function makeTestClients(userInfos, options) {
const clients = [];
const clientMap = {};
const sendToDevice = function(type, map) {
// console.log(this.getUserId(), "sends", type, map);
// logger.log(this.getUserId(), "sends", type, map);
for (const [userId, devMap] of Object.entries(map)) {
if (userId in clientMap) {
for (const [deviceId, msg] of Object.entries(devMap)) {
@@ -33,31 +34,85 @@ export async function makeTestClients(userInfos, options) {
type: type,
content: msg,
});
setTimeout(
() => clientMap[userId][deviceId]
.emit("toDeviceEvent", event),
0,
const client = clientMap[userId][deviceId];
const decryptionPromise = event.isEncrypted() ?
event.attemptDecryption(client._crypto) :
Promise.resolve();
decryptionPromise.then(
() => client.emit("toDeviceEvent", event),
);
}
}
}
}
};
const sendEvent = function(room, type, content) {
// make up a unique ID as the event ID
const eventId = "$" + this.makeTxnId(); // eslint-disable-line babel/no-invalid-this
const rawEvent = {
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
type: type,
content: content,
room_id: room,
event_id: eventId,
origin_server_ts: Date.now(),
};
const event = new MatrixEvent(rawEvent);
const remoteEcho = new MatrixEvent(Object.assign({}, rawEvent, {
unsigned: {
transaction_id: this.makeTxnId(), // eslint-disable-line babel/no-invalid-this
},
}));
setImmediate(() => {
for (const tc of clients) {
if (tc.client === this) { // eslint-disable-line babel/no-invalid-this
logger.log("sending remote echo!!");
tc.client.emit("Room.timeline", remoteEcho);
} else {
tc.client.emit("Room.timeline", event);
}
}
});
return Promise.resolve({event_id: eventId});
};
for (const userInfo of userInfos) {
const client = (new TestClient(
let keys = {};
if (!options) options = {};
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
}
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined,
options,
)).client;
);
if (!(userInfo.userId in clientMap)) {
clientMap[userInfo.userId] = {};
}
clientMap[userInfo.userId][userInfo.deviceId] = client;
client.sendToDevice = sendToDevice;
clients.push(client);
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
testClient.client.sendToDevice = sendToDevice;
testClient.client.sendEvent = sendEvent;
clients.push(testClient);
}
await Promise.all(clients.map((client) => client.initCrypto()));
await Promise.all(clients.map((testClient) => testClient.client.initCrypto()));
return clients;
}
export function setupWebcrypto() {
global.crypto = {
getRandomValues: (buf) => {
return nodeCrypto.randomFillSync(buf);
},
};
}
export function teardownWebcrypto() {
global.crypto = undefined;
}
@@ -0,0 +1,285 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE} from
"../../../../src/crypto/verification/request/VerificationRequest";
import {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
import {ToDeviceChannel} from
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
import {setupWebcrypto, teardownWebcrypto} from "./util";
function makeMockClient(userId, deviceId) {
let counter = 1;
let events = [];
const deviceEvents = {};
return {
getUserId() { return userId; },
getDeviceId() { return deviceId; },
sendEvent(roomId, type, content) {
counter = counter + 1;
const eventId = `$${userId}-${deviceId}-${counter}`;
events.push(new MatrixEvent({
sender: userId,
event_id: eventId,
room_id: roomId,
type,
content,
origin_server_ts: Date.now(),
}));
return Promise.resolve({event_id: eventId});
},
sendToDevice(type, msgMap) {
for (const userId of Object.keys(msgMap)) {
const deviceMap = msgMap[userId];
for (const deviceId of Object.keys(deviceMap)) {
const content = deviceMap[deviceId];
const event = new MatrixEvent({content, type});
deviceEvents[userId] = deviceEvents[userId] || {};
deviceEvents[userId][deviceId] = deviceEvents[userId][deviceId] || [];
deviceEvents[userId][deviceId].push(event);
}
}
return Promise.resolve();
},
popEvents() {
const e = events;
events = [];
return e;
},
popDeviceEvents(userId, deviceId) {
const forDevice = deviceEvents[userId];
const events = forDevice && forDevice[deviceId];
const result = events || [];
if (events) {
delete forDevice[deviceId];
}
return result;
},
};
}
const MOCK_METHOD = "mock-verify";
class MockVerifier {
constructor(channel, client, userId, deviceId, startEvent) {
this._channel = channel;
this._startEvent = startEvent;
}
get events() {
return [DONE_TYPE];
}
async start() {
if (this._startEvent) {
await this._channel.send(DONE_TYPE, {});
} else {
await this._channel.send(START_TYPE, {method: MOCK_METHOD});
}
}
async handleEvent(event) {
if (event.getType() === DONE_TYPE && !this._startEvent) {
await this._channel.send(DONE_TYPE, {});
}
}
canSwitchStartEvent() {
return false;
}
}
function makeRemoteEcho(event) {
return new MatrixEvent(Object.assign({}, event.event, {
unsigned: {
transaction_id: "abc",
},
}));
}
async function distributeEvent(ownRequest, theirRequest, event) {
await ownRequest.channel.handleEvent(
makeRemoteEcho(event), ownRequest, true);
await theirRequest.channel.handleEvent(event, theirRequest, true);
}
jest.useFakeTimers();
describe("verification request unit tests", function() {
beforeAll(function() {
setupWebcrypto();
});
afterAll(() => {
teardownWebcrypto();
});
it("transition from UNSENT to DONE through happy path", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([[MOCK_METHOD, MockVerifier]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([[MOCK_METHOD, MockVerifier]]), bob);
expect(aliceRequest.invalid).toBe(true);
expect(bobRequest.invalid).toBe(true);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
expect(requestEvent.getType()).toBe("m.room.message");
await distributeEvent(aliceRequest, bobRequest, requestEvent);
expect(aliceRequest.requested).toBe(true);
expect(bobRequest.requested).toBe(true);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
expect(readyEvent.getType()).toBe(READY_TYPE);
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(bobRequest.ready).toBe(true);
expect(aliceRequest.ready).toBe(true);
const verifier = aliceRequest.beginKeyVerification(MOCK_METHOD);
await verifier.start();
const [startEvent] = alice.popEvents();
expect(startEvent.getType()).toBe(START_TYPE);
await distributeEvent(aliceRequest, bobRequest, startEvent);
expect(aliceRequest.started).toBe(true);
expect(aliceRequest.verifier).toBeInstanceOf(MockVerifier);
expect(bobRequest.started).toBe(true);
expect(bobRequest.verifier).toBeInstanceOf(MockVerifier);
await bobRequest.verifier.start();
const [bobDoneEvent] = bob.popEvents();
expect(bobDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(bobRequest, aliceRequest, bobDoneEvent);
const [aliceDoneEvent] = alice.popEvents();
expect(aliceDoneEvent.getType()).toBe(DONE_TYPE);
await distributeEvent(aliceRequest, bobRequest, aliceDoneEvent);
expect(aliceRequest.done).toBe(true);
expect(bobRequest.done).toBe(true);
});
it("methods only contains common methods", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()),
new Map([["c", function() {}], ["a", function() {}]]), alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map([["c", function() {}], ["b", function() {}]]), bob);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
await distributeEvent(aliceRequest, bobRequest, requestEvent);
await bobRequest.accept();
const [readyEvent] = bob.popEvents();
await distributeEvent(bobRequest, aliceRequest, readyEvent);
expect(aliceRequest.methods).toStrictEqual(["c"]);
expect(bobRequest.methods).toStrictEqual(["c"]);
});
it("other client accepting request puts it in observeOnly mode", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob1.getUserId()), new Map(), alice);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bob1Request = new VerificationRequest(
new InRoomChannel(bob1, "!room"), new Map(), bob1);
const bob2Request = new VerificationRequest(
new InRoomChannel(bob2, "!room"), new Map(), bob2);
await bob1Request.channel.handleEvent(requestEvent, bob1Request, true);
await bob2Request.channel.handleEvent(requestEvent, bob2Request, true);
await bob1Request.accept();
const [readyEvent] = bob1.popEvents();
expect(bob2Request.observeOnly).toBe(false);
await bob2Request.channel.handleEvent(readyEvent, bob2Request, true);
expect(bob2Request.observeOnly).toBe(true);
});
it("verify own device with to_device messages", async function() {
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const bob1Request = new VerificationRequest(
new ToDeviceChannel(bob1, bob1.getUserId(), ["device1", "device2"],
ToDeviceChannel.makeTransactionId(), "device2"),
new Map([[MOCK_METHOD, MockVerifier]]), bob1);
const to = {userId: "@bob:matrix.tld", deviceId: "device2"};
const verifier = bob1Request.beginKeyVerification(MOCK_METHOD, to);
expect(verifier).toBeInstanceOf(MockVerifier);
await verifier.start();
const [startEvent] = bob1.popDeviceEvents(to.userId, to.deviceId);
expect(startEvent.getType()).toBe(START_TYPE);
const bob2Request = new VerificationRequest(
new ToDeviceChannel(bob2, bob2.getUserId(), ["device1"]),
new Map([[MOCK_METHOD, MockVerifier]]), bob2);
await bob2Request.channel.handleEvent(startEvent, bob2Request, true);
await bob2Request.verifier.start();
const [doneEvent1] = bob2.popDeviceEvents("@bob:matrix.tld", "device1");
expect(doneEvent1.getType()).toBe(DONE_TYPE);
await bob1Request.channel.handleEvent(doneEvent1, bob1Request, true);
const [doneEvent2] = bob1.popDeviceEvents("@bob:matrix.tld", "device2");
expect(doneEvent2.getType()).toBe(DONE_TYPE);
await bob2Request.channel.handleEvent(doneEvent2, bob2Request, true);
expect(bob1Request.done).toBe(true);
expect(bob2Request.done).toBe(true);
});
it("request times out after 10 minutes", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()), new Map(), alice);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
await aliceRequest.channel.handleEvent(requestEvent, aliceRequest, true,
true, true);
expect(aliceRequest.cancelled).toBe(false);
expect(aliceRequest._cancellingUserId).toBe(undefined);
jest.advanceTimersByTime(10 * 60 * 1000);
expect(aliceRequest._cancellingUserId).toBe(alice.getUserId());
});
it("request times out 2 minutes after receipt", async function() {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()), new Map(), alice);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"), new Map(), bob);
await bobRequest.channel.handleEvent(requestEvent, bobRequest, true);
expect(bobRequest.cancelled).toBe(false);
expect(bobRequest._cancellingUserId).toBe(undefined);
jest.advanceTimersByTime(2 * 60 * 1000);
expect(bobRequest._cancellingUserId).toBe(bob.getUserId());
});
});
+14 -20
View File
@@ -1,16 +1,12 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
function mockRoomStates(timeline) {
timeline._startState = utils.mock(sdk.RoomState, "startState");
timeline._endState = utils.mock(sdk.RoomState, "endState");
timeline._startState = utils.mock(RoomState, "startState");
timeline._endState = utils.mock(RoomState, "endState");
}
import expect from 'expect';
describe("EventTimeline", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -18,8 +14,6 @@ describe("EventTimeline", function() {
let timeline;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// XXX: this is a horrid hack; should use sinon or something instead to mock
const timelineSet = { room: { roomId: roomId }};
timelineSet.room.getUnfilteredTimelineSet = function() {
@@ -78,7 +72,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.initialiseState(state);
}).toNotThrow();
}).not.toThrow();
timeline.addEvent(event, false);
expect(function() {
timeline.initialiseState(state);
@@ -121,7 +115,7 @@ describe("EventTimeline", function() {
const next = {b: "b"};
expect(function() {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS))
.toBe(prev);
expect(function() {
@@ -130,7 +124,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS))
.toBe(next);
expect(function() {
@@ -187,14 +181,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -229,14 +223,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -281,7 +275,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(true);
expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
@@ -311,7 +305,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(false);
expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
});
+6 -13
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundaction C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,20 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import sdk from '../..';
const MatrixEvent = sdk.MatrixEvent;
import testUtils from '../test-utils';
import expect from 'expect';
import Promise from 'bluebird';
import logger from '../../src/logger';
import {logger} from "../../src/logger";
import {MatrixEvent} from "../../src/models/event";
describe("MatrixEvent", () => {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe(".attemptDecryption", () => {
let encryptedEvent;
@@ -45,6 +36,7 @@ describe("MatrixEvent", () => {
let callCount = 0;
let prom2;
let prom2Fulfilled = false;
const crypto = {
decryptEvent: function() {
@@ -54,12 +46,13 @@ describe("MatrixEvent", () => {
// schedule a second decryption attempt while
// the first one is still running.
prom2 = encryptedEvent.attemptDecryption(crypto);
prom2.then(() => prom2Fulfilled = true);
const error = new Error("nope");
error.name = 'DecryptionError';
return Promise.reject(error);
} else {
expect(prom2.isFulfilled()).toBe(
expect(prom2Fulfilled).toBe(
false, 'second attemptDecryption resolved too soon');
return Promise.resolve({
+34
View File
@@ -0,0 +1,34 @@
import {FilterComponent} from "../../src/filter-component";
import {mkEvent} from '../test-utils';
describe("Filter Component", function() {
describe("types", function() {
it("should filter out events with other types", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.member',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(false);
});
it("should validate events with the same type", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.message',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(true);
});
});
});
+1 -8
View File
@@ -1,10 +1,4 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const Filter = sdk.Filter;
const utils = require("../test-utils");
import expect from 'expect';
import {Filter} from "../../src/filter";
describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul";
@@ -12,7 +6,6 @@ describe("Filter", function() {
let filter;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
filter = new Filter(userId);
});
+53 -35
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,18 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const utils = require("../test-utils");
const InteractiveAuth = sdk.InteractiveAuth;
const MatrixError = sdk.MatrixError;
import expect from 'expect';
import logger from '../../src/logger';
import {logger} from "../../src/logger";
import {InteractiveAuth} from "../../src/interactive-auth";
import {MatrixError} from "../../src/http-api";
// Trivial client object to test interactive auth
// (we do not need TestClient here)
@@ -35,13 +28,9 @@ class FakeClient {
}
describe("InteractiveAuth", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
it("should start an auth stage and complete it", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should start an auth stage and complete it", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -64,7 +53,7 @@ describe("InteractiveAuth", function() {
});
// first we expect a call here
stateUpdated.andCall(function(stage) {
stateUpdated.mockImplementation(function(stage) {
logger.log('aaaa');
expect(stage).toEqual("logintype");
ia.submitAuthDict({
@@ -75,7 +64,7 @@ describe("InteractiveAuth", function() {
// .. which should trigger a call here
const requestRes = {"a": "b"};
doRequest.andCall(function(authData) {
doRequest.mockImplementation(function(authData) {
logger.log('cccc');
expect(authData).toEqual({
session: "sessionId",
@@ -85,16 +74,16 @@ describe("InteractiveAuth", function() {
return Promise.resolve(requestRes);
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(1);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(1);
expect(stateUpdated).toBeCalledTimes(1);
});
});
it("should make a request if no authdata is provided", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should make a request if no authdata is provided", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -106,9 +95,9 @@ describe("InteractiveAuth", function() {
expect(ia.getStageParams("logintype")).toBe(undefined);
// first we expect a call to doRequest
doRequest.andCall(function(authData) {
doRequest.mockImplementation(function(authData) {
logger.log("request1", authData);
expect(authData).toEqual({});
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
session: "sessionId",
flows: [
@@ -124,7 +113,7 @@ describe("InteractiveAuth", function() {
// .. which should be followed by a call to stateUpdated
const requestRes = {"a": "b"};
stateUpdated.andCall(function(stage) {
stateUpdated.mockImplementation(function(stage) {
expect(stage).toEqual("logintype");
expect(ia.getSessionId()).toEqual("sessionId");
expect(ia.getStageParams("logintype")).toEqual({
@@ -132,7 +121,7 @@ describe("InteractiveAuth", function() {
});
// submitAuthDict should trigger another call to doRequest
doRequest.andCall(function(authData) {
doRequest.mockImplementation(function(authData) {
logger.log("request2", authData);
expect(authData).toEqual({
session: "sessionId",
@@ -148,10 +137,39 @@ describe("InteractiveAuth", function() {
});
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(2);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(2);
expect(stateUpdated).toBeCalledTimes(1);
});
});
it("should start an auth stage and reject if no auth flow", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
doRequest: doRequest,
stateUpdated: stateUpdated,
});
doRequest.mockImplementation(function(authData) {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
session: "sessionId",
flows: [],
params: {
"logintype": { param: "aa" },
},
});
err.httpStatus = 401;
throw err;
});
return ia.attemptAuth().catch(function(error) {
expect(error.message).toBe('No appropriate authentication flow found');
});
});
});
+1 -2
View File
@@ -1,5 +1,4 @@
import expect from 'expect';
import TestClient from '../TestClient';
import {TestClient} from '../TestClient';
describe('Login request', function() {
let client;
+30 -45
View File
@@ -1,13 +1,8 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const MatrixClient = sdk.MatrixClient;
const utils = require("../test-utils");
import {logger} from "../../src/logger";
import {MatrixClient} from "../../src/client";
import {Filter} from "../../src/filter";
import expect from 'expect';
import lolex from 'lolex';
import logger from '../../src/logger';
jest.useFakeTimers();
describe("MatrixClient", function() {
const userId = "@alice:bar";
@@ -16,7 +11,6 @@ describe("MatrixClient", function() {
let client;
let store;
let scheduler;
let clock;
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
@@ -85,7 +79,7 @@ describe("MatrixClient", function() {
);
}
pendingLookup = {
promise: Promise.defer().promise,
promise: new Promise(() => {}),
method: method,
path: path,
};
@@ -121,28 +115,26 @@ describe("MatrixClient", function() {
return Promise.resolve(next.data);
}
expect(true).toBe(false, "Expected different request. " + logLine);
return Promise.defer().promise;
return new Promise(() => {});
}
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
"getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
store.getClientOptions = expect.createSpy().andReturn(Promise.resolve(null));
store.storeClientOptions = expect.createSpy().andReturn(Promise.resolve(null));
store.isNewlyCreated = expect.createSpy().andReturn(Promise.resolve(true));
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
store.getClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.storeClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.isNewlyCreated = jest.fn().mockReturnValue(Promise.resolve(true));
client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: identityServerUrl,
@@ -154,13 +146,10 @@ describe("MatrixClient", function() {
});
// FIXME: We shouldn't be yanking _http like this.
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
client._http.authedRequest.andCall(httpReq);
client._http.authedRequestWithPrefix.andCall(httpReq);
client._http.requestWithPrefix.andCall(httpReq);
client._http.request.andCall(httpReq);
"authedRequest", "getContentUri", "request", "uploadContent",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
// set reasonable working defaults
acceptKeepalives = true;
@@ -172,17 +161,13 @@ describe("MatrixClient", function() {
});
afterEach(function() {
clock.uninstall();
// need to re-stub the requests with NOPs because there are no guarantees
// clients from previous tests will be GC'd before the next test. This
// means they may call /events and then fail an expect() which will fail
// a DIFFERENT test (pollution between tests!) - we return unresolved
// promises to stop the client from continuing to run.
client._http.authedRequest.andCall(function() {
return Promise.defer().promise;
});
client._http.authedRequestWithPrefix.andCall(function() {
return Promise.defer().promise;
client._http.authedRequest.mockImplementation(function() {
return new Promise(() => {});
});
});
@@ -191,10 +176,10 @@ describe("MatrixClient", function() {
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
const filterId = "ehfewf";
store.getFilterIdByName.andReturn(filterId);
const filter = new sdk.Filter(0, filterId);
store.getFilterIdByName.mockReturnValue(filterId);
const filter = new Filter(0, filterId);
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
store.getFilter.andReturn(filter);
store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => {
client.on("sync", function syncListener(state) {
if (state === "SYNCING") {
@@ -255,11 +240,11 @@ describe("MatrixClient", function() {
},
});
httpLookups.push(FILTER_RESPONSE);
store.getFilterIdByName.andReturn(invalidFilterId);
store.getFilterIdByName.mockReturnValue(invalidFilterId);
const filterName = getFilterName(client.credentials.userId);
client.store.setFilterIdByName(filterName, invalidFilterId);
const filter = new sdk.Filter(client.credentials.userId);
const filter = new Filter(client.credentials.userId);
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
@@ -287,7 +272,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(2);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -313,9 +298,9 @@ describe("MatrixClient", function() {
expect(client.retryImmediately()).toBe(
true, "retryImmediately returned false",
);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
clock.tick(10000);
jest.advanceTimersByTime(10000);
} else if (state === "SYNCING" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -337,7 +322,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(3);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -368,7 +353,7 @@ describe("MatrixClient", function() {
done();
}
// standard retry time is 5 to 10 seconds
clock.tick(10000);
jest.advanceTimersByTime(10000);
};
}
+2 -6
View File
@@ -1,9 +1,5 @@
"use strict";
import 'source-map-support/register';
const PushProcessor = require("../../lib/pushprocessor");
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {PushProcessor} from "../../src/pushprocessor";
describe('NotificationService', function() {
const testUserId = "@ali:matrix.org";
+43 -55
View File
@@ -1,53 +1,43 @@
"use strict";
import * as callbacks from "../../src/realtime-callbacks";
import 'source-map-support/register';
const callbacks = require("../../lib/realtime-callbacks");
const testUtils = require("../test-utils.js");
import expect from 'expect';
import lolex from 'lolex';
let wallTime = 1234567890;
jest.useFakeTimers();
describe("realtime-callbacks", function() {
let clock;
function tick(millis) {
clock.tick(millis);
wallTime += millis;
jest.advanceTimersByTime(millis);
}
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
const fakeDate = clock.Date;
callbacks.setNow(fakeDate.now.bind(fakeDate));
callbacks.setNow(() => wallTime);
});
afterEach(function() {
callbacks.setNow();
clock.uninstall();
});
describe("setTimeout", function() {
it("should call the callback after the timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 100);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(100);
expect(callback).toHaveBeenCalled();
});
it("should default to a zero timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(0);
expect(callback).toHaveBeenCalled();
});
it("should pass any parameters to the callback", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 0, "a", "b", "c");
tick(0);
expect(callback).toHaveBeenCalledWith("a", "b", "c");
@@ -66,10 +56,10 @@ describe("realtime-callbacks", function() {
});
it("should handle timeouts of several seconds", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 2000);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
for (let i = 0; i < 4; i++) {
tick(500);
}
@@ -77,24 +67,24 @@ describe("realtime-callbacks", function() {
});
it("should call multiple callbacks in the right order", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback3 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
const callback3 = jest.fn();
callbacks.setTimeout(callback2, 200);
callbacks.setTimeout(callback1, 100);
callbacks.setTimeout(callback3, 300);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -102,35 +92,34 @@ describe("realtime-callbacks", function() {
});
it("should treat -ve timeouts the same as a zero timeout", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
// check that cb1 is called before cb2
callback1.andCall(function() {
expect(callback2).toNotHaveBeenCalled();
callback1.mockImplementation(function() {
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
callbacks.setTimeout(callback2, -100);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
});
it("should not get confused by chained calls", function() {
const callback2 = expect.createSpy();
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback2 = jest.fn();
const callback1 = jest.fn(function() {
callbacks.setTimeout(callback2, 0);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
// the fake timer won't actually run callbacks registered during
@@ -140,16 +129,15 @@ describe("realtime-callbacks", function() {
});
it("should be immune to exceptions", function() {
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback1 = jest.fn(function() {
throw new Error("prepare to die");
});
const callback2 = expect.createSpy();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 0);
callbacks.setTimeout(callback2, 0);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -158,16 +146,16 @@ describe("realtime-callbacks", function() {
describe("cancelTimeout", function() {
it("should cancel a pending timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
const k = callbacks.setTimeout(callback);
callbacks.clearTimeout(k);
tick(0);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
});
it("should not affect sooner timeouts", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 100);
const k = callbacks.setTimeout(callback2, 200);
@@ -175,10 +163,10 @@ describe("realtime-callbacks", function() {
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(150);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
});
});
+7 -19
View File
@@ -1,10 +1,5 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {RoomMember} from "../../src/models/room-member";
describe("RoomMember", function() {
const roomId = "!foo:bar";
@@ -14,7 +9,6 @@ describe("RoomMember", function() {
let member;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
member = new RoomMember(roomId, userA);
});
@@ -36,13 +30,7 @@ describe("RoomMember", function() {
const url = member.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
"was no m.room.member event", function() {
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", true);
expect(url.indexOf("http")).toEqual(0); // don't care about form
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return nothing if there is no m.room.member and allowDefault=false",
@@ -255,9 +243,9 @@ describe("RoomMember", function() {
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).toNotEqual("Alice"); // it should disambig.
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).toNotEqual(-1);
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should emit 'RoomMember.membership' if the membership changes", function() {
@@ -328,9 +316,9 @@ describe("RoomMember", function() {
};
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).toNotEqual("Alíce"); // it should disambig.
expect(member.name).not.toEqual("Alíce"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).toNotEqual(-1);
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
});
});
+9 -15
View File
@@ -1,11 +1,6 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const RoomState = sdk.RoomState;
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {RoomState} from "../../src/models/room-state";
import {RoomMember} from "../../src/models/room-member";
describe("RoomState", function() {
const roomId = "!foo:bar";
@@ -17,7 +12,6 @@ describe("RoomState", function() {
let state;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
state = new RoomState(roomId);
state.setStateEvents([
utils.mkMembership({ // userA joined
@@ -49,8 +43,8 @@ describe("RoomState", function() {
const members = state.getMembers();
expect(members.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(members[0].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[0].userId)).not.toEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).not.toEqual(-1);
});
});
@@ -120,8 +114,8 @@ describe("RoomState", function() {
const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1);
});
it("should return a single MatrixEvent if a state_key was specified",
@@ -258,7 +252,7 @@ describe("RoomState", function() {
});
state.setStateEvents([memberEvent]);
expect(state.members[userA].setMembershipEvent).toNotHaveBeenCalled();
expect(state.members[userA].setMembershipEvent).not.toHaveBeenCalled();
expect(state.members[userB].setMembershipEvent).toHaveBeenCalledWith(
memberEvent, state,
);
@@ -306,7 +300,7 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const memberA = state.getMember(userA);
expect(memberA.events.member.getId()).toNotEqual(oobMemberEvent.getId());
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA.isOutOfBand()).toEqual(false);
});
+40 -56
View File
@@ -1,14 +1,8 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const Room = sdk.Room;
const RoomState = sdk.RoomState;
const MatrixEvent = sdk.MatrixEvent;
const EventStatus = sdk.EventStatus;
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
import expect from 'expect';
import * as utils from "../test-utils";
import {EventStatus, MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
import {Room} from "../../src/models/room";
describe("Room", function() {
const roomId = "!foo:bar";
@@ -19,20 +13,19 @@ describe("Room", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
room = new Room(roomId);
// mock RoomStates
room.oldState = room.getLiveTimeline()._startState =
utils.mock(sdk.RoomState, "oldState");
utils.mock(RoomState, "oldState");
room.currentState = room.getLiveTimeline()._endState =
utils.mock(sdk.RoomState, "currentState");
utils.mock(RoomState, "currentState");
});
describe("getAvatarUrl", function() {
const hsUrl = "https://my.home.server";
it("should return the URL from m.room.avatar preferentially", function() {
room.currentState.getStateEvents.andCall(function(type, key) {
room.currentState.getStateEvents.mockImplementation(function(type, key) {
if (type === "m.room.avatar" && key === "") {
return utils.mkEvent({
event: true,
@@ -49,13 +42,7 @@ describe("Room", function() {
const url = room.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
"was no m.room.avatar event", function() {
const url = room.getAvatarUrl(hsUrl, 64, 64, "crop", true);
expect(url.indexOf("http")).toEqual(0); // don't care about form
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return nothing if there is no m.room.avatar and allowDefault=false",
@@ -67,13 +54,13 @@ describe("Room", function() {
describe("getMember", function() {
beforeEach(function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": {
userId: userA,
roomId: roomId,
},
}[userId];
}[userId] || null;
});
});
@@ -82,7 +69,7 @@ describe("Room", function() {
});
it("should return the member from current state", function() {
expect(room.getMember(userA)).toNotEqual(null);
expect(room.getMember(userA)).not.toEqual(null);
});
});
@@ -174,7 +161,7 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(true);
expect(events[1].forwardLooking).toBe(true);
expect(room.oldState.setStateEvents).toNotHaveBeenCalled();
expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
});
it("should synthesize read receipts for the senders of events", function() {
@@ -183,7 +170,7 @@ describe("Room", function() {
membership: "join",
name: "Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
@@ -292,13 +279,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -331,13 +318,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -379,7 +366,7 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
});
});
@@ -545,7 +532,7 @@ describe("Room", function() {
describe("getJoinedMembers", function() {
it("should return members whose membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@alice:bar", membership: "join" },
{ userId: "@bob:bar", membership: "invite" },
@@ -558,7 +545,7 @@ describe("Room", function() {
});
it("should return an empty list if no membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@bob:bar", membership: "invite" },
];
@@ -571,7 +558,7 @@ describe("Room", function() {
describe("hasMembershipState", function() {
it("should return true for a matching userId and membership",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
@@ -582,7 +569,7 @@ describe("Room", function() {
it("should return false if match membership but no match userId",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -592,7 +579,7 @@ describe("Room", function() {
it("should return false if match userId but no match membership",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -602,7 +589,7 @@ describe("Room", function() {
it("should return false if no match membership or userId",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -624,13 +611,10 @@ describe("Room", function() {
}, event: true,
})]);
};
const setAliases = function(aliases, stateKey) {
if (!stateKey) {
stateKey = "flibble";
}
const setAltAliases = function(aliases) {
room.addLiveEvents([utils.mkEvent({
type: "m.room.aliases", room: roomId, skey: stateKey, content: {
aliases: aliases,
type: "m.room.canonical_alias", room: roomId, skey: "", content: {
alt_aliases: aliases,
}, event: true,
})]);
};
@@ -814,8 +798,8 @@ describe("Room", function() {
addMember(userC);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should return the names of members in a public (public join_rules)" +
@@ -827,8 +811,8 @@ describe("Room", function() {
addMember(userC);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should show the other user's name for public (public join_rules)" +
@@ -839,7 +823,7 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private " +
@@ -850,7 +834,7 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private" +
@@ -860,14 +844,14 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the room alias if one exists for private " +
"(invite join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("invite");
setAliases([alias, "#another:one"]);
setAltAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
@@ -877,7 +861,7 @@ describe("Room", function() {
"(public join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("public");
setAliases([alias, "#another:one"]);
setAltAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
@@ -1004,7 +988,7 @@ describe("Room", function() {
it("should emit an event when a receipt is added",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.receipt", listener);
const ts = 13787898424;
@@ -1175,7 +1159,7 @@ describe("Room", function() {
it("should emit Room.tags event when new tags are " +
"received on the event stream",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.tags", listener);
const tags = { "m.foo": { "order": 0.5 } };
@@ -1389,7 +1373,7 @@ describe("Room", function() {
let hasThrown = false;
try {
await room.loadMembersIfNeeded();
} catch(err) {
} catch (err) {
hasThrown = true;
}
expect(hasThrown).toEqual(true);
+22 -32
View File
@@ -1,22 +1,18 @@
// This file had a function whose name is all caps, which displeases eslint
/* eslint new-cap: "off" */
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const MatrixScheduler = sdk.MatrixScheduler;
const MatrixError = sdk.MatrixError;
const utils = require("../test-utils");
import {defer} from '../../src/utils';
import {MatrixError} from "../../src/http-api";
import {MatrixScheduler} from "../../src/scheduler";
import * as utils from "../test-utils";
import expect from 'expect';
import lolex from 'lolex';
jest.useFakeTimers();
describe("MatrixScheduler", function() {
let clock;
let scheduler;
let retryFn;
let queueFn;
let defer;
let deferred;
const roomId = "!foo:bar";
const eventA = utils.mkMessage({
user: "@alice:bar", room: roomId, event: true,
@@ -26,8 +22,6 @@ describe("MatrixScheduler", function() {
});
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
scheduler = new MatrixScheduler(function(ev, attempts, err) {
if (retryFn) {
return retryFn(ev, attempts, err);
@@ -41,11 +35,7 @@ describe("MatrixScheduler", function() {
});
retryFn = null;
queueFn = null;
defer = Promise.defer();
});
afterEach(function() {
clock.uninstall();
deferred = defer();
});
it("should process events in a queue in a FIFO manner", async function() {
@@ -55,8 +45,8 @@ describe("MatrixScheduler", function() {
queueFn = function() {
return "one_big_queue";
};
const deferA = Promise.defer();
const deferB = Promise.defer();
const deferA = defer();
const deferB = defer();
let yieldedA = false;
scheduler.setProcessFunction(function(event) {
if (yieldedA) {
@@ -82,7 +72,7 @@ describe("MatrixScheduler", function() {
it("should invoke the retryFn on failure and wait the amount of time specified",
async function() {
const waitTimeMs = 1500;
const retryDefer = Promise.defer();
const retryDefer = defer();
retryFn = function() {
retryDefer.resolve();
return waitTimeMs;
@@ -96,9 +86,9 @@ describe("MatrixScheduler", function() {
procCount += 1;
if (procCount === 1) {
expect(ev).toEqual(eventA);
return defer.promise;
return deferred.promise;
} else if (procCount === 2) {
// don't care about this defer
// don't care about this deferred
return new Promise();
}
expect(procCount).toBeLessThan(3);
@@ -109,10 +99,10 @@ describe("MatrixScheduler", function() {
// wait just long enough before it does
await Promise.resolve();
expect(procCount).toEqual(1);
defer.reject({});
deferred.reject({});
await retryDefer.promise;
expect(procCount).toEqual(1);
clock.tick(waitTimeMs);
jest.advanceTimersByTime(waitTimeMs);
await Promise.resolve();
expect(procCount).toEqual(2);
});
@@ -129,8 +119,8 @@ describe("MatrixScheduler", function() {
return "yep";
};
const deferA = Promise.defer();
const deferB = Promise.defer();
const deferA = defer();
const deferB = defer();
let procCount = 0;
scheduler.setProcessFunction(function(ev) {
procCount += 1;
@@ -153,7 +143,7 @@ describe("MatrixScheduler", function() {
deferA.reject({});
try {
await globalA;
} catch(err) {
} catch (err) {
await Promise.resolve();
expect(procCount).toEqual(2);
}
@@ -185,14 +175,14 @@ describe("MatrixScheduler", function() {
const expectOrder = [
eventA.getId(), eventB.getId(), eventD.getId(),
];
const deferA = Promise.defer();
const deferA = defer();
scheduler.setProcessFunction(function(event) {
const id = expectOrder.shift();
expect(id).toEqual(event.getId());
if (expectOrder.length === 0) {
done();
}
return id === eventA.getId() ? deferA.promise : defer.promise;
return id === eventA.getId() ? deferA.promise : deferred.promise;
});
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
@@ -203,7 +193,7 @@ describe("MatrixScheduler", function() {
setTimeout(function() {
deferA.resolve({});
}, 1000);
clock.tick(1000);
jest.advanceTimersByTime(1000);
});
describe("queueEvent", function() {
@@ -306,7 +296,7 @@ describe("MatrixScheduler", function() {
scheduler.setProcessFunction(function(ev) {
procCount += 1;
expect(ev).toEqual(eventA);
return defer.promise;
return deferred.promise;
});
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
@@ -322,7 +312,7 @@ describe("MatrixScheduler", function() {
let procCount = 0;
scheduler.setProcessFunction(function(ev) {
procCount += 1;
return defer.promise;
return deferred.promise;
});
expect(procCount).toEqual(0);
});
+67 -7
View File
@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,19 +15,45 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import utils from "../test-utils";
import sdk from "../..";
import expect from 'expect';
import {SyncAccumulator} from "../../src/sync-accumulator";
const SyncAccumulator = sdk.SyncAccumulator;
// The event body & unsigned object get frozen to assert that they don't get altered
// by the impl
const RES_WITH_AGE = {
next_batch: "abc",
rooms: {
invite: {},
leave: {},
join: {
"!foo:bar": {
account_data: { events: [] },
ephemeral: { events: [] },
unread_notifications: {},
timeline: {
events: [
Object.freeze({
content: {
body: "This thing is happening right now!",
},
origin_server_ts: 123456789,
sender: "@alice:localhost",
type: "m.room.message",
unsigned: Object.freeze({
age: 50,
}),
}),
],
prev_batch: "something",
},
},
},
},
};
describe("SyncAccumulator", function() {
let sa;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
sa = new SyncAccumulator({
maxTimelineEntries: 10,
});
@@ -374,6 +401,39 @@ describe("SyncAccumulator", function() {
expect(summary["m.joined_member_count"]).toEqual(5);
expect(summary["m.heroes"]).toEqual(["@bob:bar"]);
});
it("should return correctly adjusted age attributes", () => {
const delta = 1000;
const startingTs = 1000;
const oldDateNow = Date.now;
try {
Date.now = jest.fn();
Date.now.mockReturnValue(startingTs);
sa.accumulate(RES_WITH_AGE);
Date.now.mockReturnValue(startingTs + delta);
const output = sa.getJSON();
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned.age + delta,
);
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
} finally {
Date.now = oldDateNow;
}
});
it("should mangle age without adding extra keys", () => {
sa.accumulate(RES_WITH_AGE);
const output = sa.getJSON();
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
});
});
});
+33 -51
View File
@@ -1,13 +1,6 @@
"use strict";
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const EventTimeline = sdk.EventTimeline;
const TimelineWindow = sdk.TimelineWindow;
const TimelineIndex = require("../../lib/timeline-window").TimelineIndex;
const utils = require("../test-utils");
import expect from 'expect';
import {EventTimeline} from "../../src/models/event-timeline";
import {TimelineIndex, TimelineWindow} from "../../src/timeline-window";
import * as utils from "../test-utils";
const ROOM_ID = "roomId";
const USER_ID = "userId";
@@ -67,10 +60,6 @@ function createLinkedTimelines() {
describe("TimelineIndex", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("minIndex", function() {
it("should return the min index relative to BaseIndex", function() {
const timelineIndex = new TimelineIndex(createTimeline(), 0);
@@ -153,7 +142,7 @@ describe("TimelineWindow", function() {
let timelineSet;
let client;
function createWindow(timeline, opts) {
timelineSet = {};
timelineSet = {getTimelineForEvent: () => null};
client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -163,12 +152,8 @@ describe("TimelineWindow", function() {
return new TimelineWindow(client, timelineSet, opts);
}
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("load", function() {
it("should initialise from the live timeline", function(done) {
it("should initialise from the live timeline", function() {
const liveTimeline = createTimeline();
const room = {};
room.getLiveTimeline = function() {
@@ -176,17 +161,17 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(undefined, room);
timelineWindow.load(undefined, 2).then(function() {
return timelineWindow.load(undefined, 2).then(function() {
const expectedEvents = liveTimeline.getEvents().slice(1);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should initialise from a specific event", function(done) {
it("should initialise from a specific event", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -195,21 +180,20 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(client, timelineSet);
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("canPaginate should return false until load has returned",
function(done) {
it("canPaginate should return false until load has returned", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok1", EventTimeline.BACKWARDS);
timeline.setPaginationToken("toktok2", EventTimeline.FORWARDS);
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
const timelineWindow = new TimelineWindow(client, timelineSet);
@@ -222,25 +206,24 @@ describe("TimelineWindow", function() {
return Promise.resolve(timeline);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
expect(timelineWindow.canPaginate(EventTimeline.BACKWARDS))
.toBe(true);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
describe("pagination", function() {
it("should be able to advance across the initial timeline",
function(done) {
it("should be able to advance across the initial timeline", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineWindow = createWindow(timeline);
timelineWindow.load(eventId, 1).then(function() {
return timelineWindow.load(eventId, 1).then(function() {
const expectedEvents = [timeline.getEvents()[1]];
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -277,15 +260,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should advance into next timeline", function(done) {
it("should advance into next timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[0].getEvents()[1].getId();
const timelineWindow = createWindow(tls[0], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[0].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -322,15 +305,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.FORWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should retreat into previous timeline", function(done) {
it("should retreat into previous timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[1].getEvents()[1].getId();
const timelineWindow = createWindow(tls[1], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[1].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -367,10 +350,10 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should make forward pagination requests", function(done) {
it("should make forward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -386,7 +369,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -399,11 +382,11 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(0, 5);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should make backward pagination requests", function(done) {
it("should make backward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.BACKWARDS);
@@ -419,7 +402,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -432,11 +415,10 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(1, 6);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should limit the number of unsuccessful pagination requests",
function(done) {
it("should limit the number of unsuccessful pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -452,7 +434,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -471,7 +453,7 @@ describe("TimelineWindow", function() {
.toBe(false);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
});
+2 -8
View File
@@ -1,17 +1,11 @@
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const User = sdk.User;
const utils = require("../test-utils");
import expect from 'expect';
import {User} from "../../src/models/user";
import * as utils from "../test-utils";
describe("User", function() {
const userId = "@alice:bar";
let user;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
user = new User(userId);
});
+3 -12
View File
@@ -1,15 +1,6 @@
"use strict";
import 'source-map-support/register';
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
import expect from 'expect';
import * as utils from "../../src/utils";
describe("utils", function() {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("encodeParams", function() {
it("should url encode and concat with &s", function() {
const params = {
@@ -135,7 +126,7 @@ describe("utils", function() {
utils.checkObjectHasKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
@@ -152,7 +143,7 @@ describe("utils", function() {
utils.checkObjectHasNoAdditionalKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
+193
View File
@@ -0,0 +1,193 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {TestClient} from '../../TestClient';
import {MatrixCall, CallErrorCode} from '../../../src/webrtc/call';
const DUMMY_SDP = (
"v=0\r\n" +
"o=- 5022425983810148698 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n" +
"a=msid-semantic: WMS h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA\r\n" +
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" +
"c=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:hLDR\r\n" +
"a=ice-pwd:bMGD9aOldHWiI+6nAq/IIlRw\r\n" +
"a=ice-options:trickle\r\n" +
"a=fingerprint:sha-256 E4:94:84:F9:4A:98:8A:56:F5:5F:FD:AF:72:B9:32:89:49:5C:4B:9A:" +
"4A:15:8E:41:8A:F3:69:E4:39:52:DC:D6\r\n" +
"a=setup:active\r\n" +
"a=mid:0\r\n" +
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" +
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" +
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n" +
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n" +
"a=sendrecv\r\n" +
"a=msid:h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA 4357098f-3795-4131-bff4-9ba9c0348c49\r\n" +
"a=rtcp-mux\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" +
"a=rtcp-fb:111 transport-cc\r\n" +
"a=fmtp:111 minptime=10;useinbandfec=1\r\n" +
"a=rtpmap:103 ISAC/16000\r\n" +
"a=rtpmap:104 ISAC/32000\r\n" +
"a=rtpmap:9 G722/8000\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" +
"a=rtpmap:106 CN/32000\r\n" +
"a=rtpmap:105 CN/16000\r\n" +
"a=rtpmap:13 CN/8000\r\n" +
"a=rtpmap:110 telephone-event/48000\r\n" +
"a=rtpmap:112 telephone-event/32000\r\n" +
"a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" +
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n"
);
class MockRTCPeerConnection {
localDescription: RTCSessionDescription;
constructor() {
this.localDescription = {
sdp: DUMMY_SDP,
type: 'offer',
toJSON: function() {},
};
}
addEventListener() {}
createOffer() {
return Promise.resolve({});
}
setRemoteDescription() {
return Promise.resolve();
}
setLocalDescription() {
return Promise.resolve();
}
close() {}
}
describe('Call', function() {
let client;
let call;
let prevNavigator;
let prevDocument;
let prevWindow;
beforeEach(function() {
prevNavigator = global.navigator;
prevDocument = global.document;
prevWindow = global.window;
global.navigator = {
mediaDevices: {
// @ts-ignore Mock
getUserMedia: () => {
return {
getTracks: () => [],
getAudioTracks: () => [],
getVideoTracks: () => [],
};
},
},
};
global.window = {
// @ts-ignore Mock
RTCPeerConnection: MockRTCPeerConnection,
// @ts-ignore Mock
RTCSessionDescription: {},
// @ts-ignore Mock
RTCIceCandidate: {},
getUserMedia: {},
};
// @ts-ignore Mock
global.document = {};
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
// We just stub out sendEvent: we're not interested in testing the client's
// event sending code here
client.client.sendEvent = () => {};
call = new MatrixCall({
client: client.client,
roomId: '!foo:bar',
});
// call checks one of these is wired up
call.on('error', () => {});
});
afterEach(function() {
client.stop();
global.navigator = prevNavigator;
global.window = prevWindow;
global.document = prevDocument;
});
it('should ignore candidate events from non-matching party ID', async function() {
await call.placeVoiceCall();
await call.onAnswerReceived({
getContent: () => {
return {
version: 0,
call_id: call.callId,
party_id: 'the_correct_party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
call.peerConn.addIceCandidate = jest.fn();
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 0,
call_id: call.callId,
party_id: 'the_correct_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 0,
call_id: call.callId,
party_id: 'some_other_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
// Hangup to stop timers
call.hangup(CallErrorCode.UserHangup, true);
});
});
+88
View File
@@ -0,0 +1,88 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export enum EventType {
// Room state events
RoomCanonicalAlias = "m.room.canonical_alias",
RoomCreate = "m.room.create",
RoomJoinRules = "m.room.join_rules",
RoomMember = "m.room.member",
RoomThirdPartyInvite = "m.room.third_party_invite",
RoomPowerLevels = "m.room.power_levels",
RoomName = "m.room.name",
RoomTopic = "m.room.topic",
RoomAvatar = "m.room.avatar",
RoomPinnedEvents = "m.room.pinned_events",
RoomEncryption = "m.room.encryption",
RoomHistoryVisibility = "m.room.history_visibility",
RoomGuestAccess = "m.room.guest_access",
RoomServerAcl = "m.room.server_acl",
RoomTombstone = "m.room.tombstone",
/**
* @deprecated Should not be used.
*/
RoomAliases = "m.room.aliases", // deprecated https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
// Room timeline events
RoomRedaction = "m.room.redaction",
RoomMessage = "m.room.message",
RoomMessageEncrypted = "m.room.encrypted",
Sticker = "m.sticker",
CallInvite = "m.call.invite",
CallCandidates = "m.call.candidates",
CallAnswer = "m.call.answer",
CallHangup = "m.call.hangup",
CallReject = "m.call.reject",
CallSelectAnswer = "m.call.select_answer",
CallNegotiate = "m.call.negotiate",
KeyVerificationRequest = "m.key.verification.request",
KeyVerificationStart = "m.key.verification.start",
KeyVerificationCancel = "m.key.verification.cancel",
KeyVerificationMac = "m.key.verification.mac",
// use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback
RoomMessageFeedback = "m.room.message.feedback",
// Room ephemeral events
Typing = "m.typing",
Receipt = "m.receipt",
Presence = "m.presence",
// Room account_data events
FullyRead = "m.fully_read",
Tag = "m.tag",
// User account_data events
PushRules = "m.push_rules",
Direct = "m.direct",
IgnoredUserList = "m.ignored_user_list",
// to_device events
RoomKey = "m.room_key",
RoomKeyRequest = "m.room_key_request",
ForwardedRoomKey = "m.forwarded_room_key",
Dummy = "m.dummy",
}
export enum MsgType {
Text = "m.text",
Emote = "m.emote",
Notice = "m.notice",
Image = "m.image",
File = "m.file",
Audio = "m.audio",
Location = "m.location",
Video = "m.video",
}
+48
View File
@@ -0,0 +1,48 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// this is needed to tell TS about global.Olm
import * as Olm from "olm"; // eslint-disable-line @typescript-eslint/no-unused-vars
export {};
declare global {
namespace NodeJS {
interface Global {
localStorage: Storage;
}
}
interface MediaDevices {
// This is experimental and types don't know about it yet
// https://github.com/microsoft/TypeScript/issues/33232
getDisplayMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
}
interface HTMLAudioElement {
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string);
}
interface DummyInterfaceWeShouldntBeUsingThis {}
interface Navigator {
// We check for the webkit-prefixed getUserMedia to detect if we're
// on webkit: we should check if we still need to do this
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ limitations under the License.
* @module
*/
export default class Reemitter {
export class ReEmitter {
constructor(target) {
this.target = target;
+30 -18
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,9 +17,8 @@ limitations under the License.
/** @module auto-discovery */
import Promise from 'bluebird';
import logger from './logger';
import { URL as NodeURL } from "url";
import {logger} from './logger';
import {URL as NodeURL} from "url";
// Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
@@ -275,21 +275,11 @@ export class AutoDiscovery {
let isUrl = "";
if (wellknown["m.identity_server"]) {
// We prepare a failing identity server response to save lines later
// in this branch. Note that we also fail the homeserver check in the
// object because according to the spec we're supposed to FAIL_ERROR
// if *anything* goes wrong with the IS validation, including invalid
// format. This means we're supposed to stop discovery completely.
// in this branch.
const failingClientConfig = {
"m.homeserver": {
state: AutoDiscovery.FAIL_ERROR,
error: AutoDiscovery.ERROR_INVALID_IS,
// We'll provide the base_url that was previously valid for
// debugging purposes.
base_url: clientConfig["m.homeserver"].base_url,
},
"m.homeserver": clientConfig["m.homeserver"],
"m.identity_server": {
state: AutoDiscovery.FAIL_ERROR,
state: AutoDiscovery.FAIL_PROMPT,
error: AutoDiscovery.ERROR_INVALID_IS,
base_url: null,
},
@@ -429,6 +419,26 @@ export class AutoDiscovery {
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw);
}
/**
* Gets the raw discovery client configuration for the given domain name.
* Should only be used if there's no validation to be done on the resulting
* object, otherwise use findClientConfig().
* @param {string} domain The domain to get the client config for.
* @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object.
*/
static async getRawClientConfig(domain) {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
const response = await this._fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!response) return {};
return response.raw || {};
}
/**
* Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and
* is suitable for the requirements laid out by .well-known auto discovery.
@@ -492,10 +502,12 @@ export class AutoDiscovery {
request(
{ method: "GET", uri: url, timeout: 5000 },
(err, response, body) => {
if (err || response.statusCode < 200 || response.statusCode >= 300) {
if (err || response &&
(response.statusCode < 200 || response.statusCode >= 300)
) {
let action = "FAIL_PROMPT";
let reason = (err ? err.message : null) || "General failure";
if (response.statusCode === 404) {
if (response && response.statusCode === 404) {
action = "IGNORE";
reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN;
}
+553 -199
View File
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import request from "browser-request";
import queryString from "qs";
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
// we mimic `request`'s query string interface to make it all work
// as expected.
// browser-request will happily take the constructed string as the
// query string without trying to modify it further.
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
return request(opts, fn);
});
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
try {
indexedDB = global.indexedDB;
} catch (e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto",
);
},
);
}
// We export 3 things to make browserify happy as well as downstream projects.
// It's awkward, but required.
export * from "./matrix";
export default matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;
+1754 -691
View File
File diff suppressed because it is too large Load Diff
+77 -78
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,88 +14,86 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/** @module ContentHelpers */
module.exports = {
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlMessage: function(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
/**
* Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlNotice: function(body, htmlBody) {
return {
msgtype: "m.notice",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlMessage(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlEmote: function(body, htmlBody) {
return {
msgtype: "m.emote",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
/**
* Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlNotice(body, htmlBody) {
return {
msgtype: "m.notice",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
makeTextMessage: function(body) {
return {
msgtype: "m.text",
body: body,
};
},
/**
* Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlEmote(body, htmlBody) {
return {
msgtype: "m.emote",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
makeNotice: function(body) {
return {
msgtype: "m.notice",
body: body,
};
},
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeTextMessage(body) {
return {
msgtype: "m.text",
body: body,
};
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
makeEmoteMessage: function(body) {
return {
msgtype: "m.emote",
body: body,
};
},
};
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
export function makeNotice(body) {
return {
msgtype: "m.notice",
body: body,
};
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeEmoteMessage(body) {
return {
msgtype: "m.emote",
body: body,
};
}
+54 -87
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,95 +17,61 @@ limitations under the License.
/**
* @module content-repo
*/
const utils = require("./utils");
/** Content Repo utility functions */
module.exports = {
/**
* Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} mxc The mxc:// URI.
* @param {Number} width The desired width of the thumbnail.
* @param {Number} height The desired height of the thumbnail.
* @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale".
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
* directly. Fetching such URLs will leak information about the user to
* anyone they share a room with. If false, will return the emptry string
* for such URLs.
* @return {string} The complete URL to the content.
*/
getHttpUriForMxc: function(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) {
import * as utils from "./utils";
/**
* Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} mxc The mxc:// URI.
* @param {Number} width The desired width of the thumbnail.
* @param {Number} height The desired height of the thumbnail.
* @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale".
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
* directly. Fetching such URLs will leak information about the user to
* anyone they share a room with. If false, will return the emptry string
* for such URLs.
* @return {string} The complete URL to the content.
*/
export function getHttpUriForMxc(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) {
return '';
}
if (mxc.indexOf("mxc://") !== 0) {
if (allowDirectLinks) {
return mxc;
} else {
return '';
}
if (mxc.indexOf("mxc://") !== 0) {
if (allowDirectLinks) {
return mxc;
} else {
return '';
}
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/r0/download/";
const params = {};
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/r0/download/";
const params = {};
if (width) {
params.width = Math.round(width);
}
if (height) {
params.height = Math.round(height);
}
if (resizeMethod) {
params.method = resizeMethod;
}
if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/r0/thumbnail/";
}
if (width) {
params.width = Math.round(width);
}
if (height) {
params.height = Math.round(height);
}
if (resizeMethod) {
params.method = resizeMethod;
}
if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/r0/thumbnail/";
}
const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = "";
if (fragmentOffset >= 0) {
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
},
/**
* Get an identicon URL from an arbitrary string.
* @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} identiconString The string to create an identicon for.
* @param {Number} width The desired width of the image in pixels. Default: 96.
* @param {Number} height The desired height of the image in pixels. Default: 96.
* @return {string} The complete URL to the identicon.
* @deprecated This is no longer in the specification.
*/
getIdenticonUri: function(baseUrl, identiconString, width, height) {
if (!identiconString) {
return null;
}
if (!width) {
width = 96;
}
if (!height) {
height = 96;
}
const params = {
width: width,
height: height,
};
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
$ident: identiconString,
});
return baseUrl + path +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params)));
},
};
const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = "";
if (fragmentOffset >= 0) {
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
}
+816
View File
@@ -0,0 +1,816 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Cross signing methods
* @module crypto/CrossSigning
*/
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
function publicKeyFromKeyInfo(keyInfo) {
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
// We assume only a single key, and we want the bare form without type
// prefix, so we select the values.
return Object.values(keyInfo.keys)[0];
}
export class CrossSigningInfo extends EventEmitter {
/**
* Information about a user's cross-signing keys
*
* @class
*
* @param {string} userId the user that the information is about
* @param {object} callbacks Callbacks used to interact with the app
* Requires getCrossSigningKey and saveCrossSigningKeys
* @param {object} cacheCallbacks Callbacks used to interact with the cache
*/
constructor(userId, callbacks, cacheCallbacks) {
super();
// you can't change the userId
Object.defineProperty(this, 'userId', {
enumerable: true,
value: userId,
});
this._callbacks = callbacks || {};
this._cacheCallbacks = cacheCallbacks || {};
this.keys = {};
this.firstUse = true;
// This tracks whether we've ever verified this user with any identity.
// When you verify a user, any devices online at the time that receive
// the verifying signature via the homeserver will latch this to true
// and can use it in the future to detect cases where the user has
// become unverifed later for any reason.
this.crossSigningVerifiedBefore = false;
}
static fromStorage(obj, userId) {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
res[prop] = obj[prop];
}
}
return res;
}
toStorage() {
return {
keys: this.keys,
firstUse: this.firstUse,
crossSigningVerifiedBefore: this.crossSigningVerifiedBefore,
};
}
/**
* Calls the app callback to ask for a private key
*
* @param {string} type The key type ("master", "self_signing", or "user_signing")
* @param {string} expectedPubkey The matching public key or undefined to use
* the stored public key for the given key type.
* @returns {Array} An array with [ public key, Olm.PkSigning ]
*/
async getCrossSigningKey(type, expectedPubkey) {
const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0;
if (!this._callbacks.getCrossSigningKey) {
throw new Error("No getCrossSigningKey callback supplied");
}
if (expectedPubkey === undefined) {
expectedPubkey = this.getId(type);
}
function validateKey(key) {
if (!key) return;
const signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(key);
if (gotPubkey === expectedPubkey) {
return [gotPubkey, signing];
}
signing.free();
}
let privkey;
if (this._cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
privkey = await this._cacheCallbacks
.getCrossSigningKeyCache(type, expectedPubkey);
}
const cacheresult = validateKey(privkey);
if (cacheresult) {
return cacheresult;
}
privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
const result = validateKey(privkey);
if (result) {
if (this._cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
await this._cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
}
return result;
}
/* No keysource even returned a key */
if (!privkey) {
throw new Error(
"getCrossSigningKey callback for " + type + " returned falsey",
);
}
/* We got some keys from the keysource, but none of them were valid */
throw new Error(
"Key type " + type + " from getCrossSigningKey callback did not match",
);
}
/**
* Check whether the private keys exist in secret storage.
* XXX: This could be static, be we often seem to have an instance when we
* want to know this anyway...
*
* @param {SecretStorage} secretStorage The secret store using account data
* @returns {object} map of key name to key info the secret is encrypted
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStoredInSecretStorage(secretStorage) {
// check what SSSS keys have encrypted the master key (if any)
const stored =
await secretStorage.isStored("m.cross_signing.master", false) || {};
// then check which of those SSSS keys have also encrypted the SSK and USK
function intersect(s) {
for (const k of Object.keys(stored)) {
if (!s[k]) {
delete stored[k];
}
}
}
for (const type of ["self_signing", "user_signing"]) {
intersect(
await secretStorage.isStored(`m.cross_signing.${type}`, false) || {},
);
}
return Object.keys(stored).length ? stored : null;
}
/**
* Store private keys in secret storage for use by other devices. This is
* typically called in conjunction with the creation of new cross-signing
* keys.
*
* @param {Map} keys The keys to store
* @param {SecretStorage} secretStorage The secret store using account data
*/
static async storeInSecretStorage(keys, secretStorage) {
for (const [type, privateKey] of keys) {
const encodedKey = encodeBase64(privateKey);
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
}
}
/**
* Get private keys from secret storage created by some other device. This
* also passes the private keys to the app-specific callback.
*
* @param {string} type The type of key to get. One of "master",
* "self_signing", or "user_signing".
* @param {SecretStorage} secretStorage The secret store using account data
* @return {Uint8Array} The private key
*/
static async getFromSecretStorage(type, secretStorage) {
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
if (!encodedKey) {
return null;
}
return decodeBase64(encodedKey);
}
/**
* Check whether the private keys exist in the local key cache.
*
* @param {string} [type] The type of key to get. One of "master",
* "self_signing", or "user_signing". Optional, will check all by default.
* @returns {boolean} True if all keys are stored in the local cache.
*/
async isStoredInKeyCache(type) {
const cacheCallbacks = this._cacheCallbacks;
if (!cacheCallbacks) return false;
const types = type ? [type] : ["master", "self_signing", "user_signing"];
for (const t of types) {
if (!await cacheCallbacks.getCrossSigningKeyCache(t)) {
return false;
}
}
return true;
}
/**
* Get cross-signing private keys from the local cache.
*
* @returns {Map} A map from key type (string) to private key (Uint8Array)
*/
async getCrossSigningKeysFromCache() {
const keys = new Map();
const cacheCallbacks = this._cacheCallbacks;
if (!cacheCallbacks) return keys;
for (const type of ["master", "self_signing", "user_signing"]) {
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
if (!privKey) {
continue;
}
keys.set(type, privKey);
}
return keys;
}
/**
* Get the ID used to identify the user. This can also be used to test for
* the existence of a given key type.
*
* @param {string} type The type of key to get the ID of. One of "master",
* "self_signing", or "user_signing". Defaults to "master".
*
* @return {string} the ID
*/
getId(type) {
type = type || "master";
if (!this.keys[type]) return null;
const keyInfo = this.keys[type];
return publicKeyFromKeyInfo(keyInfo);
}
/**
* Create new cross-signing keys for the given key types. The public keys
* will be held in this class, while the private keys are passed off to the
* `saveCrossSigningKeys` application callback.
*
* @param {CrossSigningLevel} level The key types to reset
*/
async resetKeys(level) {
if (!this._callbacks.saveCrossSigningKeys) {
throw new Error("No saveCrossSigningKeys callback supplied");
}
// If we're resetting the master key, we reset all keys
if (
level === undefined ||
level & CrossSigningLevel.MASTER ||
!this.keys.master
) {
level = (
CrossSigningLevel.MASTER |
CrossSigningLevel.USER_SIGNING |
CrossSigningLevel.SELF_SIGNING
);
} else if (level === 0) {
return;
}
const privateKeys = {};
const keys = {};
let masterSigning;
let masterPub;
try {
if (level & CrossSigningLevel.MASTER) {
masterSigning = new global.Olm.PkSigning();
privateKeys.master = masterSigning.generate_seed();
masterPub = masterSigning.init_with_seed(privateKeys.master);
keys.master = {
user_id: this.userId,
usage: ['master'],
keys: {
['ed25519:' + masterPub]: masterPub,
},
};
} else {
[masterPub, masterSigning] = await this.getCrossSigningKey("master");
}
if (level & CrossSigningLevel.SELF_SIGNING) {
const sskSigning = new global.Olm.PkSigning();
try {
privateKeys.self_signing = sskSigning.generate_seed();
const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
keys.self_signing = {
user_id: this.userId,
usage: ['self_signing'],
keys: {
['ed25519:' + sskPub]: sskPub,
},
};
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
} finally {
sskSigning.free();
}
}
if (level & CrossSigningLevel.USER_SIGNING) {
const uskSigning = new global.Olm.PkSigning();
try {
privateKeys.user_signing = uskSigning.generate_seed();
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
keys.user_signing = {
user_id: this.userId,
usage: ['user_signing'],
keys: {
['ed25519:' + uskPub]: uskPub,
},
};
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
} finally {
uskSigning.free();
}
}
Object.assign(this.keys, keys);
this._callbacks.saveCrossSigningKeys(privateKeys);
} finally {
if (masterSigning) {
masterSigning.free();
}
}
}
/**
* unsets the keys, used when another session has reset the keys, to disable cross-signing
*/
clearKeys() {
this.keys = {};
}
setKeys(keys) {
const signingKeys = {};
if (keys.master) {
if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in master key from " + this.userId;
logger.error(error);
throw new Error(error);
}
if (!this.keys.master) {
// this is the first key we've seen, so first-use is true
this.firstUse = true;
} else if (publicKeyFromKeyInfo(keys.master) !== this.getId()) {
// this is a different key, so first-use is false
this.firstUse = false;
} // otherwise, same key, so no change
signingKeys.master = keys.master;
} else if (this.keys.master) {
signingKeys.master = this.keys.master;
} else {
throw new Error("Tried to set cross-signing keys without a master key");
}
const masterKey = publicKeyFromKeyInfo(signingKeys.master);
// verify signatures
if (keys.user_signing) {
if (keys.user_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in user_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.user_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on user-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
if (keys.self_signing) {
if (keys.self_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in self_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.self_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on self-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
// if everything checks out, then save the keys
if (keys.master) {
this.keys.master = keys.master;
// if the master key is set, then the old self-signing and
// user-signing keys are obsolete
this.keys.self_signing = null;
this.keys.user_signing = null;
}
if (keys.self_signing) {
this.keys.self_signing = keys.self_signing;
}
if (keys.user_signing) {
this.keys.user_signing = keys.user_signing;
}
}
updateCrossSigningVerifiedBefore(isCrossSigningVerified) {
// It is critical that this value latches forward from false to true but
// never back to false to avoid a downgrade attack.
if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
this.crossSigningVerifiedBefore = true;
}
}
async signObject(data, type) {
if (!this.keys[type]) {
throw new Error(
"Attempted to sign with " + type + " key but no such key present",
);
}
const [pubkey, signing] = await this.getCrossSigningKey(type);
try {
pkSign(data, signing, this.userId, pubkey);
return data;
} finally {
signing.free();
}
}
async signUser(key) {
if (!this.keys.user_signing) {
logger.info("No user signing key: not signing user");
return;
}
return this.signObject(key.keys.master, "user_signing");
}
async signDevice(userId, device) {
if (userId !== this.userId) {
throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`,
);
}
if (!this.keys.self_signing) {
logger.info("No self signing key: not signing device");
return;
}
return this.signObject(
{
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
}, "self_signing",
);
}
/**
* Check whether a given user is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
*
* @returns {UserTrustLevel}
*/
checkUserTrust(userCrossSigning) {
// if we're checking our own key, then it's trusted if the master key
// and self-signing key match
if (this.userId === userCrossSigning.userId
&& this.getId() && this.getId() === userCrossSigning.getId()
&& this.getId("self_signing")
&& this.getId("self_signing") === userCrossSigning.getId("self_signing")
) {
return new UserTrustLevel(true, true, this.firstUse);
}
if (!this.keys.user_signing) {
// If there's no user signing key, they can't possibly be verified.
// They may be TOFU trusted though.
return new UserTrustLevel(false, false, userCrossSigning.firstUse);
}
let userTrusted;
const userMaster = userCrossSigning.keys.master;
const uskId = this.getId('user_signing');
try {
pkVerify(userMaster, uskId, this.userId);
userTrusted = true;
} catch (e) {
userTrusted = false;
}
return new UserTrustLevel(
userTrusted,
userCrossSigning.crossSigningVerifiedBefore,
userCrossSigning.firstUse,
);
}
/**
* Check whether a given device is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
* @param {module:crypto/deviceinfo} device The device to check
* @param {bool} localTrust Whether the device is trusted locally
* @param {bool} trustCrossSignedDevices Whether we trust cross signed devices
*
* @returns {DeviceTrustLevel}
*/
checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) {
const userTrust = this.checkUserTrust(userCrossSigning);
const userSSK = userCrossSigning.keys.self_signing;
if (!userSSK) {
// if the user has no self-signing key then we cannot make any
// trust assertions about this device from cross-signing
return new DeviceTrustLevel(
false, false, localTrust, trustCrossSignedDevices,
);
}
const deviceObj = deviceToObject(device, userCrossSigning.userId);
try {
// if we can verify the user's SSK from their master key...
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
// ...and this device's key from their SSK...
pkVerify(
deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId,
);
// ...then we trust this device as much as far as we trust the user
return DeviceTrustLevel.fromUserTrustLevel(
userTrust, localTrust, trustCrossSignedDevices,
);
} catch (e) {
return new DeviceTrustLevel(
false, false, localTrust, trustCrossSignedDevices,
);
}
}
/**
* @returns {object} Cache callbacks
*/
getCacheCallbacks() {
return this._cacheCallbacks;
}
}
function deviceToObject(device, userId) {
return {
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
signatures: device.signatures,
};
}
export const CrossSigningLevel = {
MASTER: 4,
USER_SIGNING: 2,
SELF_SIGNING: 1,
};
/**
* Represents the ways in which we trust a user
*/
export class UserTrustLevel {
constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) {
this._crossSigningVerified = crossSigningVerified;
this._crossSigningVerifiedBefore = crossSigningVerifiedBefore;
this._tofu = tofu;
}
/**
* @returns {bool} true if this user is verified via any means
*/
isVerified() {
return this.isCrossSigningVerified();
}
/**
* @returns {bool} true if this user is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if we ever verified this user before (at least for
* the history of verifications observed by this device).
*/
wasCrossSigningVerified() {
return this._crossSigningVerifiedBefore;
}
/**
* @returns {bool} true if this user's key is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
/**
* Represents the ways in which we trust a device
*/
export class DeviceTrustLevel {
constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices) {
this._crossSigningVerified = crossSigningVerified;
this._tofu = tofu;
this._localVerified = localVerified;
this._trustCrossSignedDevices = trustCrossSignedDevices;
}
static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) {
return new DeviceTrustLevel(
userTrustLevel._crossSigningVerified,
userTrustLevel._tofu,
localVerified,
trustCrossSignedDevices,
);
}
/**
* @returns {bool} true if this device is verified via any means
*/
isVerified() {
return Boolean(this.isLocallyVerified() || (
this._trustCrossSignedDevices && this.isCrossSigningVerified()
));
}
/**
* @returns {bool} true if this device is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if this device is verified locally
*/
isLocallyVerified() {
return this._localVerified;
}
/**
* @returns {bool} true if this device is trusted from a user's key
* that is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
export function createCryptoStoreCacheCallbacks(store, olmdevice) {
return {
getCrossSigningKeyCache: async function(type, _expectedPublicKey) {
const key = await new Promise((resolve) => {
return store.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.getSecretStorePrivateKey(txn, resolve, type);
},
);
});
if (key && key.ciphertext) {
const pickleKey = Buffer.from(olmdevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, type);
return decodeBase64(decrypted);
} else {
return key;
}
},
storeCrossSigningKeyCache: async function(type, key) {
if (!(key instanceof Uint8Array)) {
throw new Error(
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
);
}
const pickleKey = Buffer.from(olmdevice._pickleKey);
key = await encryptAES(encodeBase64(key), pickleKey, type);
return store.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.storeSecretStorePrivateKey(txn, type, key);
},
);
},
};
}
/**
* Request cross-signing keys from another device during verification.
*
* @param {module:base-apis~MatrixBaseApis} baseApis base Matrix API interface
* @param {string} userId The user ID being verified
* @param {string} deviceId The device ID being verified
*/
export async function requestKeysDuringVerification(baseApis, userId, deviceId) {
// If this is a self-verification, ask the other party for keys
if (baseApis.getUserId() !== userId) {
return;
}
logger.log("Cross-signing: Self-verification done; requesting keys");
// This happens asynchronously, and we're not concerned about waiting for
// it. We return here in order to test.
return new Promise((resolve, reject) => {
const client = baseApis;
const original = client._crypto._crossSigningInfo;
// We already have all of the infrastructure we need to validate and
// cache cross-signing keys, so instead of replicating that, here we set
// up callbacks that request them from the other device and call
// CrossSigningInfo.getCrossSigningKey() to validate/cache
const crossSigning = new CrossSigningInfo(
original.userId,
{ getCrossSigningKey: async (type) => {
logger.debug("Cross-signing: requesting secret",
type, deviceId);
const { promise } = client.requestSecret(
`m.cross_signing.${type}`, [deviceId],
);
const result = await promise;
const decoded = decodeBase64(result);
return Uint8Array.from(decoded);
} },
original._cacheCallbacks,
);
crossSigning.keys = original.keys;
// XXX: get all keys out if we get one key out
// https://github.com/vector-im/element-web/issues/12604
// then change here to reject on the timeout
// Requests can be ignored, so don't wait around forever
const timeout = new Promise((resolve, reject) => {
setTimeout(
resolve,
KEY_REQUEST_TIMEOUT_MS,
new Error("Timeout"),
);
});
// also request and cache the key backup key
const backupKeyPromise = new Promise(async resolve => {
const cachedKey = await client._crypto.getSessionBackupPrivateKey();
if (!cachedKey) {
logger.info("No cached backup key found. Requesting...");
const secretReq = client.requestSecret(
'm.megolm_backup.v1', [deviceId],
);
const base64Key = await secretReq.promise;
logger.info("Got key backup key, decoding...");
const decodedKey = decodeBase64(base64Key);
logger.info("Decoded backup key, storing...");
client._crypto.storeSessionBackupPrivateKey(
Uint8Array.from(decodedKey),
);
logger.info("Backup key stored. Starting backup restore...");
const backupInfo = await client.getKeyBackupVersion();
// no need to await for this - just let it go in the bg
client.restoreKeyBackupWithCache(
undefined, undefined, backupInfo,
).then(() => {
logger.info("Backup restored.");
});
}
resolve();
});
// We call getCrossSigningKey() for its side-effects
return Promise.race([
Promise.all([
crossSigning.getCrossSigningKey("master"),
crossSigning.getCrossSigningKey("self_signing"),
crossSigning.getCrossSigningKey("user_signing"),
backupKeyPromise,
]),
timeout,
]).then(resolve, reject);
}).catch((e) => {
logger.warn("Cross-signing: failure while requesting keys:", e);
});
}
+164 -53
View File
@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/DeviceList
@@ -22,12 +22,13 @@ limitations under the License.
* Manages the list of other users' devices
*/
import Promise from 'bluebird';
import logger from '../logger';
import DeviceInfo from './deviceinfo';
import olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {DeviceInfo} from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import * as olmlib from './olmlib';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
@@ -60,8 +61,10 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
/**
* @alias module:crypto/DeviceList
*/
export default class DeviceList {
export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
super();
this._cryptoStore = cryptoStore;
// userId -> {
@@ -71,6 +74,11 @@ export default class DeviceList {
// }
this._devices = {};
// userId -> {
// [key info]
// }
this._crossSigningInfo = {};
// map of identity keys to the user who owns it
this._userByIdentityKey = {};
@@ -101,6 +109,9 @@ export default class DeviceList {
this._savePromiseTime = null;
// The timer used to delay the save
this._saveTimer = null;
// True if we have fetched data from the server or loaded a non-empty
// set of device data from the store
this._hasFetched = null;
}
/**
@@ -110,7 +121,10 @@ export default class DeviceList {
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
this._hasFetched = Boolean(deviceData && deviceData.devices);
this._devices = deviceData ? deviceData.devices : {},
this._crossSigningInfo = deviceData ?
deviceData.crossSigningInfo || {} : {};
this._deviceTrackingStatus = deviceData ?
deviceData.trackingStatus : {};
this._syncToken = deviceData ? deviceData.syncToken : null;
@@ -187,26 +201,33 @@ export default class DeviceList {
const resolveSavePromise = this._resolveSavePromise;
this._savePromiseTime = targetTime;
this._saveTimer = setTimeout(() => {
logger.log('Saving device tracking data at token ' + this._syncToken);
logger.log('Saving device tracking data', this._syncToken);
// null out savePromise now (after the delay but before the write),
// otherwise we could return the existing promise when the save has
// actually already happened. Likewise for the dirty flag.
// actually already happened.
this._savePromiseTime = null;
this._saveTimer = null;
this._savePromise = null;
this._resolveSavePromise = null;
this._dirty = false;
this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.storeEndToEndDeviceData({
devices: this._devices,
crossSigningInfo: this._crossSigningInfo,
trackingStatus: this._deviceTrackingStatus,
syncToken: this._syncToken,
}, txn);
},
).then(() => {
// The device list is considered dirty until the write
// completes.
this._dirty = false;
resolveSavePromise();
}, err => {
logger.error('Failed to save device tracking data', this._syncToken);
logger.error(err);
});
}, delay);
}
@@ -300,6 +321,15 @@ export default class DeviceList {
return stored;
}
/**
* Returns a list of all user IDs the DeviceList knows about
*
* @return {array} All known user IDs
*/
getKnownUserIds() {
return Object.keys(this._devices);
}
/**
* Get the stored device keys for a user id
*
@@ -334,6 +364,17 @@ export default class DeviceList {
return this._devices[userId];
}
getStoredCrossSigningForUser(userId) {
if (!this._crossSigningInfo[userId]) return null;
return CrossSigningInfo.fromStorage(this._crossSigningInfo[userId], userId);
}
storeCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
this._dirty = true;
}
/**
* Get the stored keys for a single device
*
@@ -351,6 +392,26 @@ export default class DeviceList {
return DeviceInfo.fromStorage(devs[deviceId], deviceId);
}
/**
* Get a user ID by one of their device's curve25519 identity key
*
* @param {string} algorithm encryption algorithm
* @param {string} senderKey curve25519 key to match
*
* @return {string} user ID
*/
getUserByIdentityKey(algorithm, senderKey) {
if (
algorithm !== olmlib.OLM_ALGORITHM &&
algorithm !== olmlib.MEGOLM_ALGORITHM
) {
// we only deal in olm keys
return null;
}
return this._userByIdentityKey[senderKey];
}
/**
* Find a device by curve25519 identity key
*
@@ -360,19 +421,11 @@ export default class DeviceList {
* @return {module:crypto/deviceinfo?}
*/
getDeviceByIdentityKey(algorithm, senderKey) {
const userId = this._userByIdentityKey[senderKey];
const userId = this.getUserByIdentityKey(algorithm, senderKey);
if (!userId) {
return null;
}
if (
algorithm !== olmlib.OLM_ALGORITHM &&
algorithm !== olmlib.MEGOLM_ALGORITHM
) {
// we only deal in olm keys
return null;
}
const devices = this._devices[userId];
if (!devices) {
return null;
@@ -561,6 +614,10 @@ export default class DeviceList {
}
}
setRawStoredCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
}
/**
* Fire off download update requests for the given users, and update the
* device list tracking status for them, and the
@@ -568,7 +625,7 @@ export default class DeviceList {
*
* @param {String[]} users list of userIds
*
* @return {module:client.Promise} resolves when all the users listed have
* @return {Promise} resolves when all the users listed have
* been updated. rejects if there was a problem updating any of the
* users.
*/
@@ -599,6 +656,7 @@ export default class DeviceList {
});
const finished = (success) => {
this.emit("crypto.willUpdateDevices", users, !this._hasFetched);
users.forEach((u) => {
this._dirty = true;
@@ -624,6 +682,8 @@ export default class DeviceList {
}
});
this.saveIfDirty();
this.emit("crypto.devicesUpdated", users, !this._hasFetched);
this._hasFetched = true;
};
return prom;
@@ -672,7 +732,7 @@ class DeviceListUpdateSerialiser {
* @param {String} syncToken sync token to pass in the query request, to
* help the HS give the most recent results
*
* @return {module:client.Promise} resolves when all the users listed have
* @return {Promise} resolves when all the users listed have
* been updated. rejects if there was a problem updating any of the
* users.
*/
@@ -682,7 +742,7 @@ class DeviceListUpdateSerialiser {
});
if (!this._queuedQueryDeferred) {
this._queuedQueryDeferred = Promise.defer();
this._queuedQueryDeferred = defer();
}
// We always take the new sync token and just use the latest one we've
@@ -722,23 +782,35 @@ class DeviceListUpdateSerialiser {
this._baseApis.downloadKeysForUsers(
downloadUsers, opts,
).then((res) => {
).then(async (res) => {
const dk = res.device_keys || {};
const masterKeys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
// do each user in a separate promise, to avoid wedging the CPU
// (https://github.com/vector-im/riot-web/issues/3158)
// yield to other things that want to execute in between users, to
// avoid wedging the CPU
// (https://github.com/vector-im/element-web/issues/3158)
//
// of course we ought to do this in a web worker or similar, but
// this serves as an easy solution for now.
let prom = Promise.resolve();
for (const userId of downloadUsers) {
prom = prom.delay(5).then(() => {
return this._processQueryResponseForUser(userId, dk[userId]);
});
await sleep(5);
try {
await this._processQueryResponseForUser(
userId, dk[userId], {
master: masterKeys[userId],
self_signing: ssks[userId],
user_signing: usks[userId],
},
);
} catch (e) {
// log the error but continue, so that one bad key
// doesn't kill the whole process
logger.error(`Error processing keys for ${userId}:`, e);
}
}
return prom;
}).done(() => {
}).then(() => {
logger.log('Completed key download for ' + downloadUsers);
this._downloadInProgress = false;
@@ -757,36 +829,66 @@ class DeviceListUpdateSerialiser {
return deferred.promise;
}
async _processQueryResponseForUser(userId, response) {
logger.log('got keys for ' + userId + ':', response);
async _processQueryResponseForUser(
userId, dkResponse, crossSigningResponse,
) {
logger.log('got device keys for ' + userId + ':', dkResponse);
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
{
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
});
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, dkResponse || {},
this._baseApis.getUserId(), this._baseApis.deviceId,
);
// put the updates into the object that will be returned as our results
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
this._deviceList._setRawStoredDevicesForUser(userId, storage);
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, response || {},
);
// now do the same for the cross-signing keys
{
// FIXME: should we be ignoring empty cross-signing responses, or
// should we be dropping the keys?
if (crossSigningResponse
&& (crossSigningResponse.master || crossSigningResponse.self_signing
|| crossSigningResponse.user_signing)) {
const crossSigning
= this._deviceList.getStoredCrossSigningForUser(userId)
|| new CrossSigningInfo(userId);
// put the updates into thr object that will be returned as our results
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
crossSigning.setKeys(crossSigningResponse);
this._deviceList._setRawStoredDevicesForUser(userId, storage);
this._deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted
this._deviceList.emit('userCrossSigningUpdated', userId);
}
}
}
}
async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
userResult) {
async function _updateStoredDeviceKeysForUser(
_olmDevice, userId, userStore, userResult, localUserId, localDeviceId,
) {
let updated = false;
// remove any devices in the store which aren't in the response
@@ -796,6 +898,13 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
}
if (!(deviceId in userResult)) {
if (userId === localUserId && deviceId === localDeviceId) {
logger.warn(
`Local device ${deviceId} missing from sync, skipping removal`,
);
continue;
}
logger.log("Device " + userId + ":" + deviceId +
" has been removed");
delete userStore[deviceId];
@@ -854,6 +963,7 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
}
const unsigned = deviceResult.unsigned || {};
const signatures = deviceResult.signatures || {};
try {
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
@@ -886,5 +996,6 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
deviceStore.keys = deviceResult.keys || {};
deviceStore.algorithms = deviceResult.algorithms || [];
deviceStore.unsigned = unsigned;
deviceStore.signatures = signatures;
return true;
}
+357
View File
@@ -0,0 +1,357 @@
import { logger } from "../logger";
import {MatrixEvent} from "../models/event";
import {EventEmitter} from "events";
import {createCryptoStoreCacheCallbacks} from "./CrossSigning";
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {
PREFIX_UNSTABLE,
} from "../http-api";
/**
* Builds an EncryptionSetupOperation by calling any of the add.. methods.
* Once done, `buildOperation()` can be called which allows to apply to operation.
*
* This is used as a helper by Crypto to keep track of all the network requests
* and other side-effects of bootstrapping, so it can be applied in one go (and retried in the future)
* Also keeps track of all the private keys created during bootstrapping, so we don't need to prompt for them
* more than once.
*/
export class EncryptionSetupBuilder {
/**
* @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written.
* @param {CryptoCallbacks} delegateCryptoCallbacks crypto callbacks to delegate to if the key isn't in cache yet
*/
constructor(accountData, delegateCryptoCallbacks) {
this.accountDataClientAdapter = new AccountDataClientAdapter(accountData);
this.crossSigningCallbacks = new CrossSigningCallbacks();
this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks);
this._crossSigningKeys = null;
this._keySignatures = null;
this._keyBackupInfo = null;
}
/**
* Adds new cross-signing public keys
*
* @param {function} authUpload Function called to await an interactive auth
* flow when uploading device signing keys.
* Args:
* {function} A function that makes the request requiring auth. Receives
* the auth data as an object. Can be called multiple times, first with
* an empty authDict, to obtain the flows.
* @param {Object} keys the new keys
*/
addCrossSigningKeys(authUpload, keys) {
this._crossSigningKeys = {authUpload, keys};
}
/**
* Adds the key backup info to be updated on the server
*
* Used either to create a new key backup, or add signatures
* from the new MSK.
*
* @param {Object} keyBackupInfo as received from/sent to the server
*/
addSessionBackup(keyBackupInfo) {
this._keyBackupInfo = keyBackupInfo;
}
/**
* Adds the session backup private key to be updated in the local cache
*
* Used after fixing the format of the key
*
* @param {Uint8Array} privateKey
*/
addSessionBackupPrivateKeyToCache(privateKey) {
this._sessionBackupPrivateKey = privateKey;
}
/**
* Add signatures from a given user and device/x-sign key
* Used to sign the new cross-signing key with the device key
*
* @param {String} userId
* @param {String} deviceId
* @param {String} signature
*/
addKeySignature(userId, deviceId, signature) {
if (!this._keySignatures) {
this._keySignatures = {};
}
const userSignatures = this._keySignatures[userId] || {};
this._keySignatures[userId] = userSignatures;
userSignatures[deviceId] = signature;
}
/**
* @param {String} type
* @param {Object} content
* @return {Promise}
*/
setAccountData(type, content) {
return this.accountDataClientAdapter.setAccountData(type, content);
}
/**
* builds the operation containing all the parts that have been added to the builder
* @return {EncryptionSetupOperation}
*/
buildOperation() {
const accountData = this.accountDataClientAdapter._values;
return new EncryptionSetupOperation(
accountData,
this._crossSigningKeys,
this._keyBackupInfo,
this._keySignatures,
);
}
/**
* Stores the created keys locally.
*
* This does not yet store the operation in a way that it can be restored,
* but that is the idea in the future.
*
* @param {Crypto} crypto
* @return {Promise}
*/
async persist(crypto) {
// store private keys in cache
if (this._crossSigningKeys) {
const cacheCallbacks = createCryptoStoreCacheCallbacks(
crypto._cryptoStore, crypto._olmDevice);
for (const type of ["master", "self_signing", "user_signing"]) {
logger.log(`Cache ${type} cross-signing private key locally`);
const privateKey = this.crossSigningCallbacks.privateKeys.get(type);
await cacheCallbacks.storeCrossSigningKeyCache(type, privateKey);
}
// store own cross-sign pubkeys as trusted
await crypto._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto._cryptoStore.storeCrossSigningKeys(
txn, this._crossSigningKeys.keys);
},
);
}
// store session backup key in cache
if (this._sessionBackupPrivateKey) {
await crypto.storeSessionBackupPrivateKey(this._sessionBackupPrivateKey);
}
}
}
/**
* Can be created from EncryptionSetupBuilder, or
* (in a follow-up PR, not implemented yet) restored from storage, to retry.
*
* It does not have knowledge of any private keys, unlike the builder.
*/
export class EncryptionSetupOperation {
/**
* @param {Map<String, Object>} accountData
* @param {Object} crossSigningKeys
* @param {Object} keyBackupInfo
* @param {Object} keySignatures
*/
constructor(accountData, crossSigningKeys, keyBackupInfo, keySignatures) {
this._accountData = accountData;
this._crossSigningKeys = crossSigningKeys;
this._keyBackupInfo = keyBackupInfo;
this._keySignatures = keySignatures;
}
/**
* Runs the (remaining part of, in the future) operation by sending requests to the server.
* @param {Crypto} crypto
*/
async apply(crypto) {
const baseApis = crypto._baseApis;
// upload cross-signing keys
if (this._crossSigningKeys) {
const keys = {};
for (const [name, key] of Object.entries(this._crossSigningKeys.keys)) {
keys[name + "_key"] = key;
}
// We must only call `uploadDeviceSigningKeys` from inside this auth
// helper to ensure we properly handle auth errors.
await this._crossSigningKeys.authUpload(authDict => {
return baseApis.uploadDeviceSigningKeys(authDict, keys);
});
// pass the new keys to the main instance of our own CrossSigningInfo.
crypto._crossSigningInfo.setKeys(this._crossSigningKeys.keys);
}
// set account data
if (this._accountData) {
for (const [type, content] of this._accountData) {
await baseApis.setAccountData(type, content);
}
}
// upload first cross-signing signatures with the new key
// (e.g. signing our own device)
if (this._keySignatures) {
await baseApis.uploadKeySignatures(this._keySignatures);
}
// need to create/update key backup info
if (this._keyBackupInfo) {
if (this._keyBackupInfo.version) {
// session backup signature
// The backup is trusted because the user provided the private key.
// Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing.
await baseApis._http.authedRequest(
undefined, "PUT", "/room_keys/version/" + this._keyBackupInfo.version,
undefined, {
algorithm: this._keyBackupInfo.algorithm,
auth_data: this._keyBackupInfo.auth_data,
},
{prefix: PREFIX_UNSTABLE},
);
} else {
// add new key backup
await baseApis._http.authedRequest(
undefined, "POST", "/room_keys/version",
undefined, this._keyBackupInfo,
{prefix: PREFIX_UNSTABLE},
);
}
}
}
}
/**
* Catches account data set by SecretStorage during bootstrapping by
* implementing the methods related to account data in MatrixClient
*/
class AccountDataClientAdapter extends EventEmitter {
/**
* @param {Object.<String, MatrixEvent>} accountData existing account data
*/
constructor(accountData) {
super();
this._existingValues = accountData;
this._values = new Map();
}
/**
* @param {String} type
* @return {Promise<Object>} the content of the account data
*/
getAccountDataFromServer(type) {
return Promise.resolve(this.getAccountData(type));
}
/**
* @param {String} type
* @return {Object} the content of the account data
*/
getAccountData(type) {
const modifiedValue = this._values.get(type);
if (modifiedValue) {
return modifiedValue;
}
const existingValue = this._existingValues[type];
if (existingValue) {
return existingValue.getContent();
}
return null;
}
/**
* @param {String} type
* @param {Object} content
* @return {Promise}
*/
setAccountData(type, content) {
const lastEvent = this._values.get(type);
this._values.set(type, content);
// ensure accountData is emitted on the next tick,
// as SecretStorage listens for it while calling this method
// and it seems to rely on this.
return Promise.resolve().then(() => {
const event = new MatrixEvent({type, content});
this.emit("accountData", event, lastEvent);
});
}
}
/**
* Catches the private cross-signing keys set during bootstrapping
* by both cache callbacks (see createCryptoStoreCacheCallbacks) as non-cache callbacks.
* See CrossSigningInfo constructor
*/
class CrossSigningCallbacks {
constructor() {
this.privateKeys = new Map();
}
// cache callbacks
getCrossSigningKeyCache(type, expectedPublicKey) {
return this.getCrossSigningKey(type, expectedPublicKey);
}
storeCrossSigningKeyCache(type, key) {
this.privateKeys.set(type, key);
return Promise.resolve();
}
// non-cache callbacks
getCrossSigningKey(type, _expectedPubkey) {
return Promise.resolve(this.privateKeys.get(type));
}
saveCrossSigningKeys(privateKeys) {
for (const [type, privateKey] of Object.entries(privateKeys)) {
this.privateKeys.set(type, privateKey);
}
}
}
/**
* Catches the 4S private key set during bootstrapping by implementing
* the SecretStorage crypto callbacks
*/
class SSSSCryptoCallbacks {
constructor(delegateCryptoCallbacks) {
this._privateKeys = new Map();
this._delegateCryptoCallbacks = delegateCryptoCallbacks;
}
async getSecretStorageKey({ keys }, name) {
for (const keyId of Object.keys(keys)) {
const privateKey = this._privateKeys.get(keyId);
if (privateKey) {
return [keyId, privateKey];
}
}
// if we don't have the key cached yet, ask
// for it to the general crypto callbacks and cache it
if (this._delegateCryptoCallbacks) {
const result = await this._delegateCryptoCallbacks.
getSecretStorageKey({keys}, name);
if (result) {
const [keyId, privateKey] = result;
this._privateKeys.set(keyId, privateKey);
}
return result;
}
}
addPrivateKey(keyId, keyInfo, privKey) {
this._privateKeys.set(keyId, privKey);
// Also pass along to application to cache if it wishes
if (
this._delegateCryptoCallbacks &&
this._delegateCryptoCallbacks.cacheSecretStorageKey
) {
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
}
}
}
+325 -37
View File
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,8 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../logger';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {logger} from '../logger';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import * as algorithms from './algorithms';
// The maximum size of an event is 65K, and we base64 the content, so this is a
// reasonable approximation to the biggest plaintext we can encrypt.
@@ -34,9 +36,15 @@ function checkPayloadLength(payloadString) {
// Note that even if we manage to do the encryption, the message send may fail,
// because by the time we've wrapped the ciphertext in the event object, it may
// exceed 65K. But at least we won't just fail with "abort()" in that case.
throw new Error("Message too long (" + payloadString.length + " bytes). " +
const err = new Error("Message too long (" + payloadString.length + " bytes). " +
"The maximum for an encrypted message is " +
MAX_PLAINTEXT_LENGTH + " bytes.");
// TODO: [TypeScript] We should have our own error types
err.data = {
errcode: "M_TOO_LARGE",
error: "Payload too large for encrypted message",
};
throw err;
}
}
@@ -69,7 +77,7 @@ function checkPayloadLength(payloadString) {
* @property {string} deviceCurve25519Key Curve25519 key for the account
* @property {string} deviceEd25519Key Ed25519 key for the account
*/
function OlmDevice(cryptoStore) {
export function OlmDevice(cryptoStore) {
this._cryptoStore = cryptoStore;
this._pickleKey = "DEFAULT_KEY";
@@ -103,22 +111,61 @@ function OlmDevice(cryptoStore) {
// Keep track of sessions that we're starting, so that we don't start
// multiple sessions for the same device at the same time.
this._sessionsInProgress = {};
// Used by olm to serialise prekey message decryptions
this._olmPrekeyPromise = Promise.resolve();
}
/**
* Initialise the OlmAccount. This must be called before any other operations
* on the OlmDevice.
*
* Data from an exported Olm device can be provided
* in order to re-create this device.
*
* Attempts to load the OlmAccount from the crypto store, or creates one if none is
* found.
*
* Reads the device keys from the OlmAccount object.
*
* @param {object} opts
* @param {object} opts.fromExportedDevice (Optional) data from exported device
* that must be re-created.
* If present, opts.pickleKey is ignored
* (exported data already provides a pickle key)
* @param {object} opts.pickleKey (Optional) pickle key to set instead of default one
*/
OlmDevice.prototype.init = async function() {
OlmDevice.prototype.init = async function(opts = {}) {
let e2eKeys;
const account = new global.Olm.Account();
const { pickleKey, fromExportedDevice } = opts;
try {
await _initialiseAccount(this._cryptoStore, this._pickleKey, account);
if (fromExportedDevice) {
if (pickleKey) {
logger.warn(
'ignoring opts.pickleKey'
+ ' because opts.fromExportedDevice is present.',
);
}
this._pickleKey = fromExportedDevice.pickleKey;
await _initialiseFromExportedDevice(
fromExportedDevice,
this._cryptoStore,
this._pickleKey,
account,
);
} else {
if (pickleKey) {
this._pickleKey = pickleKey;
}
await _initialiseAccount(
this._cryptoStore,
this._pickleKey,
account,
);
}
e2eKeys = JSON.parse(account.identity_keys());
this._maxOneTimeKeys = account.max_number_of_one_time_keys();
@@ -130,18 +177,67 @@ OlmDevice.prototype.init = async function() {
this.deviceEd25519Key = e2eKeys.ed25519;
};
async function _initialiseAccount(cryptoStore, pickleKey, account) {
await cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
cryptoStore.getAccount(txn, (pickledAccount) => {
if (pickledAccount !== null) {
account.unpickle(pickleKey, pickledAccount);
} else {
account.create();
pickledAccount = account.pickle(pickleKey);
cryptoStore.storeAccount(txn, pickledAccount);
}
});
/**
* Populates the crypto store using data that was exported from an existing device.
* Note that for now only the account and sessions stores are populated;
* Other stores will be as with a new device.
*
* @param {Object} exportedData Data exported from another device
* through the export method.
* @param {module:crypto/store/base~CryptoStore} cryptoStore storage for the crypto layer
* @param {string} pickleKey the key that was used to pickle the exported data
* @param {Olm.Account} account an olm account to initialize
*/
async function _initialiseFromExportedDevice(
exportedData,
cryptoStore,
pickleKey,
account,
) {
await cryptoStore.doTxn(
'readwrite',
[
IndexedDBCryptoStore.STORE_ACCOUNT,
IndexedDBCryptoStore.STORE_SESSIONS,
],
(txn) => {
cryptoStore.storeAccount(txn, exportedData.pickledAccount);
exportedData.sessions.forEach((session) => {
const {
deviceKey,
sessionId,
} = session;
const sessionInfo = {
session: session.session,
lastReceivedMessageTs: session.lastReceivedMessageTs,
};
cryptoStore.storeEndToEndSession(
deviceKey,
sessionId,
sessionInfo,
txn,
);
});
});
account.unpickle(pickleKey, exportedData.pickledAccount);
}
async function _initialiseAccount(cryptoStore, pickleKey, account) {
await cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
cryptoStore.getAccount(txn, (pickledAccount) => {
if (pickledAccount !== null) {
account.unpickle(pickleKey, pickledAccount);
} else {
account.create();
pickledAccount = account.pickle(pickleKey);
cryptoStore.storeAccount(txn, pickledAccount);
}
});
},
);
}
/**
@@ -189,6 +285,38 @@ OlmDevice.prototype._storeAccount = function(txn, account) {
this._cryptoStore.storeAccount(txn, account.pickle(this._pickleKey));
};
/**
* Export data for re-creating the Olm device later.
* TODO export data other than just account and (P2P) sessions.
*
* @return {Promise<object>} The exported data
*/
OlmDevice.prototype.export = async function() {
const result = {
pickleKey: this._pickleKey,
};
await this._cryptoStore.doTxn(
'readonly',
[
IndexedDBCryptoStore.STORE_ACCOUNT,
IndexedDBCryptoStore.STORE_SESSIONS,
],
(txn) => {
this._cryptoStore.getAccount(txn, (pickledAccount) => {
result.pickledAccount = pickledAccount;
});
result.sessions = [];
// Note that the pickledSession object we get in the callback
// is not exactly the same thing you get in method _getSession
// see documentation of IndexedDBCryptoStore.getAllEndToEndSessions
this._cryptoStore.getAllEndToEndSessions(txn, (pickledSession) => {
result.sessions.push(pickledSession);
});
},
);
return result;
};
/**
* extract an OlmSession from the session store and call the given function
* The session is useable only within the callback passed to this
@@ -349,6 +477,36 @@ OlmDevice.prototype.generateOneTimeKeys = function(numKeys) {
);
};
/**
* Generate a new fallback keys
*
* @return {Promise} Resolved once the account is saved back having generated the key
*/
OlmDevice.prototype.generateFallbackKey = async function() {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this._getAccount(txn, (account) => {
account.generate_fallback_key();
this._storeAccount(txn, account);
});
},
);
};
OlmDevice.prototype.getFallbackKey = async function() {
let result;
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this._getAccount(txn, (account) => {
result = JSON.parse(account.fallback_key());
});
},
);
return result;
};
/**
* Generate a new outbound session
*
@@ -462,7 +620,7 @@ OlmDevice.prototype.createInboundSession = async function(
*/
OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityKey) {
if (this._sessionsInProgress[theirDeviceIdentityKey]) {
logger.log("waiting for session to be created");
logger.log("waiting for olm session to be created");
try {
await this._sessionsInProgress[theirDeviceIdentityKey];
} catch (e) {
@@ -543,7 +701,7 @@ OlmDevice.prototype.getSessionIdForDevice = async function(
*/
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey, nowait) {
if (this._sessionsInProgress[deviceIdentityKey] && !nowait) {
logger.log("waiting for session to be created");
logger.log("waiting for olm session to be created");
try {
await this._sessionsInProgress[deviceIdentityKey];
} catch (e) {
@@ -594,6 +752,11 @@ OlmDevice.prototype.encryptMessage = async function(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
const sessionDesc = sessionInfo.session.describe();
logger.log(
"encryptMessage: Olm Session ID " + sessionId + " to " +
theirDeviceIdentityKey + ": " + sessionDesc,
);
res = sessionInfo.session.encrypt(payloadString);
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
});
@@ -621,6 +784,11 @@ OlmDevice.prototype.decryptMessage = async function(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
const sessionDesc = sessionInfo.session.describe();
logger.log(
"decryptMessage: Olm Session ID " + sessionId + " from " +
theirDeviceIdentityKey + ": " + sessionDesc,
);
payloadString = sessionInfo.session.decrypt(messageType, ciphertext);
sessionInfo.lastReceivedMessageTs = Date.now();
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
@@ -661,6 +829,18 @@ OlmDevice.prototype.matchesSession = async function(
return matches;
};
OlmDevice.prototype.recordSessionProblem = async function(deviceKey, type, fixed) {
await this._cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
};
OlmDevice.prototype.sessionMayHaveProblems = async function(deviceKey, timestamp) {
return await this._cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
};
OlmDevice.prototype.filterOutNotifiedErrorDevices = async function(devices) {
return await this._cryptoStore.filterOutNotifiedErrorDevices(devices);
};
// Outbound group session
// ======================
@@ -730,6 +910,8 @@ OlmDevice.prototype.createOutboundGroupSession = function() {
OlmDevice.prototype.encryptGroupMessage = function(sessionId, payloadString) {
const self = this;
logger.log(`encrypting msg with megolm session ${sessionId}`);
checkPayloadLength(payloadString);
return this._getOutboundGroupSession(sessionId, function(session) {
@@ -806,9 +988,9 @@ OlmDevice.prototype._getInboundGroupSession = function(
roomId, senderKey, sessionId, txn, func,
) {
this._cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData) => {
senderKey, sessionId, txn, (sessionData, withheld) => {
if (sessionData === null) {
func(null);
func(null, null, withheld);
return;
}
@@ -822,7 +1004,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
}
this._unpickleInboundGroupSession(sessionData, (session) => {
func(session, sessionData);
func(session, sessionData, withheld);
});
},
);
@@ -840,14 +1022,18 @@ OlmDevice.prototype._getInboundGroupSession = function(
* @param {Object<string, string>} keysClaimed Other keys the sender claims.
* @param {boolean} exportFormat true if the megolm keys are in export format
* (ie, they lack an ed25519 signature)
* @param {Object} [extraSessionData={}] any other data to be include with the session
*/
OlmDevice.prototype.addInboundGroupSession = async function(
roomId, senderKey, forwardingCurve25519KeyChain,
sessionId, sessionKey, keysClaimed,
exportFormat,
exportFormat, extraSessionData = {},
) {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
/* if we already have this session, consider updating it */
this._getInboundGroupSession(
roomId, senderKey, sessionId, txn,
@@ -876,17 +1062,24 @@ OlmDevice.prototype.addInboundGroupSession = async function(
<= session.first_known_index()) {
// existing session has lower index (i.e. can
// decrypt more), so keep it
logger.log("Keeping existing session");
logger.log(
`Keeping existing megolm session ${sessionId}`,
);
return;
}
}
const sessionData = {
logger.info(
"Storing megolm session " + senderKey + "/" + sessionId +
" with first index " + session.first_known_index(),
);
const sessionData = Object.assign({}, extraSessionData, {
room_id: roomId,
session: session.pickle(this._pickleKey),
keysClaimed: keysClaimed,
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
};
});
this._cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, sessionData, txn,
@@ -900,6 +1093,60 @@ OlmDevice.prototype.addInboundGroupSession = async function(
);
};
/**
* Record in the data store why an inbound group session was withheld.
*
* @param {string} roomId room that the session belongs to
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier
* @param {string} code reason code
* @param {string} reason human-readable version of `code`
*/
OlmDevice.prototype.addInboundGroupSessionWithheld = async function(
roomId, senderKey, sessionId, code, reason,
) {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD],
(txn) => {
this._cryptoStore.storeEndToEndInboundGroupSessionWithheld(
senderKey, sessionId,
{
room_id: roomId,
code: code,
reason: reason,
},
txn,
);
},
);
};
export const WITHHELD_MESSAGES = {
"m.unverified": "The sender has disabled encrypting to unverified devices.",
"m.blacklisted": "The sender has blocked you.",
"m.unauthorised": "You are not authorised to read the message.",
"m.no_olm": "Unable to establish a secure channel.",
};
/**
* Calculate the message to use for the exception when a session key is withheld.
*
* @param {object} withheld An object that describes why the key was withheld.
*
* @return {string} the message
*
* @private
*/
function _calculateWithheldMessage(withheld) {
if (withheld.code && withheld.code in WITHHELD_MESSAGES) {
return WITHHELD_MESSAGES[withheld.code];
} else if (withheld.reason) {
return withheld.reason;
} else {
return "decryption key withheld";
}
}
/**
* Decrypt a received message with an inbound group session
*
@@ -920,16 +1167,49 @@ OlmDevice.prototype.decryptGroupMessage = async function(
roomId, senderKey, sessionId, body, eventId, timestamp,
) {
let result;
// when the localstorage crypto store is used as an indexeddb backend,
// exceptions thrown from within the inner function are not passed through
// to the top level, so we store exceptions in a variable and raise them at
// the end
let error;
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => {
roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null) {
if (withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
_calculateWithheldMessage(withheld),
{
session: senderKey + '|' + sessionId,
},
);
}
result = null;
return;
}
const res = session.decrypt(body);
let res;
try {
res = session.decrypt(body);
} catch (e) {
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
_calculateWithheldMessage(withheld),
{
session: senderKey + '|' + sessionId,
},
);
} else {
error = e;
}
return;
}
let plaintext = res.plaintext;
if (plaintext === undefined) {
@@ -951,10 +1231,11 @@ OlmDevice.prototype.decryptGroupMessage = async function(
msgInfo.id !== eventId ||
msgInfo.timestamp !== timestamp
) {
throw new Error(
error = new Error(
"Duplicate message index, possible replay attack: " +
messageIndexKey,
);
return;
}
}
this._inboundGroupSessionMessageIndexes[messageIndexKey] = {
@@ -974,12 +1255,16 @@ OlmDevice.prototype.decryptGroupMessage = async function(
forwardingCurve25519KeyChain: (
sessionData.forwardingCurve25519KeyChain || []
),
untrusted: sessionData.untrusted,
};
},
);
},
);
if (error) {
throw error;
}
return result;
};
@@ -988,14 +1273,17 @@ OlmDevice.prototype.decryptGroupMessage = async function(
*
* @param {string} roomId room in which the message was received
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {sring} sessionId session identifier
* @param {string} sessionId session identifier
*
* @returns {Promise<boolean>} true if we have the keys to this session
*/
OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) {
let result;
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
'readonly', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData) => {
if (sessionData === null) {
@@ -1046,7 +1334,10 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
) {
let result;
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
'readonly', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => {
if (session === null) {
@@ -1125,6 +1416,3 @@ OlmDevice.prototype.verifySignature = function(
util.ed25519_verify(key, message, signature);
});
};
/** */
module.exports = OlmDevice;
+29 -20
View File
@@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import logger from '../logger';
import utils from '../utils';
import {logger} from '../logger';
import * as utils from '../utils';
/**
* Internal module. Management of outgoing room key requests.
@@ -60,7 +58,7 @@ const SEND_KEY_REQUESTS_DELAY_MS = 500;
*
* @enum {number}
*/
const ROOM_KEY_REQUEST_STATES = {
export const ROOM_KEY_REQUEST_STATES = {
/** request not yet sent */
UNSENT: 0,
@@ -77,7 +75,7 @@ const ROOM_KEY_REQUEST_STATES = {
CANCELLATION_PENDING_AND_WILL_RESEND: 3,
};
export default class OutgoingRoomKeyRequestManager {
export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) {
this._baseApis = baseApis;
this._deviceId = deviceId;
@@ -99,10 +97,6 @@ export default class OutgoingRoomKeyRequestManager {
*/
start() {
this._clientRunning = true;
// set the timer going, to handle any requests which didn't get sent
// on the previous run of the client.
this._startTimer();
}
/**
@@ -115,7 +109,14 @@ export default class OutgoingRoomKeyRequestManager {
}
/**
* Send off a room key request, if we haven't already done so.
* Send any requests that have been queued
*/
sendQueuedRequests() {
this._startTimer();
}
/**
* Queue up a room key request, if we haven't already queued or sent one.
*
* The `requestBody` is compared (with a deep-equality check) against
* previous queued or sent requests and if it matches, no change is made.
@@ -131,7 +132,7 @@ export default class OutgoingRoomKeyRequestManager {
* pending list (or we have established that a similar request already
* exists)
*/
async sendRoomKeyRequest(requestBody, recipients, resend=false) {
async queueRoomKeyRequest(requestBody, recipients, resend=false) {
const req = await this._cryptoStore.getOutgoingRoomKeyRequest(
requestBody,
);
@@ -186,7 +187,7 @@ export default class OutgoingRoomKeyRequestManager {
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.sendRoomKeyRequest(
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
@@ -222,9 +223,6 @@ export default class OutgoingRoomKeyRequestManager {
throw new Error('unhandled state: ' + req.state);
}
}
// some of the branches require the timer to be started. Just start it
// all the time, because it doesn't hurt to start it.
this._startTimer();
}
/**
@@ -329,6 +327,21 @@ export default class OutgoingRoomKeyRequestManager {
);
}
/**
* Find anything in `sent` state, and kick it around the loop again.
* This is intended for situations where something substantial has changed, and we
* don't really expect the other end to even care about the cancellation.
* For example, after initialization or self-verification.
* @return {Promise} An array of `queueRoomKeyRequest` outputs.
*/
async cancelAndResendAllOutgoingRequests() {
const outgoings = await this._cryptoStore.getAllOutgoingRoomKeyRequestsByState(
ROOM_KEY_REQUEST_STATES.SENT,
);
return Promise.all(outgoings.map(({ requestBody, recipients }) =>
this.queueRoomKeyRequest(requestBody, recipients, true)));
}
// start the background timer to send queued requests, if the timer isn't
// already running
_startTimer() {
@@ -368,15 +381,12 @@ export default class OutgoingRoomKeyRequestManager {
return Promise.resolve();
}
logger.log("Looking for queued outgoing room key requests");
return this._cryptoStore.getOutgoingRoomKeyRequestByState([
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND,
ROOM_KEY_REQUEST_STATES.UNSENT,
]).then((req) => {
if (!req) {
logger.log("No more outgoing room key requests");
this._sendOutgoingRoomKeyRequestsTimer = null;
return;
}
@@ -400,7 +410,6 @@ export default class OutgoingRoomKeyRequestManager {
}).catch((e) => {
logger.error("Error sending room key request; will retry later.", e);
this._sendOutgoingRoomKeyRequestsTimer = null;
this._startTimer();
});
});
}
+2 -2
View File
@@ -20,12 +20,12 @@ limitations under the License.
* Manages the list of encrypted rooms
*/
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
/**
* @alias module:crypto/RoomList
*/
export default class RoomList {
export class RoomList {
constructor(cryptoStore) {
this._cryptoStore = cryptoStore;
+586
View File
@@ -0,0 +1,586 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {EventEmitter} from 'events';
import {logger} from '../logger';
import * as olmlib from './olmlib';
import {randomString} from '../randomstring';
import {encryptAES, decryptAES} from './aes';
import {encodeBase64} from "./olmlib";
export const SECRET_STORAGE_ALGORITHM_V1_AES
= "m.secret_storage.v1.aes-hmac-sha2";
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage
*/
export class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks) {
super();
this._baseApis = baseApis;
this._cryptoCallbacks = cryptoCallbacks;
this._requests = {};
this._incomingRequests = {};
}
async getDefaultKeyId() {
const defaultKey = await this._baseApis.getAccountDataFromServer(
'm.secret_storage.default_key',
);
if (!defaultKey) return null;
return defaultKey.key;
}
setDefaultKeyId(keyId) {
return new Promise(async (resolve, reject) => {
const listener = (ev) => {
if (
ev.getType() === 'm.secret_storage.default_key' &&
ev.getContent().key === keyId
) {
this._baseApis.removeListener('accountData', listener);
resolve();
}
};
this._baseApis.on('accountData', listener);
try {
await this._baseApis.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
);
} catch (e) {
this._baseApis.removeListener('accountData', listener);
reject(e);
}
});
}
/**
* Add a key for encrypting secrets.
*
* @param {string} algorithm the algorithm used by the key.
* @param {object} opts the options for the algorithm. The properties used
* depend on the algorithm given.
* @param {string} [keyId] the ID of the key. If not given, a random
* ID will be generated.
*
* @return {object} An object with:
* keyId: {string} the ID of the key
* keyInfo: {object} details about the key (iv, mac, passphrase)
*/
async addKey(algorithm, opts, keyId) {
const keyInfo = {algorithm};
if (!opts) opts = {};
if (opts.name) {
keyInfo.name = opts.name;
}
if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (opts.passphrase) {
keyInfo.passphrase = opts.passphrase;
}
if (opts.key) {
const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key);
keyInfo.iv = iv;
keyInfo.mac = mac;
}
} else {
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
}
if (!keyId) {
do {
keyId = randomString(32);
} while (
await this._baseApis.getAccountDataFromServer(
`m.secret_storage.key.${keyId}`,
)
);
}
await this._baseApis.setAccountData(
`m.secret_storage.key.${keyId}`, keyInfo,
);
return {
keyId,
keyInfo,
};
}
/**
* Get the key information for a given ID.
*
* @param {string} [keyId = default key's ID] The ID of the key to check
* for. Defaults to the default key ID if not provided.
* @returns {Array?} If the key was found, the return value is an array of
* the form [keyId, keyInfo]. Otherwise, null is returned.
*/
async getKey(keyId) {
if (!keyId) {
keyId = await this.getDefaultKeyId();
}
if (!keyId) {
return null;
}
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
return keyInfo ? [keyId, keyInfo] : null;
}
/**
* Check whether we have a key with a given ID.
*
* @param {string} [keyId = default key's ID] The ID of the key to check
* for. Defaults to the default key ID if not provided.
* @return {boolean} Whether we have the key.
*/
async hasKey(keyId) {
return !!(await this.getKey(keyId));
}
/**
* Check whether a key matches what we expect based on the key info
*
* @param {Uint8Array} key the key to check
* @param {object} info the key info
*
* @return {boolean} whether or not the key matches
*/
async checkKey(key, info) {
if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (info.mac) {
const {mac} = await SecretStorage._calculateKeyCheck(key, info.iv);
return info.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '');
} else {
// if we have no information, we have to assume the key is right
return true;
}
} else {
throw new Error("Unknown algorithm");
}
}
static async _calculateKeyCheck(key, iv) {
return await encryptAES(ZERO_STR, key, "", iv);
}
/**
* Store an encrypted secret on the server
*
* @param {string} name The name of the secret
* @param {string} secret The secret contents.
* @param {Array} keys The IDs of the keys to use to encrypt the secret
* or null/undefined to use the default key.
*/
async store(name, secret, keys) {
const encrypted = {};
if (!keys) {
const defaultKeyId = await this.getDefaultKeyId();
if (!defaultKeyId) {
throw new Error("No keys specified and no default key present");
}
keys = [defaultKeyId];
}
if (keys.length === 0) {
throw new Error("Zero keys given to encrypt with!");
}
for (const keyId of keys) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) {
throw new Error("Unknown key: " + keyId);
}
// encrypt secret, based on the algorithm
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const keys = {[keyId]: keyInfo};
const [, encryption] = await this._getSecretStorageKey(keys, name);
encrypted[keyId] = await encryption.encrypt(secret);
} else {
logger.warn("unknown algorithm for secret storage key " + keyId
+ ": " + keyInfo.algorithm);
// do nothing if we don't understand the encryption algorithm
}
}
// save encrypted secret
await this._baseApis.setAccountData(name, {encrypted});
}
/**
* Temporary method to fix up existing accounts where secrets
* are incorrectly stored without the 'encrypted' level
*
* @param {string} name The name of the secret
* @param {object} secretInfo The account data object
* @returns {object} The fixed object or null if no fix was performed
*/
async _fixupStoredSecret(name, secretInfo) {
// We assume the secret was only stored passthrough for 1
// key - this was all the broken code supported.
const keys = Object.keys(secretInfo);
if (
keys.length === 1 && keys[0] !== 'encrypted' &&
secretInfo[keys[0]].passthrough
) {
const hasKey = await this.hasKey(keys[0]);
if (hasKey) {
logger.log("Fixing up passthrough secret: " + name);
await this.storePassthrough(name, keys[0]);
const newData = await this._baseApis.getAccountDataFromServer(name);
return newData;
}
}
return null;
}
/**
* Get a secret from storage.
*
* @param {string} name the name of the secret
*
* @return {string} the contents of the secret
*/
async get(name) {
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
if (!secretInfo) {
return;
}
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
throw new Error("Content is not encrypted!");
}
}
// get possible keys to decrypt
const keys = {};
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {
keys[keyId] = keyInfo;
}
}
}
if (Object.keys(keys).length === 0) {
throw new Error(`Could not decrypt ${name} because none of ` +
`the keys it is encrypted with are for a supported algorithm`);
}
let keyId;
let decryption;
try {
// fetch private key from app
[keyId, decryption] = await this._getSecretStorageKey(keys, name);
const encInfo = secretInfo.encrypted[keyId];
// We don't actually need the decryption object if it's a passthrough
// since we just want to return the key itself. It must be base64
// encoded, since this is how a key would normally be stored.
if (encInfo.passthrough) return encodeBase64(decryption.get_private_key());
return await decryption.decrypt(encInfo);
} finally {
if (decryption && decryption.free) decryption.free();
}
}
/**
* Check if a secret is stored on the server.
*
* @param {string} name the name of the secret
* @param {boolean} checkKey check if the secret is encrypted by a trusted key
*
* @return {object?} map of key name to key info the secret is encrypted
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStored(name, checkKey) {
// check if secret exists
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
if (!secretInfo) return null;
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
return null;
}
}
if (checkKey === undefined) checkKey = true;
const ret = {};
// filter secret encryption keys with supported algorithm
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) continue;
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {
ret[keyId] = keyInfo;
}
}
}
return Object.keys(ret).length ? ret : null;
}
/**
* Request a secret from another device
*
* @param {string} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from
*
* @return {string} the contents of the secret
*/
request(name, devices) {
const requestId = this._baseApis.makeTxnId();
const requestControl = this._requests[requestId] = {
name,
devices,
};
const promise = new Promise((resolve, reject) => {
requestControl.resolve = resolve;
requestControl.reject = reject;
});
const cancel = (reason) => {
// send cancellation event
const cancelData = {
action: "request_cancellation",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = cancelData;
}
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
// and reject the promise so that anyone waiting on it will be
// notified
requestControl.reject(new Error(reason || "Cancelled"));
};
// send request to devices
const requestData = {
name,
action: "request",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = requestData;
}
logger.info(`Request secret ${name} from ${devices}, id ${requestId}`);
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
return {
request_id: requestId,
promise,
cancel,
};
}
async _onRequestReceived(event) {
const sender = event.getSender();
const content = event.getContent();
if (sender !== this._baseApis.getUserId()
|| !(content.name && content.action
&& content.requesting_device_id && content.request_id)) {
// ignore requests from anyone else, for now
return;
}
const deviceId = content.requesting_device_id;
// check if it's a cancel
if (content.action === "request_cancellation") {
if (this._incomingRequests[deviceId]
&& this._incomingRequests[deviceId][content.request_id]) {
logger.info("received request cancellation for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
this.baseApis.emit("crypto.secrets.requestCancelled", {
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
});
}
} else if (content.action === "request") {
if (deviceId === this._baseApis.deviceId) {
// no point in trying to send ourself the secret
return;
}
// check if we have the secret
logger.info("received request for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
if (!this._cryptoCallbacks.onSecretRequested) {
return;
}
const secret = await this._cryptoCallbacks.onSecretRequested(
sender,
deviceId,
content.request_id,
content.name,
this._baseApis.checkDeviceTrust(sender, deviceId),
);
if (secret) {
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
const payload = {
type: "m.secret.send",
content: {
request_id: content.request_id,
secret: secret,
},
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._baseApis._crypto._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this._baseApis._crypto._olmDevice,
this._baseApis,
{
[sender]: [
this._baseApis.getStoredDevice(sender, deviceId),
],
},
);
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._baseApis.getUserId(),
this._baseApis.deviceId,
this._baseApis._crypto._olmDevice,
sender,
this._baseApis.getStoredDevice(sender, deviceId),
payload,
);
const contentMap = {
[sender]: {
[deviceId]: encryptedContent,
},
};
logger.info(`Sending ${content.name} secret for ${deviceId}`);
this._baseApis.sendToDevice("m.room.encrypted", contentMap);
} else {
logger.info(`Request denied for ${content.name} secret for ${deviceId}`);
}
}
}
_onSecretReceived(event) {
if (event.getSender() !== this._baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data
return;
}
const content = event.getContent();
logger.log("got secret share for request", content.request_id);
const requestControl = this._requests[content.request_id];
if (requestControl) {
// make sure that the device that sent it is one of the devices that
// we requested from
const deviceInfo = this._baseApis._crypto._deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
);
if (!deviceInfo) {
logger.log(
"secret share from unknown device with key", event.getSenderKey(),
);
return;
}
if (!requestControl.devices.includes(deviceInfo.deviceId)) {
logger.log("unsolicited secret share from device", deviceInfo.deviceId);
return;
}
logger.log(
`Successfully received secret ${requestControl.name} ` +
`from ${deviceInfo.deviceId}`,
);
requestControl.resolve(content.secret);
}
}
async _getSecretStorageKey(keys, name) {
if (!this._cryptoCallbacks.getSecretStorageKey) {
throw new Error("No getSecretStorageKey callback supplied");
}
const returned = await this._cryptoCallbacks.getSecretStorageKey({ keys }, name);
if (!returned) {
throw new Error("getSecretStorageKey callback returned falsey");
}
if (returned.length < 2) {
throw new Error("getSecretStorageKey callback returned invalid data");
}
const [keyId, privateKey] = returned;
if (!keys[keyId]) {
throw new Error("App returned unknown key from getSecretStorageKey!");
}
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const decryption = {
encrypt: async function(secret) {
return await encryptAES(secret, privateKey, name);
},
decrypt: async function(encInfo) {
return await decryptAES(encInfo, privateKey, name);
},
};
return [keyId, decryption];
} else {
throw new Error("Unknown key type: " + keys[keyId].algorithm);
}
}
}
+251
View File
@@ -0,0 +1,251 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {getCrypto} from '../utils';
import {decodeBase64, encodeBase64} from './olmlib';
const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
// salt for HKDF, with 8 bytes of zeros
const zerosalt = new Uint8Array(8);
/**
* encrypt a string in Node.js
*
* @param {string} data the plaintext to encrypt
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptNode(data, key, name, ivStr) {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
}
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = crypto.randomBytes(16);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const cipher = crypto.createCipheriv("aes-256-ctr", aesKey, iv);
const ciphertext = cipher.update(data, "utf-8", "base64")
+ cipher.final("base64");
const hmac = crypto.createHmac("sha256", hmacKey)
.update(ciphertext, "base64").digest("base64");
return {
iv: encodeBase64(iv),
ciphertext: ciphertext,
mac: hmac,
};
}
/**
* decrypt a string in Node.js
*
* @param {object} data the encrypted data
* @param {string} data.ciphertext the ciphertext in base64
* @param {string} data.iv the initialization vector in base64
* @param {string} data.mac the HMAC in base64
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptNode(data, key, name) {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
}
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const hmac = crypto.createHmac("sha256", hmacKey)
.update(data.ciphertext, "base64").digest("base64").replace(/=+$/g, '');
if (hmac !== data.mac.replace(/=+$/g, '')) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
}
const decipher = crypto.createDecipheriv(
"aes-256-ctr", aesKey, decodeBase64(data.iv),
);
return decipher.update(data.ciphertext, "base64", "utf-8")
+ decipher.final("utf-8");
}
function deriveKeysNode(key, name) {
const crypto = getCrypto();
const prk = crypto.createHmac("sha256", zerosalt)
.update(key).digest();
const b = Buffer.alloc(1, 1);
const aesKey = crypto.createHmac("sha256", prk)
.update(name, "utf-8").update(b).digest();
b[0] = 2;
const hmacKey = crypto.createHmac("sha256", prk)
.update(aesKey).update(name, "utf-8").update(b).digest();
return [aesKey, hmacKey];
}
/**
* encrypt a string in Node.js
*
* @param {string} data the plaintext to encrypt
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptBrowser(data, key, name, ivStr) {
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = new Uint8Array(16);
window.crypto.getRandomValues(iv);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const encodedData = new TextEncoder().encode(data);
const ciphertext = await subtleCrypto.encrypt(
{
name: "AES-CTR",
counter: iv,
length: 64,
},
aesKey,
encodedData,
);
const hmac = await subtleCrypto.sign(
{name: 'HMAC'},
hmacKey,
ciphertext,
);
return {
iv: encodeBase64(iv),
ciphertext: encodeBase64(ciphertext),
mac: encodeBase64(hmac),
};
}
/**
* decrypt a string in the browser
*
* @param {object} data the encrypted data
* @param {string} data.ciphertext the ciphertext in base64
* @param {string} data.iv the initialization vector in base64
* @param {string} data.mac the HMAC in base64
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptBrowser(data, key, name) {
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const ciphertext = decodeBase64(data.ciphertext);
if (!await subtleCrypto.verify(
{name: "HMAC"},
hmacKey,
decodeBase64(data.mac),
ciphertext,
)) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
}
const plaintext = await subtleCrypto.decrypt(
{
name: "AES-CTR",
counter: decodeBase64(data.iv),
length: 64,
},
aesKey,
ciphertext,
);
return new TextDecoder().decode(new Uint8Array(plaintext));
}
async function deriveKeysBrowser(key, name) {
const hkdfkey = await subtleCrypto.importKey(
'raw',
key,
{name: "HKDF"},
false,
["deriveBits"],
);
const keybits = await subtleCrypto.deriveBits(
{
name: "HKDF",
salt: zerosalt,
info: (new TextEncoder().encode(name)),
hash: "SHA-256",
},
hkdfkey,
512,
);
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
const aesProm = subtleCrypto.importKey(
'raw',
aesKey,
{name: 'AES-CTR'},
false,
['encrypt', 'decrypt'],
);
const hmacProm = subtleCrypto.importKey(
'raw',
hmacKey,
{
name: 'HMAC',
hash: {name: 'SHA-256'},
},
false,
['sign', 'verify'],
);
return await Promise.all([aesProm, hmacProm]);
}
export function encryptAES(...args) {
return subtleCrypto ? encryptBrowser(...args) : encryptNode(...args);
}
export function decryptAES(...args) {
return subtleCrypto ? decryptBrowser(...args) : decryptNode(...args);
}
+23 -9
View File
@@ -20,8 +20,6 @@ limitations under the License.
* @module
*/
import Promise from 'bluebird';
/**
* map of registered encryption algorithm classes. A map from string to {@link
* module:crypto/algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class
@@ -52,7 +50,7 @@ export const DECRYPTION_CLASSES = {};
* @param {string} params.roomId The ID of the room we will be sending to
* @param {object} params.config The body of the m.room.encryption event
*/
class EncryptionAlgorithm {
export class EncryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._deviceId = params.deviceId;
@@ -62,6 +60,15 @@ class EncryptionAlgorithm {
this._roomId = params.roomId;
}
/**
* Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message.
*
* @param {module:models/room} room the room the event is in
*/
prepareToEncrypt(room) {
}
/**
* Encrypt a message event
*
@@ -72,7 +79,7 @@ class EncryptionAlgorithm {
* @param {string} eventType
* @param {object} plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
* @return {Promise} Promise which resolves to the new event body
*/
/**
@@ -86,7 +93,6 @@ class EncryptionAlgorithm {
onRoomMembership(event, member, oldMembership) {
}
}
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* base type for decryption implementations
@@ -100,7 +106,7 @@ export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
* @param {string=} params.roomId The ID of the room we will be receiving
* from. Null for to-device events.
*/
class DecryptionAlgorithm {
export class DecryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._crypto = params.crypto;
@@ -161,8 +167,17 @@ class DecryptionAlgorithm {
shareKeysWithDevice(keyRequest) {
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
}
/**
* Retry decrypting all the events from a sender that haven't been
* decrypted yet.
*
* @param {string} senderKey the sender's key
*/
async retryDecryptionFromSender(senderKey) {
// ignore by default
}
}
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* Exception thrown when decryption fails
@@ -175,7 +190,7 @@ export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
*
* @extends Error
*/
class DecryptionError extends Error {
export class DecryptionError extends Error {
constructor(code, msg, details) {
super(msg);
this.code = code;
@@ -183,7 +198,6 @@ class DecryptionError extends Error {
this.detailedString = _detailedStringForDecryptionError(this, details);
}
}
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
function _detailedStringForDecryptionError(err, details) {
let result = err.name + '[msg: ' + err.message;
+4 -19
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,28 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/algorithms
*/
const base = require("./base");
import "./olm";
import "./megolm";
require("./olm");
require("./megolm");
/**
* @see module:crypto/algorithms/base.ENCRYPTION_CLASSES
*/
module.exports.ENCRYPTION_CLASSES = base.ENCRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DECRYPTION_CLASSES
*/
module.exports.DECRYPTION_CLASSES = base.DECRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DecryptionError
*/
module.exports.DecryptionError = base.DecryptionError;
export * from "./base";
File diff suppressed because it is too large Load Diff
+50 -28
View File
@@ -13,45 +13,48 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* Defines m.olm encryption/decryption
*
* @module crypto/algorithms/olm
*/
import Promise from 'bluebird';
import logger from '../../logger';
const utils = require("../../utils");
const olmlib = require("../olmlib");
const DeviceInfo = require("../deviceinfo");
import {logger} from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import * as olmlib from "../olmlib";
import {DeviceInfo} from "../deviceinfo";
import {
DecryptionAlgorithm,
DecryptionError,
EncryptionAlgorithm,
registerAlgorithm,
} from "./base";
const DeviceVerification = DeviceInfo.DeviceVerification;
const base = require("./base");
/**
* Olm encryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
*
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
* {@link module:crypto/algorithms/EncryptionAlgorithm}
*/
function OlmEncryption(params) {
base.EncryptionAlgorithm.call(this, params);
polyfillSuper(this, EncryptionAlgorithm, params);
this._sessionPrepared = false;
this._prepPromise = null;
}
utils.inherits(OlmEncryption, base.EncryptionAlgorithm);
utils.inherits(OlmEncryption, EncryptionAlgorithm);
/**
* @private
* @param {string[]} roomMembers list of currently-joined users in the room
* @return {module:client.Promise} Promise which resolves when setup is complete
* @return {Promise} Promise which resolves when setup is complete
*/
OlmEncryption.prototype._ensureSession = function(roomMembers) {
if (this._prepPromise) {
@@ -82,7 +85,7 @@ OlmEncryption.prototype._ensureSession = function(roomMembers) {
* @param {string} eventType
* @param {object} content plaintext event content
*
* @return {module:client.Promise} Promise which resolves to the new event body
* @return {Promise} Promise which resolves to the new event body
*/
OlmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
// pick the list of recipients based on the membership list.
@@ -139,21 +142,21 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
}
}
return await Promise.all(promises).return(encryptedContent);
return await Promise.all(promises).then(() => encryptedContent);
};
/**
* Olm decryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
* {@link module:crypto/algorithms/DecryptionAlgorithm}
*/
function OlmDecryption(params) {
base.DecryptionAlgorithm.call(this, params);
polyfillSuper(this, DecryptionAlgorithm, params);
}
utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
utils.inherits(OlmDecryption, DecryptionAlgorithm);
/**
* @inheritdoc
@@ -171,14 +174,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
const ciphertext = content.ciphertext;
if (!ciphertext) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_MISSING_CIPHERTEXT",
"Missing ciphertext",
);
}
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
"Not included in recipients",
);
@@ -189,7 +192,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
try {
payloadString = await this._decryptMessage(deviceKey, message);
} catch (e) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ENCRYPTED_MESSAGE",
"Bad Encrypted Message", {
sender: deviceKey,
@@ -203,14 +206,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// check that we were the intended recipient, to avoid unknown-key attack
// https://github.com/vector-im/vector-web/issues/2483
if (payload.recipient != this._userId) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT",
"Message was intented for " + payload.recipient,
);
}
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT_KEY",
"Message not intended for this device", {
intended: payload.recipient_keys.ed25519,
@@ -224,7 +227,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// (this check is also provided via the sender's embedded ed25519 key,
// which is checked elsewhere).
if (payload.sender != event.getSender()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, {
reported_sender: event.getSender(),
@@ -234,7 +237,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// Olm events intended for a room have a room_id.
if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ROOM",
"Message intended for room " + payload.room_id, {
reported_room: event.room_id,
@@ -261,6 +264,25 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
*/
OlmDecryption.prototype._decryptMessage = async function(
theirDeviceIdentityKey, message,
) {
// This is a wrapper that serialises decryptions of prekey messages, because
// otherwise we race between deciding we have no active sessions for the message
// and creating a new one, which we can only do once because it removes the OTK.
if (message.type !== 0) {
// not a prekey message: we can safely just try & decrypt it
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
} else {
const myPromise = this._olmDevice._olmPrekeyPromise.then(() => {
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
});
// we want the error, but don't propagate it to the next decryption
this._olmDevice._olmPrekeyPromise = myPromise.catch(() => {});
return await myPromise;
}
};
OlmDecryption.prototype._reallyDecryptMessage = async function(
theirDeviceIdentityKey, message,
) {
const sessionIds = await this._olmDevice.getSessionIdsForDevice(
theirDeviceIdentityKey,
@@ -337,4 +359,4 @@ OlmDecryption.prototype._decryptMessage = async function(
};
base.registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
+288
View File
@@ -0,0 +1,288 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {decodeBase64, encodeBase64} from './olmlib';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
import anotherjson from "another-json";
import {logger} from '../logger';
// FIXME: these types should eventually go in a different file
type Signatures = Record<string, Record<string, string>>;
interface DeviceKeys {
algorithms: Array<string>;
device_id: string; // eslint-disable-line camelcase
user_id: string; // eslint-disable-line camelcase
keys: Record<string, string>;
signatures?: Signatures;
}
interface OneTimeKey {
key: string;
fallback?: boolean;
signatures?: Signatures;
}
export const DEHYDRATION_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle";
const oneweek = 7 * 24 * 60 * 60 * 1000;
export class DehydrationManager {
private inProgress = false;
private timeoutId: any;
private key: Uint8Array;
private keyInfo: {[props: string]: any};
private deviceDisplayName: string;
constructor(private crypto) {
this.getDehydrationKeyFromCache();
}
async getDehydrationKeyFromCache(): Promise<void> {
return await this.crypto._cryptoStore.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.getSecretStorePrivateKey(
txn,
async (result) => {
if (result) {
const {key, keyInfo, deviceDisplayName, time} = result;
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
this.key = decodeBase64(decrypted);
this.keyInfo = keyInfo;
this.deviceDisplayName = deviceDisplayName;
const now = Date.now();
const delay = Math.max(1, time + oneweek - now);
this.timeoutId = global.setTimeout(
this.dehydrateDevice.bind(this), delay,
);
}
},
"dehydration",
);
},
);
}
/** set the key, and queue periodic dehydration to the server in the background */
async setKeyAndQueueDehydration(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<void> {
const matches = await this.setKey(key, keyInfo, deviceDisplayName);
if (!matches) {
// start dehydration in the background
this.dehydrateDevice();
}
}
async setKey(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<boolean> {
if (!key) {
// unsetting the key -- cancel any pending dehydration task
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
// clear storage
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.storeSecretStorePrivateKey(
txn, "dehydration", null,
);
},
);
this.key = undefined;
this.keyInfo = undefined;
return;
}
// Check to see if it's the same key as before. If it's different,
// dehydrate a new device. If it's the same, we can keep the same
// device. (Assume that keyInfo and deviceDisplayName will be the
// same if the key is the same.)
let matches: boolean = this.key && key.length == this.key.length;
for (let i = 0; matches && i < key.length; i++) {
if (key[i] != this.key[i]) {
matches = false;
}
}
if (!matches) {
this.key = key;
this.keyInfo = keyInfo;
this.deviceDisplayName = deviceDisplayName;
}
return matches;
}
/** returns the device id of the newly created dehydrated device */
async dehydrateDevice(): Promise<string> {
if (this.inProgress) {
logger.log("Dehydration already in progress -- not starting new dehydration");
return;
}
this.inProgress = true;
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
try {
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
// update the crypto store with the timestamp
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.storeSecretStorePrivateKey(
txn, "dehydration",
{
keyInfo: this.keyInfo,
key,
deviceDisplayName: this.deviceDisplayName,
time: Date.now(),
},
);
},
);
logger.log("Attempting to dehydrate device");
logger.log("Creating account");
// create the account and all the necessary keys
const account = new global.Olm.Account();
account.create();
const e2eKeys = JSON.parse(account.identity_keys());
const maxKeys = account.max_number_of_one_time_keys();
// FIXME: generate in small batches?
account.generate_one_time_keys(maxKeys / 2);
account.generate_fallback_key();
const otks: Record<string, string> = JSON.parse(account.one_time_keys());
const fallbacks: Record<string, string> = JSON.parse(account.fallback_key());
account.mark_keys_as_published();
// dehydrate the account and store it on the server
const pickledAccount = account.pickle(new Uint8Array(this.key));
const deviceData: {[props: string]: any} = {
algorithm: DEHYDRATION_ALGORITHM,
account: pickledAccount,
};
if (this.keyInfo.passphrase) {
deviceData.passphrase = this.keyInfo.passphrase;
}
logger.log("Uploading account to server");
const dehydrateResult = await this.crypto._baseApis._http.authedRequest(
undefined,
"PUT",
"/dehydrated_device",
undefined,
{
device_data: deviceData,
initial_device_display_name: this.deviceDisplayName,
},
{
prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2",
},
);
// send the keys to the server
const deviceId = dehydrateResult.device_id;
logger.log("Preparing device keys", deviceId);
const deviceKeys: DeviceKeys = {
algorithms: this.crypto._supportedAlgorithms,
device_id: deviceId,
user_id: this.crypto._userId,
keys: {
[`ed25519:${deviceId}`]: e2eKeys.ed25519,
[`curve25519:${deviceId}`]: e2eKeys.curve25519,
},
};
const deviceSignature = account.sign(anotherjson.stringify(deviceKeys));
deviceKeys.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: deviceSignature,
},
};
if (this.crypto._crossSigningInfo.getId("self_signing")) {
await this.crypto._crossSigningInfo.signObject(deviceKeys, "self_signing");
}
logger.log("Preparing one-time keys");
const oneTimeKeys = {};
for (const [keyId, key] of Object.entries(otks.curve25519)) {
const k: OneTimeKey = {key};
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: signature,
},
};
oneTimeKeys[`signed_curve25519:${keyId}`] = k;
}
logger.log("Preparing fallback keys");
const fallbackKeys = {};
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
const k: OneTimeKey = {key, fallback: true};
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: signature,
},
};
fallbackKeys[`signed_curve25519:${keyId}`] = k;
}
logger.log("Uploading keys to server");
await this.crypto._baseApis._http.authedRequest(
undefined,
"POST",
"/keys/upload/" + encodeURI(deviceId),
undefined,
{
"device_keys": deviceKeys,
"one_time_keys": oneTimeKeys,
"org.matrix.msc2732.fallback_keys": fallbackKeys,
},
);
logger.log("Done dehydrating");
// dehydrate again in a week
this.timeoutId = global.setTimeout(
this.dehydrateDevice.bind(this), oneweek,
);
return deviceId;
} finally {
this.inProgress = false;
}
}
private stop() {
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
}
}
+4 -5
View File
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module crypto/deviceinfo
@@ -44,7 +43,7 @@ limitations under the License.
*
* @param {string} deviceId id of the device
*/
function DeviceInfo(deviceId) {
export function DeviceInfo(deviceId) {
// you can't change the deviceId
Object.defineProperty(this, 'deviceId', {
enumerable: true,
@@ -56,6 +55,7 @@ function DeviceInfo(deviceId) {
this.verified = DeviceVerification.UNVERIFIED;
this.known = false;
this.unsigned = {};
this.signatures = {};
}
/**
@@ -88,6 +88,7 @@ DeviceInfo.prototype.toStorage = function() {
verified: this.verified,
known: this.known,
unsigned: this.unsigned,
signatures: this.signatures,
};
};
@@ -165,5 +166,3 @@ DeviceInfo.DeviceVerification = {
const DeviceVerification = DeviceInfo.DeviceVerification;
/** */
module.exports = DeviceInfo;
+2021 -438
View File
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { randomString } from '../randomstring';
import {randomString} from '../randomstring';
const DEFAULT_ITERATIONS = 500000;
export async function keyForExistingBackup(backupData, password) {
const DEFAULT_BITSIZE = 256;
export async function keyFromAuthData(authData, password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const authData = backupData.auth_data;
if (!authData.private_key_salt || !authData.private_key_iterations) {
throw new Error(
"Salt and/or iterations not found: " +
@@ -33,24 +34,25 @@ export async function keyForExistingBackup(backupData, password) {
}
return await deriveKey(
password, backupData.auth_data.private_key_salt,
backupData.auth_data.private_key_iterations,
password, authData.private_key_salt,
authData.private_key_iterations,
authData.private_key_bits || DEFAULT_BITSIZE,
);
}
export async function keyForNewBackup(password) {
export async function keyFromPassphrase(password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const salt = randomString(32);
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS);
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE);
return { key, salt, iterations: DEFAULT_ITERATIONS };
}
async function deriveKey(password, salt, iterations) {
export async function deriveKey(password, salt, iterations, numBits = DEFAULT_BITSIZE) {
const subtleCrypto = global.crypto.subtle;
const TextEncoder = global.TextEncoder;
if (!subtleCrypto || !TextEncoder) {
@@ -74,7 +76,7 @@ async function deriveKey(password, salt, iterations) {
hash: 'SHA-512',
},
key,
global.Olm.PRIVATE_KEY_LENGTH * 8,
numBits,
);
return new Uint8Array(keybits);
+217 -39
View File
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,26 +22,24 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/
import Promise from 'bluebird';
const anotherjson = require('another-json');
import logger from '../logger';
const utils = require("../utils");
import {logger} from '../logger';
import * as utils from "../utils";
import anotherjson from "another-json";
/**
* matrix algorithm tag for olm
*/
module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
export const OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
/**
* matrix algorithm tag for megolm
*/
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
/**
* matrix algorithm tag for megolm backups
*/
module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
export const MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
/**
@@ -59,7 +58,7 @@ module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2
* Returns a promise which resolves (to undefined) when the payload
* has been encrypted into `resultsObject`
*/
module.exports.encryptMessageForDevice = async function(
export async function encryptMessageForDevice(
resultsObject,
ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice,
payloadFields,
@@ -112,7 +111,58 @@ module.exports.encryptMessageForDevice = async function(
resultsObject[deviceKey] = await olmDevice.encryptMessage(
deviceKey, sessionId, JSON.stringify(payload),
);
};
}
/**
* Get the existing olm sessions for the given devices, and the devices that
* don't have olm sessions.
*
* @param {module:crypto/OlmDevice} olmDevice
*
* @param {module:base-apis~MatrixBaseApis} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices to ensure sessions for
*
* @return {Promise} resolves to an array. The first element of the array is a
* a map of user IDs to arrays of deviceInfo, representing the devices that
* don't have established olm sessions. The second element of the array is
* a map from userId to deviceId to {@link module:crypto~OlmSessionResult}
*/
export async function getExistingOlmSessions(
olmDevice, baseApis, devicesByUser,
) {
const devicesWithoutSession = {};
const sessions = {};
const promises = [];
for (const [userId, devices] of Object.entries(devicesByUser)) {
for (const deviceInfo of devices) {
const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey();
promises.push((async () => {
const sessionId = await olmDevice.getSessionIdForDevice(
key, true,
);
if (sessionId === null) {
devicesWithoutSession[userId] = devicesWithoutSession[userId] || [];
devicesWithoutSession[userId].push(deviceInfo);
} else {
sessions[userId] = sessions[userId] || {};
sessions[userId][deviceId] = {
device: deviceInfo,
sessionId: sessionId,
};
}
})());
}
}
await Promise.all(promises);
return [devicesWithoutSession, sessions];
}
/**
* Try to make sure we have established olm sessions for the given devices.
@@ -124,32 +174,58 @@ module.exports.encryptMessageForDevice = async function(
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices to ensure sessions for
*
* @param {bolean} force If true, establish a new session even if one already exists.
* Optional.
* @param {boolean} [force=false] If true, establish a new session even if one
* already exists.
*
* @return {module:client.Promise} resolves once the sessions are complete, to
* @param {Number} [otkTimeout] The timeout in milliseconds when requesting
* one-time keys for establishing new olm sessions.
*
* @param {Array} [failedServers] An array to fill with remote servers that
* failed to respond to one-time-key requests.
*
* @return {Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
module.exports.ensureOlmSessionsForDevices = async function(
olmDevice, baseApis, devicesByUser, force,
export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers,
) {
if (typeof force === "number") {
failedServers = otkTimeout;
otkTimeout = force;
force = false;
}
const devicesWithoutSession = [
// [userId, deviceId], ...
];
const result = {};
const resolveSession = {};
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
for (const [userId, devices] of Object.entries(devicesByUser)) {
result[userId] = {};
const devices = devicesByUser[userId];
for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j];
for (const deviceInfo of devices) {
const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey();
if (key === olmDevice.deviceCurve25519Key) {
// We should never be trying to start a session with ourself.
// Apart from talking to yourself being the first sign of madness,
// olm sessions can't do this because they get confused when
// they get a message and see that the 'other side' has started a
// new chain when this side has an active sender chain.
// If you see this message being logged in the wild, we should find
// the thing that is trying to send Olm messages to itself and fix it.
logger.info("Attempted to start session with ourself! Ignoring");
// We must fill in the section in the return value though, as callers
// expect it to be there.
result[userId][deviceId] = {
device: deviceInfo,
sessionId: null,
};
continue;
}
if (!olmDevice._sessionsInProgress[key]) {
// pre-emptively mark the session as in-progress to avoid race
// conditions. If we find that we already have a session, then
@@ -181,6 +257,11 @@ module.exports.ensureOlmSessionsForDevices = async function(
delete resolveSession[key];
}
if (sessionId === null || force) {
if (force) {
logger.info("Forcing new Olm session for " + userId + ":" + deviceId);
} else {
logger.info("Making new Olm session for " + userId + ":" + deviceId);
}
devicesWithoutSession.push([userId, deviceId]);
}
result[userId][deviceId] = {
@@ -198,7 +279,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
let res;
try {
res = await baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm,
devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout,
);
} catch (e) {
for (const resolver of Object.values(resolveSession)) {
@@ -208,18 +289,26 @@ module.exports.ensureOlmSessionsForDevices = async function(
throw e;
}
if (failedServers && "failures" in res) {
failedServers.push(...Object.keys(res.failures));
}
const otk_res = res.one_time_keys || {};
const promises = [];
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
for (const [userId, devices] of Object.entries(devicesByUser)) {
const userRes = otk_res[userId] || {};
const devices = devicesByUser[userId];
for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j];
const deviceId = deviceInfo.deviceId;
const key = deviceInfo.getIdentityKey();
if (key === olmDevice.deviceCurve25519Key) {
// We've already logged about this above. Skip here too
// otherwise we'll log saying there are no one-time keys
// which will be confusing.
continue;
}
if (result[userId][deviceId].sessionId && !force) {
// we already have a result for this device
continue;
@@ -263,12 +352,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
await Promise.all(promises);
return result;
};
}
async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
const deviceId = deviceInfo.deviceId;
try {
await _verifySignature(
await verifySignature(
olmDevice, oneTimeKey, userId, deviceId,
deviceInfo.getFingerprint(),
);
@@ -287,12 +376,12 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
);
} catch (e) {
// possibly a bad key
logger.error("Error starting session with device " +
logger.error("Error starting olm session with device " +
userId + ":" + deviceId + ": " + e);
return null;
}
logger.log("Started new sessionid " + sid +
logger.log("Started new olm sessionid " + sid +
" for device " + userId + ":" + deviceId);
return sid;
}
@@ -303,8 +392,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
*
* @param {module:crypto/OlmDevice} olmDevice olm wrapper to use for verify op
*
* @param {Object} obj object to check signature on. Note that this will be
* stripped of its 'signatures' and 'unsigned' properties.
* @param {Object} obj object to check signature on.
*
* @param {string} signingUserId ID of the user whose signature should be checked
*
@@ -315,7 +403,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
* Returns a promise which resolves (to undefined) if the the signature is good,
* or rejects with an Error if it is bad.
*/
const _verifySignature = module.exports.verifySignature = async function(
export async function verifySignature(
olmDevice, obj, signingUserId, signingDeviceId, signingKey,
) {
const signKeyId = "ed25519:" + signingDeviceId;
@@ -328,11 +416,101 @@ const _verifySignature = module.exports.verifySignature = async function(
// prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson
delete obj.unsigned;
delete obj.signatures;
const json = anotherjson.stringify(obj);
const mangledObj = Object.assign({}, obj);
delete mangledObj.unsigned;
delete mangledObj.signatures;
const json = anotherjson.stringify(mangledObj);
olmDevice.verifySignature(
signingKey, json, signature,
);
};
}
/**
* Sign a JSON object using public key cryptography
* @param {Object} obj Object to sign. The object will be modified to include
* the new signature
* @param {Olm.PkSigning|Uint8Array} key the signing object or the private key
* seed
* @param {string} userId The user ID who owns the signing key
* @param {string} pubkey The public key (ignored if key is a seed)
* @returns {string} the signature for the object
*/
export function pkSign(obj, key, userId, pubkey) {
let createdKey = false;
if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning();
pubkey = keyObj.init_with_seed(key);
key = keyObj;
createdKey = true;
}
const sigs = obj.signatures || {};
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
const mysigs = sigs[userId] || {};
sigs[userId] = mysigs;
return mysigs['ed25519:' + pubkey] = key.sign(anotherjson.stringify(obj));
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
if (createdKey) {
key.free();
}
}
}
/**
* Verify a signed JSON object
* @param {Object} obj Object to verify
* @param {string} pubkey The public key to use to verify
* @param {string} userId The user ID who signed the object
*/
export function pkVerify(obj, pubkey, userId) {
const keyId = "ed25519:" + pubkey;
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
throw new Error("No signature");
}
const signature = obj.signatures[userId][keyId];
const util = new global.Olm.Utility();
const sigs = obj.signatures;
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
util.ed25519_verify(pubkey, anotherjson.stringify(obj), signature);
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
util.free();
}
}
/**
* Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
export function encodeBase64(uint8Array) {
return Buffer.from(uint8Array).toString("base64");
}
/**
* Encode a typed array of uint8 as unpadded base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The unpadded base64.
*/
export function encodeUnpaddedBase64(uint8Array) {
return encodeBase64(uint8Array).replace(/=+$/g, '');
}
/**
* Decode a base64 string to a typed array of uint8.
* @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data.
*/
export function decodeBase64(base64) {
return Buffer.from(base64, "base64");
}

Some files were not shown because too many files have changed in this diff Show More