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