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