1/* Copyright 2016 OpenMarket Ltd
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "olm/outbound_group_session.h"
17
18#include <string.h>
19
20#include "olm/base64.h"
21#include "olm/cipher.h"
22#include "olm/crypto.h"
23#include "olm/error.h"
24#include "olm/megolm.h"
25#include "olm/memory.h"
26#include "olm/message.h"
27#include "olm/pickle.h"
28#include "olm/pickle_encoding.h"
29
30#define OLM_PROTOCOL_VERSION 3
31#define GROUP_SESSION_ID_LENGTH ED25519_PUBLIC_KEY_LENGTH
32#define PICKLE_VERSION 1
33#define SESSION_KEY_VERSION 2
34
35struct OlmOutboundGroupSession {
36 /** the Megolm ratchet providing the encryption keys */
37 Megolm ratchet;
38
39 /** The ed25519 keypair used for signing the messages */
40 struct _olm_ed25519_key_pair signing_key;
41
42 enum OlmErrorCode last_error;
43};
44
45
46size_t olm_outbound_group_session_size(void) {
47 return sizeof(OlmOutboundGroupSession);
48}
49
50OlmOutboundGroupSession * olm_outbound_group_session(
51 void *memory
52) {
53 OlmOutboundGroupSession *session = memory;
54 olm_clear_outbound_group_session(session);
55 return session;
56}
57
58const char *olm_outbound_group_session_last_error(
59 const OlmOutboundGroupSession *session
60) {
61 return _olm_error_to_string(error: session->last_error);
62}
63
64enum OlmErrorCode olm_outbound_group_session_last_error_code(
65 const OlmOutboundGroupSession *session
66) {
67 return session->last_error;
68}
69
70size_t olm_clear_outbound_group_session(
71 OlmOutboundGroupSession *session
72) {
73 _olm_unset(buffer: session, buffer_length: sizeof(OlmOutboundGroupSession));
74 return sizeof(OlmOutboundGroupSession);
75}
76
77static size_t raw_pickle_length(
78 const OlmOutboundGroupSession *session
79) {
80 size_t length = 0;
81 length += _olm_pickle_uint32_length(PICKLE_VERSION);
82 length += megolm_pickle_length(megolm: &(session->ratchet));
83 length += _olm_pickle_ed25519_key_pair_length(value: &(session->signing_key));
84 return length;
85}
86
87size_t olm_pickle_outbound_group_session_length(
88 const OlmOutboundGroupSession *session
89) {
90 return _olm_enc_output_length(raw_length: raw_pickle_length(session));
91}
92
93size_t olm_pickle_outbound_group_session(
94 OlmOutboundGroupSession *session,
95 void const * key, size_t key_length,
96 void * pickled, size_t pickled_length
97) {
98 size_t raw_length = raw_pickle_length(session);
99 uint8_t *pos;
100
101 if (pickled_length < _olm_enc_output_length(raw_length)) {
102 session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
103 return (size_t)-1;
104 }
105
106#ifndef OLM_FUZZING
107 pos = _olm_enc_output_pos(output: pickled, raw_length);
108#else
109 pos = pickled;
110#endif
111
112 pos = _olm_pickle_uint32(pos, PICKLE_VERSION);
113 pos = megolm_pickle(megolm: &(session->ratchet), pos);
114 pos = _olm_pickle_ed25519_key_pair(pos, value: &(session->signing_key));
115
116#ifndef OLM_FUZZING
117 return _olm_enc_output(key, key_length, pickle: pickled, raw_length);
118#else
119 return raw_length;
120#endif
121}
122
123size_t olm_unpickle_outbound_group_session(
124 OlmOutboundGroupSession *session,
125 void const * key, size_t key_length,
126 void * pickled, size_t pickled_length
127) {
128 const uint8_t *pos;
129 const uint8_t *end;
130 uint32_t pickle_version;
131
132#ifndef OLM_FUZZING
133 size_t raw_length = _olm_enc_input(
134 key, key_length, input: pickled, b64_length: pickled_length, last_error: &(session->last_error)
135 );
136#else
137 size_t raw_length = pickled_length;
138#endif
139
140 if (raw_length == (size_t)-1) {
141 return raw_length;
142 }
143
144 pos = pickled;
145 end = pos + raw_length;
146
147 pos = _olm_unpickle_uint32(pos, end, value: &pickle_version);
148 FAIL_ON_CORRUPTED_PICKLE(pos, session);
149
150 if (pickle_version != PICKLE_VERSION) {
151 session->last_error = OLM_UNKNOWN_PICKLE_VERSION;
152 return (size_t)-1;
153 }
154
155 pos = megolm_unpickle(megolm: &(session->ratchet), pos, end);
156 FAIL_ON_CORRUPTED_PICKLE(pos, session);
157
158 pos = _olm_unpickle_ed25519_key_pair(pos, end, value: &(session->signing_key));
159 FAIL_ON_CORRUPTED_PICKLE(pos, session);
160
161 if (pos != end) {
162 /* Input was longer than expected. */
163 session->last_error = OLM_PICKLE_EXTRA_DATA;
164 return (size_t)-1;
165 }
166
167 return pickled_length;
168}
169
170
171size_t olm_init_outbound_group_session_random_length(
172 const OlmOutboundGroupSession *session
173) {
174 /* we need data to initialize the megolm ratchet, plus some more for the
175 * session id.
176 */
177 return MEGOLM_RATCHET_LENGTH +
178 ED25519_RANDOM_LENGTH;
179}
180
181size_t olm_init_outbound_group_session(
182 OlmOutboundGroupSession *session,
183 uint8_t *random, size_t random_length
184) {
185 const uint8_t *random_ptr = random;
186
187 if (random_length < olm_init_outbound_group_session_random_length(session)) {
188 /* Insufficient random data for new session */
189 session->last_error = OLM_NOT_ENOUGH_RANDOM;
190 return (size_t)-1;
191 }
192
193 megolm_init(megolm: &(session->ratchet), random_data: random_ptr, counter: 0);
194 random_ptr += MEGOLM_RATCHET_LENGTH;
195
196 _olm_crypto_ed25519_generate_key(random_bytes: random_ptr, output: &(session->signing_key));
197 random_ptr += ED25519_RANDOM_LENGTH;
198
199 _olm_unset(buffer: random, buffer_length: random_length);
200 return 0;
201}
202
203static size_t raw_message_length(
204 OlmOutboundGroupSession *session,
205 size_t plaintext_length)
206{
207 size_t ciphertext_length, mac_length;
208
209 ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
210 megolm_cipher, plaintext_length
211 );
212
213 mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
214
215 return _olm_encode_group_message_length(
216 chain_index: session->ratchet.counter,
217 ciphertext_length, mac_length, ED25519_SIGNATURE_LENGTH
218 );
219}
220
221size_t olm_group_encrypt_message_length(
222 OlmOutboundGroupSession *session,
223 size_t plaintext_length
224) {
225 size_t message_length = raw_message_length(session, plaintext_length);
226 return _olm_encode_base64_length(input_length: message_length);
227}
228
229/** write an un-base64-ed message to the buffer */
230static size_t _encrypt(
231 OlmOutboundGroupSession *session, uint8_t const * plaintext, size_t plaintext_length,
232 uint8_t * buffer
233) {
234 size_t ciphertext_length, mac_length, message_length;
235 size_t result;
236 uint8_t *ciphertext_ptr;
237
238 ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
239 megolm_cipher,
240 plaintext_length
241 );
242
243 mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
244
245 /* first we build the message structure, then we encrypt
246 * the plaintext into it.
247 */
248 message_length = _olm_encode_group_message(
249 OLM_PROTOCOL_VERSION,
250 message_index: session->ratchet.counter,
251 ciphertext_length,
252 output: buffer,
253 ciphertext_ptr: &ciphertext_ptr);
254
255 message_length += mac_length;
256
257 result = megolm_cipher->ops->encrypt(
258 megolm_cipher,
259 megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH,
260 plaintext, plaintext_length,
261 ciphertext_ptr, ciphertext_length,
262 buffer, message_length
263 );
264
265 if (result == (size_t)-1) {
266 return result;
267 }
268
269 megolm_advance(megolm: &(session->ratchet));
270
271 /* sign the whole thing with the ed25519 key. */
272 _olm_crypto_ed25519_sign(
273 our_key: &(session->signing_key),
274 message: buffer, message_length,
275 output: buffer + message_length
276 );
277
278 return result;
279}
280
281size_t olm_group_encrypt(
282 OlmOutboundGroupSession *session,
283 uint8_t const * plaintext, size_t plaintext_length,
284 uint8_t * message, size_t max_message_length
285) {
286 size_t rawmsglen;
287 size_t result;
288 uint8_t *message_pos;
289
290 rawmsglen = raw_message_length(session, plaintext_length);
291
292 if (max_message_length < _olm_encode_base64_length(input_length: rawmsglen)) {
293 session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
294 return (size_t)-1;
295 }
296
297 /* we construct the message at the end of the buffer, so that
298 * we have room to base64-encode it once we're done.
299 */
300 message_pos = message + _olm_encode_base64_length(input_length: rawmsglen) - rawmsglen;
301
302 /* write the message, and encrypt it, at message_pos */
303 result = _encrypt(session, plaintext, plaintext_length, buffer: message_pos);
304 if (result == (size_t)-1) {
305 return result;
306 }
307
308 /* bas64-encode it */
309 return _olm_encode_base64(
310 input: message_pos, input_length: rawmsglen, output: message
311 );
312}
313
314
315size_t olm_outbound_group_session_id_length(
316 const OlmOutboundGroupSession *session
317) {
318 return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH);
319}
320
321size_t olm_outbound_group_session_id(
322 OlmOutboundGroupSession *session,
323 uint8_t * id, size_t id_length
324) {
325 if (id_length < olm_outbound_group_session_id_length(session)) {
326 session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
327 return (size_t)-1;
328 }
329
330 return _olm_encode_base64(
331 input: session->signing_key.public_key.public_key, GROUP_SESSION_ID_LENGTH, output: id
332 );
333}
334
335uint32_t olm_outbound_group_session_message_index(
336 OlmOutboundGroupSession *session
337) {
338 return session->ratchet.counter;
339}
340
341#define SESSION_KEY_RAW_LENGTH \
342 (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\
343 + ED25519_SIGNATURE_LENGTH)
344
345size_t olm_outbound_group_session_key_length(
346 const OlmOutboundGroupSession *session
347) {
348 return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH);
349}
350
351size_t olm_outbound_group_session_key(
352 OlmOutboundGroupSession *session,
353 uint8_t * key, size_t key_length
354) {
355 uint8_t *raw;
356 uint8_t *ptr;
357 size_t encoded_length = olm_outbound_group_session_key_length(session);
358
359 if (key_length < encoded_length) {
360 session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
361 return (size_t)-1;
362 }
363
364 /* put the raw data at the end of the output buffer. */
365 raw = ptr = key + encoded_length - SESSION_KEY_RAW_LENGTH;
366 *ptr++ = SESSION_KEY_VERSION;
367
368 uint32_t counter = session->ratchet.counter;
369 // Encode counter as a big endian 32-bit number.
370 for (unsigned i = 0; i < 4; i++) {
371 *ptr++ = 0xFF & (counter >> 24); counter <<= 8;
372 }
373
374 memcpy(dest: ptr, megolm_get_data(&session->ratchet), MEGOLM_RATCHET_LENGTH);
375 ptr += MEGOLM_RATCHET_LENGTH;
376
377 memcpy(
378 dest: ptr, src: session->signing_key.public_key.public_key,
379 ED25519_PUBLIC_KEY_LENGTH
380 );
381 ptr += ED25519_PUBLIC_KEY_LENGTH;
382
383 /* sign the whole thing with the ed25519 key. */
384 _olm_crypto_ed25519_sign(
385 our_key: &(session->signing_key),
386 message: raw, message_length: ptr - raw, output: ptr
387 );
388
389 return _olm_encode_base64(input: raw, SESSION_KEY_RAW_LENGTH, output: key);
390}
391