package iotcloud; import java.io.*; import java.net.*; import java.util.Arrays; import javax.crypto.*; import javax.crypto.spec.*; import java.security.SecureRandom; /** * This class provides a communication API to the webserver. It also * validates the HMACs on the slots and handles encryption. * @author Brian Demsky * @version 1.0 */ class CloudComm { String baseurl; Cipher encryptCipher; Cipher decryptCipher; Mac mac; String password; SecureRandom random; static final int SALT_SIZE = 8; byte salt[]; Table table; /** * Empty Constructor needed for child class. */ CloudComm() { } /** * Constructor for actual use. Takes in the url and password. */ CloudComm(Table _table, String _baseurl, String _password) { this.table = _table; this.baseurl = _baseurl; this.password = _password; this.random = new SecureRandom(); } /** * Generates Key from password. */ private SecretKeySpec initKey() { try { PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec); return new SecretKeySpec(tmpkey.getEncoded(), "AES"); } catch (Exception e) { e.printStackTrace(); throw new Error("Failed generating key."); } } /** * Inits the HMAC generator. */ private void initCrypt() { try { SecretKeySpec key = initKey(); password = null; // drop password mac = Mac.getInstance("HmacSHA256"); mac.init(key); encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); encryptCipher.init(Cipher.ENCRYPT_MODE, key); decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); decryptCipher.init(Cipher.DECRYPT_MODE, key); } catch (Exception e) { e.printStackTrace(); throw new Error("Failed To Initialize Ciphers"); } } /* * Builds the URL for the given request. */ private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException { String reqstring = isput ? "req=putslot" : "req=getslot"; String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber; if (maxentries != 0) urlstr += "&max=" + maxentries; return new URL(urlstr); } public void setSalt() { try { salt = new byte[SALT_SIZE]; random.nextBytes(salt); URL url = new URL(baseurl + "?req=setsalt"); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setFixedLengthStreamingMode(salt.length); http.setDoOutput(true); http.connect(); OutputStream os = http.getOutputStream(); os.write(salt); int responsecode = http.getResponseCode(); if (responsecode != HttpURLConnection.HTTP_OK) throw new Error("Invalid response"); } catch (Exception e) { e.printStackTrace(); throw new Error("Failed setting salt"); } initCrypt(); } private void getSalt() throws Exception { URL url = new URL(baseurl + "?req=getsalt"); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.connect(); InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); int salt_length = dis.readInt(); byte [] tmp = new byte[salt_length]; dis.readFully(tmp); salt = tmp; } /* * API for putting a slot into the queue. Returns null on success. * On failure, the server will send slots with newer sequence * numbers. */ Slot[] putSlot(Slot slot, int max) { try { if (salt == null) { getSalt(); initCrypt(); } long sequencenumber = slot.getSequenceNumber(); byte[] bytes = slot.encode(mac); bytes = encryptCipher.doFinal(bytes); URL url = buildRequest(true, sequencenumber, max); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setFixedLengthStreamingMode(bytes.length); http.setDoOutput(true); http.connect(); OutputStream os = http.getOutputStream(); os.write(bytes); InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); byte[] resptype = new byte[7]; dis.readFully(resptype); if (Arrays.equals(resptype, "getslot".getBytes())) return processSlots(dis); else if (Arrays.equals(resptype, "putslot".getBytes())) return null; else throw new Error("Bad response to putslot"); } catch (Exception e) { e.printStackTrace(); throw new Error("putSlot failed"); } } /** * Request the server to send all slots with the given * sequencenumber or newer. */ Slot[] getSlots(long sequencenumber) { try { if (salt == null) { getSalt(); initCrypt(); } URL url = buildRequest(false, sequencenumber, 0); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.connect(); InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); byte[] resptype = new byte[7]; dis.readFully(resptype); if (!Arrays.equals(resptype, "getslot".getBytes())) throw new Error("Bad Response: " + new String(resptype)); else return processSlots(dis); } catch (Exception e) { e.printStackTrace(); throw new Error("getSlots failed"); } } /** * Method that actually handles building Slot objects from the * server response. Shared by both putSlot and getSlots. */ private Slot[] processSlots(DataInputStream dis) throws Exception { int numberofslots = dis.readInt(); int[] sizesofslots = new int[numberofslots]; Slot[] slots = new Slot[numberofslots]; for (int i = 0; i < numberofslots; i++) sizesofslots[i] = dis.readInt(); for (int i = 0; i < numberofslots; i++) { byte[] data = new byte[sizesofslots[i]]; dis.readFully(data); data = decryptCipher.doFinal(data); slots[i] = Slot.decode(table, data, mac); } dis.close(); return slots; } }