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 = 2000; // 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;
36 private byte iv[] = null;
38 private int listeningPort = -1;
39 private Thread localServerThread = null;
40 private boolean doEnd = false;
42 private TimingSingleton timer = null;
45 * Empty Constructor needed for child class.
48 timer = TimingSingleton.getInstance();
52 * Constructor for actual use. Takes in the url and password.
54 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
55 timer = TimingSingleton.getInstance();
57 this.baseurl = _baseurl;
58 this.password = _password;
59 this.random = new SecureRandom();
60 this.listeningPort = _listeningPort;
62 if (this.listeningPort > 0) {
63 localServerThread = new Thread(new Runnable() {
65 localServerWorkerFunction();
68 localServerThread.start();
73 * Generates Key from password.
75 private SecretKeySpec initKey() {
77 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
81 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
82 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
83 } catch (Exception e) {
85 throw new Error("Failed generating key.");
90 * Inits all the security stuff
92 public void initSecurity() throws ServerException {
93 // try to get the salt and if one does not exist set one
103 * Inits the HMAC generator.
105 private void initCrypt() {
107 if (password == null) {
113 password = null; // drop password
114 mac = Mac.getInstance("HmacSHA256");
116 } catch (Exception e) {
118 throw new Error("Failed To Initialize Ciphers");
123 * Builds the URL for the given request.
125 private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
126 String reqstring = isput ? "req=putslot" : "req=getslot";
127 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
129 urlstr += "&max=" + maxentries;
130 return new URL(urlstr);
133 private void setSalt() throws ServerException {
136 // Salt already sent to server so dont set it again
141 byte[] saltTmp = new byte[SALT_SIZE];
142 random.nextBytes(saltTmp);
144 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 * Generate random numbers for IV from machine ID and local sequence number
254 private byte[] createIV(long machineId, long localSequenceNumber) {
255 ByteBuffer buffer = ByteBuffer.allocate(IV_SIZE);
256 buffer.putLong(machineId);
257 long localSequenceNumberShifted = localSequenceNumber << 16;
258 buffer.putLong(localSequenceNumberShifted);
259 return buffer.array();
264 * Generate random numbers for IV from random bits
266 private byte[] createIV() {
268 iv = new byte[IV_SIZE];
269 random.nextBytes(iv);
274 private byte[] encryptSlotAndPrependIV(byte[] rawData, byte[] ivBytes) {
276 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
277 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
278 cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
280 byte[] encryptedBytes = cipher.doFinal(rawData);
282 byte[] bytes = new byte[encryptedBytes.length + IV_SIZE];
283 System.arraycopy(ivBytes, 0, bytes, 0, ivBytes.length);
284 System.arraycopy(encryptedBytes, 0, bytes, IV_SIZE, encryptedBytes.length);
288 } catch (Exception e) {
290 throw new Error("Failed To Encrypt");
295 private byte[] stripIVAndDecryptSlot(byte[] rawData) {
297 byte[] ivBytes = new byte[IV_SIZE];
298 byte[] encryptedBytes = new byte[rawData.length - IV_SIZE];
299 System.arraycopy(rawData, 0, ivBytes, 0, IV_SIZE);
300 System.arraycopy(rawData, IV_SIZE, encryptedBytes, 0 , encryptedBytes.length);
302 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
304 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
305 cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
306 return cipher.doFinal(encryptedBytes);
308 } catch (Exception e) {
310 throw new Error("Failed To Decrypt");
316 * API for putting a slot into the queue. Returns null on success.
317 * On failure, the server will send slots with newer sequence
320 public Slot[] putSlot(Slot slot, int max) throws ServerException {
322 URLConnection con = null;
323 HttpURLConnection http = null;
328 throw new ServerException("putSlot failed", ServerException.TypeSalt);
333 long sequencenumber = slot.getSequenceNumber();
334 byte[] slotBytes = slot.encode(mac);
335 // slotBytes = encryptCipher.doFinal(slotBytes);
337 // byte[] iVBytes = slot.getSlotCryptIV();
339 // byte[] bytes = new byte[slotBytes.length + IV_SIZE];
340 // System.arraycopy(iVBytes, 0, bytes, 0, iVBytes.length);
341 // System.arraycopy(slotBytes, 0, bytes, IV_SIZE, slotBytes.length);
342 byte[] bytes = encryptSlotAndPrependIV(slotBytes, createIV());
344 url = buildRequest(true, sequencenumber, max);
347 con = url.openConnection();
348 http = (HttpURLConnection) con;
350 http.setRequestMethod("POST");
351 http.setFixedLengthStreamingMode(bytes.length);
352 http.setDoOutput(true);
353 http.setConnectTimeout(TIMEOUT_MILLIS);
354 http.setReadTimeout(TIMEOUT_MILLIS);
357 OutputStream os = http.getOutputStream();
364 // System.out.println("Bytes Sent: " + bytes.length);
365 } catch (ServerException e) {
369 } catch (SocketTimeoutException e) {
372 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
373 } catch (Exception e) {
374 // e.printStackTrace();
375 throw new Error("putSlot failed");
382 InputStream is = http.getInputStream();
383 DataInputStream dis = new DataInputStream(is);
384 byte[] resptype = new byte[7];
385 dis.readFully(resptype);
388 if (Arrays.equals(resptype, "getslot".getBytes())) {
389 return processSlots(dis);
390 } else if (Arrays.equals(resptype, "putslot".getBytes())) {
393 throw new Error("Bad response to putslot");
395 } catch (SocketTimeoutException e) {
397 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
398 } catch (Exception e) {
399 // e.printStackTrace();
400 throw new Error("putSlot failed");
405 * Request the server to send all slots with the given
406 * sequencenumber or newer.
408 public Slot[] getSlots(long sequencenumber) throws ServerException {
410 URLConnection con = null;
411 HttpURLConnection http = null;
416 throw new ServerException("getSlots failed", ServerException.TypeSalt);
420 url = buildRequest(false, sequencenumber, 0);
422 con = url.openConnection();
423 http = (HttpURLConnection) con;
424 http.setRequestMethod("POST");
425 http.setConnectTimeout(TIMEOUT_MILLIS);
426 http.setReadTimeout(TIMEOUT_MILLIS);
432 } catch (SocketTimeoutException e) {
435 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
436 } catch (ServerException e) {
439 } catch (Exception e) {
440 // e.printStackTrace();
441 throw new Error("getSlots failed");
446 InputStream is = http.getInputStream();
447 DataInputStream dis = new DataInputStream(is);
448 byte[] resptype = new byte[7];
450 dis.readFully(resptype);
453 if (!Arrays.equals(resptype, "getslot".getBytes()))
454 throw new Error("Bad Response: " + new String(resptype));
456 return processSlots(dis);
457 } catch (SocketTimeoutException e) {
460 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
461 } catch (Exception e) {
462 // e.printStackTrace();
463 throw new Error("getSlots failed");
468 * Method that actually handles building Slot objects from the
469 * server response. Shared by both putSlot and getSlots.
471 private Slot[] processSlots(DataInputStream dis) throws Exception {
472 int numberofslots = dis.readInt();
473 int[] sizesofslots = new int[numberofslots];
475 Slot[] slots = new Slot[numberofslots];
476 for (int i = 0; i < numberofslots; i++)
477 sizesofslots[i] = dis.readInt();
479 for (int i = 0; i < numberofslots; i++) {
481 byte[] rawData = new byte[sizesofslots[i]];
482 dis.readFully(rawData);
485 // byte[] data = new byte[rawData.length - IV_SIZE];
486 // System.arraycopy(rawData, IV_SIZE, data, 0, data.length);
489 byte[] data = stripIVAndDecryptSlot(rawData);
491 // data = decryptCipher.doFinal(data);
493 slots[i] = Slot.decode(table, data, mac);
499 public byte[] sendLocalData(byte[] sendData, long localSequenceNumber, String host, int port) {
505 System.out.println("Passing Locally");
507 mac.update(sendData);
508 byte[] genmac = mac.doFinal();
509 byte[] totalData = new byte[sendData.length + genmac.length];
510 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
511 System.arraycopy(genmac, 0, totalData, sendData.length, genmac.length);
513 // Encrypt the data for sending
514 // byte[] encryptedData = encryptCipher.doFinal(totalData);
515 // byte[] encryptedData = encryptCipher.doFinal(totalData);
516 byte[] iv = createIV();
517 byte[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
519 // Open a TCP socket connection to a local device
520 Socket socket = new Socket(host, port);
521 socket.setReuseAddress(true);
522 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
523 DataInputStream input = new DataInputStream(socket.getInputStream());
527 // Send data to output (length of data, the data)
528 output.writeInt(encryptedData.length);
529 output.write(encryptedData, 0, encryptedData.length);
532 int lengthOfReturnData = input.readInt();
533 byte[] returnData = new byte[lengthOfReturnData];
534 input.readFully(returnData);
538 // returnData = decryptCipher.doFinal(returnData);
539 returnData = stripIVAndDecryptSlot(returnData);
540 // returnData = decryptCipher.doFinal(returnData);
542 // We are done with this socket
545 mac.update(returnData, 0, returnData.length - HMAC_SIZE);
546 byte[] realmac = mac.doFinal();
547 byte[] recmac = new byte[HMAC_SIZE];
548 System.arraycopy(returnData, returnData.length - realmac.length, recmac, 0, realmac.length);
550 if (!Arrays.equals(recmac, realmac))
551 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
553 byte[] returnData2 = new byte[lengthOfReturnData - recmac.length];
554 System.arraycopy(returnData, 0, returnData2, 0, returnData2.length);
557 } catch (Exception e) {
559 // throw new Error("Local comms failure...");
566 private void localServerWorkerFunction() {
568 ServerSocket inputSocket = null;
571 // Local server socket
572 inputSocket = new ServerSocket(listeningPort);
573 inputSocket.setReuseAddress(true);
574 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
575 } catch (Exception e) {
577 throw new Error("Local server setup failure...");
583 // Accept incoming socket
584 Socket socket = inputSocket.accept();
586 DataInputStream input = new DataInputStream(socket.getInputStream());
587 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
589 // Get the encrypted data from the server
590 int dataSize = input.readInt();
591 byte[] readData = new byte[dataSize];
592 input.readFully(readData);
597 // readData = decryptCipher.doFinal(readData);
598 readData = stripIVAndDecryptSlot(readData);
600 mac.update(readData, 0, readData.length - HMAC_SIZE);
601 byte[] genmac = mac.doFinal();
602 byte[] recmac = new byte[HMAC_SIZE];
603 System.arraycopy(readData, readData.length - recmac.length, recmac, 0, recmac.length);
605 if (!Arrays.equals(recmac, genmac))
606 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
608 byte[] returnData = new byte[readData.length - recmac.length];
609 System.arraycopy(readData, 0, returnData, 0, returnData.length);
612 // byte[] sendData = table.acceptDataFromLocal(readData);
613 byte[] sendData = table.acceptDataFromLocal(returnData);
616 mac.update(sendData);
617 byte[] realmac = mac.doFinal();
618 byte[] totalData = new byte[sendData.length + realmac.length];
619 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
620 System.arraycopy(realmac, 0, totalData, sendData.length, realmac.length);
622 // Encrypt the data for sending
623 // byte[] encryptedData = encryptCipher.doFinal(totalData);
624 byte[] iv = createIV();
625 byte[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
629 // Send data to output (length of data, the data)
630 output.writeInt(encryptedData.length);
631 output.write(encryptedData, 0, encryptedData.length);
636 } catch (Exception e) {
641 if (inputSocket != null) {
644 } catch (Exception e) {
646 throw new Error("Local server close failure...");
651 public void close() {
654 if (localServerThread != null) {
656 localServerThread.join();
657 } catch (Exception e) {
659 throw new Error("Local Server thread join issue...");
663 // System.out.println("Done Closing Cloud Comm");
666 protected void finalize() throws Throwable {
668 close(); // close open files