1 module matrix.inbound_group;
2 
3 import std.file : read;
4 import std.experimental.allocator : processAllocator;
5 import std.exception : assumeUnique, assertThrown;
6 
7 import matrix.olm : olm_error;
8 import matrix.session : cstr2dstr;
9 
10 import std.stdio; // TODO debug only!
11 
12 public class InboundGroupSession {
13     OlmInboundGroupSession *session;
14     private this() {
15         const len = olm_inbound_group_session_size();
16         auto mem = processAllocator.allocate(len);
17         this.session = olm_inbound_group_session(mem.ptr);
18     }
19     /// serialize session data, locked by key
20     public string pickle(string key) {
21         char[] ret;
22         ret.length = olm_pickle_inbound_group_session_length(this.session);
23         const r = olm_pickle_inbound_group_session(this.session,
24                 key.ptr, key.length, ret.ptr, ret.length);
25         error_check(r);
26         return assumeUnique(ret);
27     }
28     /// deserialize session data, unlocked by key
29     static public InboundGroupSession unpickle(string key, string pickle) {
30         auto a = new InboundGroupSession();
31         char[] p = pickle.dup; // p is destroyed!
32         const r = olm_unpickle_inbound_group_session(a.session,
33                 key.ptr, key.length, p.ptr, p.length);
34         a.error_check(r);
35         return a;
36     }
37     static public InboundGroupSession init(string session_key) {
38         auto s = new InboundGroupSession();
39         olm_init_inbound_group_session(s.session, session_key.ptr, session_key.length);
40         return s;
41     }
42     static public InboundGroupSession import_session(string session_key) {
43         auto s = new InboundGroupSession();
44         olm_import_inbound_group_session(s.session, session_key.ptr, session_key.length);
45         return s;
46     }
47     public string decrypt(string msg, uint* msg_index) {
48         char[] dummy = msg.dup; // dummy is destroyed!
49         auto len = olm_group_decrypt_max_plaintext_length(session,
50             dummy.ptr, dummy.length);
51         error_check(len);
52         char[] ret;
53         ret.length = len;
54         dummy = msg.dup; // dummy is destroyed!
55         len = olm_group_decrypt(session, dummy.ptr, dummy.length,
56             ret.ptr, len, msg_index);
57         error_check(len);
58         return assumeUnique(ret[0..len]);
59     }
60     public string session_id() {
61         char[] ret;
62         ret.length = olm_inbound_group_session_id_length(session);
63         auto r = olm_inbound_group_session_id(session, ret.ptr, ret.length);
64         error_check(r);
65         return assumeUnique(ret);
66     }
67     public @property uint first_known_index() const {
68         return olm_inbound_group_session_first_known_index(session);
69     }
70     public string export_session(uint message_index) {
71         auto len = olm_export_inbound_group_session_length(session);
72         char[] ret;
73         ret.length = len;
74         auto r = olm_export_inbound_group_session(session,
75             ret.ptr, len, message_index);
76         error_check(r);
77         return assumeUnique(ret);
78 
79     }
80     private void error_check(size_t x) {
81         if (x == olm_error()) {
82             auto errmsg = olm_inbound_group_session_last_error(this.session);
83             throw new Exception(cstr2dstr(errmsg));
84         }
85     }
86 }
87 
88 unittest {
89     auto igs = new InboundGroupSession();
90     auto p = igs.pickle("foo");
91     auto dp = InboundGroupSession.unpickle("foo", p);
92 }
93 
94 unittest {
95     import matrix.outbound_group;
96     auto ogs = new OutboundGroupSession();
97     auto session_key = ogs.session_key;
98     auto plain = "Hello World!";
99     auto cipher = ogs.encrypt(plain);
100     /* transfer: session_key, cipher, msg_index */
101     uint msg_index;
102     auto igs = InboundGroupSession.init(session_key);
103     auto dec = igs.decrypt(cipher, &msg_index);
104     //writeln(msg_index, " ", ogs.message_index);
105     assert (dec == plain);
106 }
107 
108 unittest {
109     import matrix.outbound_group;
110     // Alice creates an outbound session
111     auto ogs = new OutboundGroupSession();
112     auto session_key = ogs.session_key;
113     // Can ask for session_key again
114     assert (session_key == ogs.session_key);
115     auto session_id = ogs.session_id;
116     assert (ogs.message_index == 0);
117     // Send session key Bob (securely using Olm/Session)
118     // Bob creates an inbound session with it
119     auto igs = InboundGroupSession.init(session_key);
120     assert (igs.first_known_index == 0);
121     assert (session_id == igs.session_id);
122     {
123         // pickling and unpickling works
124         auto pigs = igs.pickle("foo");
125         auto igs2 = InboundGroupSession.unpickle("foo", pigs);
126         assert (igs.session_id == igs2.session_id);
127     }
128     // Alice encrypts a plain text msg
129     auto plain = "Hello World!";
130     auto cipher = ogs.encrypt(plain);
131     assert (ogs.message_index == 1);
132     // Encryption changes the session_key (the ratchet)
133     auto session_key2 = ogs.session_key;
134     assert (session_key != session_key2);
135     assert (session_id == ogs.session_id);
136     // Bob decrypts it
137     uint msg_index;
138     auto dec = igs.decrypt(cipher, &msg_index);
139     assert (dec == plain);
140     assert (msg_index == 0);
141     {
142         // Using the new session key does not work
143         auto igs2 = InboundGroupSession.init(session_key2);
144         assert (igs2.first_known_index == 1);
145         assertThrown!Exception(igs2.decrypt(cipher, &msg_index));
146     }
147     {
148         // Bob can decrypt again
149         auto dec2 = igs.decrypt(cipher, &msg_index);
150         assert (dec2 == plain);
151         assert (msg_index == 0);
152     }
153     // Alice sends a second message
154     auto cipher2 = ogs.encrypt(plain);
155     assert (ogs.message_index == 2);
156     assert (cipher != cipher2);
157     auto dec2 = igs.decrypt(cipher2, &msg_index);
158     assert (dec2 == plain);
159     assert (msg_index == 1);
160     {
161         // Bob can still decrypt the old msg again
162         auto dec3 = igs.decrypt(cipher, &msg_index);
163         assert (dec3 == plain);
164         assert (msg_index == 0);
165     }
166     {
167         // For the second msg, Bob CAN use the second session key
168         auto igs2 = InboundGroupSession.init(session_key2);
169         assert (igs2.first_known_index == 1);
170         auto dec3 = igs2.decrypt(cipher2, &msg_index);
171         assert (dec3 == plain);
172         assert (msg_index == 1);
173     }
174     {
175         // Bob can reuse the first session key
176         auto igs2 = InboundGroupSession.init(session_key);
177         assert (igs2.first_known_index == 0);
178         auto dec3 = igs2.decrypt(cipher2, &msg_index);
179         assert (dec3 == plain);
180         assert (msg_index == 1);
181     }
182 }
183 
184 extern (C):
185 // copy&pasted from inbound_group_session.h
186 struct OlmInboundGroupSession;
187 
188 /** get the size of an inbound group session, in bytes. */
189 size_t olm_inbound_group_session_size();
190 
191 /**
192  * Initialise an inbound group session object using the supplied memory
193  * The supplied memory should be at least olm_inbound_group_session_size()
194  * bytes.
195  */
196 OlmInboundGroupSession * olm_inbound_group_session(
197     void *memory
198 );
199 
200 /**
201  * A null terminated string describing the most recent error to happen to a
202  * group session */
203 const(char)* olm_inbound_group_session_last_error(
204     const OlmInboundGroupSession *session
205 );
206 
207 /** Clears the memory used to back this group session */
208 size_t olm_clear_inbound_group_session(
209     OlmInboundGroupSession *session
210 );
211 
212 /** Returns the number of bytes needed to store an inbound group session */
213 size_t olm_pickle_inbound_group_session_length(
214     const OlmInboundGroupSession *session
215 );
216 
217 /**
218  * Stores a group session as a base64 string. Encrypts the session using the
219  * supplied key. Returns the length of the session on success.
220  *
221  * Returns olm_error() on failure. If the pickle output buffer
222  * is smaller than olm_pickle_inbound_group_session_length() then
223  * olm_inbound_group_session_last_error() will be "OUTPUT_BUFFER_TOO_SMALL"
224  */
225 size_t olm_pickle_inbound_group_session(
226     OlmInboundGroupSession *session,
227     const(void)* key, size_t key_length,
228     void * pickled, size_t pickled_length
229 );
230 
231 /**
232  * Loads a group session from a pickled base64 string. Decrypts the session
233  * using the supplied key.
234  *
235  * Returns olm_error() on failure. If the key doesn't match the one used to
236  * encrypt the account then olm_inbound_group_session_last_error() will be
237  * "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded then
238  * olm_inbound_group_session_last_error() will be "INVALID_BASE64". The input
239  * pickled buffer is destroyed
240  */
241 size_t olm_unpickle_inbound_group_session(
242     OlmInboundGroupSession *session,
243     const(void)* key, size_t key_length,
244     void * pickled, size_t pickled_length
245 );
246 
247 
248 /**
249  * Start a new inbound group session, from a key exported from
250  * olm_outbound_group_session_key
251  *
252  * Returns olm_error() on failure. On failure last_error will be set with an
253  * error code. The last_error will be:
254  *
255  *  * OLM_INVALID_BASE64  if the session_key is not valid base64
256  *  * OLM_BAD_SESSION_KEY if the session_key is invalid
257  */
258 size_t olm_init_inbound_group_session(
259     OlmInboundGroupSession *session,
260     /* base64-encoded keys */
261     const(char)* session_key, size_t session_key_length
262 );
263 
264 /**
265  * Import an inbound group session, from a previous export.
266  *
267  * Returns olm_error() on failure. On failure last_error will be set with an
268  * error code. The last_error will be:
269  *
270  *  * OLM_INVALID_BASE64  if the session_key is not valid base64
271  *  * OLM_BAD_SESSION_KEY if the session_key is invalid
272  */
273 size_t olm_import_inbound_group_session(
274     OlmInboundGroupSession *session,
275     /* base64-encoded keys; note that it will be overwritten with the base64-decoded
276        data. */
277     const(char)* session_key, size_t session_key_length
278 );
279 
280 
281 /**
282  * Get an upper bound on the number of bytes of plain-text the decrypt method
283  * will write for a given input message length. The actual size could be
284  * different due to padding.
285  *
286  * The input message buffer is destroyed.
287  *
288  * Returns olm_error() on failure.
289  */
290 size_t olm_group_decrypt_max_plaintext_length(
291     OlmInboundGroupSession *session,
292     char* message, size_t message_length
293 );
294 
295 /**
296  * Decrypt a message.
297  *
298  * The input message buffer is destroyed.
299  *
300  * Returns the length of the decrypted plain-text, or olm_error() on failure.
301  *
302  * On failure last_error will be set with an error code. The last_error will
303  * be:
304  *   * OLM_OUTPUT_BUFFER_TOO_SMALL if the plain-text buffer is too small
305  *   * OLM_INVALID_BASE64 if the message is not valid base-64
306  *   * OLM_BAD_MESSAGE_VERSION if the message was encrypted with an unsupported
307  *     version of the protocol
308  *   * OLM_BAD_MESSAGE_FORMAT if the message headers could not be decoded
309  *   * OLM_BAD_MESSAGE_MAC    if the message could not be verified
310  *   * OLM_UNKNOWN_MESSAGE_INDEX  if we do not have a session key corresponding to the
311  *     message's index (ie, it was sent before the session key was shared with
312  *     us)
313  */
314 size_t olm_group_decrypt(
315     OlmInboundGroupSession *session,
316 
317     /* input; note that it will be overwritten with the base64-decoded
318        message. */
319     char* message, size_t message_length,
320 
321     /* output */
322     char* plaintext, size_t max_plaintext_length,
323     uint * message_index
324 );
325 
326 
327 /**
328  * Get the number of bytes returned by olm_inbound_group_session_id()
329  */
330 size_t olm_inbound_group_session_id_length(
331     const OlmInboundGroupSession *session
332 );
333 
334 /**
335  * Get a base64-encoded identifier for this session.
336  *
337  * Returns the length of the session id on success or olm_error() on
338  * failure. On failure last_error will be set with an error code. The
339  * last_error will be OUTPUT_BUFFER_TOO_SMALL if the id buffer was too
340  * small.
341  */
342 size_t olm_inbound_group_session_id(
343     OlmInboundGroupSession *session,
344     char* id, size_t id_length
345 );
346 
347 /**
348  * Get the first message index we know how to decrypt.
349  */
350 uint olm_inbound_group_session_first_known_index(
351     const OlmInboundGroupSession *session
352 );
353 
354 
355 /**
356  * Check if the session has been verified as a valid session.
357  *
358  * (A session is verified either because the original session share was signed,
359  * or because we have subsequently successfully decrypted a message.)
360  *
361  * This is mainly intended for the unit tests, currently.
362  */
363 int olm_inbound_group_session_is_verified(
364     const OlmInboundGroupSession *session
365 );
366 
367 /**
368  * Get the number of bytes returned by olm_export_inbound_group_session()
369  */
370 size_t olm_export_inbound_group_session_length(
371     const OlmInboundGroupSession *session
372 );
373 
374 /**
375  * Export the base64-encoded ratchet key for this session, at the given index,
376  * in a format which can be used by olm_import_inbound_group_session
377  *
378  * Returns the length of the ratchet key on success or olm_error() on
379  * failure. On failure last_error will be set with an error code. The
380  * last_error will be:
381  *   * OUTPUT_BUFFER_TOO_SMALL if the buffer was too small
382  *   * OLM_UNKNOWN_MESSAGE_INDEX  if we do not have a session key corresponding to the
383  *     given index (ie, it was sent before the session key was shared with
384  *     us)
385  */
386 size_t olm_export_inbound_group_session(
387     OlmInboundGroupSession *session,
388     char* key, size_t key_length, uint message_index
389 );
390