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 = 2000; // 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;
37 private TimingSingleton timer = null;
40 * Empty Constructor needed for child class.
43 timer = TimingSingleton.getInstance();
47 * Constructor for actual use. Takes in the url and password.
49 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
50 timer = TimingSingleton.getInstance();
52 this.baseurl = _baseurl;
53 this.password = _password;
54 this.random = new SecureRandom();
55 this.listeningPort = _listeningPort;
57 if (this.listeningPort > 0) {
58 localServerThread = new Thread(new Runnable() {
60 localServerWorkerFunction();
63 localServerThread.start();
68 * Generates Key from password.
70 private SecretKeySpec initKey() {
72 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
76 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
77 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
78 } catch (Exception e) {
80 throw new Error("Failed generating key.");
85 * Inits all the security stuff
87 public void initSecurity() throws ServerException {
88 // try to get the salt and if one does not exist set one
98 * Inits the HMAC generator.
100 private void initCrypt() {
102 if (password == null) {
107 SecretKeySpec key = initKey();
108 password = null; // drop password
109 mac = Mac.getInstance("HmacSHA256");
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) {
117 throw new Error("Failed To Initialize Ciphers");
122 * Builds the URL for the given request.
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;
128 urlstr += "&max=" + maxentries;
129 return new URL(urlstr);
132 private void setSalt() throws ServerException {
135 // Salt already sent to server so dont set it again
140 byte[] saltTmp = new byte[SALT_SIZE];
141 random.nextBytes(saltTmp);
143 for(int i = 0; i < SALT_SIZE;i++)
145 System.out.println((int)saltTmp[i]&255);
149 URL url = new URL(baseurl + "?req=setsalt");
152 URLConnection con = url.openConnection();
153 HttpURLConnection http = (HttpURLConnection) con;
155 http.setRequestMethod("POST");
156 http.setFixedLengthStreamingMode(saltTmp.length);
157 http.setDoOutput(true);
158 http.setConnectTimeout(TIMEOUT_MILLIS);
163 OutputStream os = http.getOutputStream();
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");
177 } catch (Exception e) {
178 // e.printStackTrace();
180 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
184 private boolean getSalt() throws ServerException {
186 URLConnection con = null;
187 HttpURLConnection http = null;
190 url = new URL(baseurl + "?req=getsalt");
191 } catch (Exception e) {
192 // e.printStackTrace();
193 throw new Error("getSlot failed");
198 con = url.openConnection();
199 http = (HttpURLConnection) con;
200 http.setRequestMethod("POST");
201 http.setConnectTimeout(TIMEOUT_MILLIS);
202 http.setReadTimeout(TIMEOUT_MILLIS);
207 } catch (SocketTimeoutException e) {
209 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
210 } catch (Exception e) {
211 // e.printStackTrace();
212 throw new Error("getSlot failed");
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");
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];
241 } catch (SocketTimeoutException e) {
244 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
245 } catch (Exception e) {
246 // e.printStackTrace();
247 throw new Error("getSlot failed");
252 * API for putting a slot into the queue. Returns null on success.
253 * On failure, the server will send slots with newer sequence
256 public Slot[] putSlot(Slot slot, int max) throws ServerException {
258 URLConnection con = null;
259 HttpURLConnection http = null;
264 throw new ServerException("putSlot failed", ServerException.TypeSalt);
269 long sequencenumber = slot.getSequenceNumber();
270 byte[] bytes = slot.encode(mac);
271 bytes = encryptCipher.doFinal(bytes);
276 url = buildRequest(true, sequencenumber, max);
279 con = url.openConnection();
280 http = (HttpURLConnection) con;
282 http.setRequestMethod("POST");
283 http.setFixedLengthStreamingMode(bytes.length);
284 http.setDoOutput(true);
285 http.setConnectTimeout(TIMEOUT_MILLIS);
286 http.setReadTimeout(TIMEOUT_MILLIS);
289 OutputStream os = http.getOutputStream();
296 // System.out.println("Bytes Sent: " + bytes.length);
297 } catch (ServerException e) {
301 } catch (SocketTimeoutException e) {
304 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
305 } catch (Exception e) {
306 // e.printStackTrace();
307 throw new Error("putSlot failed");
314 InputStream is = http.getInputStream();
315 DataInputStream dis = new DataInputStream(is);
316 byte[] resptype = new byte[7];
317 dis.readFully(resptype);
320 if (Arrays.equals(resptype, "getslot".getBytes()))
322 return processSlots(dis);
324 else if (Arrays.equals(resptype, "putslot".getBytes()))
329 throw new Error("Bad response to putslot");
331 } catch (SocketTimeoutException e) {
333 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
334 } catch (Exception e) {
335 // e.printStackTrace();
336 throw new Error("putSlot failed");
341 * Request the server to send all slots with the given
342 * sequencenumber or newer.
344 public Slot[] getSlots(long sequencenumber) throws ServerException {
346 URLConnection con = null;
347 HttpURLConnection http = null;
352 throw new ServerException("getSlots failed", ServerException.TypeSalt);
357 url = buildRequest(false, sequencenumber, 0);
359 con = url.openConnection();
360 http = (HttpURLConnection) con;
361 http.setRequestMethod("POST");
362 http.setConnectTimeout(TIMEOUT_MILLIS);
363 http.setReadTimeout(TIMEOUT_MILLIS);
370 } catch (SocketTimeoutException e) {
373 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
374 } catch (ServerException e) {
378 } catch (Exception e) {
379 // e.printStackTrace();
380 throw new Error("getSlots failed");
386 InputStream is = http.getInputStream();
387 DataInputStream dis = new DataInputStream(is);
388 byte[] resptype = new byte[7];
390 dis.readFully(resptype);
393 if (!Arrays.equals(resptype, "getslot".getBytes()))
394 throw new Error("Bad Response: " + new String(resptype));
396 return processSlots(dis);
397 } catch (SocketTimeoutException e) {
400 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
401 } catch (Exception e) {
402 // e.printStackTrace();
403 throw new Error("getSlots failed");
408 * Method that actually handles building Slot objects from the
409 * server response. Shared by both putSlot and getSlots.
411 private Slot[] processSlots(DataInputStream dis) throws Exception {
412 int numberofslots = dis.readInt();
413 int[] sizesofslots = new int[numberofslots];
415 Slot[] slots = new Slot[numberofslots];
416 for (int i = 0; i < numberofslots; i++)
417 sizesofslots[i] = dis.readInt();
419 for (int i = 0; i < numberofslots; i++) {
421 byte[] data = new byte[sizesofslots[i]];
424 data = decryptCipher.doFinal(data);
426 slots[i] = Slot.decode(table, data, mac);
432 public byte[] sendLocalData(byte[] sendData, String host, int port) {
439 System.out.println("Passing Locally");
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);
447 // Encrypt the data for sending
448 // byte[] encryptedData = encryptCipher.doFinal(totalData);
449 byte[] encryptedData = encryptCipher.doFinal(totalData);
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());
459 // Send data to output (length of data, the data)
460 output.writeInt(encryptedData.length);
461 output.write(encryptedData, 0, encryptedData.length);
464 int lengthOfReturnData = input.readInt();
465 byte[] returnData = new byte[lengthOfReturnData];
466 input.readFully(returnData);
470 returnData = decryptCipher.doFinal(returnData);
472 // We are done with this socket
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);
480 if (!Arrays.equals(recmac, realmac))
481 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
483 byte[] returnData2 = new byte[lengthOfReturnData - recmac.length];
484 System.arraycopy(returnData, 0, returnData2, 0, returnData2.length);
487 } catch (SocketTimeoutException e) {
489 } catch (BadPaddingException e) {
491 } catch (IllegalBlockSizeException e) {
493 } catch (UnknownHostException e) {
495 } catch (IOException e) {
502 private void localServerWorkerFunction() {
504 ServerSocket inputSocket = null;
507 // Local server socket
508 inputSocket = new ServerSocket(listeningPort);
509 inputSocket.setReuseAddress(true);
510 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
511 } catch (Exception e) {
513 throw new Error("Local server setup failure...");
519 // Accept incoming socket
520 Socket socket = inputSocket.accept();
522 DataInputStream input = new DataInputStream(socket.getInputStream());
523 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
525 // Get the encrypted data from the server
526 int dataSize = input.readInt();
527 byte[] readData = new byte[dataSize];
528 input.readFully(readData);
533 readData = decryptCipher.doFinal(readData);
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);
540 if (!Arrays.equals(recmac, genmac))
541 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
543 byte[] returnData = new byte[readData.length - recmac.length];
544 System.arraycopy(readData, 0, returnData, 0, returnData.length);
547 // byte[] sendData = table.acceptDataFromLocal(readData);
548 byte[] sendData = table.acceptDataFromLocal(returnData);
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);
556 // Encrypt the data for sending
557 byte[] encryptedData = encryptCipher.doFinal(totalData);
561 // Send data to output (length of data, the data)
562 output.writeInt(encryptedData.length);
563 output.write(encryptedData, 0, encryptedData.length);
568 } catch (SocketTimeoutException e) {
570 } catch (BadPaddingException e) {
572 } catch (IllegalBlockSizeException e) {
574 } catch (UnknownHostException e) {
576 } catch (IOException e) {
581 if (inputSocket != null) {
584 } catch (Exception e) {
586 throw new Error("Local server close failure...");
591 public void close() {
594 if (localServerThread != null) {
596 localServerThread.join();
597 } catch (Exception e) {
599 throw new Error("Local Server thread join issue...");
603 // System.out.println("Done Closing Cloud Comm");
606 protected void finalize() throws Throwable {
608 close(); // close open files