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