1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
109
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
Table of Contents
1. Introduction
2. Major Additions
2.1. Fragmentation
2.2. Socialist Millionaires' Protocol
3. Required Changes
3.1. OtrlMessageAppOps Callbacks
3.1.1. Max Message Size
3.1.2. Account Name
3.2. Using Fragmentation
3.3. Using SMP
3.3.1. Initiating
3.3.2. Responding
3.3.3. Aborting
3.3.4. Control Flow and Errors
3.4. Miscellaneous
1. Introduction
This file contains information about the changes between the 3.0.0 and
the 3.2.0 APIs for libotr. Note that, as a minor release, applications
compiled against 3.0.0 will continue to work with 3.2.0. This document
explains how to add the new functionality in 3.2.0.
2. Major Additions
This section describes the new features in OTR 3.2.0 along with a short
history or motivation for each.
2.1. Fragmentation [Added in 3.1.0]
Most IM networks supported by Pidgin have fixed maximum message sizes
(MMS) of approximately 1-3 kB. The longer messages in the initial key
exchange and the new socialist millionaires' protocol may exceed these
common MMS values. To allow these protocols to work properly even over
networks with low MMS values, support for fragmentation was added.
OTR version 3.0.0 added support for recombining message fragments to
recover the original message. Now that users may be assumed to be able to
handle message fragments, support for fragmenting and sending large
messages has been added to OTR 3.2.0.
2.2. Socialist Millionaires' Protocol [Added in 3.1.0, revised in 3.2.0]
In version 3.0.0, the only method available to authenticate a buddy was
fingerprint verification. However, many users who are unfamiliar with
cryptography do not understand what a fingerprint is or how it is useful.
Also, the verification itself relied on the user obtaining an authentic
copy of the other party's fingerprint somehow. The simplest way to do so
may be to relay the displayed hexadecimal values during a phone call,
but this is a large enough hassle that many users omit fingerprint
verification altogether.
To allow for a method of authentication that is both easier to understand
and easier to use, OTR version 3.2.0 includes the Socialist Millionaires'
Protocol (SMP). SMP runs as follows: each user inputs a secret string,
say "x" and "y". They then exchange a series of messages which reveal
the value of (x==y), but no additional information about the inputs.
This allows users to determine whether they hold the same secret
information with no danger of revealing that secret to an attacker.
To see how this is useful for authentication in OTR, assume that Alice
and Bob are chatting over OTR for the first time, though they know each
other well in real life. Alice may send Bob the following message:
"Let's make our shared secret the name of that restaurant we both like
in Ottawa."
Now Alice and Bob run SMP. If Alice is actually talking to Bob directly,
then they will both type in the same restaurant name and SMP will return
success (x==y). However, if an attacker is impersonating Bob or trying
to eavesdrop on the conversation, they will have no idea which restaurant
Alice has in mind, and will type in an incorrect value, causing SMP to
fail. Note that for security reasons, the values compared in the SMP
are actually hashes of several pieces of data, including both parties'
fingerprints, along with their respective secrets. The users, however,
are never exposed to this additional data.
Thus, SMP turns the problem of obtaining an authentic copy of a
fingerprint into the much simpler problem of obtaining any shared secret,
or simply of drawing on shared experiences to generate one.
For detailed information on how SMP works, see the paper by Boudot,
Schoenmakers and Traore titled "A Fair and Efficient Solution to the
Socialist Millionaires Problem" (2001), on which our solution is based.
3. Required Changes [Added in 3.1.0]
3.1. OtrlMessageAppOps Callbacks
Three new callbacks have been added to the end of OtrlMessageAppOps. If
the version number passed to otrl_init is less than 3.1.0 then libotr will
not call any of the new callbacks. As well, you may disable individual
callbacks by setting them to NULL. In either case, libotr will revert to
the standard behaviour of version 3.0.0.
3.1.1. Max Message Size
The first new callback has the following signature:
int (*max_message_size)(void *opdata, ConnContext *context);
This method is called whenever a message is about to be sent with
fragmentation enabled. The return value is checked against the size of
the message to be sent to determine whether fragmentation is necessary.
Although the maximum message size depends on a number of factors, we
found experimentally that the following rough values based solely on the
(pidgin) protocol name work well:
"prpl-msn", 1409
"prpl-icq", 2346
"prpl-aim", 2343
"prpl-yahoo", 832
"prpl-gg", 1999
"prpl-irc", 417
"prpl-oscar", 2343
Setting max_message_size to NULL will disable the fragmentation of all
sent messages; returning 0 from this callback will disable fragmentation
of a particular message. The latter is useful, for example, for
protocols like XMPP (Jabber) that do not require fragmentation at all.
3.1.2. Account Name
The other two new callbacks have the following signatures:
const char *(*account_name)(void *opdata, const char *account,
const char *protocol);
void (*account_name_free)(void *opdata, const char *account_name);
Normally, if an error message needs to be sent from Alice to Bob,
containing Alice's account name, the value of ConnContext->accountname
will be used. However, if this default name is unsuitable for your
application, you can use the above methods to provide replacement values
for displayed account names.
account_name is called when libotr requires a human-readable version of
an account name. account_name_free is called once the name has
been used, and the memory allocated by account_name (if any) must be
released.
Setting account_name to NULL will cause libotr to use
ConnContext->accountname as the displayed name for an account.
3.2. Using Fragmentation [Added in 3.1.0]
To make use of fragmentation, first make sure that the max_message_size
callback described in 3.1.1. has been implemented. Then, whenever you
would normally send a message through your IM client, call
otrl_message_fragment_and_send instead:
gcry_error_t otrl_message_fragment_and_send(const OtrlMessageAppOps *ops,
void *opdata, ConnContext *context, const char *message,
OtrlFragmentPolicy fragPolicy, char **returnFragment);
Here, message is the original, encrypted, unfragmented message.
This method will break the message into fragments and send either all of
them or almost all of them according to the OtrlFragmentPolicy:
OTRL_FRAGMENT_SEND_ALL sends all fragments at once
OTRL_FRAGMENT_SEND_ALL_BUT_FIRST sends all but the first fragment
OTRL_FRAGMENT_SEND_ALL_BUT_LAST sends all but the last fragment
You may wish to use one of the latter two options if you still wish to
pass a message through your IM client. In this case, the unsent
fragment will be returned in returnFragment and should be sent as a
regular message. In order to reassemble the fragments, however, they
must be received in order, so at most one of the latter two options
will result in readable messages.
3.3. Using SMP [Added in 3.1.0, revised in 3.2.0]
Recall from section 2.2. above that SMP takes one input string from each
user and outputs either failure or success.
3.3.1. Initiating
If you wish to initiate SMP for a user named Alice, you would use
otrl_message_initiate_smp.
void otrl_message_initiate_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata, ConnContext *context, const unsigned char *secret,
size_t secretlen);
Here, secret and secretlen describe the secret text as entered by Alice,
for example, ("kitten", 6). The other parameters are common to many
otrl_message functions. This method will cause a message to be sent
containing an appropriate OTRL_TLV_SMP1 (see below).
3.3.2. Responding
If you wish to continue SMP by supplying the secret for a second user
named Bob, you would use otrl_message_respond_smp:
void otrl_message_respond_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata, ConnContext *context, const unsigned char *secret,
size_t secretlen);
The arguments for this method are the same as otrl_message_initiate_smp.
This method will send a message with an appropriate OTRL_TLV_SMP2
(see below).
3.3.3. Aborting
If you wish to abort SMP for any reason, including errors occuring
during the protocol, you should use otrl_message_abort_smp:
void otrl_message_abort_smp(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata, ConnContext *context);
This method will cause the other user to abandon the current state of
SMP by sending an appropriate OTRL_TLV_SMP_ABORT (see below).
3.3.4. Control Flow and Errors
The protocol itself consists of 4 messages passed between the two users,
say Alice and Bob. These messages are identified through their TLVs:
OTRL_TLV_SMP1 The initial message, containing a commitment to
Alice's secret
OTRL_TLV_SMP1Q Like OTRL_TLV_SMP1, but also containing a question
to display to Bob
OTRL_TLV_SMP2 A response containing a commitment to Bob's secret
OTRL_TLV_SMP3 The next message in the chain, from Alice to Bob
OTRL_TLV_SMP4 The final message in the chain, from Bob to Alice
OTRL_TLV_SMP_ABORT Indicates an error has occurred. Will reset SMP state
To determine whether the protocol is proceeding correctly, additional
information has been added to ConnContext. You may access
context->smstate->nextExpected to find out which TLV should come next,
so you can compare this to what was actually received and take an
appropriate action. The value is of type NextExpectedSMP and could be
any of:
OTRL_SMP_EXPECT1 Next SMP TLV should be OTRL_TLV_SMP1
OTRL_SMP_EXPECT2 Next SMP TLV should be OTRL_TLV_SMP2
OTRL_SMP_EXPECT3 Next SMP TLV should be OTRL_TLV_SMP3
OTRL_SMP_EXPECT4 Next SMP TLV should be OTRL_TLV_SMP4
If at any point, an SMP TLV of an unexpected type is received, the
protocol should abort. Also, if the correct TLV type is received, then
the state should be updated accordingly. A typical control flow looks
like this:
OtrlTLV *tlvs = NULL;
OtrlTLV *tlv = NULL;
[Initialize tlvs to the list of tlvs. This can be done
as part of otrl_message_receiving.]
ConnContext *context = [correct context];
NextExpectedSMP nextMsg = context->smstate->nextExpected;
if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) {
otrg_plugin_abort_smp(context);
otrg_dialog_update_smp(context, 0.0);
context->smstate->nextExpected = OTRL_SMP_EXPECT1;
context->smstate->sm_prog_state = OTRL_SMP_PROG_OK;
} else {
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT1)
[abort SMP];
else {
char *question = (char *)tlv->data;
char *eoq = memchr(question, '\0', tlv->len);
if (eoq) {
[prompt the user with question, get the response,
and continue SMP];
}
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT1)
[abort SMP];
else {
[get secret from user and continue SMP];
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT2)
[abort SMP];
else {
// If we received TLV2, we will send TLV3 and expect TLV4
context->smstate->nextExpected = OTRL_SMP_EXPECT4;
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT3)
[abort SMP];
else {
// If we received TLV3, we will send TLV4
// We will not expect more messages, so prepare for next SMP
context->smstate->nextExpected = OTRL_SMP_EXPECT1;
// Report result to user
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT4)
[abort SMP];
else {
// We will not expect more messages, so prepare for next SMP
context->smstate->nextExpected = OTRL_SMP_EXPECT1;
// Report result to user
}
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
if (tlv) {
// The message we are waiting for will not arrive, so reset
// and prepare for the next SMP
context->smstate->nextExpected = OTRL_SMP_EXPECT1;
}
}
To report the result to the user after receiving OTRL_TLV_SMP3 or
OTRL_TLV_SMP4, check whether context->active_fingerprint->trust is a
non-empty string. (That is, check that it's not NULL, and that its
first character is not '\0'.) If that is the case, then the SMP
completed successfully. Otherwise, the parties entered different secrets.
3.4 Miscellaneous
b64.h underwent a minor change in OTR 3.1.0. It was purely a
housekeeping change and should not require any changes to dependent code.
The arguments to otrl_base64_encode and otrl_base64_decode did not agree
in terms of which were of type char* and which were unsigned char*
instead. This has been corrected. The new method signatures are:
size_t otrl_base64_encode(char *base64data, const unsigned char *data,
size_t datalen);
size_t otrl_base64_decode(unsigned char *data, const char *base64data,
size_t base64len);
|