Adding Fidelius manual.
[iotcloud.git] / version2 / backup / 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                         throw new ServerException("Failed setting salt");
115                 }
116                 initCrypt();
117         }
118
119         private void getSalt() throws Exception {
120                 URL url = new URL(baseurl + "?req=getsalt");
121                 URLConnection con = url.openConnection();
122                 HttpURLConnection http = (HttpURLConnection) con;
123                 http.setRequestMethod("POST");
124                 http.connect();
125
126                 InputStream is = http.getInputStream();
127                 DataInputStream dis = new DataInputStream(is);
128                 int salt_length = dis.readInt();
129                 byte [] tmp = new byte[salt_length];
130                 dis.readFully(tmp);
131                 salt = tmp;
132         }
133
134         /*
135          * API for putting a slot into the queue.  Returns null on success.
136          * On failure, the server will send slots with newer sequence
137          * numbers.
138          */
139
140         Slot[] putSlot(Slot slot, int max) throws ServerException {
141                 try {
142                         if (salt == null) {
143                                 getSalt();
144                                 initCrypt();
145                         }
146
147                         long sequencenumber = slot.getSequenceNumber();
148                         byte[] bytes = slot.encode(mac);
149                         bytes = encryptCipher.doFinal(bytes);
150
151
152                         URL url = buildRequest(true, sequencenumber, max);
153                         URLConnection con = url.openConnection();
154                         HttpURLConnection http = (HttpURLConnection) con;
155
156                         http.setRequestMethod("POST");
157                         http.setFixedLengthStreamingMode(bytes.length);
158                         http.setDoOutput(true);
159                         http.setConnectTimeout(TIMEOUT_MILLIS);
160                         // http.setReadTimeout(TIMEOUT_MILLIS);
161                         http.connect();
162
163                         OutputStream os = http.getOutputStream();
164                         os.write(bytes);
165                         os.flush();
166
167
168                         InputStream is = http.getInputStream();
169                         DataInputStream dis = new DataInputStream(is);
170                         byte[] resptype = new byte[7];
171                         dis.readFully(resptype);
172
173                         if (Arrays.equals(resptype, "getslot".getBytes()))
174                                 return processSlots(dis);
175                         else if (Arrays.equals(resptype, "putslot".getBytes()))
176                                 return null;
177                         else
178                                 throw new Error("Bad response to putslot");
179
180                 } catch (Exception e) {
181                         throw new ServerException("putSlot failed");
182                 }
183         }
184
185
186         /**
187          * Request the server to send all slots with the given
188          * sequencenumber or newer.
189          */
190         Slot[] getSlots(long sequencenumber) throws ServerException {
191                 try {
192                         if (salt == null) {
193                                 getSalt();
194                                 initCrypt();
195                         }
196
197                         URL url = buildRequest(false, sequencenumber, 0);
198                         URLConnection con = url.openConnection();
199                         HttpURLConnection http = (HttpURLConnection) con;
200                         http.setRequestMethod("POST");
201                         http.setConnectTimeout(TIMEOUT_MILLIS);
202                         // http.setReadTimeout(TIMEOUT_MILLIS);
203                         http.connect();
204                         InputStream is = http.getInputStream();
205                         DataInputStream dis = new DataInputStream(is);
206
207                         int responsecode = http.getResponseCode();
208                         if (responsecode != HttpURLConnection.HTTP_OK) {
209                                 // TODO: Remove this print
210                                 // System.out.println("Code:  " + responsecode);
211                                 throw new ServerException("getSlots failed");
212                         }
213
214                         byte[] resptype = new byte[7];
215                         dis.readFully(resptype);
216                         if (!Arrays.equals(resptype, "getslot".getBytes()))
217                                 throw new Error("Bad Response: " + new String(resptype));
218                         else
219                                 return processSlots(dis);
220                 } catch (Exception e) {
221                         // e.printStackTrace();
222                         throw new ServerException("getSlots failed");
223                 }
224         }
225
226         public boolean hasConnection() {
227                 try {
228                         InetAddress address = InetAddress.getByName(hostname);
229                         return address.isReachable(TIMEOUT_MILLIS);
230                 } catch (Exception e) {
231                         return false;
232                 }
233         }
234
235         /**
236          * Method that actually handles building Slot objects from the
237          * server response.  Shared by both putSlot and getSlots.
238          */
239         private Slot[] processSlots(DataInputStream dis) throws Exception {
240                 int numberofslots = dis.readInt();
241                 int[] sizesofslots = new int[numberofslots];
242                 Slot[] slots = new Slot[numberofslots];
243                 for (int i = 0; i < numberofslots; i++)
244                         sizesofslots[i] = dis.readInt();
245
246                 for (int i = 0; i < numberofslots; i++) {
247                         byte[] data = new byte[sizesofslots[i]];
248                         dis.readFully(data);
249
250                         data = decryptCipher.doFinal(data);
251
252                         slots[i] = Slot.decode(table, data, mac);
253                 }
254                 dis.close();
255                 return slots;
256         }
257
258
259
260
261 }