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 the HMAC generator.
80 private void initCrypt() {
82 if (password == null) {
87 SecretKeySpec key = initKey();
88 password = null; // drop password
89 mac = Mac.getInstance("HmacSHA256");
91 encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
92 encryptCipher.init(Cipher.ENCRYPT_MODE, key);
93 decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
94 decryptCipher.init(Cipher.DECRYPT_MODE, key);
95 } catch (Exception e) {
97 throw new Error("Failed To Initialize Ciphers");
102 * Builds the URL for the given request.
104 private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
105 String reqstring = isput ? "req=putslot" : "req=getslot";
106 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
108 urlstr += "&max=" + maxentries;
109 return new URL(urlstr);
112 public void setSalt() throws ServerException {
115 // Salt already sent to server so dont set it again
118 byte[] saltTmp = new byte[SALT_SIZE];
119 random.nextBytes(saltTmp);
122 URLConnection con = null;
123 HttpURLConnection http = null;
126 url = new URL(baseurl + "?req=setsalt");
127 con = url.openConnection();
128 http = (HttpURLConnection) con;
129 http.setRequestMethod("POST");
130 http.setFixedLengthStreamingMode(saltTmp.length);
131 http.setDoOutput(true);
132 http.setConnectTimeout(TIMEOUT_MILLIS);
134 OutputStream os = http.getOutputStream();
136 int responsecode = http.getResponseCode();
137 if (responsecode != HttpURLConnection.HTTP_OK) {
138 // TODO: Remove this print
139 System.out.println(responsecode);
140 throw new Error("Invalid response");
143 } catch (Exception e) {
144 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
149 InputStream is = http.getInputStream();
150 DataInputStream dis = new DataInputStream(is);
151 // byte [] tmp = new byte[1];
152 byte tmp = dis.readByte();
158 getSalt(); // there was already a salt so we need to get it
161 } catch (SocketTimeoutException e) {
162 throw new ServerException("setSalt failed", ServerException.TypeInputTimeout);
163 } catch (Exception e) {
165 throw new Error("setSlot failed");
169 private void getSalt() throws ServerException {
171 URLConnection con = null;
172 HttpURLConnection http = null;
175 url = new URL(baseurl + "?req=getsalt");
176 } catch (Exception e) {
178 throw new Error("getSlot failed");
182 con = url.openConnection();
183 http = (HttpURLConnection) con;
184 http.setRequestMethod("POST");
185 http.setConnectTimeout(TIMEOUT_MILLIS);
186 http.setReadTimeout(TIMEOUT_MILLIS);
188 } catch (SocketTimeoutException e) {
189 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
190 } catch (Exception e) {
192 throw new Error("getSlot failed");
196 InputStream is = http.getInputStream();
197 DataInputStream dis = new DataInputStream(is);
198 int salt_length = dis.readInt();
199 byte [] tmp = new byte[salt_length];
202 } catch (SocketTimeoutException e) {
203 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
204 } catch (Exception e) {
206 throw new Error("getSlot failed");
211 * API for putting a slot into the queue. Returns null on success.
212 * On failure, the server will send slots with newer sequence
215 Slot[] putSlot(Slot slot, int max) throws ServerException {
217 URLConnection con = null;
218 HttpURLConnection http = null;
226 long sequencenumber = slot.getSequenceNumber();
227 byte[] bytes = slot.encode(mac);
228 bytes = encryptCipher.doFinal(bytes);
230 url = buildRequest(true, sequencenumber, max);
231 con = url.openConnection();
232 http = (HttpURLConnection) con;
234 http.setRequestMethod("POST");
235 http.setFixedLengthStreamingMode(bytes.length);
236 http.setDoOutput(true);
237 http.setConnectTimeout(TIMEOUT_MILLIS);
238 http.setReadTimeout(TIMEOUT_MILLIS);
241 OutputStream os = http.getOutputStream();
245 // System.out.println("Bytes Sent: " + bytes.length);
246 } catch (SocketTimeoutException e) {
247 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
248 } catch (Exception e) {
250 throw new Error("putSlot failed");
256 InputStream is = http.getInputStream();
257 DataInputStream dis = new DataInputStream(is);
258 byte[] resptype = new byte[7];
259 dis.readFully(resptype);
261 if (Arrays.equals(resptype, "getslot".getBytes()))
262 return processSlots(dis);
263 else if (Arrays.equals(resptype, "putslot".getBytes()))
266 throw new Error("Bad response to putslot");
268 } catch (SocketTimeoutException e) {
269 throw new ServerException("putSlot failed", ServerException.TypeInputTimeout);
270 } catch (Exception e) {
272 throw new Error("putSlot failed");
277 * Request the server to send all slots with the given
278 * sequencenumber or newer.
280 Slot[] getSlots(long sequencenumber) throws ServerException {
282 URLConnection con = null;
283 HttpURLConnection http = null;
291 url = buildRequest(false, sequencenumber, 0);
292 con = url.openConnection();
293 http = (HttpURLConnection) con;
294 http.setRequestMethod("POST");
295 http.setConnectTimeout(TIMEOUT_MILLIS);
296 http.setReadTimeout(TIMEOUT_MILLIS);
298 } catch (SocketTimeoutException e) {
299 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
300 } catch (ServerException e) {
302 } catch (Exception e) {
304 throw new Error("getSlots failed");
308 InputStream is = http.getInputStream();
309 DataInputStream dis = new DataInputStream(is);
310 byte[] resptype = new byte[7];
311 dis.readFully(resptype);
312 if (!Arrays.equals(resptype, "getslot".getBytes()))
313 throw new Error("Bad Response: " + new String(resptype));
315 return processSlots(dis);
316 } catch (SocketTimeoutException e) {
317 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
318 } catch (Exception e) {
320 throw new Error("getSlots failed");
325 * Method that actually handles building Slot objects from the
326 * server response. Shared by both putSlot and getSlots.
328 private Slot[] processSlots(DataInputStream dis) throws Exception {
329 int numberofslots = dis.readInt();
330 int[] sizesofslots = new int[numberofslots];
333 // System.out.println("number of slots: " + numberofslots);
337 Slot[] slots = new Slot[numberofslots];
338 for (int i = 0; i < numberofslots; i++)
339 sizesofslots[i] = dis.readInt();
341 for (int i = 0; i < numberofslots; i++) {
343 // System.out.println("Size of slot: " + sizesofslots[i]);
345 byte[] data = new byte[sizesofslots[i]];
348 data = decryptCipher.doFinal(data);
350 slots[i] = Slot.decode(table, data, mac);
356 public byte[] sendLocalData(byte[] sendData, String host, int port) {
362 // Encrypt the data for sending
363 byte[] encryptedData = encryptCipher.doFinal(sendData);
365 // Open a TCP socket connection to a local device
366 Socket socket = new Socket(host, port);
367 socket.setReuseAddress(true);
368 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
369 DataInputStream input = new DataInputStream(socket.getInputStream());
371 // Send data to output (length of data, the data)
372 output.writeInt(encryptedData.length);
373 output.write(encryptedData, 0, encryptedData.length);
376 int lengthOfReturnData = input.readInt();
377 byte[] returnData = new byte[lengthOfReturnData];
378 input.readFully(returnData);
379 returnData = decryptCipher.doFinal(returnData);
381 // We are dont with this socket
385 } catch (SocketTimeoutException e) {
387 } catch (BadPaddingException e) {
389 } catch (IllegalBlockSizeException e) {
391 } catch (UnknownHostException e) {
393 } catch (IOException e) {
400 private void localServerWorkerFunction() {
402 ServerSocket inputSocket = null;
405 // Local server socket
406 inputSocket = new ServerSocket(listeningPort);
407 inputSocket.setReuseAddress(true);
408 inputSocket.setSoTimeout(TIMEOUT_MILLIS);
409 } catch (Exception e) {
411 throw new Error("Local server setup failure...");
417 // Accept incoming socket
418 Socket socket = inputSocket.accept();
420 DataInputStream input = new DataInputStream(socket.getInputStream());
421 DataOutputStream output = new DataOutputStream(socket.getOutputStream());
423 // Get the encrypted data from the server
424 int dataSize = input.readInt();
425 byte[] readData = new byte[dataSize];
426 input.readFully(readData);
429 readData = decryptCipher.doFinal(readData);
432 byte[] sendData = table.acceptDataFromLocal(readData);
434 // Encrypt the data for sending
435 sendData = encryptCipher.doFinal(sendData);
437 // Send data to output (length of data, the data)
438 output.writeInt(sendData.length);
439 output.write(sendData, 0, sendData.length);
444 } catch (SocketTimeoutException e) {
446 } catch (BadPaddingException e) {
448 } catch (IllegalBlockSizeException e) {
450 } catch (UnknownHostException e) {
452 } catch (IOException e) {
457 if (inputSocket != null) {
460 } catch (Exception e) {
462 throw new Error("Local server close failure...");
467 public void close() {
471 localServerThread.join();
472 } catch (Exception e) {
474 throw new Error("Local Server thread join issue...");
477 System.out.println("Done Closing");
480 protected void finalize() throws Throwable {
482 close(); // close open files