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