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