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
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         if (password == NULL) {
86                 return;
87         }
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         IoTString *reqstring = isput ? "req=putslot" : "req=getslot";
104         IoTString *urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
105         if (maxentries != 0)
106                 urlstr += "&max=" + maxentries;
107         return new URL(urlstr);
108 }
109
110 void CloudComm::setSalt() {
111
112         if (salt != NULL) {
113                 // Salt already sent to server so dont set it again
114                 return;
115         }
116
117         try {
118                 Array<char> *saltTmp = new Array<char>(CloudComm_SALT_SIZE);
119                 random->nextBytes(saltTmp);
120
121                 for (int i = 0; i < CloudComm_SALT_SIZE; i++) {
122                         printf("%d\n", (int)saltTmp->get(i) & 255);
123                 }
124
125                 URL *url = new URL(baseurl + "?req=setsalt");
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(CloudComm_TIMEOUT_MILLIS);
134                 http->connect();
135
136                 OutputStream *os = http->getOutputStream();
137                 os->write(saltTmp);
138                 os->flush();
139
140                 int responsecode = http->getResponseCode();
141                 if (responsecode != HttpURLConnection_HTTP_OK) {
142                         // TODO: Remove this print
143                         printf("%d\n", responsecode);
144                         throw new Error("Invalid response");
145                 }
146
147                 timer->endTime();
148                 salt = saltTmp;
149         } catch (Exception *e) {
150                 timer->endTime();
151                 throw new ServerException("Failed setting salt", ServerException_TypeConnectTimeout);
152         }
153 }
154
155 bool CloudComm::getSalt() {
156         URL *url = NULL;
157         URLConnection *con = NULL;
158         HttpURLConnection *http = NULL;
159
160         try {
161                 url = new URL(baseurl + "?req=getsalt");
162         } catch (Exception *e) {
163                 throw new Error("getSlot failed");
164         }
165         try {
166                 timer->startTime();
167                 con = url->openConnection();
168                 http = (HttpURLConnection *) con;
169                 http->setRequestMethod("POST");
170                 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
171                 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
172                 http->connect();
173                 timer->endTime();
174         } catch (SocketTimeoutException *e) {
175                 timer->endTime();
176                 throw new ServerException("getSalt failed", ServerException_TypeConnectTimeout);
177         } catch (Exception *e) {
178                 throw new Error("getSlot failed");
179         }
180
181         try {
182                 timer->startTime();
183                 int responsecode = http->getResponseCode();
184                 if (responsecode != HttpURLConnection_HTTP_OK) {
185                         throw new Error("Invalid response");
186                 }
187                 InputStream *is = http->getInputStream();
188                 if (is->available() > 0) {
189                         DataInputStream *dis = new DataInputStream(is);
190                         int salt_length = dis->readInt();
191                         Array<char> *tmp = new Array<char>(salt_length);
192                         dis->readFully(tmp);
193                         salt = tmp;
194                         timer->endTime();
195                         return true;
196                 } else {
197                         timer->endTime();
198                         return false;
199                 }
200         } catch (SocketTimeoutException *e) {
201                 timer->endTime();
202                 throw new ServerException("getSalt failed", ServerException_TypeInputTimeout);
203         } catch (Exception *e) {
204                 throw new Error("getSlot failed");
205         }
206 }
207
208 Array<char> *CloudComm::createIV(int64_t machineId, int64_t localSequenceNumber) {
209         ByteBuffer *buffer = ByteBuffer_allocate(CloudComm_IV_SIZE);
210         buffer->putLong(machineId);
211         int64_t localSequenceNumberShifted = localSequenceNumber << 16;
212         buffer->putLong(localSequenceNumberShifted);
213         return buffer->array();
214 }
215
216 Array<char> *CloudComm::encryptSlotAndPrependIV(Array<char> *rawData, Array<char> *ivBytes) {
217         try {
218                 IvParameterSpec *ivSpec = new IvParameterSpec(ivBytes);
219                 Cipher *cipher = Cipher_getInstance("AES/CTR/NoPadding");
220                 cipher->init(Cipher_ENCRYPT_MODE, key, ivSpec);
221
222                 Array<char> *encryptedBytes = cipher->doFinal(rawData);
223
224                 Array<char> *chars = new Array<char>(encryptedBytes->length() + CloudComm_IV_SIZE);
225                 System_arraycopy(ivBytes, 0, chars, 0, ivBytes->length());
226                 System_arraycopy(encryptedBytes, 0, chars, CloudComm_IV_SIZE, encryptedBytes->length());
227
228                 return chars;
229         } catch (Exception *e) {
230                 throw new Error("Failed To Encrypt");
231         }
232 }
233
234 Array<char> *CloudComm::stripIVAndDecryptSlot(Array<char> *rawData) {
235         try {
236                 Array<char> *ivBytes = new Array<char>(CloudComm_IV_SIZE);
237                 Array<char> *encryptedBytes = new Array<char>(rawData->length() - CloudComm_IV_SIZE);
238                 System_arraycopy(rawData, 0, ivBytes, 0, CloudComm_IV_SIZE);
239                 System_arraycopy(rawData, CloudComm_IV_SIZE, encryptedBytes, 0, encryptedBytes->length);
240
241                 IvParameterSpec *ivSpec = new IvParameterSpec(ivBytes);
242                 Cipher *cipher = Cipher_getInstance("AES/CTR/NoPadding");
243                 cipher->init(Cipher_DECRYPT_MODE, key, ivSpec);
244                 return cipher->doFinal(encryptedBytes);
245         } catch (Exception *e) {
246                 throw new Error("Failed To Decrypt");
247         }
248 }
249
250 /*
251  * API for putting a slot into the queue.  Returns NULL on success.
252  * On failure, the server will send slots with newer sequence
253  * numbers.
254  */
255 Array<Slot *> *CloudComm::putSlot(Slot *slot, int max) {
256         try {
257                 if (salt == NULL) {
258                         if (!getSalt()) {
259                                 throw new ServerException("putSlot failed", ServerException_TypeSalt);
260                         }
261                         initCrypt();
262                 }
263
264                 int64_t sequencenumber = slot->getSequenceNumber();
265                 Array<char> *slotBytes = slot->encode(mac);
266                 Array<char> *chars = encryptSlotAndPrependIV(slotBytes, slot->getSlotCryptIV());
267                 URL *url = buildRequest(true, sequencenumber, max);
268                 timer->startTime();
269                 URLConnection *con = url->openConnection();
270                 HttpURLConnection *http = (HttpURLConnection *) con;
271                 http->setRequestMethod("POST");
272                 http->setFixedLengthStreamingMode(chars->length);
273                 http->setDoOutput(true);
274                 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
275                 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
276                 http->connect();
277                 OutputStream *os = http->getOutputStream();
278                 os->write(chars);
279                 os->flush();
280                 timer->endTime();
281         } catch (ServerException *e) {
282                 timer->endTime();
283                 throw e;
284         } catch (SocketTimeoutException *e) {
285                 timer->endTime();
286                 throw new ServerException("putSlot failed", ServerException_TypeConnectTimeout);
287         } catch (Exception *e) {
288                 throw new Error("putSlot failed");
289         }
290
291         try {
292                 timer->startTime();
293                 InputStream *is = http->getInputStream();
294                 DataInputStream *dis = new DataInputStream(is);
295                 Array<char> *resptype = new Array<char>(7);
296                 dis->readFully(resptype);
297                 timer->endTime();
298
299                 if (Arrays->equals(resptype, "getslot"->getBytes())) {
300                         return processSlots(dis);
301                 } else if (Arrays->equals(resptype, "putslot"->getBytes())) {
302                         return NULL;
303                 } else
304                         throw new Error("Bad response to putslot");
305         } catch (SocketTimeoutException *e) {
306                 timer->endTime();
307                 throw new ServerException("putSlot failed", ServerException->TypeInputTimeout);
308         } catch (Exception *e) {
309                 throw new Error("putSlot failed");
310         }
311 }
312
313 /**
314  * Request the server to send all slots with the given
315  * sequencenumber or newer->
316  */
317 Array<Slot *> *CloudComm::getSlots(int64_t sequencenumber) {
318         try {
319                 if (salt == NULL) {
320                         if (!getSalt()) {
321                                 throw new ServerException("getSlots failed", ServerException_TypeSalt);
322                         }
323                         initCrypt();
324                 }
325
326                 URL *url = buildRequest(false, sequencenumber, 0);
327                 timer->startTime();
328                 URLConnection *con = url->openConnection();
329                 HttpURLConnection *http = (HttpURLConnection *) con;
330                 http->setRequestMethod("POST");
331                 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
332                 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
333                 http->connect();
334                 timer->endTime();
335         } catch (SocketTimeoutException *e) {
336                 timer->endTime();
337                 throw new ServerException("getSlots failed", ServerException_TypeConnectTimeout);
338         } catch (ServerException *e) {
339                 timer->endTime();
340
341                 throw e;
342         } catch (Exception *e) {
343                 throw new Error("getSlots failed");
344         }
345
346         try {
347                 timer->startTime();
348                 InputStream *is = http->getInputStream();
349                 DataInputStream *dis = new DataInputStream(is);
350                 Array<char> *resptype = new Array<char>(7);
351                 dis->readFully(resptype);
352                 timer->endTime();
353                 if (!resptype->equals("getslot"->getBytes()))
354                         throw new Error("Bad Response: " + new String(resptype));
355
356                 return processSlots(dis);
357         } catch (SocketTimeoutException *e) {
358                 timer->endTime();
359                 throw new ServerException("getSlots failed", ServerException_TypeInputTimeout);
360         } catch (Exception *e) {
361                 throw new Error("getSlots failed");
362         }
363 }
364
365 /**
366  * Method that actually handles building Slot objects from the
367  * server response.  Shared by both putSlot and getSlots.
368  */
369 Array<Slot *> *CloudComm::processSlots(DataInputStream *dis) {
370         int numberofslots = dis->readInt();
371         Array<int> *sizesofslots = new Array<int>(numberofslots);
372         Array<Slot *> *slots = new Array<Slot *>(numberofslots);
373
374         for (int i = 0; i < numberofslots; i++)
375                 sizesofslots->set(i, dis->readInt());
376         for (int i = 0; i < numberofslots; i++) {
377                 Array<char> *rawData = new Array<char>(sizesofslots->get(i));
378                 dis->readFully(rawData);
379                 Array<char> *data = stripIVAndDecryptSlot(rawData);
380                 slots->set(i, Slot_decode(table, data, mac));
381         }
382         dis->close();
383         return slots;
384 }
385
386 Array<char> *sendLocalData(Array<char> *sendData, int64_t localSequenceNumber, String host, int port) {
387         if (salt == NULL)
388                 return NULL;
389         try {
390                 printf("Passing Locally\n");
391                 mac->update(sendData);
392                 Array<char> *genmac = mac->doFinal();
393                 Array<char> *totalData = new Array<char>(sendData->length() + genmac->length());
394                 System_arraycopy(sendData, 0, totalData, 0, sendData->length());
395                 System_arraycopy(genmac, 0, totalData, sendData->length(), genmac->length());
396
397                 // Encrypt the data for sending
398                 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
399                 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
400
401                 // Open a TCP socket connection to a local device
402                 Socket *socket = new Socket(host, port);
403                 socket->setReuseAddress(true);
404                 DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
405                 DataInputStream *input = new DataInputStream(socket->getInputStream());
406
407                 timer->startTime();
408                 // Send data to output (length of data, the data)
409                 output->writeInt(encryptedData->length);
410                 output->write(encryptedData, 0, encryptedData->length);
411                 output->flush();
412
413                 int lengthOfReturnData = input->readInt();
414                 Array<char> *returnData = new Array<char>(lengthOfReturnData);
415                 input->readFully(returnData);
416                 timer->endTime();
417                 returnData = stripIVAndDecryptSlot(returnData);
418
419                 // We are done with this socket
420                 socket->close();
421                 mac->update(returnData, 0, returnData->length - HMAC_SIZE);
422                 Array<char> *realmac = mac->doFinal();
423                 Array<char> *recmac = new Array<char>(HMAC_SIZE);
424                 System_arraycopy(returnData, returnData->length - realmac->length, recmac, 0, realmac->length);
425
426                 if (!recmac->equals(realmac))
427                         throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
428
429                 Array<char> *returnData2 = new Array<char>(lengthOfReturnData - recmac->length());
430                 System_arraycopy(returnData, 0, returnData2, 0, returnData2->length);
431
432                 return returnData2;
433         } catch (Exception *e) {
434                 printf("Exception\n");
435         }
436
437         return NULL;
438 }
439
440 void CloudComm::localServerWorkerFunction() {
441         ServerSocket *inputSocket = NULL;
442
443         try {
444                 // Local server socket
445                 inputSocket = new ServerSocket(listeningPort);
446                 inputSocket->setReuseAddress(true);
447                 inputSocket->setSoTimeout(CloudComm_TIMEOUT_MILLIS);
448         } catch (Exception *e) {
449                 throw new Error("Local server setup failure...");
450         }
451
452         while (!doEnd) {
453                 try {
454                         // Accept incoming socket
455                         Socket *socket = inputSocket->accept();
456                         DataInputStream *input = new DataInputStream(socket->getInputStream());
457                         DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
458
459                         // Get the encrypted data from the server
460                         int dataSize = input->readInt();
461                         Array<char> *readData = new Array<char>(dataSize);
462                         input->readFully(readData);
463                         timer->endTime();
464
465                         // Decrypt the data
466                         readData = stripIVAndDecryptSlot(readData);
467                         mac->update(readData, 0, readData->length - HMAC_SIZE);
468                         Array<char> *genmac = mac->doFinal();
469                         Array<char> *recmac = new Array<char>(HMAC_SIZE);
470                         System_arraycopy(readData, readData->length() - recmac->length(), recmac, 0, recmac->length());
471
472                         if (!recmac->equals(genmac))
473                                 throw new Error("Local Error: Invalid HMAC!  Potential Attack!");
474
475                         Array<char> *returnData = new Array<char>(readData->length() - recmac->length());
476                         System_arraycopy(readData, 0, returnData, 0, returnData->length());
477
478                         // Process the data
479                         Array<char> *sendData = table->acceptDataFromLocal(returnData);
480                         mac->update(sendData);
481                         Array<char> *realmac = mac->doFinal();
482                         Array<char> *totalData = new Array<char>(sendData->length() + realmac->length());
483                         System_arraycopy(sendData, 0, totalData, 0, sendData->length());
484                         System_arraycopy(realmac, 0, totalData, sendData->length(), realmac->length());
485
486                         // Encrypt the data for sending
487                         Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
488                         Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
489
490                         timer->startTime();
491                         // Send data to output (length of data, the data)
492                         output->writeInt(encryptedData->length());
493                         output->write(encryptedData, 0, encryptedData->length());
494                         output->flush();
495
496                         // close the socket
497                         socket->close();
498                 } catch (Exception *e) {
499                 }
500         }
501
502         if (inputSocket != NULL) {
503                 try {
504                         inputSocket->close();
505                 } catch (Exception *e) {
506                         throw new Error("Local server close failure...");
507                 }
508         }
509 }
510
511 void CloudComm::close() {
512         doEnd = true;
513
514         if (localServerThread != NULL) {
515                 try {
516                         localServerThread->join();
517                 } catch (Exception *e) {
518                         throw new Error("Local Server thread join issue...");
519                 }
520         }
521 }