def _getKeyForLevel(userLevel: UserLevel, settings):
        _LOGGER.debug(f"get key for level {userLevel}")
        if settings.initializedKeys == False and userLevel != UserLevel.setup:
            raise CrownstoneBleException(
                EncryptionError.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.basic:
            key = settings.basicKey
        elif userLevel == UserLevel.setup:
            key = settings.setupKey
        else:
            raise CrownstoneBleException(
                EncryptionError.NO_ENCRYPTION_KEYS_SET,
                "Could not encrypt: Invalid key for encryption.")

        if key is None:
            raise CrownstoneBleException(
                EncryptionError.NO_ENCRYPTION_KEYS_SET,
                "Could not encrypt: Keys not set.")

        return key
Esempio n. 2
0
    async def waitForMode(self,
                          address,
                          requiredMode: CrownstoneOperationMode,
                          scanDuration=5):
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode. We will
        scan for the scanDuration amount of seconds or until the Crownstone is in the required mode.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        - BleError.DIFFERENT_MODE_THAN_REQUIRED
            During the {scanDuration} seconds of scanning, the Crownstone was not in the required mode.
        """
        _LOGGER.debug(
            f"waitForMode address={address} requiredMode={requiredMode} scanDuration={scanDuration}"
        )
        checker = ModeChecker(address, requiredMode, True)
        subscriptionId = BleEventBus.subscribe(
            BleTopics.rawAdvertisement,
            lambda scanData: checker.handleAdvertisement(scanData))
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )
        if result != requiredMode:
            raise CrownstoneBleException(
                BleError.DIFFERENT_MODE_THAN_REQUIRED,
                f'During the {scanDuration} seconds of scanning, the Crownstone was not in the required mode..'
            )
    def encrypt(dataArray, settings):
        if settings.sessionNonce is None:
            raise CrownstoneBleException(
                EncryptionError.NO_SESSION_NONCE_SET,
                "Can't Decrypt: No session nonce set")

        if settings.userLevel == UserLevel.unknown:
            raise CrownstoneBleException(
                EncryptionError.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, key)

        result = packetNonce + [settings.userLevel.value]

        for byte in encryptedData:
            result.append(byte)

        return bytes(result)
Esempio n. 4
0
    async def setupNotificationStream(self, serviceUUID, characteristicUUID,
                                      writeCommand, resultHandler, timeout):
        _LOGGER.debug(
            f"setupNotificationStream serviceUUID={serviceUUID} characteristicUUID={characteristicUUID} timeout={timeout}"
        )
        await self.is_connected_guard()

        # setup the collecting of the notification data.
        _LOGGER.debug(f"setupNotificationStream: subscribe for notifications.")
        notificationDelegate = NotificationDelegate(None, self.settings)
        await self.activeClient.subscribeNotifications(
            characteristicUUID, notificationDelegate.handleNotification)

        # execute something that will trigger the notifications
        _LOGGER.debug(f"setupNotificationStream: writeCommand().")
        await writeCommand()

        # wait for the results to come in.
        self.notificationLoopActive = True
        loopCount = 0
        successful = False
        polInterval = 0.1
        while self.notificationLoopActive and loopCount < (timeout /
                                                           polInterval):
            await asyncio.sleep(polInterval)
            _LOGGER.debug(
                f"loopActive={self.notificationLoopActive} loopCount={loopCount}"
            )
            loopCount += 1
            if notificationDelegate.result is not None:
                command = resultHandler(notificationDelegate.result)
                notificationDelegate.reset()
                if command == ProcessType.ABORT_ERROR:
                    _LOGGER.debug("abort")
                    self.notificationLoopActive = False
                    self.activeClient.unsubscribeNotifications(
                        characteristicUUID)
                    raise CrownstoneBleException(
                        BleError.ABORT_NOTIFICATION_STREAM_W_ERROR,
                        "Aborting the notification stream because the resultHandler raised an error."
                    )
                elif command == ProcessType.FINISHED:
                    _LOGGER.debug("finished")
                    self.notificationLoopActive = False
                    successful = True
                elif command == ProcessType.CONTINUE:
                    _LOGGER.debug("continue")

        if not successful:
            self.activeClient.unsubscribeNotifications(characteristicUUID)
            raise CrownstoneBleException(
                BleError.NOTIFICATION_STREAM_TIMEOUT,
                "Notification stream not finished within timeout.")

        # remove subscription from this characteristic
        self.activeClient.unsubscribeNotifications(characteristicUUID)
Esempio n. 5
0
    async def getMode(self,
                      address,
                      scanDuration=3) -> CrownstoneOperationMode:
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode.

        We will return once we know.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        """
        _LOGGER.debug(f"getMode address={address} scanDuration={scanDuration}")
        checker = ModeChecker(address, None)
        subscriptionId = BleEventBus.subscribe(
            BleTopics.rawAdvertisement,
            lambda scanData: checker.handleAdvertisement(scanData))
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )

        return result
Esempio n. 6
0
    async def isCrownstoneInSetupMode(self,
                                      address: str,
                                      scanDuration=3,
                                      waitUntilInSetupMode=False) -> bool:
        _LOGGER.warning(
            "isCrownstoneInSetupMode is deprecated. Will be removed in v3. Use either getMode or waitForMode instead."
        )
        """
        This will wait until it has received an advertisement from the Crownstone with the specified address. Once it has received an advertisement, it knows the mode.
        With default value for waitUntilInSetupMode (False), it will return True if the Crownstone is in setup mode, False if it isn't.

        You can use the boolean waitUntilInSetupMode to have it ignore advertisements from this Crownstone in other modes than setup mode.

        It can throw the following CrownstoneBleException
        - BleError.NO_SCANS_RECEIVED
            We have not received any scans from this Crownstone, and can't say anything about it's state.
        """
        _LOGGER.debug(
            f"isCrownstoneInSetupMode address={address} scanDuration={scanDuration} waitUntilInSetupMode={waitUntilInSetupMode}"
        )
        checker = ModeChecker(address, CrownstoneOperationMode.SETUP,
                              waitUntilInSetupMode)
        subscriptionId = BleEventBus.subscribe(BleTopics.advertisement,
                                               checker.handleAdvertisement)
        await self.ble.scan(duration=scanDuration)
        BleEventBus.unsubscribe(subscriptionId)
        result = checker.getResult()

        if result is None:
            raise CrownstoneBleException(
                BleError.NO_SCANS_RECEIVED,
                f'During the {scanDuration} seconds of scanning, no advertisement was received from this address.'
            )

        return result
Esempio n. 7
0
 async def is_connected_guard(self):
     connected = await self.is_connected()
     if not connected:
         _LOGGER.debug(
             f"Could not perform action since the client is not connected!."
         )
         raise CrownstoneBleException("Not connected.")
Esempio n. 8
0
    async def setupSingleNotification(self, serviceUUID, characteristicUUID,
                                      writeCommand):
        _LOGGER.debug(
            f"setupSingleNotification serviceUUID={serviceUUID} characteristicUUID={characteristicUUID}"
        )
        await self.is_connected_guard()

        # setup the collecting of the notification data.
        _LOGGER.debug(f"setupSingleNotification: subscribe for notifications.")
        notificationDelegate = NotificationDelegate(self._killNotificationLoop,
                                                    self.settings)
        await self.activeClient.subscribeNotifications(
            characteristicUUID, notificationDelegate.handleNotification)

        # execute something that will trigger the notifications
        _LOGGER.debug(f"setupSingleNotification: writeCommand().")
        await writeCommand()

        # wait for the results to come in.
        self.notificationLoopActive = True
        loopCount = 0
        polInterval = 0.1
        while self.notificationLoopActive and loopCount < (12.5 / polInterval):
            await asyncio.sleep(polInterval)
            loopCount += 1

        if notificationDelegate.result is None:
            self.activeClient.unsubscribeNotifications(characteristicUUID)
            raise CrownstoneBleException(
                BleError.NO_NOTIFICATION_DATA_RECEIVED,
                "No notification data received.")

        self.activeClient.unsubscribeNotifications(characteristicUUID)
        return notificationDelegate.result
    def __init__(self, dataArray):
        self.nonce = None
        self.userLevel = None
        self.payload = None

        prefixLength = PACKET_NONCE_LENGTH + PACKET_USER_LEVEL_LENGTH
        if len(dataArray) < prefixLength + BLOCK_LENGTH:
            raise CrownstoneBleException(
                EncryptionError.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 CrownstoneBleException(
                EncryptionError.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 CrownstoneBleException(
                EncryptionError.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 CrownstoneBleException(
                EncryptionError.INVALID_ENCRYPTION_PACKAGE,
                f"Invalid size for encrypted payload: len={len(payload)} payload={payload}"
            )

        self.payload = payload
Esempio n. 10
0
    async def setupCrownstone(self, address, sphereId, crownstoneId,
                              meshDeviceKey, ibeaconUUID, ibeaconMajor,
                              ibeaconMinor):
        if not self.defaultKeysOverridden:
            raise CrownstoneBleException(
                BleError.NO_ENCRYPTION_KEYS_SET,
                "Keys are not initialized so I can't put anything on the Crownstone. Make sure you call .setSettings, loadSettingsFromFile or loadSettingsFromDictionary"
            )

        await self.setup.setup(address, sphereId, crownstoneId, meshDeviceKey,
                               ibeaconUUID, ibeaconMajor, ibeaconMinor)
Esempio n. 11
0
def ProcessSessionNoncePacket(encryptedPacket, key, settings):
    # decrypt it
    decrypted = EncryptionHandler.decryptECB(encryptedPacket, key)

    packet = SessionDataPacket(decrypted)
    if packet.validation == CHECKSUM:
        # load into the settings object
        settings.setSessionNonce(packet.sessionNonce)
        settings.setValidationKey(packet.validationKey)
        settings.setCrownstoneProtocolVersion(packet.protocol)
    else:
        raise CrownstoneBleException(BleError.COULD_NOT_VALIDATE_SESSION_NONCE,
                                     "Could not validate the session nonce.")
    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 CrownstoneBleException(
                EncryptionError.ENCRYPTION_VALIDATION_FAILED,
                "Failed to validate result, Could not decrypt")
    def decrypt(data, settings):
        if settings.sessionNonce is None:
            raise CrownstoneBleException(
                EncryptionError.NO_SESSION_NONCE_SET,
                "Can't Decrypt: No session nonce set")

        if settings.userLevel == UserLevel.unknown:
            raise CrownstoneBleException(
                EncryptionError.NO_ENCRYPTION_KEYS_SET,
                "Can't Decrypt: No encryption keys set.")

        #unpack the session data
        package = EncryptedPackage(data)

        key = EncryptionHandler._getKeyForLevel(package.userLevel, settings)

        # decrypt data
        decrypted = EncryptionHandler.decryptCTR(package.payload,
                                                 package.nonce,
                                                 settings.sessionNonce, key)

        return EncryptionHandler._verifyDecryption(decrypted,
                                                   settings.validationKey)
    def generateIV(packetNonce, sessionData):
        if len(packetNonce) != PACKET_NONCE_LENGTH:
            raise CrownstoneBleException(
                EncryptionError.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
Esempio n. 15
0
    async def fastSetupV2(self, sphereId, crownstoneId, meshDeviceKey,
                          ibeaconUUID, ibeaconMajor, ibeaconMinor):
        if not self.core.settings.initializedKeys:
            raise CrownstoneBleException(
                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"
            )

        await self.handleSetupPhaseEncryption()
        await self.core.ble.setupNotificationStream(
            CSServices.SetupService,
            SetupCharacteristics.Result, lambda: self._writeFastSetupV2(
                sphereId, crownstoneId, meshDeviceKey, ibeaconUUID,
                ibeaconMajor, ibeaconMinor),
            lambda notificationResult: self._handleResult(notificationResult),
            3)

        _LOGGER.info("Closing Setup V2.")
        self.core.settings.exitSetup()
Esempio n. 16
0
    def loadSettingsFromDictionary(self, data):
        if "admin" not in data:
            raise CrownstoneBleException(CrownstoneError.ADMIN_KEY_REQURED)
        if "member" not in data:
            raise CrownstoneBleException(CrownstoneError.MEMBER_KEY_REQUIRED)
        if "basic" not in data:
            raise CrownstoneBleException(CrownstoneError.BASIC_KEY_REQURED)
        if "serviceDataKey" not in data:
            raise CrownstoneBleException(
                CrownstoneError.SERVICE_DATA_KEY_REQUIRED)
        if "localizationKey" not in data:
            raise CrownstoneBleException(
                CrownstoneError.LOCALIZATION_KEY_REQUIRED)
        if "meshApplicationKey" not in data:
            raise CrownstoneBleException(CrownstoneError.MESH_APP_KEY)
        if "meshNetworkKey" not in data:
            raise CrownstoneBleException(CrownstoneError.MESH_NETWORK_KEY)

        self.setSettings(data["admin"], data["member"], data["basic"],
                         data["serviceDataKey"], data["localizationKey"],
                         data["meshApplicationKey"], data["meshNetworkKey"])
 def setValidationKey(self, validationKey):
     if len(validationKey) != SESSION_KEY_LENGTH:
         raise CrownstoneBleException(EncryptionError.INVALID_SESSION_DATA,
                                      "Invalid Session Data")
     self.validationKey = validationKey
 def setSessionNonce(self, sessionNonce):
     if len(sessionNonce) != SESSION_DATA_LENGTH:
         raise CrownstoneBleException(EncryptionError.INVALID_SESSION_DATA,
                                      "Invalid Session Data")
     self.sessionNonce = sessionNonce