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 | |
35 | struct 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 | |
46 | size_t olm_outbound_group_session_size(void) { |
47 | return sizeof(OlmOutboundGroupSession); |
48 | } |
49 | |
50 | OlmOutboundGroupSession * olm_outbound_group_session( |
51 | void *memory |
52 | ) { |
53 | OlmOutboundGroupSession *session = memory; |
54 | olm_clear_outbound_group_session(session); |
55 | return session; |
56 | } |
57 | |
58 | const char *olm_outbound_group_session_last_error( |
59 | const OlmOutboundGroupSession *session |
60 | ) { |
61 | return _olm_error_to_string(error: session->last_error); |
62 | } |
63 | |
64 | enum OlmErrorCode olm_outbound_group_session_last_error_code( |
65 | const OlmOutboundGroupSession *session |
66 | ) { |
67 | return session->last_error; |
68 | } |
69 | |
70 | size_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 | |
77 | static 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 | |
87 | size_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 | |
93 | size_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 | |
123 | size_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 | |
171 | size_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 | |
181 | size_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 | |
203 | static 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 | |
221 | size_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 */ |
230 | static 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 | |
281 | size_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 | |
315 | size_t olm_outbound_group_session_id_length( |
316 | const OlmOutboundGroupSession *session |
317 | ) { |
318 | return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH); |
319 | } |
320 | |
321 | size_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 | |
335 | uint32_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 | |
345 | size_t olm_outbound_group_session_key_length( |
346 | const OlmOutboundGroupSession *session |
347 | ) { |
348 | return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH); |
349 | } |
350 | |
351 | size_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 | |