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