2 #include "TimingSingleton.h"
3 #include "SecureRandom.h"
9 * Empty Constructor needed for child class.
11 CloudComm::CloudComm() :
20 localServerThread(NULL),
22 timer(TimingSingleton_getInstance())
27 * Constructor for actual use. Takes in the url and password.
29 CloudComm::CloudComm(Table *_table, IoTString *_baseurl, IoTString *_password, int _listeningPort) :
34 random(new SecureRandom()),
37 listeningPort(_listeningPort),
38 localServerThread(NULL),
40 timer(TimingSingleton_getInstance()) {
41 if (listeningPort > 0) {
42 localServerThread = new Thread(new Runnable() {
44 localServerWorkerFunction();
47 localServerThread->start();
52 * Generates Key from password.
54 SecretKeySpec *CloudComm::initKey() {
56 PBEKeySpec keyspec = new PBEKeySpec(password->internalBytes(),
60 SecretKey tmpkey = SecretKeyFactory_getInstance("PBKDF2WithHmacSHA256")->generateSecret(keyspec);
61 return new SecretKeySpec(tmpkey->getEncoded(), "AES");
62 } catch (Exception *e) {
63 throw new Error("Failed generating key.");
68 * Inits all the security stuff
71 void CloudComm::initSecurity() {
72 // try to get the salt and if one does not exist set one
82 * Inits the HMAC generator.
84 void CloudComm::initCrypt() {
86 if (password == NULL) {
92 password = NULL;// drop password
93 mac = Mac_getInstance("HmacSHA256");
95 } catch (Exception *e) {
96 throw new Error("Failed To Initialize Ciphers");
101 * Builds the URL for the given request.
103 URL *CloudComm::buildRequest(bool isput, int64_t sequencenumber, int64_t maxentries) {
104 IoTString *reqstring = isput ? "req=putslot" : "req=getslot";
105 IoTString *urlstr = baseurl + "?" + reqstring + "&seq=" + sequencenumber;
107 urlstr += "&max=" + maxentries;
108 return new URL(urlstr);
111 void CloudComm::setSalt() {
114 // Salt already sent to server so dont set it again
119 Array<char> *saltTmp = new Array<char>(CloudComm_SALT_SIZE);
120 random->nextBytes(saltTmp);
122 for (int i = 0; i < CloudComm_SALT_SIZE; i++) {
123 printf("%d\n", (int)saltTmp->get(i) & 255);
127 URL *url = new URL(baseurl + "?req=setsalt");
130 URLConnection con = url->openConnection();
131 HttpURLConnection http = (HttpURLConnection) con;
133 http->setRequestMethod("POST");
134 http->setFixedLengthStreamingMode(saltTmp->length());
135 http->setDoOutput(true);
136 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
141 OutputStream *os = http->getOutputStream();
145 int responsecode = http->getResponseCode();
146 if (responsecode != HttpURLConnection.HTTP_OK) {
147 // TODO: Remove this print
148 System.out.println(responsecode);
149 throw new Error("Invalid response");
155 } catch (Exception *e) {
157 throw new ServerException("Failed setting salt", ServerException.TypeConnectTimeout);
161 bool CloudComm::getSalt() {
163 URLConnection *con = NULL;
164 HttpURLConnection *http = NULL;
167 url = new URL(baseurl + "?req=getsalt");
168 } catch (Exception *e) {
169 throw new Error("getSlot failed");
174 con = url->openConnection();
175 http = (HttpURLConnection) con;
176 http->setRequestMethod("POST");
177 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
178 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
183 } catch (SocketTimeoutException *e) {
185 throw new ServerException("getSalt failed", ServerException.TypeConnectTimeout);
186 } catch (Exception *e) {
187 throw new Error("getSlot failed");
194 int responsecode = http.getResponseCode();
195 if (responsecode != HttpURLConnection.HTTP_OK) {
196 // TODO: Remove this print
197 // System.out.println(responsecode);
198 throw new Error("Invalid response");
201 InputStream is = http->getInputStream();
202 if (is->available() > 0) {
203 DataInputStream *dis = new DataInputStream(is);
204 int salt_length = dis->readInt();
205 Array<char> *tmp = new Array<char>(salt_length);
216 } catch (SocketTimeoutException *e) {
219 throw new ServerException("getSalt failed", ServerException.TypeInputTimeout);
220 } catch (Exception *e) {
221 throw new Error("getSlot failed");
225 Array<char> *CloudComm::createIV(int64_t machineId, int64_t localSequenceNumber) {
226 ByteBuffer buffer = ByteBuffer.allocate(CloudComm_IV_SIZE);
227 buffer->putLong(machineId);
228 int64_t localSequenceNumberShifted = localSequenceNumber << 16;
229 buffer->putLong(localSequenceNumberShifted);
230 return buffer->array();
233 Array<char> *CloudComm::encryptSlotAndPrependIV(Array<char> *rawData, Array<char> *ivBytes) {
235 IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
236 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
237 cipher->init(Cipher.ENCRYPT_MODE, key, ivSpec);
239 Array<char> *encryptedBytes = cipher->doFinal(rawData);
241 Array<char> *chars = new Array<char>(encryptedBytes->length() + CloudComm_IV_SIZE);
242 System_arraycopy(ivBytes, 0, chars, 0, ivBytes.length());
243 System_arraycopy(encryptedBytes, 0, chars, CloudComm_IV_SIZE, encryptedBytes.length);
247 } catch (Exception *e) {
248 throw new Error("Failed To Encrypt");
253 Array<char> *CloudComm::stripIVAndDecryptSlot(Array<char> *rawData) {
255 Array<char> *ivBytes = new Array<char>(CloudComm_IV_SIZE);
256 Array<char> *encryptedBytes = new Array<char>(rawData->length() - CloudComm_IV_SIZE);
257 System_arraycopy(rawData, 0, ivBytes, 0, CloudComm_IV_SIZE);
258 System_arraycopy(rawData, CloudComm_IV_SIZE, encryptedBytes, 0, encryptedBytes->length);
260 IvParameterSpec *ivSpec = new IvParameterSpec(ivBytes);
262 Cipher *cipher = Cipher_getInstance("AES/CTR/NoPadding");
263 cipher->init(Cipher_DECRYPT_MODE, key, ivSpec);
264 return cipher->doFinal(encryptedBytes);
266 } catch (Exception *e) {
267 throw new Error("Failed To Decrypt");
273 * API for putting a slot into the queue. Returns NULL on success.
274 * On failure, the server will send slots with newer sequence
277 Array<Slot *> *CloudComm::putSlot(Slot *slot, int max) {
281 throw new ServerException("putSlot failed", ServerException.TypeSalt);
286 int64_t sequencenumber = slot->getSequenceNumber();
287 Array<char> *slotBytes = slot->encode(mac);
289 Array<char> *chars = encryptSlotAndPrependIV(slotBytes, slot->getSlotCryptIV());
291 URL *url = buildRequest(true, sequencenumber, max);
294 URLConnection *con = url->openConnection();
295 HttpURLConnection *http = (HttpURLConnection *) con;
297 http->setRequestMethod("POST");
298 http->setFixedLengthStreamingMode(chars->length);
299 http->setDoOutput(true);
300 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
301 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
304 OutputStream *os = http->getOutputStream();
311 } catch (ServerException *e) {
315 } catch (SocketTimeoutException *e) {
318 throw new ServerException("putSlot failed", ServerException.TypeConnectTimeout);
319 } catch (Exception *e) {
320 throw new Error("putSlot failed");
327 InputStream is = http->getInputStream();
328 DataInputStream *dis = new DataInputStream(is);
329 Array<char> *resptype = new Array<char>(7);
330 dis->readFully(resptype);
333 if (Arrays->equals(resptype, "getslot"->getBytes())) {
334 return processSlots(dis);
335 } else if (Arrays->equals(resptype, "putslot"->getBytes())) {
338 throw new Error("Bad response to putslot");
340 } catch (SocketTimeoutException *e) {
342 throw new ServerException("putSlot failed", ServerException->TypeInputTimeout);
343 } catch (Exception *e) {
344 throw new Error("putSlot failed");
349 * Request the server to send all slots with the given
350 * sequencenumber or newer->
352 Array<Slot *> *CloudComm::getSlots(int64_t sequencenumber) {
356 throw new ServerException("getSlots failed", ServerException.TypeSalt);
361 URL *url = buildRequest(false, sequencenumber, 0);
363 URLConnection *con = url->openConnection();
364 HttpURLConnection *http = (HttpURLConnection) con;
365 http->setRequestMethod("POST");
366 http->setConnectTimeout(CloudComm_TIMEOUT_MILLIS);
367 http->setReadTimeout(CloudComm_TIMEOUT_MILLIS);
374 } catch (SocketTimeoutException *e) {
377 throw new ServerException("getSlots failed", ServerException.TypeConnectTimeout);
378 } catch (ServerException *e) {
382 } catch (Exception *e) {
383 throw new Error("getSlots failed");
389 InputStream *is = http->getInputStream();
390 DataInputStream *dis = new DataInputStream(is);
391 Array<char> *resptype = new Array<char>(7);
393 dis->readFully(resptype);
396 if (!resptype->equals("getslot".getBytes()))
397 throw new Error("Bad Response: " + new String(resptype));
399 return processSlots(dis);
400 } catch (SocketTimeoutException *e) {
403 throw new ServerException("getSlots failed", ServerException.TypeInputTimeout);
404 } catch (Exception *e) {
405 throw new Error("getSlots failed");
410 * Method that actually handles building Slot objects from the
411 * server response. Shared by both putSlot and getSlots.
413 Array<Slot *> *CloudComm::processSlots(DataInputStream *dis) {
414 int numberofslots = dis->readInt();
415 Array<int> *sizesofslots = new Array<int>(numberofslots);
417 Array<Slot *> *slots = new Array<Slot *>(numberofslots);
418 for (int i = 0; i < numberofslots; i++)
419 sizesofslots->set(i, dis->readInt());
421 for (int i = 0; i < numberofslots; i++) {
422 Array<char> *rawData = new Array<char>(sizesofslots->get(i));
423 dis->readFully(rawData);
425 Array<char> *data = stripIVAndDecryptSlot(rawData);
426 slots->set(i, Slot_decode(table, data, mac));
432 Array<char> *sendLocalData(Array<char> *sendData, int64_t localSequenceNumber, String host, int port) {
437 printf("Passing Locally\n");
439 mac->update(sendData);
440 Array<char> *genmac = mac->doFinal();
441 Array<char> *totalData = new Array<char>(sendData->length() + genmac->length());
442 System_arraycopy(sendData, 0, totalData, 0, sendData.length());
443 System - arraycopy(genmac, 0, totalData, sendData.length, genmac->length());
445 // Encrypt the data for sending
446 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
447 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
449 // Open a TCP socket connection to a local device
450 Socket *socket = new Socket(host, port);
451 socket->setReuseAddress(true);
452 DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
453 DataInputStream *input = new DataInputStream(socket->getInputStream());
456 // Send data to output (length of data, the data)
457 output->writeInt(encryptedData->length);
458 output->write(encryptedData, 0, encryptedData->length);
461 int lengthOfReturnData = input->readInt();
462 Array<char> *returnData = new Array<char>(lengthOfReturnData);
463 input->readFully(returnData);
467 returnData = stripIVAndDecryptSlot(returnData);
469 // We are done with this socket
472 mac->update(returnData, 0, returnData->length - HMAC_SIZE);
473 Array<char> *realmac = mac->doFinal();
474 Array<char> *recmac = new Array<char>(HMAC_SIZE);
475 System_arraycopy(returnData, returnData->length - realmac->length, recmac, 0, realmac->length);
477 if (!Arrays->equals(recmac, realmac))
478 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
480 Array<char> *returnData2 = new Array<char>(lengthOfReturnData - recmac->length());
481 System_arraycopy(returnData, 0, returnData2, 0, returnData2->length);
484 } catch (Exception *e) {
485 printf("Exception\n");
491 void CloudComm::localServerWorkerFunction() {
492 ServerSocket *inputSocket = NULL;
495 // Local server socket
496 inputSocket = new ServerSocket(listeningPort);
497 inputSocket->setReuseAddress(true);
498 inputSocket->setSoTimeout(CloudComm_TIMEOUT_MILLIS);
499 } catch (Exception *e) {
500 throw new Error("Local server setup failure...");
505 // Accept incoming socket
506 Socket *socket = inputSocket->accept();
508 DataInputStream *input = new DataInputStream(socket->getInputStream());
509 DataOutputStream *output = new DataOutputStream(socket->getOutputStream());
511 // Get the encrypted data from the server
512 int dataSize = input->readInt();
513 Array<char> *readData = new Array<char>(dataSize);
514 input->readFully(readData);
519 readData = stripIVAndDecryptSlot(readData);
521 mac->update(readData, 0, readData->length - HMAC_SIZE);
522 Array<char> *genmac = mac->doFinal();
523 Array<char> *recmac = new Array<char>(HMAC_SIZE);
524 System_arraycopy(readData, readData->length() - recmac->length(), recmac, 0, recmac->length());
526 if (!recmac->equals(genmac))
527 throw new Error("Local Error: Invalid HMAC! Potential Attack!");
529 Array<char> *returnData = new Array<char>(readData->length() - recmac->length());
530 System_arraycopy(readData, 0, returnData, 0, returnData->length());
533 Array<char> *sendData = table->acceptDataFromLocal(returnData);
536 mac->update(sendData);
537 Array<char> *realmac = mac->doFinal();
538 Array<char> *totalData = new Array<char>(sendData->length() + realmac->length());
539 System_arraycopy(sendData, 0, totalData, 0, sendData->length());
540 System_arraycopy(realmac, 0, totalData, sendData->length(), realmac->length());
542 // Encrypt the data for sending
543 Array<char> *iv = createIV(table->getMachineId(), table->getLocalSequenceNumber());
544 Array<char> *encryptedData = encryptSlotAndPrependIV(totalData, iv);
548 // Send data to output (length of data, the data)
549 output->writeInt(encryptedData->length());
550 output->write(encryptedData, 0, encryptedData->length());
555 } catch (Exception *e) {
559 if (inputSocket != NULL) {
561 inputSocket->close();
562 } catch (Exception *e) {
563 throw new Error("Local server close failure...");
568 void CloudComm::close() {
571 if (localServerThread != NULL) {
573 localServerThread->join();
574 } catch (Exception *e) {
575 throw new Error("Local Server thread join issue...");