97a96c25520f51376ef67edbfea1e977ba231737
[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 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
423                 Array<char> *rawData = new Array<char>(sizesofslots->get(i));
424                 dis->readFully(rawData);
425
426
427                 // Array<char> * data = new char[rawData.length - IV_SIZE];
428                 // System.arraycopy(rawData, IV_SIZE, data, 0, data.length);
429
430
431                 Array<char> *data = stripIVAndDecryptSlot(rawData);
432
433                 // data = decryptCipher.doFinal(data);
434                 
435                 slots->set(i, Slot_decode(table, data, mac));
436         }
437         dis->close();
438         return slots;
439 }
440
441 Array<char> *sendLocalData(Array<char> *sendData, int64_t localSequenceNumber, String host, int port) {
442         if (salt == NULL) {
443                 return NULL;
444         }
445         try {
446                 printf("Passing Locally"\m);
447
448                 mac->update(sendData);
449                 Array<char> *genmac = mac->doFinal();
450                 Array<char> *totalData = new Array<char>(sendData->length() + genmac->length());
451                 System_arraycopy(sendData, 0, totalData, 0, sendData.length());
452                 System-arraycopy(genmac, 0, totalData, sendData.length, genmac->length());
453
454                 // Encrypt the data for sending
455                 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
456                 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
457
458                 // Open a TCP socket connection to a local device
459                 Socket* socket = new Socket(host, port);
460                 socket->setReuseAddress(true);
461                 DataOutputStream* output = new DataOutputStream(socket->getOutputStream());
462                 DataInputStream* input = new DataInputStream(socket->getInputStream());
463
464                 timer->startTime();
465                 // Send data to output (length of data, the data)
466                 output->writeInt(encryptedData->length);
467                 output->write(encryptedData, 0, encryptedData->length);
468                 output->flush();
469
470                 int lengthOfReturnData = input->readInt();
471                 Array<char> *returnData = new Array<char>(lengthOfReturnData);
472                 input->readFully(returnData);
473
474                 timer->endTime();
475
476                 returnData = stripIVAndDecryptSlot(returnData);
477
478                 // We are done with this socket
479                 socket->close();
480
481                 mac->update(returnData, 0, returnData->length - HMAC_SIZE);
482                 Array<char> *realmac = mac->doFinal();
483                 Array<char> *recmac = new Array<char>(HMAC_SIZE);
484                 System_arraycopy(returnData, returnData->length - realmac->length, recmac, 0, realmac->length);
485
486                 if (!Arrays->equals(recmac, realmac))
487                         throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
488
489                 Array<char> *returnData2 = new Array<char>(lengthOfReturnData - recmac->length());
490                 System_arraycopy(returnData, 0, returnData2, 0, returnData2->length);
491
492                 return returnData2;
493         } catch (Exception *e) {
494                 printf("Exception\n");
495         }
496
497         return NULL;
498 }
499
500 void CloudComm::localServerWorkerFunction() {
501         ServerSocket *inputSocket = NULL;
502
503         try {
504                 // Local server socket
505                 inputSocket = new ServerSocket(listeningPort);
506                 inputSocket->setReuseAddress(true);
507                 inputSocket->setSoTimeout(CloudComm_TIMEOUT_MILLIS);
508         } catch (Exception *e) {
509                 throw new Error("Local server setup failure...");
510         }
511
512         while (!doEnd) {
513                 try {
514                         // Accept incoming socket
515                         Socket *socket = inputSocket->accept();
516
517                         DataInputStream *input = new DataInputStream(socket->getInputStream());
518                         DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
519
520                         // Get the encrypted data from the server
521                         int dataSize = input->readInt();
522                         Array<char> *readData = new Array<char>(dataSize);
523                         input->readFully(readData);
524
525                         timer->endTime();
526
527                         // Decrypt the data
528                         readData = stripIVAndDecryptSlot(readData);
529
530                         mac->update(readData, 0, readData->length - HMAC_SIZE);
531                         Array<char> *genmac = mac->doFinal();
532                         Array<char> *recmac = new Array<char>(HMAC_SIZE);
533                         System_arraycopy(readData, readData->length() - recmac->length(), recmac, 0, recmac->length());
534
535                         if (!recmac->equals(genmac))
536                                 throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
537
538                         Array<char> *returnData = new Array<char>(readData->length() - recmac->length());
539                         System_arraycopy(readData, 0, returnData, 0, returnData->length());
540
541                         // Process the data
542                         Array<char> *sendData = table->acceptDataFromLocal(returnData);
543
544
545                         mac->update(sendData);
546                         Array<char> *realmac = mac->doFinal();
547                         Array<char> *totalData = new Array<char>(sendData->length() + realmac->length());
548                         System_arraycopy(sendData, 0, totalData, 0, sendData->length());
549                         System_arraycopy(realmac, 0, totalData, sendData->length(), realmac->length());
550
551                         // Encrypt the data for sending
552                         Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
553                         Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
554
555
556                         timer->startTime();
557                         // Send data to output (length of data, the data)
558                         output->writeInt(encryptedData->length());
559                         output->write(encryptedData, 0, encryptedData->length());
560                         output->flush();
561
562                         // close the socket
563                         socket->close();
564                 } catch (Exception *e) {
565                 }
566         }
567
568         if (inputSocket != NULL) {
569                 try {
570                         inputSocket->close();
571                 } catch (Exception *e) {
572                         throw new Error("Local server close failure...");
573                 }
574         }
575 }
576
577 void CloudComm::close() {
578         doEnd = true;
579
580         if (localServerThread != NULL) {
581                 try {
582                         localServerThread->join();
583                 } catch (Exception *e) {
584                         throw new Error("Local Server thread join issue...");
585                 }
586         }
587 }
588