Compiles
[iotcloud.git] / version2 / src / C / CloudComm.cc
1 #include "CloudComm.h"
2 #include "TimingSingleton.h"
3 #include "SecureRandom.h"
4 #include "IoTString.h"
5 #include "Error.h"
6 #include "URL.h"
7 #include "Mac.h"
8 #include "Table.h"
9 #include "Slot.h"
10 #include "Crypto.h"
11 #include "ByteBuffer.h"
12 #include <sys/socket.h>
13 #include <unistd.h>
14
15 /**
16  * Empty Constructor needed for child class.
17  */
18 CloudComm::CloudComm() :
19         baseurl(NULL),
20         key(NULL),
21         mac(NULL),
22         password(NULL),
23         random(NULL),
24         salt(NULL),
25         table(NULL),
26         listeningPort(-1),
27         localServerThread(NULL),
28         doEnd(false),
29         timer(TimingSingleton_getInstance()),
30         getslot(new Array<char>("getslot", 7)),
31         putslot(new Array<char>("putslot", 7))
32 {
33 }
34
35 void * threadWrapper(void * cloud) {
36         CloudComm *c = (CloudComm *) cloud;
37         c->localServerWorkerFunction();
38         return NULL;
39 }
40
41 /**
42  * Constructor for actual use. Takes in the url and password.
43  */
44 CloudComm::CloudComm(Table *_table,  IoTString *_baseurl, IoTString *_password, int _listeningPort) :
45         baseurl(_baseurl),
46         key(NULL),
47         mac(NULL),
48         password(_password),
49         random(new SecureRandom()),
50         salt(NULL),
51         table(_table),
52         listeningPort(_listeningPort),
53         localServerThread(NULL),
54         doEnd(false),
55         timer(TimingSingleton_getInstance()) {
56         if (listeningPort > 0) {
57                 pthread_create(&localServerThread, NULL, threadWrapper, this);
58         }
59 }
60
61 /**
62  * Generates Key from password.
63  */
64 AESKey *CloudComm::initKey() {
65         try {
66                 AESKey * key = new AESKey(password->internalBytes(),
67                                                                                                                         salt,
68                                                                                                                         65536,
69                                                                                                                         128);
70                 return key;
71         } catch (Exception *e) {
72                 throw new Error("Failed generating key.");
73         }
74 }
75
76 /**
77  * Inits all the security stuff
78  */
79
80 void CloudComm::initSecurity() {
81         // try to get the salt and if one does not exist set one
82         if (!getSalt()) {
83                 //Set the salt
84                 setSalt();
85         }
86
87         initCrypt();
88 }
89
90 /**
91  * Inits the HMAC generator.
92  */
93 void CloudComm::initCrypt() {
94         if (password == NULL) {
95                 return;
96         }
97         try {
98                 key = initKey();
99                 password = NULL;// drop password
100                 mac = Mac_getInstance("HmacSHA256");
101                 mac->init(key);
102         } catch (Exception *e) {
103                 throw new Error("Failed To Initialize Ciphers");
104         }
105 }
106
107 /*
108  * Builds the URL for the given request.
109  */
110 IoTString *CloudComm::buildRequest(bool isput, int64_t sequencenumber, int64_t maxentries) {
111         const char *reqstring = isput ? "req=putslot" : "req=getslot";
112         char *buffer = (char *) malloc(baseurl->length() + 200);
113         memcpy(buffer, baseurl->internalBytes(), baseurl->length());
114         int offset = baseurl->length();
115         offset += sprintf(&buffer[offset], "?%s&seq=%" PRId64, reqstring, sequencenumber);
116         if (maxentries != 0)
117                 sprintf(&buffer[offset], "&max=%" PRId64, maxentries);
118         IoTString *urlstr = new IoTString(buffer);
119         return urlstr;
120 }
121
122 int openURL(IoTString *url, bool isPost) {
123         return 0;
124 }
125
126 int createSocket(IoTString *host, int port) {
127         return 0;
128 }
129
130 int createSocket(int port) {
131         return 0;
132 }
133
134 int acceptSocket(int socket) {
135         return 0;
136 }
137
138 void writeSocketData(int fd, Array<char> *data) {}
139
140 void writeSocketInt(int fd, int value) {}
141
142 int readSocketInt(int fd) {return 0;}
143
144 void readSocketData(int fd, Array<char> *data) {}
145
146 void writeURLData(int fd, Array<char> *data) {
147 }
148
149 void readURLData(int fd, Array<char> * output) {
150 }
151
152 int readURLInt(int fd) {
153         return 0;
154 }
155
156 int getResponseCode(int fd) {
157         return 0;
158 }
159
160 void CloudComm::setSalt() {
161         if (salt != NULL) {
162                 // Salt already sent to server so don't set it again
163                 return;
164         }
165
166         int fd = -1;
167         try {
168                 Array<char> *saltTmp = new Array<char>(CloudComm_SALT_SIZE);
169                 random->nextBytes(saltTmp);
170
171                 char *buffer = (char *) malloc(baseurl->length() + 100);
172                 memcpy(buffer, baseurl->internalBytes(), baseurl->length());
173                 int offset = baseurl->length();
174                 offset += sprintf(&buffer[offset], "?req=setsalt");
175                 IoTString *urlstr = new IoTString(buffer);
176                 free(buffer);
177
178                 timer->startTime();
179                 fd = openURL(urlstr, true);
180                 writeURLData(fd, saltTmp);
181
182                 int responsecode = getResponseCode(fd);
183                 if (responsecode != HttpURLConnection_HTTP_OK) {
184                         throw new Error("Invalid response");
185                 }
186
187                 timer->endTime();
188                 salt = saltTmp;
189         } catch (Exception *e) {
190                 timer->endTime();
191                 throw new ServerException("Failed setting salt", ServerException_TypeConnectTimeout);
192         }
193 }
194
195 bool CloudComm::getSalt() {
196         int fd = -1;
197         IoTString *urlstr = NULL;
198         
199         try {
200                 char *buffer = (char *) malloc(baseurl->length() + 100);
201                 memcpy(buffer, baseurl->internalBytes(), baseurl->length());
202                 int offset = baseurl->length();
203                 offset += sprintf(&buffer[offset], "?req=getsalt");
204                 urlstr = new IoTString(buffer);
205                 free(buffer);
206         } catch (Exception *e) {
207                 throw new Error("getSlot failed");
208         }
209         try {
210                 timer->startTime();
211                 fd = openURL(urlstr, true);
212                 timer->endTime();
213         } catch (SocketTimeoutException *e) {
214                 timer->endTime();
215                 throw new ServerException("getSalt failed", ServerException_TypeConnectTimeout);
216         } catch (Exception *e) {
217                 throw new Error("getSlot failed");
218         }
219
220         try {
221                 timer->startTime();
222                 int responsecode = getResponseCode(fd);
223                 if (responsecode != HttpURLConnection_HTTP_OK) {
224                         throw new Error("Invalid response");
225                 }
226                 int salt_length = readURLInt(fd);
227                 Array<char> *tmp = new Array<char>(salt_length);
228                 readURLData(fd, tmp);
229                 salt = tmp;
230                 timer->endTime();
231                 return true;
232         } catch (SocketTimeoutException *e) {
233                 timer->endTime();
234                 throw new ServerException("getSalt failed", ServerException_TypeInputTimeout);
235         } catch (Exception *e) {
236                 throw new Error("getSlot failed");
237         }
238 }
239
240 Array<char> *CloudComm::createIV(int64_t machineId, int64_t localSequenceNumber) {
241         ByteBuffer *buffer = ByteBuffer_allocate(CloudComm_IV_SIZE);
242         buffer->putLong(machineId);
243         int64_t localSequenceNumberShifted = localSequenceNumber << 16;
244         buffer->putLong(localSequenceNumberShifted);
245         return buffer->array();
246 }
247
248 Array<char> *AESEncrypt(Array<char> * ivBytes, AESKey * key, Array<char> * data) {
249         return NULL;
250 }
251
252 Array<char> *AESDecrypt(Array<char> * ivBytes, AESKey * key, Array<char> * data) {
253         return NULL;
254 }
255
256 Array<char> *CloudComm::encryptSlotAndPrependIV(Array<char> *rawData, Array<char> *ivBytes) {
257         try {
258                 Array<char> *encryptedBytes = AESEncrypt(ivBytes, key, rawData);
259                 Array<char> *chars = new Array<char>(encryptedBytes->length() + CloudComm_IV_SIZE);
260                 System_arraycopy(ivBytes, 0, chars, 0, ivBytes->length());
261                 System_arraycopy(encryptedBytes, 0, chars, CloudComm_IV_SIZE, encryptedBytes->length());
262
263                 return chars;
264         } catch (Exception *e) {
265                 throw new Error("Failed To Encrypt");
266         }
267 }
268
269 Array<char> *CloudComm::stripIVAndDecryptSlot(Array<char> *rawData) {
270         try {
271                 Array<char> *ivBytes = new Array<char>(CloudComm_IV_SIZE);
272                 Array<char> *encryptedBytes = new Array<char>(rawData->length() - CloudComm_IV_SIZE);
273                 System_arraycopy(rawData, 0, ivBytes, 0, CloudComm_IV_SIZE);
274                 System_arraycopy(rawData, CloudComm_IV_SIZE, encryptedBytes, 0, encryptedBytes->length());
275                 return AESDecrypt(ivBytes, key, encryptedBytes);
276         } catch (Exception *e) {
277                 throw new Error("Failed To Decrypt");
278         }
279 }
280
281 /*
282  * API for putting a slot into the queue.  Returns NULL on success.
283  * On failure, the server will send slots with newer sequence
284  * numbers.
285  */
286 Array<Slot *> *CloudComm::putSlot(Slot *slot, int max) {
287         int fd = -1;
288         try {
289                 if (salt == NULL) {
290                         if (!getSalt()) {
291                                 throw new ServerException("putSlot failed", ServerException_TypeSalt);
292                         }
293                         initCrypt();
294                 }
295
296                 int64_t sequencenumber = slot->getSequenceNumber();
297                 Array<char> *slotBytes = slot->encode(mac);
298                 Array<char> *chars = encryptSlotAndPrependIV(slotBytes, slot->getSlotCryptIV());
299                 IoTString *url = buildRequest(true, sequencenumber, max);
300                 timer->startTime();
301                 fd = openURL(url, true);
302                 writeURLData(fd, chars);
303                 timer->endTime();
304         } catch (ServerException *e) {
305                 timer->endTime();
306                 throw e;
307         } catch (SocketTimeoutException *e) {
308                 timer->endTime();
309                 throw new ServerException("putSlot failed", ServerException_TypeConnectTimeout);
310         } catch (Exception *e) {
311                 throw new Error("putSlot failed");
312         }
313
314         try {
315                 timer->startTime();
316                 Array<char> *resptype = new Array<char>(7);
317                 readURLData(fd, resptype);
318                 timer->endTime();
319
320                 if (resptype->equals(getslot)) {
321                         return processSlots(fd);
322                 } else if (resptype->equals(putslot)) {
323                         return NULL;
324                 } else
325                         throw new Error("Bad response to putslot");
326         } catch (SocketTimeoutException *e) {
327                 timer->endTime();
328                 throw new ServerException("putSlot failed", ServerException_TypeInputTimeout);
329         } catch (Exception *e) {
330                 throw new Error("putSlot failed");
331         }
332 }
333
334 /**
335  * Request the server to send all slots with the given
336  * sequencenumber or newer->
337  */
338 Array<Slot *> *CloudComm::getSlots(int64_t sequencenumber) {
339         int fd = -1;
340         try {
341                 if (salt == NULL) {
342                         if (!getSalt()) {
343                                 throw new ServerException("getSlots failed", ServerException_TypeSalt);
344                         }
345                         initCrypt();
346                 }
347
348                 IoTString *url = buildRequest(false, sequencenumber, 0);
349                 timer->startTime();
350                 fd = openURL(url, true);
351                 timer->endTime();
352         } catch (SocketTimeoutException *e) {
353                 timer->endTime();
354                 throw new ServerException("getSlots failed", ServerException_TypeConnectTimeout);
355         } catch (ServerException *e) {
356                 timer->endTime();
357
358                 throw e;
359         } catch (Exception *e) {
360                 throw new Error("getSlots failed");
361         }
362
363         try {
364                 timer->startTime();
365                 Array<char> *resptype = new Array<char>(7);
366                 readURLData(fd, resptype);
367                 timer->endTime();
368                 if (!resptype->equals(getslot))
369                         throw new Error("Bad Response: ");
370
371                 return processSlots(fd);
372         } catch (SocketTimeoutException *e) {
373                 timer->endTime();
374                 throw new ServerException("getSlots failed", ServerException_TypeInputTimeout);
375         } catch (Exception *e) {
376                 throw new Error("getSlots failed");
377         }
378 }
379
380 /**
381  * Method that actually handles building Slot objects from the
382  * server response.  Shared by both putSlot and getSlots.
383  */
384 Array<Slot *> *CloudComm::processSlots(int fd) {
385         int numberofslots = readURLInt(fd);
386         Array<int> *sizesofslots = new Array<int>(numberofslots);
387         Array<Slot *> *slots = new Array<Slot *>(numberofslots);
388
389         for (int i = 0; i < numberofslots; i++)
390                 sizesofslots->set(i, readURLInt(fd));
391         for (int i = 0; i < numberofslots; i++) {
392                 Array<char> *rawData = new Array<char>(sizesofslots->get(i));
393                 readURLData(fd, rawData);
394                 Array<char> *data = stripIVAndDecryptSlot(rawData);
395                 slots->set(i, Slot_decode(table, data, mac));
396         }
397         return slots;
398 }
399
400 Array<char> *CloudComm::sendLocalData(Array<char> *sendData, int64_t localSequenceNumber, IoTString * host, int port) {
401         if (salt == NULL)
402                 return NULL;
403         try {
404                 printf("Passing Locally\n");
405                 mac->update(sendData, 0, sendData->length());
406                 Array<char> *genmac = mac->doFinal();
407                 Array<char> *totalData = new Array<char>(sendData->length() + genmac->length());
408                 System_arraycopy(sendData, 0, totalData, 0, sendData->length());
409                 System_arraycopy(genmac, 0, totalData, sendData->length(), genmac->length());
410
411                 // Encrypt the data for sending
412                 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
413                 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
414
415                 // Open a TCP socket connection to a local device
416                 int socket = createSocket(host, port);
417
418                 timer->startTime();
419                 // Send data to output (length of data, the data)
420                 writeSocketInt(socket, encryptedData->length());
421                 writeSocketData(socket, encryptedData);
422
423                 int lengthOfReturnData = readSocketInt(socket);
424                 Array<char> *returnData = new Array<char>(lengthOfReturnData);
425                 readSocketData(socket, returnData);
426                 timer->endTime();
427                 returnData = stripIVAndDecryptSlot(returnData);
428
429                 // We are done with this socket
430                 close(socket);
431                 mac->update(returnData, 0, returnData->length() - CloudComm_HMAC_SIZE);
432                 Array<char> *realmac = mac->doFinal();
433                 Array<char> *recmac = new Array<char>(CloudComm_HMAC_SIZE);
434                 System_arraycopy(returnData, returnData->length() - realmac->length(), recmac, 0, realmac->length());
435
436                 if (!recmac->equals(realmac))
437                         throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
438
439                 Array<char> *returnData2 = new Array<char>(lengthOfReturnData - recmac->length());
440                 System_arraycopy(returnData, 0, returnData2, 0, returnData2->length());
441
442                 return returnData2;
443         } catch (Exception *e) {
444                 printf("Exception\n");
445         }
446
447         return NULL;
448 }
449
450 void CloudComm::localServerWorkerFunction() {
451         int inputSocket = -1;
452
453         try {
454                 // Local server socket
455                 inputSocket = createSocket(listeningPort);
456         } catch (Exception *e) {
457                 throw new Error("Local server setup failure...");
458         }
459
460         while (!doEnd) {
461                 try {
462                         // Accept incoming socket
463                         int socket = acceptSocket(inputSocket);
464
465                         // Get the encrypted data from the server
466                         int dataSize = readSocketInt(socket);
467                         Array<char> *readData = new Array<char>(dataSize);
468                         readSocketData(socket, readData);
469                         timer->endTime();
470
471                         // Decrypt the data
472                         readData = stripIVAndDecryptSlot(readData);
473                         mac->update(readData, 0, readData->length() - CloudComm_HMAC_SIZE);
474                         Array<char> *genmac = mac->doFinal();
475                         Array<char> *recmac = new Array<char>(CloudComm_HMAC_SIZE);
476                         System_arraycopy(readData, readData->length() - recmac->length(), recmac, 0, recmac->length());
477
478                         if (!recmac->equals(genmac))
479                                 throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
480
481                         Array<char> *returnData = new Array<char>(readData->length() - recmac->length());
482                         System_arraycopy(readData, 0, returnData, 0, returnData->length());
483
484                         // Process the data
485                         Array<char> *sendData = table->acceptDataFromLocal(returnData);
486                         mac->update(sendData, 0, sendData->length());
487                         Array<char> *realmac = mac->doFinal();
488                         Array<char> *totalData = new Array<char>(sendData->length() + realmac->length());
489                         System_arraycopy(sendData, 0, totalData, 0, sendData->length());
490                         System_arraycopy(realmac, 0, totalData, sendData->length(), realmac->length());
491
492                         // Encrypt the data for sending
493                         Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
494                         Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
495
496                         timer->startTime();
497                         // Send data to output (length of data, the data)
498                         writeSocketInt(socket, encryptedData->length());
499                         writeSocketData(socket, encryptedData);
500                         close(socket);
501                 } catch (Exception *e) {
502                 }
503         }
504
505         if (inputSocket != -1) {
506                 try {
507                         close(inputSocket);
508                 } catch (Exception *e) {
509                         throw new Error("Local server close failure...");
510                 }
511         }
512 }
513
514 void CloudComm::closeCloud() {
515         doEnd = true;
516
517         if (localServerThread != NULL) {
518                 if (pthread_join(localServerThread, NULL) != 0)
519                         throw new Error("Local Server thread join issue...");
520         }
521 }