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