async-matrix 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cde8ca8632d237c371ec9a8a4ee8a422414b7e69222ca263ba505107d1dd234
4
- data.tar.gz: 24583da0bbd5f8552990e5e1ce21d1b89dbdc41f7024da7d0377e5393665ce4e
3
+ metadata.gz: fbcd762ae4584a018455d875317aed89d4a40751b3c5ded892451411d95cf89c
4
+ data.tar.gz: 49f07effb6009235cab4ac84d727a4b34cde54c7406463dedd625f037eac7a81
5
5
  SHA512:
6
- metadata.gz: f2d44ac4cbd06266e2988bb99af9de6051a0504e448ec8c730fc8978d5e91e023eae0f2dc01b7c45100344285b2e591237ffa349f72e8466dc634552947e7e74
7
- data.tar.gz: aaaf8397d618585875a7447c7b46ca8e1d4802cda7dbbf98d6a3b66e9e3decda4a29d6f61ae9a04acaa12abaebb888d18137bc2efed91ec628b5eedd9a4332ca
6
+ metadata.gz: d01ca57fe8befccd8d0e66e23b2fe52ee76cdc934785106d620c29f267467aa387437097c0e23861de8fe0da4f71e3193f201b43bc12d0bf6184343cb7038759
7
+ data.tar.gz: df8f42bfd7c0b163b17707be2412ef16f75350246ee2ddfed29217046c81277040800d75e8058da4e0d83a67c5ad39db30b5cdc2ce3b396035194325d8af4752
@@ -20,8 +20,9 @@ use vodozemac::olm::{
20
20
  SessionConfig as OlmSessionConfig, SessionPickle,
21
21
  };
22
22
  use vodozemac::megolm::{
23
- GroupSession as MegolmGroupSession, GroupSessionPickle, InboundGroupSession as MegolmInbound,
24
- InboundGroupSessionPickle, MegolmMessage, SessionConfig as MegolmSessionConfig, SessionKey,
23
+ ExportedSessionKey, GroupSession as MegolmGroupSession, GroupSessionPickle,
24
+ InboundGroupSession as MegolmInbound, InboundGroupSessionPickle, MegolmMessage,
25
+ SessionConfig as MegolmSessionConfig, SessionKey,
25
26
  };
26
27
 
27
28
  // --- helpers ---------------------------------------------------------------
@@ -236,6 +237,27 @@ impl InboundGroupSession {
236
237
  ))))
237
238
  }
238
239
 
240
+ /// Build from a base64 **exported** session key.
241
+ ///
242
+ /// This is the format room keys travel in outside of `m.room_key`: server-side
243
+ /// key backup (`m.megolm_backup.v1.curve25519-aes-sha2`) and
244
+ /// `m.forwarded_room_key` both carry an `ExportedSessionKey`, which is
245
+ /// version 1 and carries no signature — so `new` (which requires a signed
246
+ /// version 2 `SessionKey`) cannot accept them. Without this, a client can
247
+ /// never read history it did not receive a live `m.room_key` for.
248
+ ///
249
+ /// The resulting session is NOT signature-verified, because an exported key
250
+ /// has no signature to verify. That is inherent to the format: the sender
251
+ /// identity of such a session is only as trustworthy as whoever handed it
252
+ /// over, which is why the spec treats backup-restored keys as unverified.
253
+ fn import(exported_key: String) -> Result<Self, Error> {
254
+ let key = ExportedSessionKey::from_base64(&exported_key).map_err(rt_err)?;
255
+ Ok(InboundGroupSession(RefCell::new(MegolmInbound::import(
256
+ &key,
257
+ MegolmSessionConfig::version_1(),
258
+ ))))
259
+ }
260
+
239
261
  fn session_id(&self) -> String {
240
262
  self.0.borrow().session_id()
241
263
  }
@@ -244,6 +266,19 @@ impl InboundGroupSession {
244
266
  self.0.borrow().first_known_index()
245
267
  }
246
268
 
269
+ /// Export this session at `index`, base64, for uploading to key backup or
270
+ /// forwarding to another device. Returns nil when the ratchet has already
271
+ /// moved past `index` and can no longer produce a key for it — megolm
272
+ /// ratchets forward only, so an earlier index is unrecoverable by design.
273
+ fn export_at(&self, index: u32) -> Option<String> {
274
+ self.0.borrow_mut().export_at(index).map(|key| key.to_base64())
275
+ }
276
+
277
+ /// Export at the earliest index this session can still reach.
278
+ fn export_at_first_known_index(&self) -> String {
279
+ self.0.borrow().export_at_first_known_index().to_base64()
280
+ }
281
+
247
282
  /// Decrypts a base64 megolm message; returns `[plaintext, message_index]`.
248
283
  fn decrypt(&self, message: String) -> Result<(String, u32), Error> {
249
284
  let msg = MegolmMessage::from_base64(&message).map_err(rt_err)?;
@@ -316,11 +351,17 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
316
351
 
317
352
  let inbound = namespace.define_class("InboundGroupSession", ruby.class_object())?;
318
353
  inbound.define_singleton_method("new", function!(InboundGroupSession::new, 1))?;
354
+ inbound.define_singleton_method("import", function!(InboundGroupSession::import, 1))?;
319
355
  inbound.define_singleton_method("from_pickle", function!(InboundGroupSession::from_pickle, 2))?;
320
356
  inbound.define_method("session_id", method!(InboundGroupSession::session_id, 0))?;
321
357
  inbound.define_method("first_known_index", method!(InboundGroupSession::first_known_index, 0))?;
322
358
  inbound.define_method("decrypt", method!(InboundGroupSession::decrypt, 1))?;
323
359
  inbound.define_method("pickle", method!(InboundGroupSession::pickle, 1))?;
360
+ inbound.define_method("export_at", method!(InboundGroupSession::export_at, 1))?;
361
+ inbound.define_method(
362
+ "export_at_first_known_index",
363
+ method!(InboundGroupSession::export_at_first_known_index, 0),
364
+ )?;
324
365
 
325
366
  namespace.define_module_function("verify_signature", function!(verify_signature, 3))?;
326
367
 
@@ -56,6 +56,43 @@ __END__
56
56
  inbound.session_id.should == group.session_id
57
57
  end
58
58
 
59
+ it "imports an exported session key" do
60
+ # The path room keys take out of server-side key backup and out of
61
+ # m.forwarded_room_key. Without .import these are unusable, and a client
62
+ # can never read history it did not get a live m.room_key for.
63
+ group = Async::Matrix::E2EE::GroupSession.new
64
+ inbound = Async::Matrix::E2EE::InboundGroupSession.new(group.session_key)
65
+ message = group.encrypt("restored from backup")
66
+
67
+ exported = inbound.export_at_first_known_index
68
+ restored = Async::Matrix::E2EE::InboundGroupSession.import(exported)
69
+
70
+ restored.session_id.should == group.session_id
71
+ restored.decrypt(message).first.should == "restored from backup"
72
+ end
73
+
74
+ it "rejects an exported key passed to .new, which requires a signature" do
75
+ # .new takes a version-2 SessionKey and verifies its signature; an exported
76
+ # key is version 1 and unsigned, so it must go through .import instead.
77
+ group = Async::Matrix::E2EE::GroupSession.new
78
+ inbound = Async::Matrix::E2EE::InboundGroupSession.new(group.session_key)
79
+ exported = inbound.export_at_first_known_index
80
+
81
+ lambda {
82
+ Async::Matrix::E2EE::InboundGroupSession.new(exported)
83
+ }.should.raise(RuntimeError)
84
+ end
85
+
86
+ it "exports at a given message index" do
87
+ group = Async::Matrix::E2EE::GroupSession.new
88
+ inbound = Async::Matrix::E2EE::InboundGroupSession.new(group.session_key)
89
+ 3.times { |i| group.encrypt("msg #{i}") }
90
+
91
+ Async::Matrix::E2EE::InboundGroupSession
92
+ .import(inbound.export_at(0))
93
+ .first_known_index.should == 0
94
+ end
95
+
59
96
  it "round-trips an olm 1:1 message" do
60
97
  alice = Async::Matrix::E2EE::Account.new
61
98
  bob = Async::Matrix::E2EE::Account.new
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Matrix
8
- VERSION = "2.0.1"
8
+ VERSION = "2.1.0"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-matrix
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Kidd
@@ -191,6 +191,20 @@ dependencies:
191
191
  - - "~>"
192
192
  - !ruby/object:Gem::Version
193
193
  version: '1.2'
194
+ - !ruby/object:Gem::Dependency
195
+ name: lefthook
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - "~>"
199
+ - !ruby/object:Gem::Version
200
+ version: '2.1'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - "~>"
206
+ - !ruby/object:Gem::Version
207
+ version: '2.1'
194
208
  description: Async-native Matrix protocol primitives built on the Socketry async ecosystem.
195
209
  Provides well-known discovery, event notification, and application service models.
196
210
  email: