def setupSingleNotification(self, serviceUUID, characteristicUUID, writeCommand): characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) peripheral = self.connectedPeripherals[self.connectedPeripheral] peripheral.withDelegate(PeripheralDelegate(lambda x: self._killNotificationLoop(x), self.settings)) characteristicCCCDList = characteristic.getDescriptors(forUUID=CCCD_UUID) if len(characteristicCCCDList) == 0: raise BluenetBleException(BleError.CAN_NOT_FIND_CCCD, "Can not find CCCD handle to use notifications for characteristic: " + characteristicUUID) characteristicCCCD = characteristicCCCDList[0] # enable notifications.. This is ugly but necessary characteristicCCCD.write(b"\x01\x00", True) # execute something that will trigger the notifications writeCommand() self.notificationLoopActive = True loopCount = 0 while self.notificationLoopActive and loopCount < 10: peripheral.waitForNotifications(0.5) loopCount += 1 if self.notificationResult is None: raise BluenetBleException(BleError.NO_NOTIFICATION_DATA_RECEIVED, "No notification data received.") result = self.notificationResult self.notificationResult = None return result
def encrypt(dataArray, settings): if settings.sessionNonce is None: raise BluenetBleException(BleError.NO_SESSION_NONCE_SET, "Can't Decrypt: No session nonce set") if settings.userLevel == UserLevel.unknown: raise BluenetBleException( BleError.NO_ENCRYPTION_KEYS_SET, "Can't Decrypt: No encryption keys set.") packetNonce = [0] * PACKET_NONCE_LENGTH # create a random nonce for i in range(0, PACKET_NONCE_LENGTH): packetNonce[i] = EncryptionHandler.getRandomNumber() key = EncryptionHandler._getKey(settings) encryptedData = EncryptionHandler.encryptCTR(dataArray, packetNonce, settings.sessionNonce, key) result = packetNonce + [settings.userLevel.value] for byte in encryptedData: result.append(byte) return bytes(result)
def __init__(self, dataArray): prefixLength = PACKET_NONCE_LENGTH + PACKET_USER_LEVEL_LENGTH # 20 is the minimal size of a packet (3+1+16) if len(dataArray) < 20: raise BluenetBleException(BleError.INVALID_ENCRYPTION_PACKAGE, "Invalid package for encryption. It is too short (min length 20) got " + str(len(dataArray)) + " bytes.") self.nonce = [0] * PACKET_NONCE_LENGTH for i in range(0, PACKET_NONCE_LENGTH): self.nonce[i] = dataArray[i] if dataArray[PACKET_NONCE_LENGTH] > 2 and dataArray[PACKET_NONCE_LENGTH] != UserLevel.setup.value: raise BluenetBleException(BleError.INVALID_ENCRYPTION_USER_LEVEL, "User level in read packet is invalid:" + str(dataArray[PACKET_NONCE_LENGTH])) try: self.userLevel = UserLevel(dataArray[PACKET_NONCE_LENGTH]) except ValueError: raise BluenetBleException(BleError.INVALID_ENCRYPTION_USER_LEVEL, "User level in read packet is invalid:" + str(dataArray[PACKET_NONCE_LENGTH])) payload = [0] * (len(dataArray) - prefixLength) for i in range(0, (len(dataArray) - prefixLength)): payload[i] = dataArray[i + prefixLength] if len(payload) % 16 != 0: raise BluenetBleException(BleError.INVALID_ENCRYPTION_PACKAGE, "Invalid size for encrypted payload") self.payload = payload
def decryptSessionNonce(inputData, key): if len(inputData) == 16: decrypted = EncryptionHandler.decryptECB(inputData, key) checksum = Conversion.uint8_array_to_uint32(decrypted) if checksum == CHECKSUM: return [decrypted[4], decrypted[5], decrypted[6], decrypted[7], decrypted[8]] else: raise BluenetBleException(BleError.COULD_NOT_VALIDATE_SESSION_NONCE, "Could not validate the session nonce.") else: raise BluenetBleException(BleError.COULD_NOT_READ_SESSION_NONCE, "Could not read session nonce, maybe encryption is disabled?")
def loadSettingsFromFile(self, path): fileReader = JsonFileStore(path) data = fileReader.getData() if "admin" not in data: raise BluenetBleException(BluenetError.ADMIN_KEY_REQURED) if "member" not in data: raise BluenetBleException(BluenetError.MEMBER_KEY_REQUIRED) if "guest" not in data: raise BluenetBleException(BluenetError.GUEST_KEY_REQURED) self.setSettings(data["admin"], data["member"], data["guest"])
def setupNotificationStream(self, serviceUUID, characteristicUUID, writeCommand, resultHandler, timeout): characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) peripheral = self.connectedPeripherals[self.connectedPeripheral] peripheral.withDelegate( PeripheralDelegate(lambda x: self._loadNotificationResult(x), self.settings)) characteristicCCCDList = characteristic.getDescriptors( forUUID=CCCD_UUID) if len(characteristicCCCDList) == 0: raise BluenetBleException( BleError.CAN_NOT_FIND_CCCD, "Can not find CCCD handle to use notifications for characteristic: " + characteristicUUID) characteristicCCCD = characteristicCCCDList[0] # enable notifications.. This is ugly but necessary characteristicCCCD.write(b"\x01\x00", True) # execute something that will trigger the notifications writeCommand() self.notificationLoopActive = True self.notificationResult = None loopCount = 0 successful = False while self.notificationLoopActive and loopCount < timeout * 2: peripheral.waitForNotifications(0.5) loopCount += 1 if self.notificationResult is not None: command = resultHandler(self.notificationResult) self.notificationResult = None if command == ProcessType.ABORT_ERROR: self.notificationLoopActive = False raise BluenetBleException( BleError.ABORT_NOTIFICATION_STREAM_W_ERROR, "Aborting the notification stream because the resultHandler raised an error." ) elif command == ProcessType.FINISHED: self.notificationLoopActive = False successful = True if not successful: raise BluenetBleException( BleError.NOTIFICATION_STREAM_TIMEOUT, "Notification stream not finished within timeout.")
def getCharacteristics(self, serviceUUID): if self.connectedPeripheral: peripheral = self.connectedPeripherals[self.connectedPeripheral] try: service = peripheral.getServiceByUUID(serviceUUID) except BTLEException: raise BluenetBleException(BleError.CAN_NOT_FIND_SERVICE, "Can not find service: " + serviceUUID) characteristics = service.getCharacteristics() return characteristics else: raise BluenetBleException(BleError.CAN_NOT_GET_CHACTERISTIC, "Can't get characteristics: Not connected.")
def decrypt(data, settings): if settings.sessionNonce is None: raise BluenetBleException(BleError.NO_SESSION_NONCE_SET, "Can't Decrypt: No session nonce set") if settings.userLevel == UserLevel.unknown: raise BluenetBleException(BleError.NO_ENCRYPTION_KEYS_SET, "Can't Decrypt: No encryption keys set.") #unpack the session data sessionData = SessionData(settings.sessionNonce) package = EncryptedPackage(data) key = EncryptionHandler._getKeyForLevel(package.userLevel, settings) # decrypt data decrypted = EncryptionHandler.decryptCTR(package.payload, package.nonce, sessionData.sessionNonce, key) return EncryptionHandler._verifyDecryption(decrypted, sessionData.validationKey)
def _getKeyForLevel(userLevel, settings): if settings.initializedKeys == False and userLevel != UserLevel.setup: raise BluenetBleException(BleError.NO_ENCRYPTION_KEYS_SET, "Could not encrypt: Keys not set.") key = None if userLevel == UserLevel.admin: key = settings.adminKey elif userLevel == UserLevel.member: key = settings.memberKey elif userLevel == UserLevel.guest: key = settings.guestKey elif userLevel == UserLevel.setup: key = settings.setupKey else: raise BluenetBleException(BleError.NO_ENCRYPTION_KEYS_SET, "Could not encrypt: Invalid key for encryption.") if key is None: raise BluenetBleException(BleError.NO_ENCRYPTION_KEYS_SET, "Could not encrypt: Keys not set.") return key
def _verifyDecryption(decrypted, validationKey): # the conversion to uint32 only takes the first 4 bytes if Conversion.uint8_array_to_uint32(decrypted) == Conversion.uint8_array_to_uint32(validationKey): # remove checksum from decryption and return payload result = [0] * (len(decrypted) - SESSION_KEY_LENGTH) for i in range(0,len(result)): result[i] = decrypted[i+SESSION_KEY_LENGTH] return result else: raise BluenetBleException(BleError.ENCRYPTION_VALIDATION_FAILED, "Failed to validate result, Could not decrypt")
def __init__(self, sessionData): if len(sessionData) != SESSION_DATA_LENGTH: raise BluenetBleException(BleError.INVALID_SESSION_DATA, "Invalid Session Data") self.sessionNonce = [0] * SESSION_DATA_LENGTH self.validationKey = [0] * SESSION_KEY_LENGTH for i in range(0,SESSION_KEY_LENGTH): self.sessionNonce[i] = sessionData[i] self.validationKey[i] = sessionData[i] self.sessionNonce[SESSION_DATA_LENGTH-1] = sessionData[SESSION_DATA_LENGTH-1]
def _writeConfigPacket(self, packet): self.core.ble.writeToCharacteristic(CSServices.SetupService, SetupCharacteristics.ConfigControl, packet) returnCode = self.core.ble.readCharacteristic( CSServices.SetupService, SetupCharacteristics.ConfigControl) if returnCode[0] != 0: raise BluenetBleException( BleError.RETURN_CODE_NOT_SUCCESSFUL, "The Crownstone returned a different code than 0 as a response to the config control write: " + str(returnCode[0]))
def classicSetup(self, crownstoneId, meshAccessAddress, ibeaconUUID, ibeaconMajor, ibeaconMinor): if not self.core.settings.initializedKeys: raise BluenetBleException( BleError.NO_ENCRYPTION_KEYS_SET, "Keys are not initialized so I can't put anything on the Crownstone. Make sure you call .setSettings(True, adminKey, memberKey, guesKey" ) sleepTime = 0.4 print("BluenetBLE: Starting Setup...") print("BluenetBLE: Setting up encryption...") self.handleSetupPhaseEncryption() try: print("BluenetBLE: Setting Admin Key...") self.writeAdminKey(self.core.settings.adminKey) time.sleep(sleepTime) print("BluenetBLE: Setting Member Key...") self.writeMemberKey(self.core.settings.memberKey) time.sleep(sleepTime) print("BluenetBLE: Setting Guest Key...") self.writeGuestKey(self.core.settings.guestKey) time.sleep(sleepTime) print("BluenetBLE: Setting Crownstone ID...") self.writeCrownstoneId(crownstoneId) time.sleep(sleepTime) print("BluenetBLE: Setting Mesh Access Address...") self.writeMeshAccessAddress(meshAccessAddress) time.sleep(sleepTime) print("BluenetBLE: Setting iBeacon UUID...") self.writeIBeaconUUID(ibeaconUUID) time.sleep(sleepTime) print("BluenetBLE: Setting iBeacon Major...") self.writeIBeaconMajor(ibeaconMajor) time.sleep(sleepTime) print("BluenetBLE: Setting iBeacon Minor...") self.writeIBeaconMinor(ibeaconMinor) time.sleep(sleepTime) print("BluenetBLE: Wrapping up...") self.validateSetup() time.sleep(2 * sleepTime) print("BluenetBLE: Setup complete!") except BTLEException as err: raise err except BluenetBleException as err: raise err finally: self.core.settings.exitSetup()
def setup(self, address, sphereId, crownstoneId, meshAccessAddress, meshDeviceKey, ibeaconUUID, ibeaconMajor, ibeaconMinor): characteristics = self.core.ble.getCharacteristics( CSServices.SetupService) try: self.fastSetupV2(sphereId, crownstoneId, meshDeviceKey, ibeaconUUID, ibeaconMajor, ibeaconMinor) except BluenetBleException as e: if e.type is not BleError.NOTIFICATION_STREAM_TIMEOUT: raise e isNormalMode = self.core.isCrownstoneInNormalMode( address, 10, waitUntilInRequiredMode=True) if not isNormalMode: raise BluenetBleException(BleError.SETUP_FAILED, "The setup has failed.")
def generateIV(packetNonce, sessionData): if len(packetNonce) != PACKET_NONCE_LENGTH: raise BluenetBleException(BleError.INVALID_SESSION_NONCE, "Invalid size for session nonce packet") IV = [0] * NONCE_LENGTH # the IV used in the CTR mode is 8 bytes, the first 3 are random for i in range(0,PACKET_NONCE_LENGTH): IV[i] = packetNonce[i] # the IV used in the CTR mode is 8 bytes, the last 5 are from the session data for i in range(0,SESSION_DATA_LENGTH): IV[i + PACKET_NONCE_LENGTH] = sessionData[i] return IV
def fastSetup(self, crownstoneId, meshAccessAddress, ibeaconUUID, ibeaconMajor, ibeaconMinor): if not self.core.settings.initializedKeys: raise BluenetBleException( BleError.NO_ENCRYPTION_KEYS_SET, "Keys are not initialized so I can't put anything on the Crownstone. Make sure you call .setSettings(True, adminKey, memberKey, guesKey" ) self.handleSetupPhaseEncryption() self.core.ble.setupNotificationStream( CSServices.SetupService, SetupCharacteristics.SetupControl, lambda: self._writeFastSetup( crownstoneId, meshAccessAddress, ibeaconUUID, ibeaconMajor, ibeaconMinor), lambda notificationResult: self._handleResult(notificationResult), 5) print("BluenetBLE: Closing Setup.") self.core.settings.exitSetup()
def fastSetupV2(self, sphereId, crownstoneId, meshDeviceKey, ibeaconUUID, ibeaconMajor, ibeaconMinor): if not self.core.settings.initializedKeys: raise BluenetBleException( BleError.NO_ENCRYPTION_KEYS_SET, "Keys are not initialized so I can't put anything on the Crownstone. Make sure you call .setSettings(adminKey, memberKey, basicKey, serviceDataKey, localizationKey, meshApplicationKey, meshNetworkKey" ) self.handleSetupPhaseEncryption() self.core.ble.setupNotificationStream( CSServices.SetupService, SetupCharacteristics.Result, lambda: self._writeFastSetupV2( sphereId, crownstoneId, meshDeviceKey, ibeaconUUID, ibeaconMajor, ibeaconMinor), lambda notificationResult: self._handleResult(notificationResult), 3) print("BluenetBLE: Closing Setup V2.") self.core.settings.exitSetup()
def loadSettingsFromFile(self, path): fileReader = JsonFileStore(path) data = fileReader.getData() if "admin" not in data: raise BluenetBleException(BluenetError.ADMIN_KEY_REQURED) if "member" not in data: raise BluenetBleException(BluenetError.MEMBER_KEY_REQUIRED) if "basic" not in data: raise BluenetBleException(BluenetError.BASIC_KEY_REQURED) if "serviceDataKey" not in data: raise BluenetBleException(BluenetError.SERVICE_DATA_KEY_REQUIRED) if "localizationKey" not in data: raise BluenetBleException(BluenetError.LOCALIZATION_KEY_REQUIRED) if "meshApplicationKey" not in data: raise BluenetBleException(BluenetError.MESH_APP_KEY) if "meshNetworkKey" not in data: raise BluenetBleException(BluenetError.MESH_NETWORK_KEY) self.setSettings(data["admin"], data["member"], data["basic"], data["serviceDataKey"], data["localizationKey"], data["meshApplicationKey"], data["meshNetworkKey"])