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