vodozemac 0.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 +7 -0
- data/Cargo.lock +975 -0
- data/Cargo.toml +7 -0
- data/LICENSE +200 -0
- data/NOTICE +43 -0
- data/ext/vodozemac/Cargo.toml +57 -0
- data/ext/vodozemac/extconf.rb +11 -0
- data/ext/vodozemac/src/lib.rs +900 -0
- data/lib/vodozemac/version.rb +8 -0
- data/lib/vodozemac/version.rb.erb +8 -0
- data/lib/vodozemac.rb +15 -0
- metadata +112 -0
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
//! Ruby bindings for vodozemac (Matrix Olm/Megolm), via magnus.
|
|
2
|
+
//!
|
|
3
|
+
//! This is the Ruby analogue of matrix-nio/vodozemac-python (which uses PyO3).
|
|
4
|
+
//! It exposes vodozemac's Account / Session (Olm), GroupSession /
|
|
5
|
+
//! InboundGroupSession (Megolm), Sas, the ed25519 and curve25519 key types and
|
|
6
|
+
//! PkEncryption / PkDecryption, under the `Vodozemac` module.
|
|
7
|
+
//!
|
|
8
|
+
//! The API is deliberately base64-string / integer centric rather than passing
|
|
9
|
+
//! wrapped key objects around: Matrix moves keys and ciphertext as base64
|
|
10
|
+
//! strings inside JSON, so a string API drops straight into the protocol layer
|
|
11
|
+
//! and keeps the magnus surface small. The key types are bound as classes too,
|
|
12
|
+
//! for the caller which wants to parse one before handing it on, but every
|
|
13
|
+
//! method takes and returns base64.
|
|
14
|
+
//!
|
|
15
|
+
//! # Provenance
|
|
16
|
+
//!
|
|
17
|
+
//! matrix-rust-sdk is the reference for how these primitives are driven, and it
|
|
18
|
+
//! is licensed Apache-2.0, as this project is. Logic may therefore be ported
|
|
19
|
+
//! from it directly: section 4(b) of the licence asks that a derived file carry
|
|
20
|
+
//! a prominent notice of the change, so a method which follows its logic says
|
|
21
|
+
//! so where it appears, and the root NOTICE records the attribution.
|
|
22
|
+
//!
|
|
23
|
+
//! Fractal is worth reading for how a client sequences these calls, but it is
|
|
24
|
+
//! GPL-3.0-or-later. Nothing may be copied from it into this file.
|
|
25
|
+
//!
|
|
26
|
+
//! - <https://github.com/matrix-org/matrix-rust-sdk> (Apache-2.0)
|
|
27
|
+
//! - <https://github.com/matrix-org/vodozemac> (Apache-2.0)
|
|
28
|
+
//! - <https://gitlab.gnome.org/World/fractal> (GPL-3.0-or-later; read only)
|
|
29
|
+
|
|
30
|
+
use std::cell::RefCell;
|
|
31
|
+
use std::collections::HashMap;
|
|
32
|
+
|
|
33
|
+
use magnus::{function, method, prelude::*, Error, Ruby};
|
|
34
|
+
|
|
35
|
+
use vodozemac_core::{
|
|
36
|
+
base64_decode, base64_encode, Curve25519PublicKey as Curve25519Public,
|
|
37
|
+
Curve25519SecretKey as Curve25519Secret, Ed25519Keypair as Ed25519Pair,
|
|
38
|
+
Ed25519PublicKey as Ed25519Public, Ed25519SecretKey as Ed25519Secret,
|
|
39
|
+
Ed25519Signature as Ed25519Sig,
|
|
40
|
+
};
|
|
41
|
+
use vodozemac_core::olm::{
|
|
42
|
+
Account as OlmAccount, AccountPickle, OlmMessage, PreKeyMessage, Session as OlmSession,
|
|
43
|
+
SessionConfig as OlmSessionConfig, SessionPickle,
|
|
44
|
+
};
|
|
45
|
+
use vodozemac_core::megolm::{
|
|
46
|
+
ExportedSessionKey, GroupSession as MegolmGroupSession, GroupSessionPickle,
|
|
47
|
+
InboundGroupSession as MegolmInbound, InboundGroupSessionPickle, MegolmMessage,
|
|
48
|
+
SessionConfig as MegolmSessionConfig, SessionKey,
|
|
49
|
+
};
|
|
50
|
+
use vodozemac_core::pk_encryption::{
|
|
51
|
+
Message as PkMessage, PkDecryption as PkDecrypt, PkEncryption as PkEncrypt,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// --- helpers ---------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
fn rt_err<E: std::fmt::Display>(e: E) -> Error {
|
|
57
|
+
// Safe: every caller runs inside a Ruby method invocation (GVL held).
|
|
58
|
+
let ruby = Ruby::get().expect("rt_err called outside the Ruby VM");
|
|
59
|
+
Error::new(ruby.exception_runtime_error(), e.to_string())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn key32(label: &str, bytes: &[u8]) -> Result<[u8; 32], Error> {
|
|
63
|
+
bytes
|
|
64
|
+
.try_into()
|
|
65
|
+
.map_err(|_| rt_err(format!("{label} must be 32 bytes, got {}", bytes.len())))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Decode a base64 string into the 32 bytes a raw key is.
|
|
69
|
+
///
|
|
70
|
+
/// The key types vodozemac builds from raw bytes take `&[u8; 32]`, and the ones
|
|
71
|
+
/// which parse base64 themselves do their own length checking. This is for the
|
|
72
|
+
/// former, so that a short string is refused by name rather than panicking.
|
|
73
|
+
fn secret32(label: &str, encoded: &str) -> Result<[u8; 32], Error> {
|
|
74
|
+
let bytes = base64_decode(encoded).map_err(rt_err)?;
|
|
75
|
+
|
|
76
|
+
key32(label, &bytes)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- Account ---------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
#[magnus::wrap(class = "Vodozemac::Account", free_immediately, size)]
|
|
82
|
+
struct Account(RefCell<OlmAccount>);
|
|
83
|
+
|
|
84
|
+
impl Account {
|
|
85
|
+
fn new() -> Self {
|
|
86
|
+
Account(RefCell::new(OlmAccount::new()))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn from_pickle(pickle: String, pickle_key: String) -> Result<Self, Error> {
|
|
90
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
91
|
+
let p = AccountPickle::from_encrypted(&pickle, &key).map_err(rt_err)?;
|
|
92
|
+
Ok(Account(RefCell::new(OlmAccount::from_pickle(p))))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn pickle(&self, pickle_key: String) -> Result<String, Error> {
|
|
96
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
97
|
+
Ok(self.0.borrow().pickle().encrypt(&key))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn ed25519_key(&self) -> String {
|
|
101
|
+
self.0.borrow().ed25519_key().to_base64()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn curve25519_key(&self) -> String {
|
|
105
|
+
self.0.borrow().curve25519_key().to_base64()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn sign(&self, message: String) -> String {
|
|
109
|
+
self.0.borrow().sign(message.as_bytes()).to_base64()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn one_time_keys(&self) -> HashMap<String, String> {
|
|
113
|
+
self.0
|
|
114
|
+
.borrow()
|
|
115
|
+
.one_time_keys()
|
|
116
|
+
.into_iter()
|
|
117
|
+
.map(|(k, v)| (k.to_base64(), v.to_base64()))
|
|
118
|
+
.collect()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn max_number_of_one_time_keys(&self) -> usize {
|
|
122
|
+
self.0.borrow().max_number_of_one_time_keys()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// How many one-time keys this account is holding, published or not.
|
|
126
|
+
fn stored_one_time_key_count(&self) -> usize {
|
|
127
|
+
self.0.borrow().stored_one_time_key_count()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn generate_one_time_keys(&self, count: usize) {
|
|
131
|
+
self.0.borrow_mut().generate_one_time_keys(count);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
fn fallback_key(&self) -> HashMap<String, String> {
|
|
136
|
+
self.0
|
|
137
|
+
.borrow()
|
|
138
|
+
.fallback_key()
|
|
139
|
+
.into_iter()
|
|
140
|
+
.map(|(k, v)| (k.to_base64(), v.to_base64()))
|
|
141
|
+
.collect()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn generate_fallback_key(&self) {
|
|
145
|
+
self.0.borrow_mut().generate_fallback_key();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn mark_keys_as_published(&self) {
|
|
149
|
+
self.0.borrow_mut().mark_keys_as_published();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Drops the private half of the previous fallback key.
|
|
153
|
+
///
|
|
154
|
+
/// The spec says to keep at most two fallback private keys — the current one
|
|
155
|
+
/// and the one before it — and to drop the older once it is reasonably
|
|
156
|
+
/// certain no more messages will arrive using it, because a fallback key is
|
|
157
|
+
/// not consumed by use and so keeps decrypting replays indefinitely.
|
|
158
|
+
/// Returns whether there was one to forget.
|
|
159
|
+
fn forget_fallback_key(&self) -> bool {
|
|
160
|
+
self.0.borrow_mut().forget_fallback_key()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Returns a Session for sending to a peer identified by their identity and
|
|
164
|
+
/// one-time keys (both base64).
|
|
165
|
+
fn create_outbound_session(
|
|
166
|
+
&self,
|
|
167
|
+
identity_key: String,
|
|
168
|
+
one_time_key: String,
|
|
169
|
+
) -> Result<Session, Error> {
|
|
170
|
+
let ik = Curve25519Public::from_base64(&identity_key).map_err(rt_err)?;
|
|
171
|
+
let otk = Curve25519Public::from_base64(&one_time_key).map_err(rt_err)?;
|
|
172
|
+
let session = self
|
|
173
|
+
.0
|
|
174
|
+
.borrow()
|
|
175
|
+
.create_outbound_session(OlmSessionConfig::version_1(), ik, otk)
|
|
176
|
+
.map_err(rt_err)?;
|
|
177
|
+
Ok(Session(RefCell::new(session)))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Establishes an inbound Session from a received pre-key message (base64
|
|
181
|
+
/// body, as carried in an m.room.encrypted olm `body`). Returns
|
|
182
|
+
/// `[session, plaintext]`.
|
|
183
|
+
fn create_inbound_session(
|
|
184
|
+
&self,
|
|
185
|
+
identity_key: String,
|
|
186
|
+
prekey_message_body: String,
|
|
187
|
+
) -> Result<(Session, String), Error> {
|
|
188
|
+
let ik = Curve25519Public::from_base64(&identity_key).map_err(rt_err)?;
|
|
189
|
+
let prekey = PreKeyMessage::from_base64(&prekey_message_body).map_err(rt_err)?;
|
|
190
|
+
let result = self
|
|
191
|
+
.0
|
|
192
|
+
.borrow_mut()
|
|
193
|
+
.create_inbound_session(OlmSessionConfig::version_1(), ik, &prekey)
|
|
194
|
+
.map_err(rt_err)?;
|
|
195
|
+
let plaintext = String::from_utf8(result.plaintext).map_err(rt_err)?;
|
|
196
|
+
Ok((Session(RefCell::new(result.session)), plaintext))
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Session (Olm 1:1) -----------------------------------------------------
|
|
201
|
+
|
|
202
|
+
#[magnus::wrap(class = "Vodozemac::Session", free_immediately, size)]
|
|
203
|
+
struct Session(RefCell<OlmSession>);
|
|
204
|
+
|
|
205
|
+
impl Session {
|
|
206
|
+
fn session_id(&self) -> String {
|
|
207
|
+
self.0.borrow().session_id()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Whether this session has ever decrypted a message from the other side.
|
|
211
|
+
///
|
|
212
|
+
/// Until it has, every message it encrypts is a pre-key message, because the
|
|
213
|
+
/// peer may not have the session yet. This is what says which.
|
|
214
|
+
fn has_received_message(&self) -> bool {
|
|
215
|
+
self.0.borrow().has_received_message()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn pickle(&self, pickle_key: String) -> Result<String, Error> {
|
|
219
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
220
|
+
Ok(self.0.borrow().pickle().encrypt(&key))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fn from_pickle(pickle: String, pickle_key: String) -> Result<Self, Error> {
|
|
224
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
225
|
+
let p = SessionPickle::from_encrypted(&pickle, &key).map_err(rt_err)?;
|
|
226
|
+
Ok(Session(RefCell::new(OlmSession::from_pickle(p))))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Encrypts plaintext; returns `[message_type, body_base64]` matching the
|
|
230
|
+
/// Matrix olm ciphertext shape.
|
|
231
|
+
fn encrypt(&self, plaintext: String) -> Result<(usize, String), Error> {
|
|
232
|
+
let message = self.0.borrow_mut().encrypt(plaintext.as_bytes()).map_err(rt_err)?;
|
|
233
|
+
let (mtype, ciphertext) = message.to_parts();
|
|
234
|
+
Ok((mtype, base64_encode(ciphertext)))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Decrypts an olm message given its type (0 = pre-key, 1 = normal) and
|
|
238
|
+
/// base64 body.
|
|
239
|
+
fn decrypt(&self, message_type: usize, body: String) -> Result<String, Error> {
|
|
240
|
+
let ciphertext = base64_decode(&body).map_err(rt_err)?;
|
|
241
|
+
let message = OlmMessage::from_parts(message_type, &ciphertext).map_err(rt_err)?;
|
|
242
|
+
let plaintext = self.0.borrow_mut().decrypt(&message).map_err(rt_err)?;
|
|
243
|
+
String::from_utf8(plaintext).map_err(rt_err)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// --- GroupSession (Megolm outbound) ---------------------------------------
|
|
248
|
+
|
|
249
|
+
#[magnus::wrap(class = "Vodozemac::GroupSession", free_immediately, size)]
|
|
250
|
+
struct GroupSession(RefCell<MegolmGroupSession>);
|
|
251
|
+
|
|
252
|
+
impl GroupSession {
|
|
253
|
+
fn new() -> Self {
|
|
254
|
+
GroupSession(RefCell::new(MegolmGroupSession::new(MegolmSessionConfig::version_1())))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn session_id(&self) -> String {
|
|
258
|
+
self.0.borrow().session_id()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn message_index(&self) -> u32 {
|
|
262
|
+
self.0.borrow().message_index()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// The session key (base64) to share with recipients via m.room_key.
|
|
266
|
+
fn session_key(&self) -> String {
|
|
267
|
+
self.0.borrow().session_key().to_base64()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Encrypts plaintext, returning the megolm message as base64.
|
|
271
|
+
fn encrypt(&self, plaintext: String) -> String {
|
|
272
|
+
self.0.borrow_mut().encrypt(plaintext.as_bytes()).to_base64()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fn pickle(&self, pickle_key: String) -> Result<String, Error> {
|
|
276
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
277
|
+
Ok(self.0.borrow().pickle().encrypt(&key))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fn from_pickle(pickle: String, pickle_key: String) -> Result<Self, Error> {
|
|
281
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
282
|
+
let p = GroupSessionPickle::from_encrypted(&pickle, &key).map_err(rt_err)?;
|
|
283
|
+
Ok(GroupSession(RefCell::new(MegolmGroupSession::from_pickle(p))))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --- InboundGroupSession (Megolm inbound) ---------------------------------
|
|
288
|
+
|
|
289
|
+
#[magnus::wrap(class = "Vodozemac::InboundGroupSession", free_immediately, size)]
|
|
290
|
+
struct InboundGroupSession(RefCell<MegolmInbound>);
|
|
291
|
+
|
|
292
|
+
impl InboundGroupSession {
|
|
293
|
+
/// Build from a base64 session key received in an m.room_key event.
|
|
294
|
+
fn new(session_key: String) -> Result<Self, Error> {
|
|
295
|
+
let key = SessionKey::from_base64(&session_key).map_err(rt_err)?;
|
|
296
|
+
Ok(InboundGroupSession(RefCell::new(MegolmInbound::new(
|
|
297
|
+
&key,
|
|
298
|
+
MegolmSessionConfig::version_1(),
|
|
299
|
+
))))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/// Build from a base64 **exported** session key.
|
|
303
|
+
///
|
|
304
|
+
/// This is the format room keys travel in outside of `m.room_key`: server-side
|
|
305
|
+
/// key backup (`m.megolm_backup.v1.curve25519-aes-sha2`) and
|
|
306
|
+
/// `m.forwarded_room_key` both carry an `ExportedSessionKey`, which is
|
|
307
|
+
/// version 1 and carries no signature — so `new` (which requires a signed
|
|
308
|
+
/// version 2 `SessionKey`) cannot accept them. Without this, a client can
|
|
309
|
+
/// never read history it did not receive a live `m.room_key` for.
|
|
310
|
+
///
|
|
311
|
+
/// The resulting session is NOT signature-verified, because an exported key
|
|
312
|
+
/// has no signature to verify. That is inherent to the format: the sender
|
|
313
|
+
/// identity of such a session is only as trustworthy as whoever handed it
|
|
314
|
+
/// over, which is why the spec treats backup-restored keys as unverified.
|
|
315
|
+
fn import(exported_key: String) -> Result<Self, Error> {
|
|
316
|
+
let key = ExportedSessionKey::from_base64(&exported_key).map_err(rt_err)?;
|
|
317
|
+
Ok(InboundGroupSession(RefCell::new(MegolmInbound::import(
|
|
318
|
+
&key,
|
|
319
|
+
MegolmSessionConfig::version_1(),
|
|
320
|
+
))))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fn session_id(&self) -> String {
|
|
324
|
+
self.0.borrow().session_id()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
fn first_known_index(&self) -> u32 {
|
|
328
|
+
self.0.borrow().first_known_index()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Whether the two sessions are the same megolm session, by their ratchets.
|
|
332
|
+
///
|
|
333
|
+
/// Two copies of one session may have been received separately — one live,
|
|
334
|
+
/// one from backup — and reached different indices. This says they belong
|
|
335
|
+
/// together without saying which is ahead.
|
|
336
|
+
fn connected(&self, other: &InboundGroupSession) -> bool {
|
|
337
|
+
self.0.borrow_mut().connected(&mut other.0.borrow_mut())
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Which of the two sessions holds more of the ratchet.
|
|
341
|
+
///
|
|
342
|
+
/// Returns one of `:equal`, `:better`, `:worse` or `:unconnected` — the last
|
|
343
|
+
/// meaning they are not the same session at all. This is how a client
|
|
344
|
+
/// decides whether a key it has just been handed is worth keeping over the
|
|
345
|
+
/// one it already has.
|
|
346
|
+
fn compare(&self, other: &InboundGroupSession) -> Result<magnus::Symbol, Error> {
|
|
347
|
+
use vodozemac_core::megolm::SessionOrdering;
|
|
348
|
+
|
|
349
|
+
let ruby = Ruby::get().map_err(rt_err)?;
|
|
350
|
+
|
|
351
|
+
let name = match self.0.borrow_mut().compare(&mut other.0.borrow_mut()) {
|
|
352
|
+
SessionOrdering::Equal => "equal",
|
|
353
|
+
SessionOrdering::Better => "better",
|
|
354
|
+
SessionOrdering::Worse => "worse",
|
|
355
|
+
SessionOrdering::Unconnected => "unconnected",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
Ok(ruby.to_symbol(name))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// Move the ratchet forward to `index`, discarding the keys before it.
|
|
362
|
+
///
|
|
363
|
+
/// Returns whether it moved. This is how a session is trimmed to the history
|
|
364
|
+
/// a room's visibility rules allow a member to read.
|
|
365
|
+
fn advance_to(&self, index: u32) -> bool {
|
|
366
|
+
self.0.borrow_mut().advance_to(index)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// Export this session at `index`, base64, for uploading to key backup or
|
|
370
|
+
/// forwarding to another device. Returns nil when the ratchet has already
|
|
371
|
+
/// moved past `index` and can no longer produce a key for it — megolm
|
|
372
|
+
/// ratchets forward only, so an earlier index is unrecoverable by design.
|
|
373
|
+
fn export_at(&self, index: u32) -> Option<String> {
|
|
374
|
+
self.0.borrow_mut().export_at(index).map(|key| key.to_base64())
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// Export at the earliest index this session can still reach.
|
|
378
|
+
fn export_at_first_known_index(&self) -> String {
|
|
379
|
+
self.0.borrow().export_at_first_known_index().to_base64()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/// Decrypts a base64 megolm message; returns `[plaintext, message_index]`.
|
|
383
|
+
fn decrypt(&self, message: String) -> Result<(String, u32), Error> {
|
|
384
|
+
let msg = MegolmMessage::from_base64(&message).map_err(rt_err)?;
|
|
385
|
+
let decrypted = self.0.borrow_mut().decrypt(&msg).map_err(rt_err)?;
|
|
386
|
+
let plaintext = String::from_utf8(decrypted.plaintext).map_err(rt_err)?;
|
|
387
|
+
Ok((plaintext, decrypted.message_index))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn pickle(&self, pickle_key: String) -> Result<String, Error> {
|
|
391
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
392
|
+
Ok(self.0.borrow().pickle().encrypt(&key))
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn from_pickle(pickle: String, pickle_key: String) -> Result<Self, Error> {
|
|
396
|
+
let key = key32("pickle_key", pickle_key.as_bytes())?;
|
|
397
|
+
let p = InboundGroupSessionPickle::from_encrypted(&pickle, &key).map_err(rt_err)?;
|
|
398
|
+
Ok(InboundGroupSession(RefCell::new(MegolmInbound::from_pickle(p))))
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// --- Sas (short authentication string) -------------------------------------
|
|
403
|
+
|
|
404
|
+
/// One half of an SAS verification, before the Diffie-Hellman.
|
|
405
|
+
///
|
|
406
|
+
/// A `Sas` establishes exactly one shared secret and is consumed doing so,
|
|
407
|
+
/// which is why `diffie_hellman` takes it out of the cell: vodozemac models that
|
|
408
|
+
/// with an owned `self`, and a second attempt is a programming error rather than
|
|
409
|
+
/// something to be silently allowed.
|
|
410
|
+
#[magnus::wrap(class = "Vodozemac::Sas", free_immediately, size)]
|
|
411
|
+
struct Sas {
|
|
412
|
+
sas: RefCell<Option<vodozemac_core::sas::Sas>>,
|
|
413
|
+
// Kept separately so that it survives the Diffie-Hellman: the SAS and MAC
|
|
414
|
+
// info strings are built from BOTH public keys, so a caller still needs its
|
|
415
|
+
// own after the exchange has consumed the Sas itself.
|
|
416
|
+
public_key: String,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
impl Sas {
|
|
420
|
+
fn new() -> Self {
|
|
421
|
+
let sas = vodozemac_core::sas::Sas::new();
|
|
422
|
+
let public_key = sas.public_key().to_base64();
|
|
423
|
+
|
|
424
|
+
Sas {
|
|
425
|
+
sas: RefCell::new(Some(sas)),
|
|
426
|
+
public_key,
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// The ephemeral public key to send in `m.key.verification.key`.
|
|
431
|
+
fn public_key(&self) -> String {
|
|
432
|
+
self.public_key.clone()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fn diffie_hellman(&self, their_public_key: String) -> Result<EstablishedSas, Error> {
|
|
436
|
+
let key = Curve25519Public::from_base64(&their_public_key).map_err(rt_err)?;
|
|
437
|
+
let sas = self
|
|
438
|
+
.sas
|
|
439
|
+
.borrow_mut()
|
|
440
|
+
.take()
|
|
441
|
+
.ok_or_else(|| rt_err("this Sas has already been established"))?;
|
|
442
|
+
|
|
443
|
+
Ok(EstablishedSas {
|
|
444
|
+
sas: sas.diffie_hellman(key).map_err(rt_err)?,
|
|
445
|
+
our_public_key: self.public_key.clone(),
|
|
446
|
+
// Re-encoded from the parsed key rather than kept as given, so that
|
|
447
|
+
// the pair is canonical base64 on both sides: the info strings are
|
|
448
|
+
// built by concatenating these, and must match byte for byte.
|
|
449
|
+
their_public_key: key.to_base64(),
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/// An established SAS channel, which carries both public keys.
|
|
455
|
+
///
|
|
456
|
+
/// vodozemac drops them at this point, but every info string the spec defines
|
|
457
|
+
/// is built from both, so carrying them here is what lets a caller construct
|
|
458
|
+
/// one without tracking the keys itself.
|
|
459
|
+
#[magnus::wrap(class = "Vodozemac::EstablishedSas", free_immediately, size)]
|
|
460
|
+
struct EstablishedSas {
|
|
461
|
+
sas: vodozemac_core::sas::EstablishedSas,
|
|
462
|
+
our_public_key: String,
|
|
463
|
+
their_public_key: String,
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl EstablishedSas {
|
|
467
|
+
/// Our own ephemeral public key, as sent in `m.key.verification.key`.
|
|
468
|
+
fn our_public_key(&self) -> String {
|
|
469
|
+
self.our_public_key.clone()
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/// The other device's ephemeral public key, as received.
|
|
473
|
+
fn their_public_key(&self) -> String {
|
|
474
|
+
self.their_public_key.clone()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/// The three numbers of the `decimal` SAS method, already offset by 1000.
|
|
478
|
+
///
|
|
479
|
+
/// This is the method to offer. Decimal is required of every client, so it
|
|
480
|
+
/// always agrees, and MSC4405 deprecates the alternative.
|
|
481
|
+
fn decimals(&self, info: String) -> (u16, u16, u16) {
|
|
482
|
+
self.sas.bytes(&info).decimals()
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/// The seven emoji indices of the `emoji` SAS method, as table offsets.
|
|
486
|
+
///
|
|
487
|
+
/// Deprecated by MSC4405, because each client names the emoji in its own
|
|
488
|
+
/// language and two users can therefore hold different words for the same
|
|
489
|
+
/// picture. Kept so that a verification with a client which offers nothing
|
|
490
|
+
/// else can still be shown.
|
|
491
|
+
fn emoji_indices(&self, info: String) -> Vec<u8> {
|
|
492
|
+
self.sas.bytes(&info).emoji_indices().to_vec()
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/// The raw bytes, for a SAS method this crate does not implement.
|
|
496
|
+
fn bytes(&self, info: String) -> Vec<u8> {
|
|
497
|
+
self.sas.bytes(&info).as_bytes().to_vec()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/// As many raw bytes as a method asks for, rather than the six `bytes` gives.
|
|
501
|
+
///
|
|
502
|
+
/// Refused above 8160 bytes, which is the most HKDF-SHA256 will expand to.
|
|
503
|
+
fn bytes_raw(&self, info: String, count: usize) -> Result<Vec<u8>, Error> {
|
|
504
|
+
self.sas.bytes_raw(&info, count).map_err(|_| {
|
|
505
|
+
rt_err(format!("cannot expand {count} bytes of SAS material"))
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn calculate_mac(&self, input: String, info: String) -> String {
|
|
510
|
+
self.sas.calculate_mac(&input, &info).to_base64()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/// The `hkdf-hmac-sha256` MAC, which libolm encoded incorrectly.
|
|
514
|
+
///
|
|
515
|
+
/// Deprecated by the spec in favour of `hkdf-hmac-sha256.v2`, and kept only
|
|
516
|
+
/// so that a verification with an older client can still be read.
|
|
517
|
+
fn calculate_mac_invalid_base64(&self, input: String, info: String) -> String {
|
|
518
|
+
self.sas.calculate_mac_invalid_base64(&input, &info)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
fn verify_mac(&self, input: String, info: String, tag: String) -> Result<bool, Error> {
|
|
522
|
+
let mac = vodozemac_core::sas::Mac::from_base64(&tag).map_err(rt_err)?;
|
|
523
|
+
Ok(self.sas.verify_mac(&input, &info, &mac).is_ok())
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// --- ed25519 ---------------------------------------------------------------
|
|
528
|
+
|
|
529
|
+
/// A long-term ed25519 signing key, which can be written down and read back.
|
|
530
|
+
///
|
|
531
|
+
/// This is the type a Matrix cross-signing key is: the private half goes into
|
|
532
|
+
/// secret storage as base64, and the public half names the key. `Ed25519Keypair`
|
|
533
|
+
/// is the faster one for signing in bulk, but it has no serialisation, so an
|
|
534
|
+
/// identity which has to survive a restart is this one.
|
|
535
|
+
#[magnus::wrap(class = "Vodozemac::Ed25519SecretKey", free_immediately, size)]
|
|
536
|
+
struct Ed25519SecretKey(Ed25519Secret);
|
|
537
|
+
|
|
538
|
+
impl Ed25519SecretKey {
|
|
539
|
+
fn new() -> Self {
|
|
540
|
+
Ed25519SecretKey(Ed25519Secret::new())
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
fn from_base64(encoded: String) -> Result<Self, Error> {
|
|
544
|
+
Ok(Ed25519SecretKey(Ed25519Secret::from_base64(&encoded).map_err(rt_err)?))
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// The private half, base64. This belongs in secret storage and nowhere else.
|
|
548
|
+
fn to_base64(&self) -> String {
|
|
549
|
+
self.0.to_base64()
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/// The public half, base64, which is also how the key is named.
|
|
553
|
+
fn public_key(&self) -> String {
|
|
554
|
+
self.0.public_key().to_base64()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/// Sign a message, returning the signature as base64.
|
|
558
|
+
fn sign(&self, message: String) -> String {
|
|
559
|
+
self.0.sign(message.as_bytes()).to_base64()
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/// An ephemeral ed25519 keypair, which signs faster and cannot be written down.
|
|
564
|
+
///
|
|
565
|
+
/// vodozemac keeps its pickle private, so there is no way to serialise one: this
|
|
566
|
+
/// is for a key which lives as long as the process does. An Account holds one of
|
|
567
|
+
/// these for its own device signature.
|
|
568
|
+
#[magnus::wrap(class = "Vodozemac::Ed25519Keypair", free_immediately, size)]
|
|
569
|
+
struct Ed25519Keypair(Ed25519Pair);
|
|
570
|
+
|
|
571
|
+
impl Ed25519Keypair {
|
|
572
|
+
fn new() -> Self {
|
|
573
|
+
Ed25519Keypair(Ed25519Pair::new())
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/// The public half, base64, which is also how the key is named.
|
|
577
|
+
fn public_key(&self) -> String {
|
|
578
|
+
self.0.public_key().to_base64()
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/// Sign a message, returning the signature as base64.
|
|
582
|
+
fn sign(&self, message: String) -> String {
|
|
583
|
+
self.0.sign(message.as_bytes()).to_base64()
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/// The public half of an ed25519 key, which is what a signature is checked against.
|
|
588
|
+
#[magnus::wrap(class = "Vodozemac::Ed25519PublicKey", free_immediately, size)]
|
|
589
|
+
struct Ed25519PublicKey(Ed25519Public);
|
|
590
|
+
|
|
591
|
+
impl Ed25519PublicKey {
|
|
592
|
+
fn from_base64(encoded: String) -> Result<Self, Error> {
|
|
593
|
+
Ok(Ed25519PublicKey(Ed25519Public::from_base64(&encoded).map_err(rt_err)?))
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
fn to_base64(&self) -> String {
|
|
597
|
+
self.0.to_base64()
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// Whether this key signed the message, given the base64 signature.
|
|
601
|
+
fn verify(&self, message: String, signature: String) -> Result<bool, Error> {
|
|
602
|
+
let sig = Ed25519Sig::from_base64(&signature).map_err(rt_err)?;
|
|
603
|
+
|
|
604
|
+
Ok(self.0.verify(message.as_bytes(), &sig).is_ok())
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/// An ed25519 signature, for the caller which wants one parsed before sending it.
|
|
609
|
+
///
|
|
610
|
+
/// Every method here takes and returns a signature as base64, because that is
|
|
611
|
+
/// how Matrix carries one. This is the type for checking that a string is a
|
|
612
|
+
/// well-formed signature at all.
|
|
613
|
+
#[magnus::wrap(class = "Vodozemac::Ed25519Signature", free_immediately, size)]
|
|
614
|
+
struct Ed25519Signature(Ed25519Sig);
|
|
615
|
+
|
|
616
|
+
impl Ed25519Signature {
|
|
617
|
+
fn from_base64(encoded: String) -> Result<Self, Error> {
|
|
618
|
+
Ok(Ed25519Signature(Ed25519Sig::from_base64(&encoded).map_err(rt_err)?))
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
fn to_base64(&self) -> String {
|
|
622
|
+
self.0.to_base64()
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// --- curve25519 ------------------------------------------------------------
|
|
627
|
+
|
|
628
|
+
/// The private half of a curve25519 key, which agrees a secret with a public one.
|
|
629
|
+
#[magnus::wrap(class = "Vodozemac::Curve25519SecretKey", free_immediately, size)]
|
|
630
|
+
struct Curve25519SecretKey(Curve25519Secret);
|
|
631
|
+
|
|
632
|
+
impl Curve25519SecretKey {
|
|
633
|
+
fn new() -> Self {
|
|
634
|
+
Curve25519SecretKey(Curve25519Secret::new())
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
fn from_base64(encoded: String) -> Result<Self, Error> {
|
|
638
|
+
let bytes = secret32("secret key", &encoded)?;
|
|
639
|
+
|
|
640
|
+
Ok(Curve25519SecretKey(Curve25519Secret::from_slice(&bytes)))
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
fn to_base64(&self) -> String {
|
|
644
|
+
base64_encode(self.0.to_bytes().as_slice())
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
fn public_key(&self) -> String {
|
|
648
|
+
Curve25519Public::from(&self.0).to_base64()
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/// The raw shared secret with another key, base64.
|
|
652
|
+
///
|
|
653
|
+
/// Nothing in Matrix wants this: Sas and PkEncryption do their own exchange
|
|
654
|
+
/// and derive from it. It is bound because vodozemac exposes it, and a raw
|
|
655
|
+
/// secret which has not been through a KDF is not a key — derive one before
|
|
656
|
+
/// it encrypts anything. Nil when the peer's key is non-contributory.
|
|
657
|
+
fn diffie_hellman(&self, their_public_key: String) -> Result<Option<String>, Error> {
|
|
658
|
+
let key = Curve25519Public::from_base64(&their_public_key).map_err(rt_err)?;
|
|
659
|
+
|
|
660
|
+
Ok(self.0.diffie_hellman(&key).map(|shared| base64_encode(shared.as_bytes())))
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/// The public half of a curve25519 key, which is how a device is addressed.
|
|
665
|
+
#[magnus::wrap(class = "Vodozemac::Curve25519PublicKey", free_immediately, size)]
|
|
666
|
+
struct Curve25519PublicKey(Curve25519Public);
|
|
667
|
+
|
|
668
|
+
impl Curve25519PublicKey {
|
|
669
|
+
fn from_base64(encoded: String) -> Result<Self, Error> {
|
|
670
|
+
Ok(Curve25519PublicKey(Curve25519Public::from_base64(&encoded).map_err(rt_err)?))
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
fn to_base64(&self) -> String {
|
|
674
|
+
self.0.to_base64()
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// --- PkEncryption / PkDecryption -------------------------------------------
|
|
679
|
+
|
|
680
|
+
/// The private half of a PkEncryption key, which reads what was sealed to it.
|
|
681
|
+
///
|
|
682
|
+
/// This is libolm's PkDecryption, which is the primitive Matrix server-side key
|
|
683
|
+
/// backup is built on: `m.megolm_backup.v1.curve25519-aes-sha2` is an ECDH to
|
|
684
|
+
/// this key, HKDF-SHA256, AES-256-CBC and a truncated HMAC.
|
|
685
|
+
///
|
|
686
|
+
/// The MAC authenticates nothing — libolm computed it over the empty string
|
|
687
|
+
/// rather than the ciphertext, and every implementation has reproduced the bug
|
|
688
|
+
/// ever since for compatibility. So a message which verifies has only been shown
|
|
689
|
+
/// to come from somebody who knew the public key, which is everybody. See the
|
|
690
|
+
/// warning in the specification, and MSC4048 for the version which fixes it.
|
|
691
|
+
#[magnus::wrap(class = "Vodozemac::PkDecryption", free_immediately, size)]
|
|
692
|
+
struct PkDecryption(PkDecrypt);
|
|
693
|
+
|
|
694
|
+
impl PkDecryption {
|
|
695
|
+
fn new() -> Self {
|
|
696
|
+
PkDecryption(PkDecrypt::new())
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/// The key a stored private half describes, which is how a backup is reopened.
|
|
700
|
+
fn from_base64(secret_key: String) -> Result<Self, Error> {
|
|
701
|
+
let bytes = secret32("secret key", &secret_key)?;
|
|
702
|
+
|
|
703
|
+
Ok(PkDecryption(PkDecrypt::from_key(Curve25519Secret::from_slice(&bytes))))
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/// The public half, base64, which anybody may seal to.
|
|
707
|
+
fn public_key(&self) -> String {
|
|
708
|
+
self.0.public_key().to_base64()
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/// The private half, base64, which belongs in secret storage and nowhere else.
|
|
712
|
+
fn secret_key(&self) -> String {
|
|
713
|
+
base64_encode(self.0.secret_key().to_bytes().as_slice())
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/// Open a message, given its three base64 parts as the specification names them.
|
|
717
|
+
fn decrypt(
|
|
718
|
+
&self,
|
|
719
|
+
ciphertext: String,
|
|
720
|
+
mac: String,
|
|
721
|
+
ephemeral_key: String,
|
|
722
|
+
) -> Result<String, Error> {
|
|
723
|
+
let message = PkMessage::from_base64(&ciphertext, &mac, &ephemeral_key).map_err(rt_err)?;
|
|
724
|
+
let plaintext = self.0.decrypt(&message).map_err(rt_err)?;
|
|
725
|
+
|
|
726
|
+
String::from_utf8(plaintext).map_err(rt_err)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/// The public half of a PkEncryption key, which seals to it.
|
|
731
|
+
///
|
|
732
|
+
/// Holding this is enough to add to a backup and not enough to read one, which
|
|
733
|
+
/// is what lets a device upload history it cannot itself recover.
|
|
734
|
+
#[magnus::wrap(class = "Vodozemac::PkEncryption", free_immediately, size)]
|
|
735
|
+
struct PkEncryption(PkEncrypt);
|
|
736
|
+
|
|
737
|
+
impl PkEncryption {
|
|
738
|
+
fn from_base64(public_key: String) -> Result<Self, Error> {
|
|
739
|
+
let key = Curve25519Public::from_base64(&public_key).map_err(rt_err)?;
|
|
740
|
+
|
|
741
|
+
Ok(PkEncryption(PkEncrypt::from_key(key)))
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/// Seal a message; returns `[ciphertext, mac, ephemeral_key]`, each base64.
|
|
745
|
+
fn encrypt(&self, plaintext: String) -> Result<(String, String, String), Error> {
|
|
746
|
+
let message = self.0.encrypt(plaintext.as_bytes()).map_err(rt_err)?;
|
|
747
|
+
|
|
748
|
+
Ok((
|
|
749
|
+
base64_encode(&message.ciphertext),
|
|
750
|
+
base64_encode(&message.mac),
|
|
751
|
+
message.ephemeral_key.to_base64(),
|
|
752
|
+
))
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// --- module-level functions ------------------------------------------------
|
|
757
|
+
|
|
758
|
+
/// Verify an ed25519 signature. `key` and `signature` are base64; returns bool.
|
|
759
|
+
fn verify_signature(key: String, message: String, signature: String) -> Result<bool, Error> {
|
|
760
|
+
let public_key = Ed25519Public::from_base64(&key).map_err(rt_err)?;
|
|
761
|
+
let sig = Ed25519Sig::from_base64(&signature).map_err(rt_err)?;
|
|
762
|
+
Ok(public_key.verify(message.as_bytes(), &sig).is_ok())
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// --- init ------------------------------------------------------------------
|
|
766
|
+
|
|
767
|
+
#[magnus::init]
|
|
768
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
769
|
+
let namespace = ruby.define_module("Vodozemac")?;
|
|
770
|
+
|
|
771
|
+
// Which vodozemac this was built against, as against Vodozemac::VERSION, which is the gem's.
|
|
772
|
+
namespace.const_set("NATIVE_VERSION", vodozemac_core::VERSION)?;
|
|
773
|
+
|
|
774
|
+
let account = namespace.define_class("Account", ruby.class_object())?;
|
|
775
|
+
account.define_singleton_method("new", function!(Account::new, 0))?;
|
|
776
|
+
account.define_singleton_method("from_pickle", function!(Account::from_pickle, 2))?;
|
|
777
|
+
account.define_method("pickle", method!(Account::pickle, 1))?;
|
|
778
|
+
account.define_method("ed25519_key", method!(Account::ed25519_key, 0))?;
|
|
779
|
+
account.define_method("curve25519_key", method!(Account::curve25519_key, 0))?;
|
|
780
|
+
account.define_method("sign", method!(Account::sign, 1))?;
|
|
781
|
+
account.define_method("one_time_keys", method!(Account::one_time_keys, 0))?;
|
|
782
|
+
account.define_method(
|
|
783
|
+
"max_number_of_one_time_keys",
|
|
784
|
+
method!(Account::max_number_of_one_time_keys, 0),
|
|
785
|
+
)?;
|
|
786
|
+
account.define_method(
|
|
787
|
+
"stored_one_time_key_count",
|
|
788
|
+
method!(Account::stored_one_time_key_count, 0),
|
|
789
|
+
)?;
|
|
790
|
+
account.define_method("generate_one_time_keys", method!(Account::generate_one_time_keys, 1))?;
|
|
791
|
+
account.define_method("fallback_key", method!(Account::fallback_key, 0))?;
|
|
792
|
+
account.define_method("generate_fallback_key", method!(Account::generate_fallback_key, 0))?;
|
|
793
|
+
account.define_method("mark_keys_as_published", method!(Account::mark_keys_as_published, 0))?;
|
|
794
|
+
account.define_method("forget_fallback_key", method!(Account::forget_fallback_key, 0))?;
|
|
795
|
+
account.define_method("create_outbound_session", method!(Account::create_outbound_session, 2))?;
|
|
796
|
+
account.define_method("create_inbound_session", method!(Account::create_inbound_session, 2))?;
|
|
797
|
+
|
|
798
|
+
let session = namespace.define_class("Session", ruby.class_object())?;
|
|
799
|
+
session.define_singleton_method("from_pickle", function!(Session::from_pickle, 2))?;
|
|
800
|
+
session.define_method("session_id", method!(Session::session_id, 0))?;
|
|
801
|
+
session.define_method("has_received_message", method!(Session::has_received_message, 0))?;
|
|
802
|
+
session.define_method("pickle", method!(Session::pickle, 1))?;
|
|
803
|
+
session.define_method("encrypt", method!(Session::encrypt, 1))?;
|
|
804
|
+
session.define_method("decrypt", method!(Session::decrypt, 2))?;
|
|
805
|
+
|
|
806
|
+
let group = namespace.define_class("GroupSession", ruby.class_object())?;
|
|
807
|
+
group.define_singleton_method("new", function!(GroupSession::new, 0))?;
|
|
808
|
+
group.define_singleton_method("from_pickle", function!(GroupSession::from_pickle, 2))?;
|
|
809
|
+
group.define_method("session_id", method!(GroupSession::session_id, 0))?;
|
|
810
|
+
group.define_method("message_index", method!(GroupSession::message_index, 0))?;
|
|
811
|
+
group.define_method("session_key", method!(GroupSession::session_key, 0))?;
|
|
812
|
+
group.define_method("encrypt", method!(GroupSession::encrypt, 1))?;
|
|
813
|
+
group.define_method("pickle", method!(GroupSession::pickle, 1))?;
|
|
814
|
+
|
|
815
|
+
let inbound = namespace.define_class("InboundGroupSession", ruby.class_object())?;
|
|
816
|
+
inbound.define_singleton_method("new", function!(InboundGroupSession::new, 1))?;
|
|
817
|
+
inbound.define_singleton_method("import", function!(InboundGroupSession::import, 1))?;
|
|
818
|
+
inbound.define_singleton_method("from_pickle", function!(InboundGroupSession::from_pickle, 2))?;
|
|
819
|
+
inbound.define_method("session_id", method!(InboundGroupSession::session_id, 0))?;
|
|
820
|
+
inbound.define_method("first_known_index", method!(InboundGroupSession::first_known_index, 0))?;
|
|
821
|
+
inbound.define_method("connected", method!(InboundGroupSession::connected, 1))?;
|
|
822
|
+
inbound.define_method("compare", method!(InboundGroupSession::compare, 1))?;
|
|
823
|
+
inbound.define_method("advance_to", method!(InboundGroupSession::advance_to, 1))?;
|
|
824
|
+
inbound.define_method("decrypt", method!(InboundGroupSession::decrypt, 1))?;
|
|
825
|
+
inbound.define_method("pickle", method!(InboundGroupSession::pickle, 1))?;
|
|
826
|
+
inbound.define_method("export_at", method!(InboundGroupSession::export_at, 1))?;
|
|
827
|
+
inbound.define_method(
|
|
828
|
+
"export_at_first_known_index",
|
|
829
|
+
method!(InboundGroupSession::export_at_first_known_index, 0),
|
|
830
|
+
)?;
|
|
831
|
+
|
|
832
|
+
let sas = namespace.define_class("Sas", ruby.class_object())?;
|
|
833
|
+
sas.define_singleton_method("new", function!(Sas::new, 0))?;
|
|
834
|
+
sas.define_method("public_key", method!(Sas::public_key, 0))?;
|
|
835
|
+
sas.define_method("diffie_hellman", method!(Sas::diffie_hellman, 1))?;
|
|
836
|
+
|
|
837
|
+
let established = namespace.define_class("EstablishedSas", ruby.class_object())?;
|
|
838
|
+
established.define_method("our_public_key", method!(EstablishedSas::our_public_key, 0))?;
|
|
839
|
+
established.define_method("their_public_key", method!(EstablishedSas::their_public_key, 0))?;
|
|
840
|
+
established.define_method("emoji_indices", method!(EstablishedSas::emoji_indices, 1))?;
|
|
841
|
+
established.define_method("decimals", method!(EstablishedSas::decimals, 1))?;
|
|
842
|
+
established.define_method("bytes", method!(EstablishedSas::bytes, 1))?;
|
|
843
|
+
established.define_method("bytes_raw", method!(EstablishedSas::bytes_raw, 2))?;
|
|
844
|
+
established.define_method("calculate_mac", method!(EstablishedSas::calculate_mac, 2))?;
|
|
845
|
+
established.define_method(
|
|
846
|
+
"calculate_mac_invalid_base64",
|
|
847
|
+
method!(EstablishedSas::calculate_mac_invalid_base64, 2),
|
|
848
|
+
)?;
|
|
849
|
+
established.define_method("verify_mac", method!(EstablishedSas::verify_mac, 3))?;
|
|
850
|
+
|
|
851
|
+
let secret = namespace.define_class("Ed25519SecretKey", ruby.class_object())?;
|
|
852
|
+
secret.define_singleton_method("new", function!(Ed25519SecretKey::new, 0))?;
|
|
853
|
+
secret.define_singleton_method("from_base64", function!(Ed25519SecretKey::from_base64, 1))?;
|
|
854
|
+
secret.define_method("to_base64", method!(Ed25519SecretKey::to_base64, 0))?;
|
|
855
|
+
secret.define_method("public_key", method!(Ed25519SecretKey::public_key, 0))?;
|
|
856
|
+
secret.define_method("sign", method!(Ed25519SecretKey::sign, 1))?;
|
|
857
|
+
|
|
858
|
+
let keypair = namespace.define_class("Ed25519Keypair", ruby.class_object())?;
|
|
859
|
+
keypair.define_singleton_method("new", function!(Ed25519Keypair::new, 0))?;
|
|
860
|
+
keypair.define_method("public_key", method!(Ed25519Keypair::public_key, 0))?;
|
|
861
|
+
keypair.define_method("sign", method!(Ed25519Keypair::sign, 1))?;
|
|
862
|
+
|
|
863
|
+
let public = namespace.define_class("Ed25519PublicKey", ruby.class_object())?;
|
|
864
|
+
public.define_singleton_method("from_base64", function!(Ed25519PublicKey::from_base64, 1))?;
|
|
865
|
+
public.define_method("to_base64", method!(Ed25519PublicKey::to_base64, 0))?;
|
|
866
|
+
public.define_method("verify", method!(Ed25519PublicKey::verify, 2))?;
|
|
867
|
+
|
|
868
|
+
let signature = namespace.define_class("Ed25519Signature", ruby.class_object())?;
|
|
869
|
+
signature.define_singleton_method("from_base64", function!(Ed25519Signature::from_base64, 1))?;
|
|
870
|
+
signature.define_method("to_base64", method!(Ed25519Signature::to_base64, 0))?;
|
|
871
|
+
|
|
872
|
+
let curve_secret = namespace.define_class("Curve25519SecretKey", ruby.class_object())?;
|
|
873
|
+
curve_secret.define_singleton_method("new", function!(Curve25519SecretKey::new, 0))?;
|
|
874
|
+
curve_secret
|
|
875
|
+
.define_singleton_method("from_base64", function!(Curve25519SecretKey::from_base64, 1))?;
|
|
876
|
+
curve_secret.define_method("to_base64", method!(Curve25519SecretKey::to_base64, 0))?;
|
|
877
|
+
curve_secret.define_method("public_key", method!(Curve25519SecretKey::public_key, 0))?;
|
|
878
|
+
curve_secret
|
|
879
|
+
.define_method("diffie_hellman", method!(Curve25519SecretKey::diffie_hellman, 1))?;
|
|
880
|
+
|
|
881
|
+
let curve_public = namespace.define_class("Curve25519PublicKey", ruby.class_object())?;
|
|
882
|
+
curve_public
|
|
883
|
+
.define_singleton_method("from_base64", function!(Curve25519PublicKey::from_base64, 1))?;
|
|
884
|
+
curve_public.define_method("to_base64", method!(Curve25519PublicKey::to_base64, 0))?;
|
|
885
|
+
|
|
886
|
+
let decryption = namespace.define_class("PkDecryption", ruby.class_object())?;
|
|
887
|
+
decryption.define_singleton_method("new", function!(PkDecryption::new, 0))?;
|
|
888
|
+
decryption.define_singleton_method("from_base64", function!(PkDecryption::from_base64, 1))?;
|
|
889
|
+
decryption.define_method("public_key", method!(PkDecryption::public_key, 0))?;
|
|
890
|
+
decryption.define_method("secret_key", method!(PkDecryption::secret_key, 0))?;
|
|
891
|
+
decryption.define_method("decrypt", method!(PkDecryption::decrypt, 3))?;
|
|
892
|
+
|
|
893
|
+
let encryption = namespace.define_class("PkEncryption", ruby.class_object())?;
|
|
894
|
+
encryption.define_singleton_method("from_base64", function!(PkEncryption::from_base64, 1))?;
|
|
895
|
+
encryption.define_method("encrypt", method!(PkEncryption::encrypt, 1))?;
|
|
896
|
+
|
|
897
|
+
namespace.define_module_function("verify_signature", function!(verify_signature, 3))?;
|
|
898
|
+
|
|
899
|
+
Ok(())
|
|
900
|
+
}
|