5 import java.util.Arrays;
7 import javax.crypto.spec.*;
8 import java.security.SecureRandom;
9 import java.nio.ByteBuffer;
15 * This class provides a communication API to the webserver. It also
16 * validates the HMACs on the slots and handles encryption.
17 * @author Brian Demsky <bdemsky@uci.edu>
23 private static final int SALT_SIZE = 8;
24 private static final int TIMEOUT_MILLIS = 5000; // 100
25 public static final int IV_SIZE = 16;
27 /** Sets the size for the HMAC. */
28 static final int HMAC_SIZE = 32;
30 private String baseurl;
31 private SecretKeySpec key;
33 private String password;
34 private SecureRandom random;
37 private int listeningPort = -1;
38 private Thread localServerThread = null;
39 private boolean doEnd = false;
41 private TimingSingleton timer = null;
44 * Empty Constructor needed for child class.
47 timer = TimingSingleton.getInstance();
51 * Constructor for actual use. Takes in the url and password.
53 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
54 timer = TimingSingleton.getInstance();
56 this.baseurl = _baseurl;
57 this.password = _password;
58 this.random = new SecureRandom();
59 this.listeningPort = _listeningPort;
61 if (this.listeningPort > 0) {
62 localServerThread = new Thread(new Runnable() {
64 localServerWorkerFunction();
67 localServerThread.start();
72 * Generates Key from password.
74 private SecretKeySpec initKey() {
76 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
80 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
81 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
82 } catch (Exception e) {
84 throw new Error("Failed generating key.");
89 * Inits all the security stuff
91 public void initSecurity() throws ServerException {
92 // try to get the salt and if one does not exist set one
102 * Inits the HMAC generator.
104 private void initCrypt() {
106 if (password == null) {
112 password = null; // drop password
113 mac = Mac.getInstance("HmacSHA256");
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++) {
144 System.out.println((int)saltTmp[i] & 255);
148 URL url = new URL(baseurl + "?req=setsalt");
151 URLConnection con = url.openConnection();
152 HttpURLConnection http = (HttpURLConnection) con;
154 http.setRequestMethod("POST");
155 http.setFixedLengthStreamingMode(saltTmp.length);
156 http.setDoOutput(true);
157 http.setConnectTimeout(TIMEOUT_MILLIS);
162 OutputStream os = http.getOutputStream();
166 int responsecode = http.getResponseCode();
167 if (responsecode != HttpURLConnection.HTTP_OK) {
168 // TODO: Remove this print
169 System.out.println(responsecode);
170 throw new Error("Invalid response");
176 } catch (Exception e) {
177 // e.printStackTrace();
179 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
183 private boolean getSalt() throws ServerException {
185 URLConnection con = null;
186 HttpURLConnection http = null;
189 url = new URL(baseurl + "?req=getsalt");
190 } catch (Exception e) {
191 // e.printStackTrace();
192 throw new Error("getSlot failed");
197 con = url.openConnection();
198 http = (HttpURLConnection) con;
199 http.setRequestMethod("POST");
200 http.setConnectTimeout(TIMEOUT_MILLIS);
201 http.setReadTimeout(TIMEOUT_MILLIS);
206 } catch (SocketTimeoutException e) {
208 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
209 } catch (Exception e) {
210 // e.printStackTrace();
211 throw new Error("getSlot failed");
218 int responsecode = http.getResponseCode();
219 if (responsecode != HttpURLConnection.HTTP_OK) {
220 // TODO: Remove this print
221 // System.out.println(responsecode);
222 throw new Error("Invalid response");
225 InputStream is = http.getInputStream();
226 if (is.available() > 0) {
227 DataInputStream dis = new DataInputStream(is);
228 int salt_length = dis.readInt();
229 byte [] tmp = new byte[salt_length];
240 } catch (SocketTimeoutException e) {
243 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
244 } catch (Exception e) {
245 // e.printStackTrace();
246 throw new Error("getSlot failed");
250 private byte[] createIV(long machineId, long localSequenceNumber) {
251 ByteBuffer buffer = ByteBuffer.allocate(IV_SIZE);
252 buffer.putLong(machineId);
253 long localSequenceNumberShifted = localSequenceNumber << 16;
254 buffer.putLong(localSequenceNumberShifted);
255 return buffer.array();
259 private byte[] encryptSlotAndPrependIV(byte[] rawData, byte[] ivBytes) {
261 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
262 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
263 cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
265 byte[] encryptedBytes = cipher.doFinal(rawData);
267 byte[] bytes = new byte[encryptedBytes.length + IV_SIZE];
268 System.arraycopy(ivBytes, 0, bytes, 0, ivBytes.length);
269 System.arraycopy(encryptedBytes, 0, bytes, IV_SIZE, encryptedBytes.length);
273 } catch (Exception e) {
275 throw new Error("Failed To Encrypt");
280 private byte[] stripIVAndDecryptSlot(byte[] rawData) {
282 byte[] ivBytes = new byte[IV_SIZE];
283 byte[] encryptedBytes = new byte[rawData.length - IV_SIZE];
284 System.arraycopy(rawData, 0, ivBytes, 0, IV_SIZE);
285 System.arraycopy(rawData, IV_SIZE, encryptedBytes, 0 , encryptedBytes.length);
287 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
289 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
290 cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
291 return cipher.doFinal(encryptedBytes);
293 } catch (Exception e) {
295 throw new Error("Failed To Decrypt");
301 * API for putting a slot into the queue. Returns null on success.
302 * On failure, the server will send slots with newer sequence
305 public Slot[] putSlot(Slot slot, int max) throws ServerException {
307 URLConnection con = null;
308 HttpURLConnection http = null;
313 throw new ServerException("putSlot failed", ServerException.TypeSalt);
318 long sequencenumber = slot.getSequenceNumber();
319 byte[] slotBytes = slot.encode(mac);
320 // slotBytes = encryptCipher.doFinal(slotBytes);
322 // byte[] iVBytes = slot.getSlotCryptIV();
324 // byte[] bytes = new byte[slotBytes.length + IV_SIZE];
325 // System.arraycopy(iVBytes, 0, bytes, 0, iVBytes.length);
326 // System.arraycopy(slotBytes, 0, bytes, IV_SIZE, slotBytes.length);
329 byte[] bytes = encryptSlotAndPrependIV(slotBytes, slot.getSlotCryptIV());
331 url = buildRequest(true, sequencenumber, max);
334 con = url.openConnection();
335 http = (HttpURLConnection) con;
337 http.setRequestMethod("POST");
338 http.setFixedLengthStreamingMode(bytes.length);
339 http.setDoOutput(true);
340 http.setConnectTimeout(TIMEOUT_MILLIS);
341 http.setReadTimeout(TIMEOUT_MILLIS);
344 OutputStream os = http.getOutputStream();
351 // System.out.println("Bytes Sent: " + bytes.length);
352 } catch (ServerException e) {
356 } catch (SocketTimeoutException e) {
359 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
360 } catch (Exception e) {
361 // e.printStackTrace();
362 throw new Error("putSlot failed");
369 InputStream is = http.getInputStream();
370 DataInputStream dis = new DataInputStream(is);
371 byte[] resptype = new byte[7];
372 dis.readFully(resptype);
375 if (Arrays.equals(resptype, "getslot".getBytes())) {
376 return processSlots(dis);
377 } else if (Arrays.equals(resptype, "putslot".getBytes())) {
380 throw new Error("Bad response to putslot");
382 } catch (SocketTimeoutException e) {
384 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
385 } catch (Exception e) {
386 // e.printStackTrace();
387 throw new Error("putSlot failed");
392 * Request the server to send all slots with the given
393 * sequencenumber or newer.
395 public Slot[] getSlots(long sequencenumber) throws ServerException {
397 URLConnection con = null;
398 HttpURLConnection http = null;
403 throw new ServerException("getSlots failed", ServerException.TypeSalt);
408 url = buildRequest(false, sequencenumber, 0);
410 con = url.openConnection();
411 http = (HttpURLConnection) con;
412 http.setRequestMethod("POST");
413 http.setConnectTimeout(TIMEOUT_MILLIS);
414 http.setReadTimeout(TIMEOUT_MILLIS);
421 } catch (SocketTimeoutException e) {
424 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
425 } catch (ServerException e) {
429 } catch (Exception e) {
430 // e.printStackTrace();
431 throw new Error("getSlots failed");
437 InputStream is = http.getInputStream();
438 DataInputStream dis = new DataInputStream(is);
439 byte[] resptype = new byte[7];
441 dis.readFully(resptype);
444 if (!Arrays.equals(resptype, "getslot".getBytes()))
445 throw new Error("Bad Response: " + new String(resptype));
447 return processSlots(dis);
448 } catch (SocketTimeoutException e) {
451 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
452 } catch (Exception e) {
453 // e.printStackTrace();
454 throw new Error("getSlots failed");
459 * Method that actually handles building Slot objects from the
460 * server response. Shared by both putSlot and getSlots.
462 private Slot[] processSlots(DataInputStream dis) throws Exception {
463 int numberofslots = dis.readInt();
464 int[] sizesofslots = new int[numberofslots];
466 Slot[] slots = new Slot[numberofslots];
467 for (int i = 0; i < numberofslots; i++)
468 sizesofslots[i] = dis.readInt();
470 for (int i = 0; i < numberofslots; i++) {
472 byte[] rawData = new byte[sizesofslots[i]];
473 dis.readFully(rawData);
476 // byte[] data = new byte[rawData.length - IV_SIZE];
477 // System.arraycopy(rawData, IV_SIZE, data, 0, data.length);
480 byte[] data = stripIVAndDecryptSlot(rawData);
482 // data = decryptCipher.doFinal(data);
484 slots[i] = Slot.decode(table, data, mac);
490 public byte[] sendLocalData(byte[] sendData, long localSequenceNumber, String host, int port) {
496 System.out.println("Passing Locally");
498 mac.update(sendData);
499 byte[] genmac = mac.doFinal();
500 byte[] totalData = new byte[sendData.length + genmac.length];
501 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
502 System.arraycopy(genmac, 0, totalData, sendData.length, genmac.length);
504 // Encrypt the data for sending
505 // byte[] encryptedData = encryptCipher.doFinal(totalData);
506 // byte[] encryptedData = encryptCipher.doFinal(totalData);
507 byte[] iv = createIV(table.getMachineId(), table.getLocalSequenceNumber());
508 byte[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
510 // Open a TCP socket connection to a local device
511 Socket socket = new Socket(host, port);
512 socket.setReuseAddress(true);
513 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
514 DataInputStream input = new DataInputStream(socket.getInputStream());
518 // Send data to output (length of data, the data)
519 output.writeInt(encryptedData.length);
520 output.write(encryptedData, 0, encryptedData.length);
523 int lengthOfReturnData = input.readInt();
524 byte[] returnData = new byte[lengthOfReturnData];
525 input.readFully(returnData);
529 // returnData = decryptCipher.doFinal(returnData);
530 returnData = stripIVAndDecryptSlot(returnData);
531 // returnData = decryptCipher.doFinal(returnData);
533 // We are done with this socket
536 mac.update(returnData, 0, returnData.length - HMAC_SIZE);
537 byte[] realmac = mac.doFinal();
538 byte[] recmac = new byte[HMAC_SIZE];
539 System.arraycopy(returnData, returnData.length - realmac.length, recmac, 0, realmac.length);
541 if (!Arrays.equals(recmac, realmac))
542 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
544 byte[] returnData2 = new byte[lengthOfReturnData - recmac.length];
545 System.arraycopy(returnData, 0, returnData2, 0, returnData2.length);
548 } catch (Exception e) {
550 // throw new Error("Local comms failure...");
557 private void localServerWorkerFunction() {
559 ServerSocket inputSocket = null;
562 // Local server socket
563 inputSocket = new ServerSocket(listeningPort);
564 inputSocket.setReuseAddress(true);
565 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
566 } catch (Exception e) {
568 throw new Error("Local server setup failure...");
574 // Accept incoming socket
575 Socket socket = inputSocket.accept();
577 DataInputStream input = new DataInputStream(socket.getInputStream());
578 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
580 // Get the encrypted data from the server
581 int dataSize = input.readInt();
582 byte[] readData = new byte[dataSize];
583 input.readFully(readData);
588 // readData = decryptCipher.doFinal(readData);
589 readData = stripIVAndDecryptSlot(readData);
591 mac.update(readData, 0, readData.length - HMAC_SIZE);
592 byte[] genmac = mac.doFinal();
593 byte[] recmac = new byte[HMAC_SIZE];
594 System.arraycopy(readData, readData.length - recmac.length, recmac, 0, recmac.length);
596 if (!Arrays.equals(recmac, genmac))
597 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
599 byte[] returnData = new byte[readData.length - recmac.length];
600 System.arraycopy(readData, 0, returnData, 0, returnData.length);
603 // byte[] sendData = table.acceptDataFromLocal(readData);
604 byte[] sendData = table.acceptDataFromLocal(returnData);
607 mac.update(sendData);
608 byte[] realmac = mac.doFinal();
609 byte[] totalData = new byte[sendData.length + realmac.length];
610 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
611 System.arraycopy(realmac, 0, totalData, sendData.length, realmac.length);
613 // Encrypt the data for sending
614 // byte[] encryptedData = encryptCipher.doFinal(totalData);
615 byte[] iv = createIV(table.getMachineId(), table.getLocalSequenceNumber());
616 byte[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
620 // Send data to output (length of data, the data)
621 output.writeInt(encryptedData.length);
622 output.write(encryptedData, 0, encryptedData.length);
627 } catch (Exception e) {
632 if (inputSocket != null) {
635 } catch (Exception e) {
637 throw new Error("Local server close failure...");
642 public void close() {
645 if (localServerThread != null) {
647 localServerThread.join();
648 } catch (Exception e) {
650 throw new Error("Local Server thread join issue...");
654 // System.out.println("Done Closing Cloud Comm");
657 protected void finalize() throws Throwable {
659 close(); // close open files