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