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