package iotcloud; import java.io.*; import java.net.*; import java.util.Arrays; import javax.crypto.*; import javax.crypto.spec.*; import java.security.SecureRandom; import android.util.*; import java.nio.charset.StandardCharsets; import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.PBEParametersGenerator; import android.content.*; /** * 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 { private static final int SALT_SIZE = 8; private static final int TIMEOUT_MILLIS = 2000; // 100 /** Sets the size for the HMAC. */ static final int HMAC_SIZE = 32; private String baseurl; private Cipher encryptCipher; private Cipher decryptCipher; private Mac mac; private String password; private SecureRandom random; private byte salt[]; private Table table; private int listeningPort = -1; private Thread localServerThread = null; private boolean doEnd = false; private TimingSingleton timer = null; private Context context; /** * Empty Constructor needed for child class. */ CloudComm() { timer = TimingSingleton.getInstance(); } private void deleteFile(Context context) { File fd = context.getFileStreamPath("config.txt"); fd.delete(); } private void writeToFile(byte[] data,Context context) { try { // OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput("config.txt", Context.MODE_PRIVATE)); // outputStreamWriter.write(data); // outputStreamWriter.close(); FileOutputStream outputStreamWriter = context.openFileOutput("config.txt", Context.MODE_PRIVATE); outputStreamWriter.write(data); outputStreamWriter.close(); } catch (IOException e) { Log.e("Exception", "File write failed: " + e.toString()); } } private byte[] readFromFile(Context context) throws FileNotFoundException { byte[] ret1 = null; try { InputStream inputStream = context.openFileInput("config.txt"); if ( inputStream != null ) { ret1 = new byte[inputStream.available()]; for(int i = 0; i < ret1.length;i++) { ret1[i] = (byte)inputStream.read(); } // InputStreamReader inputStreamReader = new InputStreamReader(inputStream); // BufferedReader bufferedReader = new BufferedReader(inputStreamReader); // String receiveString = ""; // StringBuilder stringBuilder = new StringBuilder(); // while ( (receiveString = bufferedReader.readLine()) != null ) { // stringBuilder.append(receiveString); // } inputStream.close(); // ret = stringBuilder.toString(); } } catch (FileNotFoundException e) { Log.e("login activity", "File not found: " + e.toString()); throw e; } catch (IOException e) { Log.e("login activity", "Can not read file: " + e.toString()); } return ret1; } /** * Constructor for actual use. Takes in the url and password. */ CloudComm(Table _table, String _baseurl, String _password, int _listeningPort, Context _context) { timer = TimingSingleton.getInstance(); this.table = _table; this.baseurl = _baseurl; this.password = _password; this.random = new SecureRandom(); this.listeningPort = _listeningPort; this.context = _context; if (this.listeningPort > 0) { localServerThread = new Thread(new Runnable() { public void run() { localServerWorkerFunction(); } }); localServerThread.start(); } } /** * Generates Key from password. */ private SecretKeySpec initKey() { try { Log.e("Ali:::::", "KEY KEY KEY......"); boolean doCrypt = false; byte[] keySaved = null; try { // String file = readFromFile(context); byte[] dat = readFromFile(context);//file.getBytes(); boolean saltMatch = true; for(int i = 0; i < salt.length; i++) { Log.e("ALIasdasdaS:", " " + ((int) salt[i] & 255) + " " + ((int) dat[i] & 255)); if(dat[i] != salt[i]) { saltMatch = false; // break; } } if(saltMatch ) { keySaved = new byte[dat.length - salt.length]; for(int i = salt.length; i < dat.length;i++) { keySaved[i-salt.length] = dat[i]; } } else { doCrypt = true; Log.e("Ali:::::", "Salt No Match......"); } } catch (Exception e) { doCrypt = true; } if(doCrypt) { Log.e("Ali:::::", "Doing Crypt......"); PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest()); generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray()), salt, 65536); KeyParameter key = (KeyParameter) generator.generateDerivedMacParameters(128); // PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(), // salt, // 65536, // 128); // SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(keyspec); // SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec); // return new SecretKeySpec(tmpkey.getEncoded(), "AES"); byte[] keyDat = key.getKey(); byte[] saveDat = new byte[salt.length + keyDat.length]; for (int i = 0 ; i < salt.length;i++) { saveDat[i] = salt[i]; } for (int i = 0 ; i < keyDat.length;i++) { saveDat[i + salt.length] = keyDat[i]; } deleteFile(context); writeToFile(saveDat, context); return new SecretKeySpec(key.getKey(), "AES"); } else{ Log.e("Ali:::::", "Using Saved......"); return new SecretKeySpec(keySaved, "AES"); } } catch (Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); // stack trace as a string throw new Error("Failed generating key. " + sw.toString()); } } /** * Inits all the security stuff */ public void initSecurity() throws ServerException { // try to get the salt and if one does not exist set one if (!getSalt()) { //Set the salt setSalt(); } initCrypt(); } /** * Inits the HMAC generator. */ private void initCrypt() { if (password == null) { return; } 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); } private void setSalt() throws ServerException { if (salt != null) { // Salt already sent to server so dont set it again return; } try { byte[] saltTmp = new byte[SALT_SIZE]; random.nextBytes(saltTmp); URL url = new URL(baseurl + "?req=setsalt"); timer.startTime(); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setFixedLengthStreamingMode(saltTmp.length); http.setDoOutput(true); http.setConnectTimeout(TIMEOUT_MILLIS); http.connect(); OutputStream os = http.getOutputStream(); os.write(saltTmp); os.flush(); int responsecode = http.getResponseCode(); if (responsecode != HttpURLConnection.HTTP_OK) { // TODO: Remove this print System.out.println(responsecode); throw new Error("Invalid response"); } timer.endTime(); salt = saltTmp; } catch (Exception e) { // e.printStackTrace(); timer.endTime(); throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout); } } private boolean getSalt() throws ServerException { URL url = null; URLConnection con = null; HttpURLConnection http = null; try { url = new URL(baseurl + "?req=getsalt"); } catch (Exception e) { // e.printStackTrace(); throw new Error("getSlot failed"); } try { timer.startTime(); con = url.openConnection(); http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setConnectTimeout(TIMEOUT_MILLIS); http.setReadTimeout(TIMEOUT_MILLIS); http.connect(); timer.endTime(); } catch (SocketTimeoutException e) { timer.endTime(); throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout); } catch (Exception e) { // e.printStackTrace(); throw new Error("getSlot failed " + e.toString()); } try { timer.startTime(); int responsecode = http.getResponseCode(); if (responsecode != HttpURLConnection.HTTP_OK) { // TODO: Remove this print // System.out.println(responsecode); throw new Error("Invalid response"); } // Log.e("Aaaaa", "Code " + responsecode); InputStream is = http.getInputStream(); // // // BufferedReader rd= new BufferedReader(new InputStreamReader(is)); // int line; // StringBuilder sb= new StringBuilder(); // while ((line = rd.read())!= -1) // { // sb.append((char)line); // Log.e("Aaaaa", "line " + line); // // } // // // int sdfsdfds = (int)sb.toString().charAt(0); // Log.e("Aaaaa", "length " + (int)sb.toString().charAt(0)); // Log.e("Aaaaa", "Res " + sb.toString().length()); // is = new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)); // if (is.available() > 0) { // if (sb.toString().length() > 0) { if(true) { try { DataInputStream dis = new DataInputStream(is); int salt_length = dis.readInt(); byte[] tmp = new byte[salt_length]; // byte [] tmp = new byte[8]; dis.readFully(tmp); salt = tmp; for (int i = 0; i < 8; i++) { Log.e("ALIasdasdaS:", "asd " + ((int) salt[i] & 255)); } timer.endTime(); return true; } catch (Exception e) { timer.endTime(); Log.e("Aaaaa", "Salt No Data"); return false; } } else { return false; } } catch (SocketTimeoutException e) { timer.endTime(); throw new ServerException("getSalt failed", ServerException.TypeInputTimeout); } catch (Exception e) { throw new Error("getSlot failed + " + e); } } /* * API for putting a slot into the queue. Returns null on success. * On failure, the server will send slots with newer sequence * numbers. */ public Slot[] putSlot(Slot slot, int max) throws ServerException { URL url = null; URLConnection con = null; HttpURLConnection http = null; try { if (salt == null) { if (!getSalt()) { throw new ServerException("putSlot failed", ServerException.TypeSalt); } initCrypt(); } long sequencenumber = slot.getSequenceNumber(); byte[] bytes = slot.encode(mac); bytes = encryptCipher.doFinal(bytes); url = buildRequest(true, sequencenumber, max); timer.startTime(); con = url.openConnection(); http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setFixedLengthStreamingMode(bytes.length); http.setDoOutput(true); http.setConnectTimeout(TIMEOUT_MILLIS); http.setReadTimeout(TIMEOUT_MILLIS); http.connect(); OutputStream os = http.getOutputStream(); os.write(bytes); os.flush(); timer.endTime(); // System.out.println("Bytes Sent: " + bytes.length); } catch (ServerException e) { timer.endTime(); throw e; } catch (SocketTimeoutException e) { timer.endTime(); throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout); } catch (Exception e) { // e.printStackTrace(); throw new Error("putSlot failed"); } try { timer.startTime(); InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); byte[] resptype = new byte[7]; dis.readFully(resptype); timer.endTime(); 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 (SocketTimeoutException e) { timer.endTime(); throw new ServerException("putSlot failed", ServerException.TypeInputTimeout); } catch (Exception e) { // e.printStackTrace(); throw new Error("putSlot failed"); } } /** * Request the server to send all slots with the given * sequencenumber or newer. */ public Slot[] getSlots(long sequencenumber) throws ServerException { URL url = null; URLConnection con = null; HttpURLConnection http = null; try { if (salt == null) { if (!getSalt()) { throw new ServerException("getSlots failed", ServerException.TypeSalt); } initCrypt(); } url = buildRequest(false, sequencenumber, 0); timer.startTime(); con = url.openConnection(); http = (HttpURLConnection) con; http.setRequestMethod("POST"); http.setConnectTimeout(TIMEOUT_MILLIS); http.setReadTimeout(TIMEOUT_MILLIS); http.connect(); timer.endTime(); } catch (SocketTimeoutException e) { timer.endTime(); throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout); } catch (ServerException e) { timer.endTime(); throw e; } catch (Exception e) { // e.printStackTrace(); throw new Error("getSlots failed " + e.toString()); } try { timer.startTime(); InputStream is = http.getInputStream(); DataInputStream dis = new DataInputStream(is); byte[] resptype = new byte[7]; dis.readFully(resptype); timer.endTime(); if (!Arrays.equals(resptype, "getslot".getBytes())) throw new Error("Bad Response: " + new String(resptype)); return processSlots(dis); } catch (SocketTimeoutException e) { timer.endTime(); throw new ServerException("getSlots failed", ServerException.TypeInputTimeout); } catch (Exception e) { // e.printStackTrace(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); throw new Error("getSlots failed " + sw.toString()); } } /** * 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); Log.e("Ali::::", "Slot Process"); } dis.close(); return slots; } public byte[] sendLocalData(byte[] sendData, String host, int port) { if (salt == null) { return null; } try { System.out.println("Passing Locally"); mac.update(sendData); byte[] genmac = mac.doFinal(); byte[] totalData = new byte[sendData.length + genmac.length]; System.arraycopy(sendData, 0, totalData, 0, sendData.length); System.arraycopy(genmac, 0, totalData, sendData.length, genmac.length); // Encrypt the data for sending // byte[] encryptedData = encryptCipher.doFinal(totalData); byte[] encryptedData = encryptCipher.doFinal(totalData); // Open a TCP socket connection to a local device Socket socket = new Socket(host, port); socket.setReuseAddress(true); DataOutputStream output = new DataOutputStream(socket.getOutputStream()); DataInputStream input = new DataInputStream(socket.getInputStream()); timer.startTime(); // Send data to output (length of data, the data) output.writeInt(encryptedData.length); output.write(encryptedData, 0, encryptedData.length); output.flush(); int lengthOfReturnData = input.readInt(); byte[] returnData = new byte[lengthOfReturnData]; input.readFully(returnData); timer.endTime(); returnData = decryptCipher.doFinal(returnData); // We are done with this socket socket.close(); mac.update(returnData, 0, returnData.length - HMAC_SIZE); byte[] realmac = mac.doFinal(); byte[] recmac = new byte[HMAC_SIZE]; System.arraycopy(returnData, returnData.length - realmac.length, recmac, 0, realmac.length); if (!Arrays.equals(recmac, realmac)) throw new Error("Local Error: Invalid HMAC! Potential Attack!"); byte[] returnData2 = new byte[lengthOfReturnData - recmac.length]; System.arraycopy(returnData, 0, returnData2, 0, returnData2.length); return returnData2; } catch (SocketTimeoutException e) { } catch (BadPaddingException e) { } catch (IllegalBlockSizeException e) { } catch (UnknownHostException e) { } catch (IOException e) { } return null; } private void localServerWorkerFunction() { ServerSocket inputSocket = null; try { // Local server socket inputSocket = new ServerSocket(listeningPort); inputSocket.setReuseAddress(true); inputSocket.setSoTimeout(TIMEOUT_MILLIS); } catch (Exception e) { e.printStackTrace(); throw new Error("Local server setup failure..."); } while (!doEnd) { try { // Accept incoming socket Socket socket = inputSocket.accept(); DataInputStream input = new DataInputStream(socket.getInputStream()); DataOutputStream output = new DataOutputStream(socket.getOutputStream()); // Get the encrypted data from the server int dataSize = input.readInt(); byte[] readData = new byte[dataSize]; input.readFully(readData); timer.endTime(); // Decrypt the data readData = decryptCipher.doFinal(readData); mac.update(readData, 0, readData.length - HMAC_SIZE); byte[] genmac = mac.doFinal(); byte[] recmac = new byte[HMAC_SIZE]; System.arraycopy(readData, readData.length - recmac.length, recmac, 0, recmac.length); if (!Arrays.equals(recmac, genmac)) throw new Error("Local Error: Invalid HMAC! Potential Attack!"); byte[] returnData = new byte[readData.length - recmac.length]; System.arraycopy(readData, 0, returnData, 0, returnData.length); // Process the data // byte[] sendData = table.acceptDataFromLocal(readData); byte[] sendData = table.acceptDataFromLocal(returnData); mac.update(sendData); byte[] realmac = mac.doFinal(); byte[] totalData = new byte[sendData.length + realmac.length]; System.arraycopy(sendData, 0, totalData, 0, sendData.length); System.arraycopy(realmac, 0, totalData, sendData.length, realmac.length); // Encrypt the data for sending byte[] encryptedData = encryptCipher.doFinal(totalData); timer.startTime(); // Send data to output (length of data, the data) output.writeInt(encryptedData.length); output.write(encryptedData, 0, encryptedData.length); output.flush(); // close the socket socket.close(); } catch (SocketTimeoutException e) { } catch (BadPaddingException e) { } catch (IllegalBlockSizeException e) { } catch (UnknownHostException e) { } catch (IOException e) { } } if (inputSocket != null) { try { inputSocket.close(); } catch (Exception e) { e.printStackTrace(); throw new Error("Local server close failure..."); } } } public void close() { doEnd = true; if (localServerThread != null) { try { localServerThread.join(); } catch (Exception e) { e.printStackTrace(); throw new Error("Local Server thread join issue..."); } } // System.out.println("Done Closing Cloud Comm"); } protected void finalize() throws Throwable { try { close(); // close open files } finally { super.finalize(); } } }