outbound_group_session.c 10.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Copyright 2016 OpenMarket Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "olm/outbound_group_session.h"

#include <string.h>

#include "olm/base64.h"
#include "olm/cipher.h"
Richard van der Hoff's avatar
Richard van der Hoff committed
22
#include "olm/crypto.h"
23
24
#include "olm/error.h"
#include "olm/megolm.h"
25
#include "olm/memory.h"
26
#include "olm/message.h"
27
28
#include "olm/pickle.h"
#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
36
37
38

struct OlmOutboundGroupSession {
    /** the Megolm ratchet providing the encryption keys */
    Megolm ratchet;

Richard van der Hoff's avatar
Richard van der Hoff committed
39
40
41
    /** The ed25519 keypair used for signing the messages */
    struct _olm_ed25519_key_pair signing_key;

42
43
44
45
    enum OlmErrorCode last_error;
};


manuroe's avatar
manuroe committed
46
size_t olm_outbound_group_session_size(void) {
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    return sizeof(OlmOutboundGroupSession);
}

OlmOutboundGroupSession * olm_outbound_group_session(
    void *memory
) {
    OlmOutboundGroupSession *session = memory;
    olm_clear_outbound_group_session(session);
    return session;
}

const char *olm_outbound_group_session_last_error(
    const OlmOutboundGroupSession *session
) {
    return _olm_error_to_string(session->last_error);
}

64
65
66
67
68
69
enum OlmErrorCode olm_outbound_group_session_last_error_code(
    const OlmOutboundGroupSession *session
) {
    return session->last_error;
}

70
71
72
size_t olm_clear_outbound_group_session(
    OlmOutboundGroupSession *session
) {
73
    _olm_unset(session, sizeof(OlmOutboundGroupSession));
74
75
76
    return sizeof(OlmOutboundGroupSession);
}

77
78
79
80
81
82
static size_t raw_pickle_length(
    const OlmOutboundGroupSession *session
) {
    size_t length = 0;
    length += _olm_pickle_uint32_length(PICKLE_VERSION);
    length += megolm_pickle_length(&(session->ratchet));
Richard van der Hoff's avatar
Richard van der Hoff committed
83
    length += _olm_pickle_ed25519_key_pair_length(&(session->signing_key));
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    return length;
}

size_t olm_pickle_outbound_group_session_length(
    const OlmOutboundGroupSession *session
) {
    return _olm_enc_output_length(raw_pickle_length(session));
}

size_t olm_pickle_outbound_group_session(
    OlmOutboundGroupSession *session,
    void const * key, size_t key_length,
    void * pickled, size_t pickled_length
) {
    size_t raw_length = raw_pickle_length(session);
    uint8_t *pos;

    if (pickled_length < _olm_enc_output_length(raw_length)) {
        session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
        return (size_t)-1;
    }

    pos = _olm_enc_output_pos(pickled, raw_length);
    pos = _olm_pickle_uint32(pos, PICKLE_VERSION);
    pos = megolm_pickle(&(session->ratchet), pos);
Richard van der Hoff's avatar
Richard van der Hoff committed
109
    pos = _olm_pickle_ed25519_key_pair(pos, &(session->signing_key));
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

    return _olm_enc_output(key, key_length, pickled, raw_length);
}

size_t olm_unpickle_outbound_group_session(
    OlmOutboundGroupSession *session,
    void const * key, size_t key_length,
    void * pickled, size_t pickled_length
) {
    const uint8_t *pos;
    const uint8_t *end;
    uint32_t pickle_version;

    size_t raw_length = _olm_enc_input(
        key, key_length, pickled, pickled_length, &(session->last_error)
    );
    if (raw_length == (size_t)-1) {
        return raw_length;
    }

    pos = pickled;
    end = pos + raw_length;
    pos = _olm_unpickle_uint32(pos, end, &pickle_version);
    if (pickle_version != PICKLE_VERSION) {
        session->last_error = OLM_UNKNOWN_PICKLE_VERSION;
        return (size_t)-1;
    }
    pos = megolm_unpickle(&(session->ratchet), pos, end);
Richard van der Hoff's avatar
Richard van der Hoff committed
138
    pos = _olm_unpickle_ed25519_key_pair(pos, end, &(session->signing_key));
139
140
141
142
143
144
145
146
147
148
149

    if (end != pos) {
        /* We had the wrong number of bytes in the input. */
        session->last_error = OLM_CORRUPTED_PICKLE;
        return (size_t)-1;
    }

    return pickled_length;
}


150
151
152
153
154
155
size_t olm_init_outbound_group_session_random_length(
    const OlmOutboundGroupSession *session
) {
    /* we need data to initialize the megolm ratchet, plus some more for the
     * session id.
     */
Richard van der Hoff's avatar
Richard van der Hoff committed
156
    return MEGOLM_RATCHET_LENGTH +
157
        ED25519_RANDOM_LENGTH;
158
159
160
161
}

size_t olm_init_outbound_group_session(
    OlmOutboundGroupSession *session,
162
    uint8_t *random, size_t random_length
163
) {
164
165
    const uint8_t *random_ptr = random;

166
167
168
169
170
171
    if (random_length < olm_init_outbound_group_session_random_length(session)) {
        /* Insufficient random data for new session */
        session->last_error = OLM_NOT_ENOUGH_RANDOM;
        return (size_t)-1;
    }

172
173
    megolm_init(&(session->ratchet), random_ptr, 0);
    random_ptr += MEGOLM_RATCHET_LENGTH;
174

175
176
    _olm_crypto_ed25519_generate_key(random_ptr, &(session->signing_key));
    random_ptr += ED25519_RANDOM_LENGTH;
Richard van der Hoff's avatar
Richard van der Hoff committed
177

178
    _olm_unset(random, random_length);
179
180
181
182
183
184
185
186
187
    return 0;
}

static size_t raw_message_length(
    OlmOutboundGroupSession *session,
    size_t plaintext_length)
{
    size_t ciphertext_length, mac_length;

188
189
    ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
        megolm_cipher, plaintext_length
190
191
    );

192
    mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
193
194

    return _olm_encode_group_message_length(
195
        session->ratchet.counter,
Richard van der Hoff's avatar
Richard van der Hoff committed
196
197
        ciphertext_length, mac_length, ED25519_SIGNATURE_LENGTH
    );
198
199
200
201
202
203
204
205
206
207
}

size_t olm_group_encrypt_message_length(
    OlmOutboundGroupSession *session,
    size_t plaintext_length
) {
    size_t message_length = raw_message_length(session, plaintext_length);
    return _olm_encode_base64_length(message_length);
}

208
209
210
211
/** write an un-base64-ed message to the buffer */
static size_t _encrypt(
    OlmOutboundGroupSession *session, uint8_t const * plaintext, size_t plaintext_length,
    uint8_t * buffer
212
) {
213
    size_t ciphertext_length, mac_length, message_length;
214
    size_t result;
215
    uint8_t *ciphertext_ptr;
216

217
218
    ciphertext_length = megolm_cipher->ops->encrypt_ciphertext_length(
        megolm_cipher,
219
220
221
        plaintext_length
    );

222
    mac_length = megolm_cipher->ops->mac_length(megolm_cipher);
223
224
225
226

    /* first we build the message structure, then we encrypt
     * the plaintext into it.
     */
227
    message_length = _olm_encode_group_message(
228
229
230
        OLM_PROTOCOL_VERSION,
        session->ratchet.counter,
        ciphertext_length,
231
        buffer,
232
233
        &ciphertext_ptr);

234
235
    message_length += mac_length;

236
237
    result = megolm_cipher->ops->encrypt(
        megolm_cipher,
238
239
240
        megolm_get_data(&(session->ratchet)), MEGOLM_RATCHET_LENGTH,
        plaintext, plaintext_length,
        ciphertext_ptr, ciphertext_length,
241
        buffer, message_length
242
243
244
245
246
247
248
249
    );

    if (result == (size_t)-1) {
        return result;
    }

    megolm_advance(&(session->ratchet));

Richard van der Hoff's avatar
Richard van der Hoff committed
250
251
252
253
254
255
256
    /* sign the whole thing with the ed25519 key. */
    _olm_crypto_ed25519_sign(
        &(session->signing_key),
        buffer, message_length,
        buffer + message_length
    );

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
    return result;
}

size_t olm_group_encrypt(
    OlmOutboundGroupSession *session,
    uint8_t const * plaintext, size_t plaintext_length,
    uint8_t * message, size_t max_message_length
) {
    size_t rawmsglen;
    size_t result;
    uint8_t *message_pos;

    rawmsglen = raw_message_length(session, plaintext_length);

    if (max_message_length < _olm_encode_base64_length(rawmsglen)) {
        session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
        return (size_t)-1;
    }

    /* we construct the message at the end of the buffer, so that
     * we have room to base64-encode it once we're done.
     */
    message_pos = message + _olm_encode_base64_length(rawmsglen) - rawmsglen;

    /* write the message, and encrypt it, at message_pos */
    result = _encrypt(session, plaintext, plaintext_length, message_pos);
    if (result == (size_t)-1) {
        return result;
    }

    /* bas64-encode it */
288
    return _olm_encode_base64(
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
        message_pos, rawmsglen, message
    );
}


size_t olm_outbound_group_session_id_length(
    const OlmOutboundGroupSession *session
) {
    return _olm_encode_base64_length(GROUP_SESSION_ID_LENGTH);
}

size_t olm_outbound_group_session_id(
    OlmOutboundGroupSession *session,
    uint8_t * id, size_t id_length
) {
    if (id_length < olm_outbound_group_session_id_length(session)) {
        session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
        return (size_t)-1;
    }

309
310
311
    return _olm_encode_base64(
        session->signing_key.public_key.public_key, GROUP_SESSION_ID_LENGTH, id
    );
312
313
314
315
316
317
318
319
}

uint32_t olm_outbound_group_session_message_index(
    OlmOutboundGroupSession *session
) {
    return session->ratchet.counter;
}

Richard van der Hoff's avatar
Richard van der Hoff committed
320
#define SESSION_KEY_RAW_LENGTH \
321
322
    (1 + 4 + MEGOLM_RATCHET_LENGTH + ED25519_PUBLIC_KEY_LENGTH\
        + ED25519_SIGNATURE_LENGTH)
Richard van der Hoff's avatar
Richard van der Hoff committed
323

324
325
326
size_t olm_outbound_group_session_key_length(
    const OlmOutboundGroupSession *session
) {
Richard van der Hoff's avatar
Richard van der Hoff committed
327
    return _olm_encode_base64_length(SESSION_KEY_RAW_LENGTH);
328
329
330
331
332
333
}

size_t olm_outbound_group_session_key(
    OlmOutboundGroupSession *session,
    uint8_t * key, size_t key_length
) {
Richard van der Hoff's avatar
Richard van der Hoff committed
334
335
336
337
338
    uint8_t *raw;
    uint8_t *ptr;
    size_t encoded_length = olm_outbound_group_session_key_length(session);

    if (key_length < encoded_length) {
339
340
341
342
        session->last_error = OLM_OUTPUT_BUFFER_TOO_SMALL;
        return (size_t)-1;
    }

Richard van der Hoff's avatar
Richard van der Hoff committed
343
344
345
346
    /* put the raw data at the end of the output buffer. */
    raw = ptr = key + encoded_length - SESSION_KEY_RAW_LENGTH;
    *ptr++ = SESSION_KEY_VERSION;

347
    uint32_t counter = session->ratchet.counter;
348
    // Encode counter as a big endian 32-bit number.
349
    for (unsigned i = 0; i < 4; i++) {
350
351
        *ptr++ = 0xFF & (counter >> 24); counter <<= 8;
    }
352

Richard van der Hoff's avatar
Richard van der Hoff committed
353
354
355
356
357
358
    memcpy(ptr, megolm_get_data(&session->ratchet), MEGOLM_RATCHET_LENGTH);
    ptr += MEGOLM_RATCHET_LENGTH;

    memcpy(
        ptr, session->signing_key.public_key.public_key,
        ED25519_PUBLIC_KEY_LENGTH
359
    );
Richard van der Hoff's avatar
Richard van der Hoff committed
360
361
    ptr += ED25519_PUBLIC_KEY_LENGTH;

362
363
364
365
366
367
    /* sign the whole thing with the ed25519 key. */
    _olm_crypto_ed25519_sign(
        &(session->signing_key),
        raw, ptr - raw, ptr
    );

Richard van der Hoff's avatar
Richard van der Hoff committed
368
    return _olm_encode_base64(raw, SESSION_KEY_RAW_LENGTH, key);
369
}