5741019513afcad9027b1d866963bce07d6e77fe
[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 = 100;
21
22         private String baseurl;
23         private Cipher encryptCipher;
24         private Cipher decryptCipher;
25         private Mac mac;
26         private String password;
27         private SecureRandom random;
28         private byte salt[];
29         private Table table;
30         private int listeningPort = -1;
31         private Thread localServerThread = null;
32         private boolean doEnd = false;
33
34         /**
35          * Empty Constructor needed for child class.
36          */
37         CloudComm() {
38         }
39
40         /**
41          * Constructor for actual use. Takes in the url and password.
42          */
43         CloudComm(Table _table,  String _baseurl, String _password, int _listeningPort) {
44                 this.table = _table;
45                 this.baseurl = _baseurl;
46                 this.password = _password;
47                 this.random = new SecureRandom();
48                 this.listeningPort = _listeningPort;
49
50                 if (this.listeningPort > 0) {
51                         localServerThread = new Thread(new Runnable() {
52                                 public void run() {
53                                         localServerWorkerFunction();
54                                 }
55                         });
56                         localServerThread.start();
57                 }
58         }
59
60         /**
61          * Generates Key from password.
62          */
63         private SecretKeySpec initKey() {
64                 try {
65                         PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
66                                                             salt,
67                                                             65536,
68                                                             128);
69                         SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
70                         return new SecretKeySpec(tmpkey.getEncoded(), "AES");
71                 } catch (Exception e) {
72                         e.printStackTrace();
73                         throw new Error("Failed generating key.");
74                 }
75         }
76
77         /**
78          * Inits the HMAC generator.
79          */
80         private void initCrypt() {
81
82                 if (password == null) {
83                         return;
84                 }
85
86                 try {
87                         SecretKeySpec key = initKey();
88                         password = null; // drop password
89                         mac = Mac.getInstance("HmacSHA256");
90                         mac.init(key);
91                         encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
92                         encryptCipher.init(Cipher.ENCRYPT_MODE, key);
93                         decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
94                         decryptCipher.init(Cipher.DECRYPT_MODE, key);
95                 } catch (Exception e) {
96                         e.printStackTrace();
97                         throw new Error("Failed To Initialize Ciphers");
98                 }
99         }
100
101         /*
102          * Builds the URL for the given request.
103          */
104         private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
105                 String reqstring = isput ? "req=putslot" : "req=getslot";
106                 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
107                 if (maxentries != 0)
108                         urlstr += "&max=" + maxentries;
109                 return new URL(urlstr);
110         }
111
112         public void setSalt() throws ServerException {
113
114                 if (salt != null) {
115                         // Salt already sent to server so dont set it again
116                         return;
117                 }
118                 byte[] saltTmp = new byte[SALT_SIZE];
119                 random.nextBytes(saltTmp);
120
121                 URL url = null;
122                 URLConnection con = null;
123                 HttpURLConnection http = null;
124
125                 try {
126                         url = new URL(baseurl + "?req=setsalt");
127                         con = url.openConnection();
128                         http = (HttpURLConnection) con;
129                         http.setRequestMethod("POST");
130                         http.setFixedLengthStreamingMode(saltTmp.length);
131                         http.setDoOutput(true);
132                         http.setConnectTimeout(TIMEOUT_MILLIS);
133                         http.connect();
134                         OutputStream os = http.getOutputStream();
135                         os.write(saltTmp);
136                         int responsecode = http.getResponseCode();
137                         if (responsecode != HttpURLConnection.HTTP_OK) {
138                                 // TODO: Remove this print
139                                 System.out.println(responsecode);
140                                 throw new Error("Invalid response");
141                         }
142
143                 } catch (Exception e) {
144                         throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
145                 }
146
147
148                 try {
149                         InputStream is = http.getInputStream();
150                         DataInputStream dis = new DataInputStream(is);
151                         // byte [] tmp = new byte[1];
152                         byte tmp = dis.readByte();
153
154                         if (tmp == 0) {
155                                 salt = saltTmp;
156                                 initCrypt();
157                         } else {
158                                 getSalt(); // there was already a salt so we need to get it
159                         }
160
161                 } catch (SocketTimeoutException e) {
162                         throw new ServerException("setSalt failed", ServerException.TypeInputTimeout);
163                 } catch (Exception e) {
164                         e.printStackTrace();
165                         throw new Error("setSlot failed");
166                 }
167         }
168
169         private void getSalt() throws ServerException {
170                 URL url = null;
171                 URLConnection con = null;
172                 HttpURLConnection http = null;
173
174                 try {
175                         url = new URL(baseurl + "?req=getsalt");
176                 } catch (Exception e) {
177                         e.printStackTrace();
178                         throw new Error("getSlot failed");
179                 }
180                 try {
181
182                         con = url.openConnection();
183                         http = (HttpURLConnection) con;
184                         http.setRequestMethod("POST");
185                         http.setConnectTimeout(TIMEOUT_MILLIS);
186                         http.setReadTimeout(TIMEOUT_MILLIS);
187                         http.connect();
188                 } catch (SocketTimeoutException e) {
189                         throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
190                 } catch (Exception e) {
191                         e.printStackTrace();
192                         throw new Error("getSlot failed");
193                 }
194
195                 try {
196                         InputStream is = http.getInputStream();
197                         DataInputStream dis = new DataInputStream(is);
198                         int salt_length = dis.readInt();
199                         byte [] tmp = new byte[salt_length];
200                         dis.readFully(tmp);
201                         salt = tmp;
202                 } catch (SocketTimeoutException e) {
203                         throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
204                 } catch (Exception e) {
205                         e.printStackTrace();
206                         throw new Error("getSlot failed");
207                 }
208         }
209
210         /*
211          * API for putting a slot into the queue.  Returns null on success.
212          * On failure, the server will send slots with newer sequence
213          * numbers.
214          */
215         Slot[] putSlot(Slot slot, int max) throws ServerException {
216                 URL url = null;
217                 URLConnection con = null;
218                 HttpURLConnection http = null;
219
220                 try {
221                         if (salt == null) {
222                                 getSalt();
223                                 initCrypt();
224                         }
225
226                         long sequencenumber = slot.getSequenceNumber();
227                         byte[] bytes = slot.encode(mac);
228                         bytes = encryptCipher.doFinal(bytes);
229
230                         url = buildRequest(true, sequencenumber, max);
231                         con = url.openConnection();
232                         http = (HttpURLConnection) con;
233
234                         http.setRequestMethod("POST");
235                         http.setFixedLengthStreamingMode(bytes.length);
236                         http.setDoOutput(true);
237                         http.setConnectTimeout(TIMEOUT_MILLIS);
238                         http.setReadTimeout(TIMEOUT_MILLIS);
239                         http.connect();
240
241                         OutputStream os = http.getOutputStream();
242                         os.write(bytes);
243                         os.flush();
244
245                         // System.out.println("Bytes Sent: " + bytes.length);
246                 } catch (SocketTimeoutException e) {
247                         throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
248                 } catch (Exception e) {
249                         e.printStackTrace();
250                         throw new Error("putSlot failed");
251                 }
252
253
254
255                 try {
256                         InputStream is = http.getInputStream();
257                         DataInputStream dis = new DataInputStream(is);
258                         byte[] resptype = new byte[7];
259                         dis.readFully(resptype);
260
261                         if (Arrays.equals(resptype, "getslot".getBytes()))
262                                 return processSlots(dis);
263                         else if (Arrays.equals(resptype, "putslot".getBytes()))
264                                 return null;
265                         else
266                                 throw new Error("Bad response to putslot");
267
268                 } catch (SocketTimeoutException e) {
269                         throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
270                 } catch (Exception e) {
271                         e.printStackTrace();
272                         throw new Error("putSlot failed");
273                 }
274         }
275
276         /**
277          * Request the server to send all slots with the given
278          * sequencenumber or newer.
279          */
280         Slot[] getSlots(long sequencenumber) throws ServerException {
281                 URL url = null;
282                 URLConnection con = null;
283                 HttpURLConnection http = null;
284
285                 try {
286                         if (salt == null) {
287                                 getSalt();
288                                 initCrypt();
289                         }
290
291                         url = buildRequest(false, sequencenumber, 0);
292                         con = url.openConnection();
293                         http = (HttpURLConnection) con;
294                         http.setRequestMethod("POST");
295                         http.setConnectTimeout(TIMEOUT_MILLIS);
296                         http.setReadTimeout(TIMEOUT_MILLIS);
297                         http.connect();
298                 } catch (SocketTimeoutException e) {
299                         throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
300                 } catch (ServerException e) {
301                         throw e;
302                 } catch (Exception e) {
303                         e.printStackTrace();
304                         throw new Error("getSlots failed");
305                 }
306
307                 try {
308                         InputStream is = http.getInputStream();
309                         DataInputStream dis = new DataInputStream(is);
310                         byte[] resptype = new byte[7];
311                         dis.readFully(resptype);
312                         if (!Arrays.equals(resptype, "getslot".getBytes()))
313                                 throw new Error("Bad Response: " + new String(resptype));
314                         else
315                                 return processSlots(dis);
316                 } catch (SocketTimeoutException e) {
317                         throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
318                 } catch (Exception e) {
319                         e.printStackTrace();
320                         throw new Error("getSlots failed");
321                 }
322         }
323
324         /**
325          * Method that actually handles building Slot objects from the
326          * server response.  Shared by both putSlot and getSlots.
327          */
328         private Slot[] processSlots(DataInputStream dis) throws Exception {
329                 int numberofslots = dis.readInt();
330                 int[] sizesofslots = new int[numberofslots];
331
332
333                 // System.out.println("number of slots: " + numberofslots);
334
335
336
337                 Slot[] slots = new Slot[numberofslots];
338                 for (int i = 0; i < numberofslots; i++)
339                         sizesofslots[i] = dis.readInt();
340
341                 for (int i = 0; i < numberofslots; i++) {
342
343                         // System.out.println("Size of slot: " + sizesofslots[i]);
344
345                         byte[] data = new byte[sizesofslots[i]];
346                         dis.readFully(data);
347
348                         data = decryptCipher.doFinal(data);
349
350                         slots[i] = Slot.decode(table, data, mac);
351                 }
352                 dis.close();
353                 return slots;
354         }
355
356         public byte[] sendLocalData(byte[] sendData, String host, int port) {
357
358                 if (salt == null) {
359                         return null;
360                 }
361                 try {
362                         // Encrypt the data for sending
363                         byte[] encryptedData = encryptCipher.doFinal(sendData);
364
365                         // Open a TCP socket connection to a local device
366                         Socket socket = new Socket(host, port);
367                         socket.setReuseAddress(true);
368                         DataOutputStream output = new DataOutputStream(socket.getOutputStream());
369                         DataInputStream input = new DataInputStream(socket.getInputStream());
370
371                         // Send data to output (length of data, the data)
372                         output.writeInt(encryptedData.length);
373                         output.write(encryptedData, 0, encryptedData.length);
374                         output.flush();
375
376                         int lengthOfReturnData = input.readInt();
377                         byte[] returnData = new byte[lengthOfReturnData];
378                         input.readFully(returnData);
379                         returnData = decryptCipher.doFinal(returnData);
380
381                         // We are dont with this socket
382                         socket.close();
383
384                         return returnData;
385                 } catch (SocketTimeoutException e) {
386
387                 } catch (BadPaddingException e) {
388
389                 } catch (IllegalBlockSizeException e) {
390
391                 } catch (UnknownHostException e) {
392
393                 } catch (IOException e) {
394
395                 }
396
397                 return null;
398         }
399
400         private void localServerWorkerFunction() {
401
402                 ServerSocket inputSocket = null;
403
404                 try {
405                         // Local server socket
406                         inputSocket = new ServerSocket(listeningPort);
407                         inputSocket.setReuseAddress(true);
408                         inputSocket.setSoTimeout(TIMEOUT_MILLIS);
409                 } catch (Exception e) {
410                         e.printStackTrace();
411                         throw new Error("Local server setup failure...");
412                 }
413
414                 while (!doEnd) {
415
416                         try {
417                                 // Accept incoming socket
418                                 Socket socket = inputSocket.accept();
419
420                                 DataInputStream input = new DataInputStream(socket.getInputStream());
421                                 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
422
423                                 // Get the encrypted data from the server
424                                 int dataSize = input.readInt();
425                                 byte[] readData = new byte[dataSize];
426                                 input.readFully(readData);
427
428                                 // Decrypt the data
429                                 readData = decryptCipher.doFinal(readData);
430
431                                 // Process the data
432                                 byte[] sendData = table.acceptDataFromLocal(readData);
433
434                                 // Encrypt the data for sending
435                                 sendData = encryptCipher.doFinal(sendData);
436
437                                 // Send data to output (length of data, the data)
438                                 output.writeInt(sendData.length);
439                                 output.write(sendData, 0, sendData.length);
440                                 output.flush();
441
442                                 // close the socket
443                                 socket.close();
444                         } catch (SocketTimeoutException e) {
445
446                         } catch (BadPaddingException e) {
447
448                         } catch (IllegalBlockSizeException e) {
449
450                         } catch (UnknownHostException e) {
451
452                         } catch (IOException e) {
453
454                         }
455                 }
456
457                 if (inputSocket != null) {
458                         try {
459                                 inputSocket.close();
460                         } catch (Exception e) {
461                                 e.printStackTrace();
462                                 throw new Error("Local server close failure...");
463                         }
464                 }
465         }
466
467         public void close() {
468                 doEnd = true;
469
470                 try {
471                         localServerThread.join();
472                 } catch (Exception e) {
473                         e.printStackTrace();
474                         throw new Error("Local Server thread join issue...");
475                 }
476
477                 System.out.println("Done Closing");
478         }
479
480         protected void finalize() throws Throwable {
481                 try {
482                         close();        // close open files
483                 } finally {
484                         super.finalize();
485                 }
486         }
487
488 }