5 import java.util.Arrays;
7 import javax.crypto.spec.*;
8 import java.security.SecureRandom;
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>
19 private static final int SALT_SIZE = 8;
20 private static final int TIMEOUT_MILLIS = 25; // 100
22 /** Sets the size for the HMAC. */
23 static final int HMAC_SIZE = 32;
25 private String baseurl;
26 private Cipher encryptCipher;
27 private Cipher decryptCipher;
29 private String password;
30 private SecureRandom random;
33 private int listeningPort = -1;
34 private Thread localServerThread = null;
35 private boolean doEnd = false;
38 * Empty Constructor needed for child class.
44 * Constructor for actual use. Takes in the url and password.
46 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
48 this.baseurl = _baseurl;
49 this.password = _password;
50 this.random = new SecureRandom();
51 this.listeningPort = _listeningPort;
53 if (this.listeningPort > 0) {
54 localServerThread = new Thread(new Runnable() {
56 localServerWorkerFunction();
59 localServerThread.start();
64 * Generates Key from password.
66 private SecretKeySpec initKey() {
68 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
72 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
73 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
74 } catch (Exception e) {
76 throw new Error("Failed generating key.");
81 * Inits all the security stuff
83 public void initSecurity() throws ServerException {
84 // try to get the salt and if one does not exist set one
94 * Inits the HMAC generator.
96 private void initCrypt() {
98 if (password == null) {
103 SecretKeySpec key = initKey();
104 password = null; // drop password
105 mac = Mac.getInstance("HmacSHA256");
107 encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
108 encryptCipher.init(Cipher.ENCRYPT_MODE, key);
109 decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
110 decryptCipher.init(Cipher.DECRYPT_MODE, key);
111 } catch (Exception e) {
113 throw new Error("Failed To Initialize Ciphers");
118 * Builds the URL for the given request.
120 private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
121 String reqstring = isput ? "req=putslot" : "req=getslot";
122 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
124 urlstr += "&max=" + maxentries;
125 return new URL(urlstr);
128 private void setSalt() throws ServerException {
131 // Salt already sent to server so dont set it again
136 byte[] saltTmp = new byte[SALT_SIZE];
137 random.nextBytes(saltTmp);
139 URL url = new URL(baseurl + "?req=setsalt");
140 URLConnection con = url.openConnection();
141 HttpURLConnection http = (HttpURLConnection) con;
143 http.setRequestMethod("POST");
144 http.setFixedLengthStreamingMode(saltTmp.length);
145 http.setDoOutput(true);
146 http.setConnectTimeout(TIMEOUT_MILLIS);
149 OutputStream os = http.getOutputStream();
153 int responsecode = http.getResponseCode();
154 if (responsecode != HttpURLConnection.HTTP_OK) {
155 // TODO: Remove this print
156 System.out.println(responsecode);
157 throw new Error("Invalid response");
161 } catch (Exception e) {
162 // e.printStackTrace();
163 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
167 private boolean getSalt() throws ServerException {
169 URLConnection con = null;
170 HttpURLConnection http = null;
173 url = new URL(baseurl + "?req=getsalt");
174 } catch (Exception e) {
175 // e.printStackTrace();
176 throw new Error("getSlot failed");
180 con = url.openConnection();
181 http = (HttpURLConnection) con;
182 http.setRequestMethod("POST");
183 http.setConnectTimeout(TIMEOUT_MILLIS);
184 http.setReadTimeout(TIMEOUT_MILLIS);
186 } catch (SocketTimeoutException e) {
187 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
188 } catch (Exception e) {
189 // e.printStackTrace();
190 throw new Error("getSlot failed");
195 int responsecode = http.getResponseCode();
196 if (responsecode != HttpURLConnection.HTTP_OK) {
197 // TODO: Remove this print
198 // System.out.println(responsecode);
199 throw new Error("Invalid response");
202 InputStream is = http.getInputStream();
203 if (is.available() > 0) {
204 DataInputStream dis = new DataInputStream(is);
205 int salt_length = dis.readInt();
206 byte [] tmp = new byte[salt_length];
213 } catch (SocketTimeoutException e) {
214 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
215 } catch (Exception e) {
216 // e.printStackTrace();
217 throw new Error("getSlot failed");
222 * API for putting a slot into the queue. Returns null on success.
223 * On failure, the server will send slots with newer sequence
226 public Slot[] putSlot(Slot slot, int max) throws ServerException {
228 URLConnection con = null;
229 HttpURLConnection http = null;
234 throw new ServerException("putSlot failed", ServerException.TypeSalt);
239 long sequencenumber = slot.getSequenceNumber();
240 byte[] bytes = slot.encode(mac);
241 bytes = encryptCipher.doFinal(bytes);
243 url = buildRequest(true, sequencenumber, max);
244 con = url.openConnection();
245 http = (HttpURLConnection) con;
247 http.setRequestMethod("POST");
248 http.setFixedLengthStreamingMode(bytes.length);
249 http.setDoOutput(true);
250 http.setConnectTimeout(TIMEOUT_MILLIS);
251 http.setReadTimeout(TIMEOUT_MILLIS);
254 OutputStream os = http.getOutputStream();
258 // System.out.println("Bytes Sent: " + bytes.length);
259 } catch (ServerException e) {
261 } catch (SocketTimeoutException e) {
262 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
263 } catch (Exception e) {
264 // e.printStackTrace();
265 throw new Error("putSlot failed");
271 InputStream is = http.getInputStream();
272 DataInputStream dis = new DataInputStream(is);
273 byte[] resptype = new byte[7];
274 dis.readFully(resptype);
276 if (Arrays.equals(resptype, "getslot".getBytes()))
277 return processSlots(dis);
278 else if (Arrays.equals(resptype, "putslot".getBytes()))
281 throw new Error("Bad response to putslot");
283 } catch (SocketTimeoutException e) {
284 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
285 } catch (Exception e) {
286 // e.printStackTrace();
287 throw new Error("putSlot failed");
292 * Request the server to send all slots with the given
293 * sequencenumber or newer.
295 public Slot[] getSlots(long sequencenumber) throws ServerException {
297 URLConnection con = null;
298 HttpURLConnection http = null;
303 throw new ServerException("getSlots failed", ServerException.TypeSalt);
308 url = buildRequest(false, sequencenumber, 0);
309 con = url.openConnection();
310 http = (HttpURLConnection) con;
311 http.setRequestMethod("POST");
312 http.setConnectTimeout(TIMEOUT_MILLIS);
313 http.setReadTimeout(TIMEOUT_MILLIS);
315 } catch (SocketTimeoutException e) {
316 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
317 } catch (ServerException e) {
319 } catch (Exception e) {
320 // e.printStackTrace();
321 throw new Error("getSlots failed");
325 InputStream is = http.getInputStream();
326 DataInputStream dis = new DataInputStream(is);
327 byte[] resptype = new byte[7];
328 dis.readFully(resptype);
329 if (!Arrays.equals(resptype, "getslot".getBytes()))
330 throw new Error("Bad Response: " + new String(resptype));
332 return processSlots(dis);
333 } catch (SocketTimeoutException e) {
334 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
335 } catch (Exception e) {
336 // e.printStackTrace();
337 throw new Error("getSlots failed");
342 * Method that actually handles building Slot objects from the
343 * server response. Shared by both putSlot and getSlots.
345 private Slot[] processSlots(DataInputStream dis) throws Exception {
346 int numberofslots = dis.readInt();
347 int[] sizesofslots = new int[numberofslots];
349 Slot[] slots = new Slot[numberofslots];
350 for (int i = 0; i < numberofslots; i++)
351 sizesofslots[i] = dis.readInt();
353 for (int i = 0; i < numberofslots; i++) {
355 byte[] data = new byte[sizesofslots[i]];
358 data = decryptCipher.doFinal(data);
360 slots[i] = Slot.decode(table, data, mac);
366 public byte[] sendLocalData(byte[] sendData, String host, int port) {
373 System.out.println("Passing Locally");
375 mac.update(sendData);
376 byte[] genmac = mac.doFinal();
377 byte[] totalData = new byte[sendData.length + genmac.length];
378 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
379 System.arraycopy(genmac, 0, totalData, sendData.length, genmac.length);
381 // Encrypt the data for sending
382 // byte[] encryptedData = encryptCipher.doFinal(totalData);
383 byte[] encryptedData = encryptCipher.doFinal(totalData);
385 // Open a TCP socket connection to a local device
386 Socket socket = new Socket(host, port);
387 socket.setReuseAddress(true);
388 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
389 DataInputStream input = new DataInputStream(socket.getInputStream());
391 // Send data to output (length of data, the data)
392 output.writeInt(encryptedData.length);
393 output.write(encryptedData, 0, encryptedData.length);
396 int lengthOfReturnData = input.readInt();
397 byte[] returnData = new byte[lengthOfReturnData];
398 input.readFully(returnData);
399 returnData = decryptCipher.doFinal(returnData);
401 // We are done with this socket
404 mac.update(returnData, 0, returnData.length - HMAC_SIZE);
405 byte[] realmac = mac.doFinal();
406 byte[] recmac = new byte[HMAC_SIZE];
407 System.arraycopy(returnData, returnData.length - realmac.length, recmac, 0, realmac.length);
409 if (!Arrays.equals(recmac, realmac))
410 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
412 byte[] returnData2 = new byte[lengthOfReturnData - recmac.length];
413 System.arraycopy(returnData, 0, returnData2, 0, returnData2.length);
416 } catch (SocketTimeoutException e) {
418 } catch (BadPaddingException e) {
420 } catch (IllegalBlockSizeException e) {
422 } catch (UnknownHostException e) {
424 } catch (IOException e) {
431 private void localServerWorkerFunction() {
433 ServerSocket inputSocket = null;
436 // Local server socket
437 inputSocket = new ServerSocket(listeningPort);
438 inputSocket.setReuseAddress(true);
439 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
440 } catch (Exception e) {
442 throw new Error("Local server setup failure...");
448 // Accept incoming socket
449 Socket socket = inputSocket.accept();
451 DataInputStream input = new DataInputStream(socket.getInputStream());
452 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
454 // Get the encrypted data from the server
455 int dataSize = input.readInt();
456 byte[] readData = new byte[dataSize];
457 input.readFully(readData);
460 readData = decryptCipher.doFinal(readData);
462 mac.update(readData, 0, readData.length - HMAC_SIZE);
463 byte[] genmac = mac.doFinal();
464 byte[] recmac = new byte[HMAC_SIZE];
465 System.arraycopy(readData, readData.length - recmac.length, recmac, 0, recmac.length);
467 if (!Arrays.equals(recmac, genmac))
468 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
470 byte[] returnData = new byte[readData.length - recmac.length];
471 System.arraycopy(readData, 0, returnData, 0, returnData.length);
474 // byte[] sendData = table.acceptDataFromLocal(readData);
475 byte[] sendData = table.acceptDataFromLocal(returnData);
477 mac.update(sendData);
478 byte[] realmac = mac.doFinal();
479 byte[] totalData = new byte[sendData.length + realmac.length];
480 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
481 System.arraycopy(realmac, 0, totalData, sendData.length, realmac.length);
483 // Encrypt the data for sending
484 byte[] encryptedData = encryptCipher.doFinal(totalData);
486 // Send data to output (length of data, the data)
487 output.writeInt(encryptedData.length);
488 output.write(encryptedData, 0, encryptedData.length);
493 } catch (SocketTimeoutException e) {
495 } catch (BadPaddingException e) {
497 } catch (IllegalBlockSizeException e) {
499 } catch (UnknownHostException e) {
501 } catch (IOException e) {
506 if (inputSocket != null) {
509 } catch (Exception e) {
511 throw new Error("Local server close failure...");
516 public void close() {
519 if (localServerThread != null) {
521 localServerThread.join();
522 } catch (Exception e) {
524 throw new Error("Local Server thread join issue...");
528 // System.out.println("Done Closing Cloud Comm");
531 protected void finalize() throws Throwable {
533 close(); // close open files