2 #include "TimingSingleton.h"
3 #include "SecureRandom.h"
10 * Empty Constructor needed for child class.
12 CloudComm::CloudComm() :
21 localServerThread(NULL),
23 timer(TimingSingleton_getInstance())
28 * Constructor for actual use. Takes in the url and password.
30 CloudComm::CloudComm(Table *_table, IoTString *_baseurl, IoTString *_password, int _listeningPort) :
35 random(new SecureRandom()),
38 listeningPort(_listeningPort),
39 localServerThread(NULL),
41 timer(TimingSingleton_getInstance()) {
42 if (listeningPort > 0) {
43 localServerThread = new Thread(new Runnable() {
45 localServerWorkerFunction();
48 localServerThread->start();
53 * Generates Key from password.
55 SecretKeySpec *CloudComm::initKey() {
57 PBEKeySpec *keyspec = new PBEKeySpec(password->internalBytes(),
61 SecretKey *tmpkey = SecretKeyFactory_getInstance("PBKDF2WithHmacSHA256")->generateSecret(keyspec);
62 return new SecretKeySpec(tmpkey->getEncoded(), "AES");
63 } catch (Exception *e) {
64 throw new Error("Failed generating key.");
69 * Inits all the security stuff
72 void CloudComm::initSecurity() {
73 // try to get the salt and if one does not exist set one
83 * Inits the HMAC generator.
85 void CloudComm::initCrypt() {
86 if (password == NULL) {
91 password = NULL;// drop password
92 mac = Mac_getInstance("HmacSHA256");
94 } catch (Exception *e) {
95 throw new Error("Failed To Initialize Ciphers");
100 * Builds the URL for the given request.
102 URL *CloudComm::buildRequest(bool isput, int64_t sequencenumber, int64_t maxentries) {
103 const char *reqstring = isput ? "req=putslot" : "req=getslot";
104 char * buffer = (char *) malloc(baseurl->length() + 200);
105 memcpy(buffer, baseurl->internalBytes(), baseurl->length());
106 int offset = baseurl->length();
107 offset+=sprintf(&buffer[offset], "?%s&seq=%" PRId64, reqstring, sequencenumber);
109 sprintf(&buffer[offset], "&max=%" PRId64, maxentries);
110 IoTString *urlstr = new IoTString(buffer);
112 return new URL(urlstr);
115 void CloudComm::setSalt() {
117 // Salt already sent to server so don't set it again
122 Array<char> *saltTmp = new Array<char>(CloudComm_SALT_SIZE);
123 random->nextBytes(saltTmp);
125 char * buffer = (char *) malloc(baseurl->length() + 100);
126 memcpy(buffer, baseurl->internalBytes(), baseurl->length());
127 int offset = baseurl->length();
128 offset+=sprintf(&buffer[offset], "?req=setsalt");
129 IoTString *urlstr = new IoTString(buffer);
132 URL *url = new URL(urlstr);
134 URLConnection *con = url->openConnection();
135 HttpURLConnection *http = (HttpURLConnection *) con;
137 http->setRequestMethod("POST");
138 http->setFixedLengthStreamingMode(saltTmp->length());
139 http->setDoOutput(true);
140 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
143 OutputStream *os = http->getOutputStream();
147 int responsecode = http->getResponseCode();
148 if (responsecode != HttpURLConnection_HTTP_OK) {
149 throw new Error("Invalid response");
154 } catch (Exception *e) {
156 throw new ServerException("Failed setting salt", ServerException_TypeConnectTimeout);
160 bool CloudComm::getSalt() {
162 URLConnection *con = NULL;
163 HttpURLConnection *http = NULL;
166 char * buffer = (char *) malloc(baseurl->length() + 100);
167 memcpy(buffer, baseurl->internalBytes(), baseurl->length());
168 int offset = baseurl->length();
169 offset+=sprintf(&buffer[offset], "?req=getsalt");
170 IoTString *urlstr = new IoTString(buffer);
173 url = new URL(urlstr);
174 } catch (Exception *e) {
175 throw new Error("getSlot failed");
179 con = url->openConnection();
180 http = (HttpURLConnection *) con;
181 http->setRequestMethod("POST");
182 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
183 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
186 } catch (SocketTimeoutException *e) {
188 throw new ServerException("getSalt failed", ServerException_TypeConnectTimeout);
189 } catch (Exception *e) {
190 throw new Error("getSlot failed");
195 int responsecode = http->getResponseCode();
196 if (responsecode != HttpURLConnection_HTTP_OK) {
197 throw new Error("Invalid response");
199 InputStream *is = http->getInputStream();
200 if (is->available() > 0) {
201 DataInputStream *dis = new DataInputStream(is);
202 int salt_length = dis->readInt();
203 Array<char> *tmp = new Array<char>(salt_length);
212 } catch (SocketTimeoutException *e) {
214 throw new ServerException("getSalt failed", ServerException_TypeInputTimeout);
215 } catch (Exception *e) {
216 throw new Error("getSlot failed");
220 Array<char> *CloudComm::createIV(int64_t machineId, int64_t localSequenceNumber) {
221 ByteBuffer *buffer = ByteBuffer_allocate(CloudComm_IV_SIZE);
222 buffer->putLong(machineId);
223 int64_t localSequenceNumberShifted = localSequenceNumber << 16;
224 buffer->putLong(localSequenceNumberShifted);
225 return buffer->array();
228 Array<char> *CloudComm::encryptSlotAndPrependIV(Array<char> *rawData, Array<char> *ivBytes) {
230 IvParameterSpec *ivSpec = new IvParameterSpec(ivBytes);
231 Cipher *cipher = Cipher_getInstance("AES/CTR/NoPadding");
232 cipher->init(Cipher_ENCRYPT_MODE, key, ivSpec);
233 Array<char> *encryptedBytes = cipher->doFinal(rawData);
234 Array<char> *chars = new Array<char>(encryptedBytes->length() + CloudComm_IV_SIZE);
235 System_arraycopy(ivBytes, 0, chars, 0, ivBytes->length());
236 System_arraycopy(encryptedBytes, 0, chars, CloudComm_IV_SIZE, encryptedBytes->length());
239 } catch (Exception *e) {
240 throw new Error("Failed To Encrypt");
244 Array<char> *CloudComm::stripIVAndDecryptSlot(Array<char> *rawData) {
246 Array<char> *ivBytes = new Array<char>(CloudComm_IV_SIZE);
247 Array<char> *encryptedBytes = new Array<char>(rawData->length() - CloudComm_IV_SIZE);
248 System_arraycopy(rawData, 0, ivBytes, 0, CloudComm_IV_SIZE);
249 System_arraycopy(rawData, CloudComm_IV_SIZE, encryptedBytes, 0, encryptedBytes->length);
250 IvParameterSpec *ivSpec = new IvParameterSpec(ivBytes);
251 Cipher *cipher = Cipher_getInstance("AES/CTR/NoPadding");
252 cipher->init(Cipher_DECRYPT_MODE, key, ivSpec);
253 return cipher->doFinal(encryptedBytes);
254 } catch (Exception *e) {
255 throw new Error("Failed To Decrypt");
260 * API for putting a slot into the queue. Returns NULL on success.
261 * On failure, the server will send slots with newer sequence
264 Array<Slot *> *CloudComm::putSlot(Slot *slot, int max) {
268 throw new ServerException("putSlot failed", ServerException_TypeSalt);
273 int64_t sequencenumber = slot->getSequenceNumber();
274 Array<char> *slotBytes = slot->encode(mac);
275 Array<char> *chars = encryptSlotAndPrependIV(slotBytes, slot->getSlotCryptIV());
276 URL *url = buildRequest(true, sequencenumber, max);
278 URLConnection *con = url->openConnection();
279 HttpURLConnection *http = (HttpURLConnection *) con;
280 http->setRequestMethod("POST");
281 http->setFixedLengthStreamingMode(chars->length);
282 http->setDoOutput(true);
283 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
284 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
286 OutputStream *os = http->getOutputStream();
290 } catch (ServerException *e) {
293 } catch (SocketTimeoutException *e) {
295 throw new ServerException("putSlot failed", ServerException_TypeConnectTimeout);
296 } catch (Exception *e) {
297 throw new Error("putSlot failed");
302 InputStream *is = http->getInputStream();
303 DataInputStream *dis = new DataInputStream(is);
304 Array<char> *resptype = new Array<char>(7);
305 dis->readFully(resptype);
308 if (Arrays->equals(resptype, "getslot"->getBytes())) {
309 return processSlots(dis);
310 } else if (Arrays->equals(resptype, "putslot"->getBytes())) {
313 throw new Error("Bad response to putslot");
314 } catch (SocketTimeoutException *e) {
316 throw new ServerException("putSlot failed", ServerException->TypeInputTimeout);
317 } catch (Exception *e) {
318 throw new Error("putSlot failed");
323 * Request the server to send all slots with the given
324 * sequencenumber or newer->
326 Array<Slot *> *CloudComm::getSlots(int64_t sequencenumber) {
330 throw new ServerException("getSlots failed", ServerException_TypeSalt);
335 URL *url = buildRequest(false, sequencenumber, 0);
337 URLConnection *con = url->openConnection();
338 HttpURLConnection *http = (HttpURLConnection *) con;
339 http->setRequestMethod("POST");
340 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
341 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
344 } catch (SocketTimeoutException *e) {
346 throw new ServerException("getSlots failed", ServerException_TypeConnectTimeout);
347 } catch (ServerException *e) {
351 } catch (Exception *e) {
352 throw new Error("getSlots failed");
357 InputStream *is = http->getInputStream();
358 DataInputStream *dis = new DataInputStream(is);
359 Array<char> *resptype = new Array<char>(7);
360 dis->readFully(resptype);
362 if (!resptype->equals("getslot"->getBytes()))
363 throw new Error("Bad Response: " + new String(resptype));
365 return processSlots(dis);
366 } catch (SocketTimeoutException *e) {
368 throw new ServerException("getSlots failed", ServerException_TypeInputTimeout);
369 } catch (Exception *e) {
370 throw new Error("getSlots failed");
375 * Method that actually handles building Slot objects from the
376 * server response. Shared by both putSlot and getSlots.
378 Array<Slot *> *CloudComm::processSlots(DataInputStream *dis) {
379 int numberofslots = dis->readInt();
380 Array<int> *sizesofslots = new Array<int>(numberofslots);
381 Array<Slot *> *slots = new Array<Slot *>(numberofslots);
383 for (int i = 0; i < numberofslots; i++)
384 sizesofslots->set(i, dis->readInt());
385 for (int i = 0; i < numberofslots; i++) {
386 Array<char> *rawData = new Array<char>(sizesofslots->get(i));
387 dis->readFully(rawData);
388 Array<char> *data = stripIVAndDecryptSlot(rawData);
389 slots->set(i, Slot_decode(table, data, mac));
395 Array<char> *sendLocalData(Array<char> *sendData, int64_t localSequenceNumber, String host, int port) {
399 printf("Passing Locally\n");
400 mac->update(sendData);
401 Array<char> *genmac = mac->doFinal();
402 Array<char> *totalData = new Array<char>(sendData->length() + genmac->length());
403 System_arraycopy(sendData, 0, totalData, 0, sendData->length());
404 System_arraycopy(genmac, 0, totalData, sendData->length(), genmac->length());
406 // Encrypt the data for sending
407 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
408 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
410 // Open a TCP socket connection to a local device
411 Socket *socket = new Socket(host, port);
412 socket->setReuseAddress(true);
413 DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
414 DataInputStream *input = new DataInputStream(socket->getInputStream());
417 // Send data to output (length of data, the data)
418 output->writeInt(encryptedData->length);
419 output->write(encryptedData, 0, encryptedData->length);
422 int lengthOfReturnData = input->readInt();
423 Array<char> *returnData = new Array<char>(lengthOfReturnData);
424 input->readFully(returnData);
426 returnData = stripIVAndDecryptSlot(returnData);
428 // We are done with this socket
430 mac->update(returnData, 0, returnData->length - HMAC_SIZE);
431 Array<char> *realmac = mac->doFinal();
432 Array<char> *recmac = new Array<char>(HMAC_SIZE);
433 System_arraycopy(returnData, returnData->length - realmac->length, recmac, 0, realmac->length);
435 if (!recmac->equals(realmac))
436 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
438 Array<char> *returnData2 = new Array<char>(lengthOfReturnData - recmac->length());
439 System_arraycopy(returnData, 0, returnData2, 0, returnData2->length);
442 } catch (Exception *e) {
443 printf("Exception\n");
449 void CloudComm::localServerWorkerFunction() {
450 ServerSocket *inputSocket = NULL;
453 // Local server socket
454 inputSocket = new ServerSocket(listeningPort);
455 inputSocket->setReuseAddress(true);
456 inputSocket->setSoTimeout(CloudComm_TIMEOUT_MILLIS);
457 } catch (Exception *e) {
458 throw new Error("Local server setup failure...");
463 // Accept incoming socket
464 Socket *socket = inputSocket->accept();
465 DataInputStream *input = new DataInputStream(socket->getInputStream());
466 DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
468 // Get the encrypted data from the server
469 int dataSize = input->readInt();
470 Array<char> *readData = new Array<char>(dataSize);
471 input->readFully(readData);
475 readData = stripIVAndDecryptSlot(readData);
476 mac->update(readData, 0, readData->length - HMAC_SIZE);
477 Array<char> *genmac = mac->doFinal();
478 Array<char> *recmac = new Array<char>(HMAC_SIZE);
479 System_arraycopy(readData, readData->length() - recmac->length(), recmac, 0, recmac->length());
481 if (!recmac->equals(genmac))
482 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
484 Array<char> *returnData = new Array<char>(readData->length() - recmac->length());
485 System_arraycopy(readData, 0, returnData, 0, returnData->length());
488 Array<char> *sendData = table->acceptDataFromLocal(returnData);
489 mac->update(sendData);
490 Array<char> *realmac = mac->doFinal();
491 Array<char> *totalData = new Array<char>(sendData->length() + realmac->length());
492 System_arraycopy(sendData, 0, totalData, 0, sendData->length());
493 System_arraycopy(realmac, 0, totalData, sendData->length(), realmac->length());
495 // Encrypt the data for sending
496 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
497 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
500 // Send data to output (length of data, the data)
501 output->writeInt(encryptedData->length());
502 output->write(encryptedData, 0, encryptedData->length());
507 } catch (Exception *e) {
511 if (inputSocket != NULL) {
513 inputSocket->close();
514 } catch (Exception *e) {
515 throw new Error("Local server close failure...");
520 void CloudComm::close() {
523 if (localServerThread != NULL) {
525 localServerThread->join();
526 } catch (Exception *e) {
527 throw new Error("Local Server thread join issue...");