API Changes
[iotcloud.git] / version2 / src / java / iotcloud / CloudComm.java
1 package iotcloud;
2 import java.io.*;
3 import java.net.*;
4 import java.util.Arrays;
5 import javax.crypto.*;
6 import javax.crypto.spec.*;
7 import java.security.SecureRandom;
8
9 /**
10  * This class provides a communication API to the webserver.  It also
11  * validates the HMACs on the slots and handles encryption.
12  * @author Brian Demsky <bdemsky@uci.edu>
13  * @version 1.0
14  */
15
16
17 class CloudComm {
18         String hostname;
19         String baseurl;
20         Cipher encryptCipher;
21         Cipher decryptCipher;
22         Mac mac;
23         String password;
24         SecureRandom random;
25         static final int SALT_SIZE = 8;
26         static final int TIMEOUT_MILLIS = 100;
27         byte salt[];
28         Table table;
29
30         /**
31          * Empty Constructor needed for child class.
32          */
33         CloudComm() {
34         }
35
36         /**
37          * Constructor for actual use. Takes in the url and password.
38          */
39         CloudComm(Table _table, String _hostname, String _baseurl, String _password) {
40                 this.table = _table;
41                 this.hostname = _hostname;
42                 this.baseurl = _baseurl;
43                 this.password = _password;
44                 this.random = new SecureRandom();
45         }
46
47         /**
48          * Generates Key from password.
49          */
50         private SecretKeySpec initKey() {
51                 try {
52                         PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
53                         SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
54                         return new SecretKeySpec(tmpkey.getEncoded(), "AES");
55                 } catch (Exception e) {
56                         e.printStackTrace();
57                         throw new Error("Failed generating key.");
58                 }
59         }
60
61         /**
62          * Inits the HMAC generator.
63          */
64         private void initCrypt() {
65                 try {
66                         SecretKeySpec key = initKey();
67                         password = null; // drop password
68                         mac = Mac.getInstance("HmacSHA256");
69                         mac.init(key);
70                         encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
71                         encryptCipher.init(Cipher.ENCRYPT_MODE, key);
72                         decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
73                         decryptCipher.init(Cipher.DECRYPT_MODE, key);
74                 } catch (Exception e) {
75                         e.printStackTrace();
76                         throw new Error("Failed To Initialize Ciphers");
77                 }
78         }
79
80         /*
81          * Builds the URL for the given request.
82          */
83         private URL buildRequest(boolean isput, long sequencenumber, long maxentries) throws IOException {
84                 String reqstring = isput ? "req=putslot" : "req=getslot";
85                 String urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
86                 if (maxentries != 0)
87                         urlstr += "&max=" + maxentries;
88                 return new URL(urlstr);
89         }
90
91         public void setSalt() throws ServerException {
92                 try {
93                         byte[] saltTmp = new byte[SALT_SIZE];
94                         random.nextBytes(saltTmp);
95                         URL url = new URL(baseurl + "?req=setsalt");
96                         URLConnection con = url.openConnection();
97                         HttpURLConnection http = (HttpURLConnection) con;
98                         http.setRequestMethod("POST");
99                         http.setFixedLengthStreamingMode(saltTmp.length);
100                         http.setDoOutput(true);
101                         http.setConnectTimeout(TIMEOUT_MILLIS);
102                         http.connect();
103                         OutputStream os = http.getOutputStream();
104                         os.write(saltTmp);
105                         int responsecode = http.getResponseCode();
106                         if (responsecode != HttpURLConnection.HTTP_OK) {
107                                 // TODO: Remove this print
108                                 // System.out.println(responsecode);
109                                 throw new Error("Invalid response");
110                         }
111
112                         salt = saltTmp;
113                 } catch (Exception e) {
114                         e.printStackTrace();
115                         throw new ServerException("Failed setting salt");
116                 }
117                 initCrypt();
118         }
119
120         private void getSalt() throws Exception {
121                 URL url = new URL(baseurl + "?req=getsalt");
122                 URLConnection con = url.openConnection();
123                 HttpURLConnection http = (HttpURLConnection) con;
124                 http.setRequestMethod("POST");
125                 http.connect();
126
127                 InputStream is = http.getInputStream();
128                 DataInputStream dis = new DataInputStream(is);
129                 int salt_length = dis.readInt();
130                 byte [] tmp = new byte[salt_length];
131                 dis.readFully(tmp);
132                 salt = tmp;
133         }
134
135         /*
136          * API for putting a slot into the queue.  Returns null on success.
137          * On failure, the server will send slots with newer sequence
138          * numbers.
139          */
140
141         Slot[] putSlot(Slot slot, int max) throws ServerException {
142                 try {
143                         if (salt == null) {
144                                 getSalt();
145                                 initCrypt();
146                         }
147
148                         long sequencenumber = slot.getSequenceNumber();
149                         byte[] bytes = slot.encode(mac);
150                         bytes = encryptCipher.doFinal(bytes);
151
152
153                         URL url = buildRequest(true, sequencenumber, max);
154                         URLConnection con = url.openConnection();
155                         HttpURLConnection http = (HttpURLConnection) con;
156
157                         http.setRequestMethod("POST");
158                         http.setFixedLengthStreamingMode(bytes.length);
159                         http.setDoOutput(true);
160                         http.setConnectTimeout(TIMEOUT_MILLIS);
161                         // http.setReadTimeout(TIMEOUT_MILLIS);
162                         http.connect();
163
164                         OutputStream os = http.getOutputStream();
165                         os.write(bytes);
166                         os.flush();
167
168
169                         InputStream is = http.getInputStream();
170                         DataInputStream dis = new DataInputStream(is);
171                         byte[] resptype = new byte[7];
172                         dis.readFully(resptype);
173
174                         if (Arrays.equals(resptype, "getslot".getBytes()))
175                                 return processSlots(dis);
176                         else if (Arrays.equals(resptype, "putslot".getBytes()))
177                                 return null;
178                         else
179                                 throw new Error("Bad response to putslot");
180
181                 } catch (Exception e) {
182                         throw new ServerException("putSlot failed");
183                 }
184         }
185
186
187         /**
188          * Request the server to send all slots with the given
189          * sequencenumber or newer.
190          */
191         Slot[] getSlots(long sequencenumber) throws ServerException {
192                 try {
193                         if (salt == null) {
194                                 getSalt();
195                                 initCrypt();
196                         }
197
198                         URL url = buildRequest(false, sequencenumber, 0);
199                         URLConnection con = url.openConnection();
200                         HttpURLConnection http = (HttpURLConnection) con;
201                         http.setRequestMethod("POST");
202                         http.setConnectTimeout(TIMEOUT_MILLIS);
203                         // http.setReadTimeout(TIMEOUT_MILLIS);
204                         http.connect();
205                         InputStream is = http.getInputStream();
206                         DataInputStream dis = new DataInputStream(is);
207
208                         int responsecode = http.getResponseCode();
209                         if (responsecode != HttpURLConnection.HTTP_OK) {
210                                 // TODO: Remove this print
211                                 // System.out.println("Code:  " + responsecode);
212                                 throw new ServerException("getSlots failed");
213                         }
214
215                         byte[] resptype = new byte[7];
216                         dis.readFully(resptype);
217                         if (!Arrays.equals(resptype, "getslot".getBytes()))
218                                 throw new Error("Bad Response: " + new String(resptype));
219                         else
220                                 return processSlots(dis);
221                 } catch (Exception e) {
222                         // e.printStackTrace();
223                         throw new ServerException("getSlots failed");
224                 }
225         }
226
227         public boolean hasConnection() {
228                 try {
229                         InetAddress address = InetAddress.getByName(hostname);
230                         return address.isReachable(TIMEOUT_MILLIS);
231                 } catch (Exception e) {
232                         return false;
233                 }
234         }
235
236         /**
237          * Method that actually handles building Slot objects from the
238          * server response.  Shared by both putSlot and getSlots.
239          */
240         private Slot[] processSlots(DataInputStream dis) throws Exception {
241                 int numberofslots = dis.readInt();
242                 int[] sizesofslots = new int[numberofslots];
243                 Slot[] slots = new Slot[numberofslots];
244                 for (int i = 0; i < numberofslots; i++)
245                         sizesofslots[i] = dis.readInt();
246
247                 for (int i = 0; i < numberofslots; i++) {
248                         byte[] data = new byte[sizesofslots[i]];
249                         dis.readFully(data);
250
251                         data = decryptCipher.doFinal(data);
252
253                         slots[i] = Slot.decode(table, data, mac);
254                 }
255                 dis.close();
256                 return slots;
257         }
258
259
260
261
262 }