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 = 100;
22 private String baseurl;
23 private Cipher encryptCipher;
24 private Cipher decryptCipher;
26 private String password;
27 private SecureRandom random;
30 private int listeningPort = -1;
31 private Thread localServerThread = null;
32 private boolean doEnd = false;
35 * Empty Constructor needed for child class.
41 * Constructor for actual use. Takes in the url and password.
43 CloudComm(Table _table, String _baseurl, String _password, int _listeningPort) {
45 this.baseurl = _baseurl;
46 this.password = _password;
47 this.random = new SecureRandom();
48 this.listeningPort = _listeningPort;
50 if (this.listeningPort > 0) {
51 localServerThread = new Thread(new Runnable() {
53 localServerWorkerFunction();
56 localServerThread.start();
61 * Generates Key from password.
63 private SecretKeySpec initKey() {
65 PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(),
69 SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
70 return new SecretKeySpec(tmpkey.getEncoded(), "AES");
71 } catch (Exception e) {
73 throw new Error("Failed generating key.");
78 * Inits all the security stuff
80 public void initSecurity() throws ServerException {
81 // try to get the salt and if one does not exist set one
91 * Inits the HMAC generator.
93 private void initCrypt() {
95 if (password == null) {
100 SecretKeySpec key = initKey();
101 password = null; // drop password
102 mac = Mac.getInstance("HmacSHA256");
104 encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
105 encryptCipher.init(Cipher.ENCRYPT_MODE, key);
106 decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
107 decryptCipher.init(Cipher.DECRYPT_MODE, key);
108 } catch (Exception e) {
110 throw new Error("Failed To Initialize Ciphers");
115 * Builds the URL for the given request.
117 private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
118 String reqstring = isput ? "req=putslot" : "req=getslot";
119 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
121 urlstr += "&max=" + maxentries;
122 return new URL(urlstr);
125 private void setSalt() throws ServerException {
128 // Salt already sent to server so dont set it again
133 byte[] saltTmp = new byte[SALT_SIZE];
134 random.nextBytes(saltTmp);
136 URL url = new URL(baseurl + "?req=setsalt");
137 URLConnection con = url.openConnection();
138 HttpURLConnection http = (HttpURLConnection) con;
140 http.setRequestMethod("POST");
141 http.setFixedLengthStreamingMode(saltTmp.length);
142 http.setDoOutput(true);
143 http.setConnectTimeout(TIMEOUT_MILLIS);
146 OutputStream os = http.getOutputStream();
150 int responsecode = http.getResponseCode();
151 if (responsecode != HttpURLConnection.HTTP_OK) {
152 // TODO: Remove this print
153 System.out.println(responsecode);
154 throw new Error("Invalid response");
158 } catch (Exception e) {
159 // e.printStackTrace();
160 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
164 private boolean getSalt() throws ServerException {
166 URLConnection con = null;
167 HttpURLConnection http = null;
170 url = new URL(baseurl + "?req=getsalt");
171 } catch (Exception e) {
172 // e.printStackTrace();
173 throw new Error("getSlot failed");
177 con = url.openConnection();
178 http = (HttpURLConnection) con;
179 http.setRequestMethod("POST");
180 http.setConnectTimeout(TIMEOUT_MILLIS);
181 http.setReadTimeout(TIMEOUT_MILLIS);
183 } catch (SocketTimeoutException e) {
184 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
185 } catch (Exception e) {
186 // e.printStackTrace();
187 throw new Error("getSlot failed");
192 int responsecode = http.getResponseCode();
193 if (responsecode != HttpURLConnection.HTTP_OK) {
194 // TODO: Remove this print
195 // System.out.println(responsecode);
196 throw new Error("Invalid response");
199 InputStream is = http.getInputStream();
200 if (is.available() > 0) {
201 DataInputStream dis = new DataInputStream(is);
202 int salt_length = dis.readInt();
203 byte [] tmp = new byte[salt_length];
210 } catch (SocketTimeoutException e) {
211 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
212 } catch (Exception e) {
213 // e.printStackTrace();
214 throw new Error("getSlot failed");
219 * API for putting a slot into the queue. Returns null on success.
220 * On failure, the server will send slots with newer sequence
223 public Slot[] putSlot(Slot slot, int max) throws ServerException {
225 URLConnection con = null;
226 HttpURLConnection http = null;
231 throw new ServerException("putSlot failed", ServerException.TypeSalt);
236 long sequencenumber = slot.getSequenceNumber();
237 byte[] bytes = slot.encode(mac);
238 bytes = encryptCipher.doFinal(bytes);
240 url = buildRequest(true, sequencenumber, max);
241 con = url.openConnection();
242 http = (HttpURLConnection) con;
244 http.setRequestMethod("POST");
245 http.setFixedLengthStreamingMode(bytes.length);
246 http.setDoOutput(true);
247 http.setConnectTimeout(TIMEOUT_MILLIS);
248 http.setReadTimeout(TIMEOUT_MILLIS);
251 OutputStream os = http.getOutputStream();
255 // System.out.println("Bytes Sent: " + bytes.length);
256 } catch (ServerException e) {
258 } catch (SocketTimeoutException e) {
259 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
260 } catch (Exception e) {
261 // e.printStackTrace();
262 throw new Error("putSlot failed");
268 InputStream is = http.getInputStream();
269 DataInputStream dis = new DataInputStream(is);
270 byte[] resptype = new byte[7];
271 dis.readFully(resptype);
273 if (Arrays.equals(resptype, "getslot".getBytes()))
274 return processSlots(dis);
275 else if (Arrays.equals(resptype, "putslot".getBytes()))
278 throw new Error("Bad response to putslot");
280 } catch (SocketTimeoutException e) {
281 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
282 } catch (Exception e) {
283 // e.printStackTrace();
284 throw new Error("putSlot failed");
289 * Request the server to send all slots with the given
290 * sequencenumber or newer.
292 public Slot[] getSlots(long sequencenumber) throws ServerException {
294 URLConnection con = null;
295 HttpURLConnection http = null;
300 throw new ServerException("getSlots failed", ServerException.TypeSalt);
305 url = buildRequest(false, sequencenumber, 0);
306 con = url.openConnection();
307 http = (HttpURLConnection) con;
308 http.setRequestMethod("POST");
309 http.setConnectTimeout(TIMEOUT_MILLIS);
310 http.setReadTimeout(TIMEOUT_MILLIS);
312 } catch (SocketTimeoutException e) {
313 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
314 } catch (ServerException e) {
316 } catch (Exception e) {
317 // e.printStackTrace();
318 throw new Error("getSlots failed");
322 InputStream is = http.getInputStream();
323 DataInputStream dis = new DataInputStream(is);
324 byte[] resptype = new byte[7];
325 dis.readFully(resptype);
326 if (!Arrays.equals(resptype, "getslot".getBytes()))
327 throw new Error("Bad Response: " + new String(resptype));
329 return processSlots(dis);
330 } catch (SocketTimeoutException e) {
331 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
332 } catch (Exception e) {
333 // e.printStackTrace();
334 throw new Error("getSlots failed");
339 * Method that actually handles building Slot objects from the
340 * server response. Shared by both putSlot and getSlots.
342 private Slot[] processSlots(DataInputStream dis) throws Exception {
343 int numberofslots = dis.readInt();
344 int[] sizesofslots = new int[numberofslots];
346 Slot[] slots = new Slot[numberofslots];
347 for (int i = 0; i < numberofslots; i++)
348 sizesofslots[i] = dis.readInt();
350 for (int i = 0; i < numberofslots; i++) {
352 byte[] data = new byte[sizesofslots[i]];
355 data = decryptCipher.doFinal(data);
357 slots[i] = Slot.decode(table, data, mac);
363 public byte[] sendLocalData(byte[] sendData, String host, int port) {
369 // Encrypt the data for sending
370 byte[] encryptedData = encryptCipher.doFinal(sendData);
372 // Open a TCP socket connection to a local device
373 Socket socket = new Socket(host, port);
374 socket.setReuseAddress(true);
375 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
376 DataInputStream input = new DataInputStream(socket.getInputStream());
378 // Send data to output (length of data, the data)
379 output.writeInt(encryptedData.length);
380 output.write(encryptedData, 0, encryptedData.length);
383 int lengthOfReturnData = input.readInt();
384 byte[] returnData = new byte[lengthOfReturnData];
385 input.readFully(returnData);
386 returnData = decryptCipher.doFinal(returnData);
388 // We are dont with this socket
392 } catch (SocketTimeoutException e) {
394 } catch (BadPaddingException e) {
396 } catch (IllegalBlockSizeException e) {
398 } catch (UnknownHostException e) {
400 } catch (IOException e) {
407 private void localServerWorkerFunction() {
409 ServerSocket inputSocket = null;
412 // Local server socket
413 inputSocket = new ServerSocket(listeningPort);
414 inputSocket.setReuseAddress(true);
415 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
416 } catch (Exception e) {
418 throw new Error("Local server setup failure...");
424 // Accept incoming socket
425 Socket socket = inputSocket.accept();
427 DataInputStream input = new DataInputStream(socket.getInputStream());
428 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
430 // Get the encrypted data from the server
431 int dataSize = input.readInt();
432 byte[] readData = new byte[dataSize];
433 input.readFully(readData);
436 readData = decryptCipher.doFinal(readData);
439 byte[] sendData = table.acceptDataFromLocal(readData);
441 // Encrypt the data for sending
442 sendData = encryptCipher.doFinal(sendData);
444 // Send data to output (length of data, the data)
445 output.writeInt(sendData.length);
446 output.write(sendData, 0, sendData.length);
451 } catch (SocketTimeoutException e) {
453 } catch (BadPaddingException e) {
455 } catch (IllegalBlockSizeException e) {
457 } catch (UnknownHostException e) {
459 } catch (IOException e) {
464 if (inputSocket != null) {
467 } catch (Exception e) {
469 throw new Error("Local server close failure...");
474 public void close() {
477 if (localServerThread != null) {
479 localServerThread.join();
480 } catch (Exception e) {
482 throw new Error("Local Server thread join issue...");
486 // System.out.println("Done Closing Cloud Comm");
489 protected void finalize() throws Throwable {
491 close(); // close open files