6 * This class provides a communication API to the webserver. It also
7 * validates the HMACs on the slots and handles encryption.
8 * @author Brian Demsky <bdemsky@uci.edu>
14 static final int SALT_SIZE = 8;
15 static final int TIMEOUT_MILLIS = 5000; // 100
16 static final int IV_SIZE = 16;
18 /** Sets the size for the HMAC. */
19 static final int HMAC_SIZE = 32;
28 int listeningPort = -1;
29 Thread localServerThread = NULL;
32 TimingSingleton timer = NULL;
35 * Empty Constructor needed for child class.
38 timer = TimingSingleton.getInstance();
42 * Constructor for actual use. Takes in the url and password.
44 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
45 timer = TimingSingleton.getInstance();
47 this.baseurl = _baseurl;
48 this.password = _password;
49 this.random = new SecureRandom();
50 this.listeningPort = _listeningPort;
52 if (this.listeningPort > 0) {
53 localServerThread = new Thread(new Runnable() {
55 localServerWorkerFunction();
58 localServerThread.start();
63 * Generates Key from password.
65 SecretKeySpec initKey() {
67 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
71 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
72 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
73 } catch (Exception e) {
75 throw new Error("Failed generating key.");
80 * Inits all the security stuff
82 void initSecurity() throws ServerException {
83 // try to get the salt and if one does not exist set one
93 * Inits the HMAC generator.
97 if (password == NULL) {
103 password = NULL; // drop password
104 mac = Mac.getInstance("HmacSHA256");
106 } catch (Exception e) {
108 throw new Error("Failed To Initialize Ciphers");
113 * Builds the URL for the given request.
115 URL buildRequest(bool isput, int64_t sequencenumber, int64_t maxentries) throws IOException {
116 String reqstring = isput ? "req=putslot" : "req=getslot";
117 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
119 urlstr += "&max=" + maxentries;
120 return new URL(urlstr);
123 void setSalt() throws ServerException {
126 // Salt already sent to server so dont set it again
131 char[] saltTmp = new char[SALT_SIZE];
132 random.nextBytes(saltTmp);
134 for (int i = 0; i < SALT_SIZE; i++) {
135 System.out.println((int)saltTmp[i] & 255);
139 URL url = new URL(baseurl + "?req=setsalt");
142 URLConnection con = url.openConnection();
143 HttpURLConnection http = (HttpURLConnection) con;
145 http.setRequestMethod("POST");
146 http.setFixedLengthStreamingMode(saltTmp.length);
147 http.setDoOutput(true);
148 http.setConnectTimeout(TIMEOUT_MILLIS);
153 OutputStream os = http.getOutputStream();
157 int responsecode = http.getResponseCode();
158 if (responsecode != HttpURLConnection.HTTP_OK) {
159 // TODO: Remove this print
160 System.out.println(responsecode);
161 throw new Error("Invalid response");
167 } catch (Exception e) {
168 // e.printStackTrace();
170 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
174 bool getSalt() throws ServerException {
176 URLConnection con = NULL;
177 HttpURLConnection http = NULL;
180 url = new URL(baseurl + "?req=getsalt");
181 } catch (Exception e) {
182 // e.printStackTrace();
183 throw new Error("getSlot failed");
188 con = url.openConnection();
189 http = (HttpURLConnection) con;
190 http.setRequestMethod("POST");
191 http.setConnectTimeout(TIMEOUT_MILLIS);
192 http.setReadTimeout(TIMEOUT_MILLIS);
197 } catch (SocketTimeoutException e) {
199 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
200 } catch (Exception e) {
201 // e.printStackTrace();
202 throw new Error("getSlot failed");
209 int responsecode = http.getResponseCode();
210 if (responsecode != HttpURLConnection.HTTP_OK) {
211 // TODO: Remove this print
212 // System.out.println(responsecode);
213 throw new Error("Invalid response");
216 InputStream is = http.getInputStream();
217 if (is.available() > 0) {
218 DataInputStream dis = new DataInputStream(is);
219 int salt_length = dis.readInt();
220 char [] tmp = new char[salt_length];
231 } catch (SocketTimeoutException e) {
234 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
235 } catch (Exception e) {
236 // e.printStackTrace();
237 throw new Error("getSlot failed");
241 char[] createIV(int64_t machineId, int64_t localSequenceNumber) {
242 ByteBuffer buffer = ByteBuffer.allocate(IV_SIZE);
243 buffer.putLong(machineId);
244 int64_t localSequenceNumberShifted = localSequenceNumber << 16;
245 buffer.putLong(localSequenceNumberShifted);
246 return buffer.array();
250 char[] encryptSlotAndPrependIV(char[] rawData, char[] ivBytes) {
252 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
253 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
254 cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
256 char[] encryptedBytes = cipher.doFinal(rawData);
258 char[] chars = new char[encryptedBytes.length + IV_SIZE];
259 System.arraycopy(ivBytes, 0, chars, 0, ivBytes.length);
260 System.arraycopy(encryptedBytes, 0, chars, IV_SIZE, encryptedBytes.length);
264 } catch (Exception e) {
266 throw new Error("Failed To Encrypt");
271 char[] stripIVAndDecryptSlot(char[] rawData) {
273 char[] ivBytes = new char[IV_SIZE];
274 char[] encryptedBytes = new char[rawData.length - IV_SIZE];
275 System.arraycopy(rawData, 0, ivBytes, 0, IV_SIZE);
276 System.arraycopy(rawData, IV_SIZE, encryptedBytes, 0 , encryptedBytes.length);
278 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
280 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
281 cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
282 return cipher.doFinal(encryptedBytes);
284 } catch (Exception e) {
286 throw new Error("Failed To Decrypt");
292 * API for putting a slot into the queue. Returns NULL on success.
293 * On failure, the server will send slots with newer sequence
296 Slot[] putSlot(Slot slot, int max) throws ServerException {
298 URLConnection con = NULL;
299 HttpURLConnection http = NULL;
304 throw new ServerException("putSlot failed", ServerException.TypeSalt);
309 int64_t sequencenumber = slot.getSequenceNumber();
310 char[] slotBytes = slot.encode(mac);
311 // slotBytes = encryptCipher.doFinal(slotBytes);
313 // char[] iVBytes = slot.getSlotCryptIV();
315 // char[] chars = new char[slotBytes.length + IV_SIZE];
316 // System.arraycopy(iVBytes, 0, chars, 0, iVBytes.length);
317 // System.arraycopy(slotBytes, 0, chars, IV_SIZE, slotBytes.length);
320 char[] chars = encryptSlotAndPrependIV(slotBytes, slot.getSlotCryptIV());
322 url = buildRequest(true, sequencenumber, max);
325 con = url.openConnection();
326 http = (HttpURLConnection) con;
328 http.setRequestMethod("POST");
329 http.setFixedLengthStreamingMode(chars.length);
330 http.setDoOutput(true);
331 http.setConnectTimeout(TIMEOUT_MILLIS);
332 http.setReadTimeout(TIMEOUT_MILLIS);
335 OutputStream os = http.getOutputStream();
342 // System.out.println("Bytes Sent: " + chars.length);
343 } catch (ServerException e) {
347 } catch (SocketTimeoutException e) {
350 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
351 } catch (Exception e) {
352 // e.printStackTrace();
353 throw new Error("putSlot failed");
360 InputStream is = http.getInputStream();
361 DataInputStream dis = new DataInputStream(is);
362 char[] resptype = new char[7];
363 dis.readFully(resptype);
366 if (Arrays.equals(resptype, "getslot".getBytes())) {
367 return processSlots(dis);
368 } else if (Arrays.equals(resptype, "putslot".getBytes())) {
371 throw new Error("Bad response to putslot");
373 } catch (SocketTimeoutException e) {
375 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
376 } catch (Exception e) {
377 // e.printStackTrace();
378 throw new Error("putSlot failed");
383 * Request the server to send all slots with the given
384 * sequencenumber or newer.
386 Slot[] getSlots(int64_t sequencenumber) throws ServerException {
388 URLConnection con = NULL;
389 HttpURLConnection http = NULL;
394 throw new ServerException("getSlots failed", ServerException.TypeSalt);
399 url = buildRequest(false, sequencenumber, 0);
401 con = url.openConnection();
402 http = (HttpURLConnection) con;
403 http.setRequestMethod("POST");
404 http.setConnectTimeout(TIMEOUT_MILLIS);
405 http.setReadTimeout(TIMEOUT_MILLIS);
412 } catch (SocketTimeoutException e) {
415 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
416 } catch (ServerException e) {
420 } catch (Exception e) {
421 // e.printStackTrace();
422 throw new Error("getSlots failed");
428 InputStream is = http.getInputStream();
429 DataInputStream dis = new DataInputStream(is);
430 char[] resptype = new char[7];
432 dis.readFully(resptype);
435 if (!Arrays.equals(resptype, "getslot".getBytes()))
436 throw new Error("Bad Response: " + new String(resptype));
438 return processSlots(dis);
439 } catch (SocketTimeoutException e) {
442 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
443 } catch (Exception e) {
444 // e.printStackTrace();
445 throw new Error("getSlots failed");
450 * Method that actually handles building Slot objects from the
451 * server response. Shared by both putSlot and getSlots.
453 Slot[] processSlots(DataInputStream dis) throws Exception {
454 int numberofslots = dis.readInt();
455 int[] sizesofslots = new int[numberofslots];
457 Slot[] slots = new Slot[numberofslots];
458 for (int i = 0; i < numberofslots; i++)
459 sizesofslots[i] = dis.readInt();
461 for (int i = 0; i < numberofslots; i++) {
463 char[] rawData = new char[sizesofslots[i]];
464 dis.readFully(rawData);
467 // char[] data = new char[rawData.length - IV_SIZE];
468 // System.arraycopy(rawData, IV_SIZE, data, 0, data.length);
471 char[] data = stripIVAndDecryptSlot(rawData);
473 // data = decryptCipher.doFinal(data);
475 slots[i] = Slot.decode(table, data, mac);
481 char[] sendLocalData(char[] sendData, int64_t localSequenceNumber, String host, int port) {
487 System.out.println("Passing Locally");
489 mac.update(sendData);
490 char[] genmac = mac.doFinal();
491 char[] totalData = new char[sendData.length + genmac.length];
492 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
493 System.arraycopy(genmac, 0, totalData, sendData.length, genmac.length);
495 // Encrypt the data for sending
496 // char[] encryptedData = encryptCipher.doFinal(totalData);
497 // char[] encryptedData = encryptCipher.doFinal(totalData);
498 char[] iv = createIV(table.getMachineId(), table.getLocalSequenceNumber());
499 char[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
501 // Open a TCP socket connection to a local device
502 Socket socket = new Socket(host, port);
503 socket.setReuseAddress(true);
504 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
505 DataInputStream input = new DataInputStream(socket.getInputStream());
509 // Send data to output (length of data, the data)
510 output.writeInt(encryptedData.length);
511 output.write(encryptedData, 0, encryptedData.length);
514 int lengthOfReturnData = input.readInt();
515 char[] returnData = new char[lengthOfReturnData];
516 input.readFully(returnData);
520 // returnData = decryptCipher.doFinal(returnData);
521 returnData = stripIVAndDecryptSlot(returnData);
522 // returnData = decryptCipher.doFinal(returnData);
524 // We are done with this socket
527 mac.update(returnData, 0, returnData.length - HMAC_SIZE);
528 char[] realmac = mac.doFinal();
529 char[] recmac = new char[HMAC_SIZE];
530 System.arraycopy(returnData, returnData.length - realmac.length, recmac, 0, realmac.length);
532 if (!Arrays.equals(recmac, realmac))
533 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
535 char[] returnData2 = new char[lengthOfReturnData - recmac.length];
536 System.arraycopy(returnData, 0, returnData2, 0, returnData2.length);
539 } catch (Exception e) {
541 // throw new Error("Local comms failure...");
548 void localServerWorkerFunction() {
550 ServerSocket inputSocket = NULL;
553 // Local server socket
554 inputSocket = new ServerSocket(listeningPort);
555 inputSocket.setReuseAddress(true);
556 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
557 } catch (Exception e) {
559 throw new Error("Local server setup failure...");
565 // Accept incoming socket
566 Socket socket = inputSocket.accept();
568 DataInputStream input = new DataInputStream(socket.getInputStream());
569 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
571 // Get the encrypted data from the server
572 int dataSize = input.readInt();
573 char[] readData = new char[dataSize];
574 input.readFully(readData);
579 // readData = decryptCipher.doFinal(readData);
580 readData = stripIVAndDecryptSlot(readData);
582 mac.update(readData, 0, readData.length - HMAC_SIZE);
583 char[] genmac = mac.doFinal();
584 char[] recmac = new char[HMAC_SIZE];
585 System.arraycopy(readData, readData.length - recmac.length, recmac, 0, recmac.length);
587 if (!Arrays.equals(recmac, genmac))
588 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
590 char[] returnData = new char[readData.length - recmac.length];
591 System.arraycopy(readData, 0, returnData, 0, returnData.length);
594 // char[] sendData = table.acceptDataFromLocal(readData);
595 char[] sendData = table.acceptDataFromLocal(returnData);
598 mac.update(sendData);
599 char[] realmac = mac.doFinal();
600 char[] totalData = new char[sendData.length + realmac.length];
601 System.arraycopy(sendData, 0, totalData, 0, sendData.length);
602 System.arraycopy(realmac, 0, totalData, sendData.length, realmac.length);
604 // Encrypt the data for sending
605 // char[] encryptedData = encryptCipher.doFinal(totalData);
606 char[] iv = createIV(table.getMachineId(), table.getLocalSequenceNumber());
607 char[] encryptedData = encryptSlotAndPrependIV(totalData, iv);
611 // Send data to output (length of data, the data)
612 output.writeInt(encryptedData.length);
613 output.write(encryptedData, 0, encryptedData.length);
618 } catch (Exception e) {
623 if (inputSocket != NULL) {
626 } catch (Exception e) {
628 throw new Error("Local server close failure...");
636 if (localServerThread != NULL) {
638 localServerThread.join();
639 } catch (Exception e) {
641 throw new Error("Local Server thread join issue...");
645 // System.out.println("Done Closing Cloud Comm");
648 protected void finalize() throws Throwable {
650 close(); // close open files