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