-
Notifications
You must be signed in to change notification settings - Fork 2
/
FiSHKeyExchanger.mm
336 lines (276 loc) · 15.4 KB
/
FiSHKeyExchanger.mm
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
// FiSHy, a plugin for Colloquy providing Blowfish encryption.
// Copyright (C) 2007 Henning Kiel
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#import "FiSHKeyExchanger.h"
#import "FiSHSecretStore.h"
#import "NSData+FiSHyExtensions.h"
#import "dh1080.h"
// Maximum amount of time we wait for a DH1080 exchange response.
#define HCKMaxTimeToWaitForDH1080Response 10.0f
// Constants used by FiSH.
NSString *HCKFiSHKeyExchangeRequest = @"DH1080_INIT ";
NSString *HCKFiSHKeyExchangeResponse = @"DH1080_FINISH ";
#define HCKFiSHMaxKeyLength 300
// Dictionary keys used to retrieve temporary key pairs
const NSString *FiSHKeyExchangeInfoDH1080Key = @"FiSHKeyExchangeDH1080Key";
const NSString *FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey = @"FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey";
@interface FiSHKeyExchanger (FiSHyPrivate)
- (void)addTemporaryKeyExchangeInfos:(NSValue *)dhInfos forNickname:(NSString *)nickname onConnection:(id)connection;
- (NSDictionary *)temporaryKeyExchangeInfosForNickname:(NSString *)nickname onConnection:(id)connection;
- (void)removeTemporaryKeyExchangeInfosForNickName:(NSString *)nickname onConnection:(id)connection;
- (void)handleKeyExchangeTimeout:(NSTimer *)aTimer;
- (void)handleFiSHKeyExchangeRequestFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
- (void)handleFiSHKeyExchangeResponseFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
@end
@implementation FiSHKeyExchanger
- (id)initWithDelegate:(id <FiSHKeyExchangerDelegate>)delegate;
{
if (self = [super init])
{
temporaryKeyExchangeInfosLock_ = [[NSRecursiveLock alloc] init];
temporaryKeyExchangeInfos_ = [[NSMutableDictionary alloc] init];
delegate_ = delegate;
}
return self;
}
- (void)dealloc;
{
[temporaryKeyExchangeInfosLock_ release];
[temporaryKeyExchangeInfos_ release];
[super dealloc];
}
/// Initiates a FiSH DH1080 key exchange for the specified nickname on the specified connection.
/**
This method first generates a temporary Private/Publiy Key Pair for us, and then sends the public key to nickname on connection.
The temporary key pair is saved, and a timer is set up to delete the key pair, if the remote party doesn't respond after a certain amount of time.
*/
- (void)requestTemporarySecretFor:(NSString *)nickname onConnection:(id)connection;
{
// Generate the keypair we use for this exchange
dhclass *dhKeyExchanger = new dhclass;
if (!dhKeyExchanger->generate())
{
free(dhKeyExchanger);
[delegate_ outputStatusInformation:NSLocalizedString(@"Unknown error during key exchange.", "Unknown error during key exchange")
forContext:nickname
on:connection];
return;
}
std::string myPubKeyTemp;
dhKeyExchanger->get_public_key(myPubKeyTemp);
NSString *myPubKey = [NSString stringWithUTF8String:myPubKeyTemp.c_str()];
[delegate_ sendPrivateMessage:[NSString stringWithFormat:@"%@%@", HCKFiSHKeyExchangeRequest, myPubKey] to:nickname on:connection];
[delegate_ outputStatusInformation:NSLocalizedString(@"Sending key exchange request.", "Sending key exchange request")
forContext:nickname
on:connection];
// Save the temporary key pair, and set up a timer to remove the key pair after a certain amount of time, if the remote party still has not responded.
[self addTemporaryKeyExchangeInfos:[NSValue valueWithPointer:dhKeyExchanger] forNickname:nickname onConnection:connection];
}
/// Checks, if the private message is to initiate or respond to a key exchange.
/**
Returns YES, if the message is about key-exchange. The delegate should then not display the raw message to the user.
The delegate should send this message for each private message it receives, but only for messages from nicknames. We do not support keyexchange for channels.
*/
- (BOOL)processPrivateMessageAsData:(NSData *)message from:(NSString *)sender on:(id)connection
{
// Key exchange message must be of a certain min. and max. length
if ([message length] < 191 || [message length] > 195)
return NO;
// Handle FiSH key exchanges.
if ([message hasStringPrefix:HCKFiSHKeyExchangeRequest encoding:NSASCIIStringEncoding])
{
// The notice is interpreted as a FiSH key exchange request, when its message body starts with "DH1080_INIT ".
DLog(@"Got remote FiSH DH1080 key exchange request.");
NSString *remotePublicKey = [[NSString alloc] initWithData:[message subDataFromIndex:[HCKFiSHKeyExchangeRequest length]] encoding:NSASCIIStringEncoding];
[self handleFiSHKeyExchangeRequestFrom:sender
on:connection
withRemotePublicKeyData:remotePublicKey];
return YES;
} else if ([message hasStringPrefix:HCKFiSHKeyExchangeResponse encoding:NSASCIIStringEncoding])
{
// The notice is interpreted as a FiSH key exchange response, when its message body starts with "DH1080_FINISH ".
DLog(@"Got FiSH DH1080 key exchange response.");
NSString *remotePublicKey = [[NSString alloc] initWithData:[message subDataFromIndex:[HCKFiSHKeyExchangeResponse length]] encoding:NSASCIIStringEncoding];
[self handleFiSHKeyExchangeResponseFrom:sender
on:connection
withRemotePublicKeyData:remotePublicKey];
return YES;
}
return NO;
}
@end
@implementation FiSHKeyExchanger (FiSHyPrivate)
- (void)addTemporaryKeyExchangeInfos:(NSValue *)dhInfos forNickname:(NSString *)nickname onConnection:(id)connection;
{
[temporaryKeyExchangeInfosLock_ lock];
// If we have infos for prior keyexchanges still around, make sure to remove them.
[self removeTemporaryKeyExchangeInfosForNickName:nickname onConnection:connection];
// Get the dictionary containing key pairs for the current connection.
NSMutableDictionary *keyExchangeInfosForNicknames = [temporaryKeyExchangeInfos_ objectForKey:connection];
if (!keyExchangeInfosForNicknames)
{
keyExchangeInfosForNicknames = [NSMutableDictionary dictionary];
[temporaryKeyExchangeInfos_ setObject:keyExchangeInfosForNicknames forKey:connection];
}
// Build a dictionary, which we pass to the timer, containing the necessary info to delete a request which timed out.
NSDictionary *timerInfoDict = [NSDictionary dictionaryWithObjectsAndKeys:
nickname, @"nickname",
connection, @"connection",
nil];
// Create the dictionary containing the keypair, and the timer used to delete the key pair if we don't use it during a certain amount of time.
NSDictionary *keyPair = [NSDictionary dictionaryWithObjectsAndKeys:
dhInfos, FiSHKeyExchangeInfoDH1080Key,
[NSTimer scheduledTimerWithTimeInterval:HCKMaxTimeToWaitForDH1080Response
target:self
selector:@selector(handleKeyExchangeTimeout:)
userInfo:timerInfoDict
repeats:NO], FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey,
nil];
// Add the keypair dictionary for the current nick and current connection.
[keyExchangeInfosForNicknames setObject:keyPair forKey:nickname];
[temporaryKeyExchangeInfosLock_ unlock];
}
- (NSDictionary *)temporaryKeyExchangeInfosForNickname:(NSString *)nickname onConnection:(id)connection;
{
[temporaryKeyExchangeInfosLock_ lock];
NSDictionary *kxInfo = [[[temporaryKeyExchangeInfos_ objectForKey:connection] objectForKey:nickname] retain];
[temporaryKeyExchangeInfosLock_ unlock];
return [kxInfo autorelease];
}
- (void)removeTemporaryKeyExchangeInfosForNickName:(NSString *)nickname onConnection:(id)connection;
{
DLog(@"Removing temporary key exchange info for %@ on %@", nickname, connection);
[temporaryKeyExchangeInfosLock_ lock];
// First, invalidate the timer set up to remove this tempo key pair after some time has passed.
[[[[temporaryKeyExchangeInfos_ objectForKey:connection] objectForKey:nickname] objectForKey:FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey] invalidate];
// Then, remove the whole entry for that key pair.
[[temporaryKeyExchangeInfos_ objectForKey:connection] removeObjectForKey:nickname];
[temporaryKeyExchangeInfosLock_ unlock];
}
- (void)handleKeyExchangeTimeout:(NSTimer *)aTimer;
{
// Retain the user info, as it would be released later when we invalidate the timer.
NSDictionary *timerInfoDict = [[aTimer userInfo] retain];
NSString *nickname = [timerInfoDict objectForKey:@"nickname"];
id connection = [timerInfoDict objectForKey:@"connection"];
DLog(@"Key exchange for %@ on %@ timed out", nickname, connection);
[self removeTemporaryKeyExchangeInfosForNickName:nickname onConnection:connection];
[delegate_ outputStatusInformation:NSLocalizedString(@"Timed out waiting for key-exchange reply.", "Timed out waiting for key-exchange reply")
forContext:nickname
on:connection];
[timerInfoDict release];
}
- (void)handleFiSHKeyExchangeRequestFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
{
[delegate_ outputStatusInformation:NSLocalizedString(@"Received key exchange request.", "Received key exchange request")
forContext:nickname
on:connection];
// Generate the keypair we use for this exchange
dhclass dhKeyExchanger;
if (!dhKeyExchanger.generate())
{
[delegate_ outputStatusInformation:NSLocalizedString(@"Unknown error during key exchange.", "Unknown error during key exchange")
forContext:nickname
on:connection];
return;
}
std::string myPubKeyTemp;
dhKeyExchanger.get_public_key(myPubKeyTemp);
NSString *myPubKey = [NSString stringWithUTF8String:myPubKeyTemp.c_str()];
[delegate_ sendPrivateMessage:[NSString stringWithFormat:@"%@%@", HCKFiSHKeyExchangeResponse, myPubKey] to:nickname on:connection];
// The remote public key has to be sent base64-encoded, so it is safe to use UTF8 here.
std::string remotePublicKeyTemp([remotePublicKeyData UTF8String]);
// Decode the base64 encoded received public key. It has to have a length of 135 to be valid.
dh_base64decode(remotePublicKeyTemp);
if (remotePublicKeyTemp.size() != 135)
{
[delegate_ outputStatusInformation:NSLocalizedString(@"Malformed request.", "Malformed request")
forContext:nickname
on:connection];
return;
}
// Compute the secret with our private key and the remote public key.
dhKeyExchanger.set_her_key(remotePublicKeyTemp);
if(!dhKeyExchanger.compute())
{
[delegate_ outputStatusInformation:NSLocalizedString(@"Unknown error during key exchange.", "Unknown error during key exchange")
forContext:nickname
on:connection];
return;
}
std::string theSecretTemp;
dhKeyExchanger.get_secret(theSecretTemp);
NSString *theSecret = [[[NSString alloc] initWithBytes:theSecretTemp.c_str()
length:theSecretTemp.length()
encoding:NSASCIIStringEncoding]
autorelease];
// TODO: Handle service/connection correctly.
[[FiSHSecretStore sharedSecretStore] storeSecret:theSecret forService:nil account:nickname isTemporary:YES];
[delegate_ outputStatusInformation:NSLocalizedString(@"Key exchange completed.", "Key exchange completed")
forContext:nickname
on:connection];
[delegate_ keyExchanger:self finishedKeyExchangeFor:nickname onConnection:connection succesfully:YES];
}
- (void)handleFiSHKeyExchangeResponseFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
{
NSValue *dhKeyExchangerValue = [[self temporaryKeyExchangeInfosForNickname:nickname onConnection:connection] objectForKey:FiSHKeyExchangeInfoDH1080Key];
dhclass *dhKeyExchanger = (dhclass *)[dhKeyExchangerValue pointerValue];
if (!dhKeyExchanger)
{
// Either we timed out waiting for a response, or we never send an exchange req to that nick on that connection. Either way just ignore it.
[delegate_ outputStatusInformation:NSLocalizedString(@"Received unrequested or too late key exchange response.", "Received unrequested or too late key exchange response")
forContext:nickname
on:connection];
return;
}
[self removeTemporaryKeyExchangeInfosForNickName:nickname onConnection:connection];
// TODO: Put the following in its own method, to share code with handleFiSHKeyExchangeRequestFrom:::
std::string remotePublicKeyTemp([remotePublicKeyData UTF8String]);
// Decode the base64 encoded received public key. It has to have a length of 135 to be valid.
dh_base64decode(remotePublicKeyTemp);
if (remotePublicKeyTemp.size() != 135)
{
free(dhKeyExchanger);
[delegate_ outputStatusInformation:NSLocalizedString(@"Malformed response.", "Malformed response")
forContext:nickname
on:connection];
return;
}
// Compute the secret with our private key and the remote public key.
dhKeyExchanger->set_her_key(remotePublicKeyTemp);
if (!dhKeyExchanger->compute())
{
free(dhKeyExchanger);
[delegate_ outputStatusInformation:NSLocalizedString(@"Unknown error during key exchange.", "Unknown error during key exchange")
forContext:nickname
on:connection];
return;
}
std::string theSecretTemp;
dhKeyExchanger->get_secret(theSecretTemp);
NSString *theSecret = [[[NSString alloc] initWithBytes:theSecretTemp.c_str()
length:theSecretTemp.length()
encoding:NSASCIIStringEncoding]
autorelease];
free(dhKeyExchanger);
// TODO: Handle service/connection correctly.
[[FiSHSecretStore sharedSecretStore] storeSecret:theSecret forService:nil account:nickname isTemporary:YES];
[delegate_ outputStatusInformation:NSLocalizedString(@"Key exchange completed.", "Key exchange completed")
forContext:nickname
on:connection];
[delegate_ keyExchanger:self finishedKeyExchangeFor:nickname onConnection:connection succesfully:YES];
}
@end