964b7c04d51f9b95b28100da31d9a19af8ec107d
[iotcloud.git] / 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 baseurl;
19         Cipher encryptCipher;
20         Cipher decryptCipher;
21         Mac mac;
22         String password;
23         SecureRandom random;
24         static final int SALT_SIZE = 8;
25         byte salt[];
26         
27         /**
28          * Empty Constructor needed for child class.
29          */
30
31         CloudComm() {
32         }
33
34         /**
35          * Constructor for actual use. Takes in the url and password.
36          */
37
38         CloudComm(String _baseurl, String _password) {
39                 this.baseurl=_baseurl;
40                 this.password = _password;
41                 this.random = new SecureRandom();
42         }
43
44         /**
45          * Generates Key from password.
46          */
47
48         private SecretKeySpec initKey() {
49                 try {
50                         PBEKeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
51                         SecretKey tmpkey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keyspec);
52                         return new SecretKeySpec(tmpkey.getEncoded(), "AES");
53                 } catch (Exception e) {
54                         e.printStackTrace();
55                         throw new Error("Failed generating key.");
56                 }
57         }
58
59         /**
60          * Inits the HMAC generator.
61          */
62
63         private void initCrypt() {
64                 try {
65                         SecretKeySpec key=initKey();
66                         password = null; // drop password
67                         mac = Mac.getInstance("HmacSHA256");
68                         mac.init(key);
69                         encryptCipher =Cipher.getInstance("AES/ECB/PKCS5Padding");
70                         encryptCipher.init(Cipher.ENCRYPT_MODE, key);
71                         decryptCipher =Cipher.getInstance("AES/ECB/PKCS5Padding");
72                         decryptCipher.init(Cipher.DECRYPT_MODE, key);
73                 } catch (Exception e) {
74                         e.printStackTrace();
75                         throw new Error("Failed To Initialize Ciphers");
76                 }
77         }
78
79         /*
80          * Builds the URL for the given request.
81          */
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() {
92                 try {
93                         salt = new byte[SALT_SIZE];
94                         random.nextBytes(salt);
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(salt.length);
100                         http.setDoOutput(true);
101                         http.connect();
102                         OutputStream os=http.getOutputStream();
103                         os.write(salt);
104                         int responsecode=http.getResponseCode();
105                         if (responsecode != HttpURLConnection.HTTP_OK)
106                                 throw new Error("Invalid response");
107                 } catch (Exception e) {
108                         e.printStackTrace();
109                         throw new Error("Failed setting salt");
110                 }
111                 initCrypt();
112         }
113
114         private void getSalt() throws Exception {
115                 URL url=new URL(baseurl+"?req=getsalt");
116                 URLConnection con=url.openConnection();
117                 HttpURLConnection http = (HttpURLConnection) con;
118                 http.setRequestMethod("POST");
119                 http.connect();
120                 
121                 InputStream is=http.getInputStream();
122                 DataInputStream dis=new DataInputStream(is);
123                 int salt_length=dis.readInt();
124                 byte [] tmp=new byte[salt_length];
125                 dis.readFully(tmp);
126                 salt=tmp;
127         }
128         
129         /*
130          * API for putting a slot into the queue.  Returns null on success.
131          * On failure, the server will send slots with newer sequence
132          * numbers.
133          */
134
135         Slot[] putSlot(Slot slot, int max) {
136                 try {
137                         if (salt == null) {
138                                 getSalt();
139                                 initCrypt();
140                         }
141                         
142                         long sequencenumber=slot.getSequenceNumber();
143                         byte[] bytes=slot.encode(mac);
144                         bytes = encryptCipher.doFinal(bytes);
145
146                         URL url=buildRequest(true, sequencenumber, max);
147                         URLConnection con=url.openConnection();
148                         HttpURLConnection http = (HttpURLConnection) con;
149
150                         http.setRequestMethod("POST");
151                         http.setFixedLengthStreamingMode(bytes.length);
152                         http.setDoOutput(true);
153                         http.connect();
154
155                         OutputStream os=http.getOutputStream();
156                         os.write(bytes);
157
158                         InputStream is=http.getInputStream();
159                         DataInputStream dis=new DataInputStream(is);
160                         byte[] resptype=new byte[7];
161                         dis.readFully(resptype);
162                         if (Arrays.equals(resptype, "getslot".getBytes()))
163                                 return processSlots(dis);
164                         else if (Arrays.equals(resptype, "putslot".getBytes()))
165                                 return null;
166                         else
167                                 throw new Error("Bad response to putslot");
168                 } catch (Exception e) {
169                         e.printStackTrace();
170                         throw new Error("putSlot failed");
171                 }
172         }
173
174         /**
175          * Request the server to send all slots with the given
176          * sequencenumber or newer.
177          */
178
179         Slot[] getSlots(long sequencenumber) {
180                 try {
181                         if (salt == null) {
182                                 getSalt();
183                                 initCrypt();
184                         }
185                         
186                         URL url=buildRequest(false, sequencenumber, 0);
187                         URLConnection con=url.openConnection();
188                         HttpURLConnection http = (HttpURLConnection) con;
189                         http.setRequestMethod("POST");
190                         http.connect();
191                         InputStream is=http.getInputStream();
192
193                         DataInputStream dis=new DataInputStream(is);
194                         
195                         byte[] resptype=new byte[7];
196                         dis.readFully(resptype);
197                         if (!Arrays.equals(resptype, "getslot".getBytes()))
198                                 throw new Error("Bad Response: "+new String(resptype));
199                         else
200                                 return processSlots(dis);
201                 } catch (Exception e) {
202                         e.printStackTrace();
203                         throw new Error("getSlots failed");
204                 }
205         }
206
207         /**
208          * Method that actually handles building Slot objects from the
209          * server response.  Shared by both putSlot and getSlots.
210          */
211
212         private Slot[] processSlots(DataInputStream dis) throws Exception {
213                 int numberofslots=dis.readInt();
214                 int[] sizesofslots=new int[numberofslots];
215                 Slot[] slots=new Slot[numberofslots];
216                 for(int i=0; i<numberofslots; i++)
217                         sizesofslots[i]=dis.readInt();
218
219                 for(int i=0; i<numberofslots; i++) {
220                         byte[] data=new byte[sizesofslots[i]];
221                         dis.readFully(data);
222
223                         data = decryptCipher.doFinal(data);
224
225                         slots[i]=Slot.decode(data, mac);
226                 }
227                 dis.close();
228                 return slots;
229         }
230 }