Example #1
0
class Nuki_EncryptedCommand(object):
	def __init__(self, authID='', nukiCommand=None, nonce='', publicKey='', privateKey=''):
		self.byteSwapper = ByteSwapper()
		self.crcCalculator = CrcCalculator()
		self.authID = authID
		self.command = nukiCommand
		self.nonce = nonce
		if nonce == '':
			self.nonce = nacl.utils.random(24).hex()
		self.publicKey = publicKey
		self.privateKey = privateKey

	def generate(self, format='BYTE_ARRAY'):
		unencrypted = self.authID + self.command.generate(format='HEX')[:-4]
		crc = self.byteSwapper.swap(self.crcCalculator.crc_ccitt(unencrypted))
		unencrypted = unencrypted + crc
		sharedKey = crypto_box_beforenm(bytes(bytearray.fromhex(self.publicKey)),bytes(bytearray.fromhex(self.privateKey))).hex()
		box = nacl.secret.SecretBox(bytes(bytearray.fromhex(sharedKey)))
		encrypted = box.encrypt(bytes(bytearray.fromhex(unencrypted)), bytes(bytearray.fromhex(self.nonce))).hex()[48:]
		length = self.byteSwapper.swap("%04X" % int((len(encrypted)/2)))
		msg = self.nonce + self.authID + length + encrypted
		if format == 'BYTE_ARRAY':
			return bytearray.fromhex(msg)
		else:
			return msg
Example #2
0
	def __init__(self, macAddress, cfg='/home/pi/nuki/nuki.cfg'):	
		self._charWriteResponse = ""
		self.parser = nuki_messages.NukiCommandParser()
		self.crcCalculator = CrcCalculator()
		self.byteSwapper = ByteSwapper()
		self.macAddress = macAddress
		self.config = ConfigParser.RawConfigParser()
		self.config.read(cfg)
		self.device = None
Example #3
0
	def __init__(self, authID='', nukiCommand=None, nonce='', publicKey='', privateKey=''):
		self.byteSwapper = ByteSwapper()
		self.crcCalculator = CrcCalculator()
		self.authID = authID
		self.command = nukiCommand
		self.nonce = nonce
		if nonce == '':
			self.nonce = nacl.utils.random(24).hex()
		self.publicKey = publicKey
		self.privateKey = privateKey
Example #4
0
class Nuki_Command(object):
    def __init__(self, payload=""):
        self.crcCalculator = CrcCalculator()
        self.byteSwapper = ByteSwapper()
        self.parser = NukiCommandParser()
        self.payload = payload

    def generate(self, format='BYTE_ARRAY'):
        msg = self.byteSwapper.swap(type(self).command) + self.payload
        crc = self.byteSwapper.swap(self.crcCalculator.crc_ccitt(msg))
        msg = msg + crc
        if format == 'BYTE_ARRAY':
            return bytearray.fromhex(msg)
        else:
            return msg

    def isError(self):
        return type(self) == Nuki_ERROR
Example #5
0
class Nuki_Command(object):
    def __init__(self, payload=""):
        self.crcCalculator = CrcCalculator()
        self.byteSwapper = ByteSwapper()
        self.parser = NukiCommandParser()
        self.command = ''
        self.payload = payload

    def generate(self, format='BYTE_ARRAY'):
        msg = self.byteSwapper.swap(self.command) + self.payload
        crc = self.byteSwapper.swap(self.crcCalculator.crc_ccitt(msg))
        msg = msg + crc
        if format == 'BYTE_ARRAY':
            return array.array('B', msg.decode("hex"))
        else:
            return msg

    def isError(self):
        return self.command == '0012'
Example #6
0
	def __init__(self):
		self.byteSwapper = ByteSwapper()
		self.commandList = ['0001','0003','0004','0005','0006','0007','000C','001E','000E','0023','0024','0026','0012']
Example #7
0
class NukiCommandParser:
	def __init__(self):
		self.byteSwapper = ByteSwapper()
		self.commandList = ['0001','0003','0004','0005','0006','0007','000C','001E','000E','0023','0024','0026','0012']

	def isNukiCommand(self, commandString):
		command = self.byteSwapper.swap(commandString[:4])
		return command.upper() in self.commandList

	def getNukiCommandText(self, command):
		return {
			'0001': 'Nuki_REQ',
			'0003': 'Nuki_PUBLIC_KEY',
			'0004': 'Nuki_CHALLENGE',
			'0005': 'Nuki_AUTH_AUTHENTICATOR',
			'0006': 'Nuki_AUTH_DATA',
			'0007': 'Nuki_AUTH_ID',
			'000C': 'Nuki_STATES',
			'001E': 'Nuki_AUTH_ID_CONFIRM',
			'000E': 'Nuki_STATUS',
			'0023': 'Nuki_LOCK_ENTRIES_REQUEST',
			'0024': 'Nuki_LOG_ENTRY',
			'0026': 'Nuki_LOG_ENTRY_COUNT',
			'0012': 'Nuki_ERROR',
		}.get(command.upper(), 'UNKNOWN')    # UNKNOWN is default if command not found

	def parse(self, commandString):
		if self.isNukiCommand(commandString):
			command = self.byteSwapper.swap(commandString[:4]).upper()
			payload = commandString[4:-4]
			crc = self.byteSwapper.swap(commandString[-4:])
			#print("command = {}, payload = {}, crc = {}" % (command, payload, crc))
			if command == '0001':
				return Nuki_REQ(payload)
			elif command == '0003':
				return Nuki_PUBLIC_KEY(payload)
			elif command == '0004':
				return Nuki_CHALLENGE(payload)
			elif command == '0005':
				return Nuki_AUTH_AUTHENTICATOR(payload)
			elif command == '0006':
				return Nuki_AUTH_DATA(payload)
			elif command == '0007':
				return Nuki_AUTH_ID(payload)
			elif command == '000C':
				return Nuki_STATES(payload)
			elif command == '001E':
				return Nuki_AUTH_ID_CONFIRM(payload)
			elif command == '000E':
				return Nuki_STATUS(payload)
			elif command == '0023':
				return Nuki_LOG_ENTRIES_REQUEST(payload)
			elif command == '0024':
				return Nuki_LOG_ENTRY(payload)
			elif command == '0026':
				return Nuki_LOG_ENTRY_COUNT(payload)
			elif command == '0012':
				return Nuki_ERROR(payload)
		else:
			return "%s does not seem to be a valid Nuki command" % commandString

	def splitEncryptedMessages(self, msg):
		msgList = []
		offset = 0
		while offset < len(msg):
			nonce = msg[offset:offset+48]
			authID = msg[offset+48:offset+56]
			length = int(self.byteSwapper.swap(msg[offset+56:offset+60]), 16)
			singleMsg = msg[offset:offset+60+(length*2)]
			msgList.append(singleMsg)
			offset = offset+60+(length*2)
		return msgList

	def decrypt(self, msg, publicKey, privateKey):
		print("msg: %s" % msg)
		nonce = msg[:48]
		#print "nonce: %s" % nonce
		authID = msg[48:56]
		#print "authID: %s" % authID
		length = int(self.byteSwapper.swap(msg[56:60]), 16)
		#print "length: %d" % length
		encrypted = nonce + msg[60:60+(length*2)]
		#print "encrypted: %s" % encrypted
		sharedKey = crypto_box_beforenm(bytes(bytearray.fromhex(publicKey)),bytes(bytearray.fromhex(privateKey))).hex()
		box = nacl.secret.SecretBox(bytes(bytearray.fromhex(sharedKey)))
		decrypted = box.decrypt(bytes(bytearray.fromhex(encrypted))).hex()
		#print "decrypted: %s" % decrypted
		return decrypted
Example #8
0
	def __init__(self, payload=""):
		self.crcCalculator = CrcCalculator()
		self.byteSwapper = ByteSwapper()
		self.parser = NukiCommandParser()
		self.command = ''
		self.payload = payload
Example #9
0
class Nuki():	
	# creates BLE connection with NUKI
	#	-macAddress: bluetooth mac-address of your Nuki Lock
	def __init__(self, macAddress, cfg='/home/pi/nuki/nuki.cfg'):	
		self._charWriteResponse = ""
		self.parser = nuki_messages.NukiCommandParser()
		self.crcCalculator = CrcCalculator()
		self.byteSwapper = ByteSwapper()
		self.macAddress = macAddress
		self.config = ConfigParser.RawConfigParser()
		self.config.read(cfg)
		self.device = None
	
	def _makeBLEConnection(self):
		if self.device == None:
			adapter = pygatt.backends.GATTToolBackend()
			nukiBleConnectionReady = False
			while nukiBleConnectionReady == False:
				print "Starting BLE adapter..."
				adapter.start()
				print "Init Nuki BLE connection..."
				try :
					self.device = adapter.connect(self.macAddress)
					nukiBleConnectionReady = True
				except:
					print "Unable to connect, retrying..."
			print "Nuki BLE connection established"
	
	def isNewNukiStateAvailable(self):
		if self.device != None:
			self.device.disconnect()
			self.device = None
		dev_id = 0
		try:
			sock = bluez.hci_open_dev(dev_id)
		except:
			print "error accessing bluetooth device..."
			sys.exit(1)
		blescan.hci_le_set_scan_parameters(sock)
		blescan.hci_enable_le_scan(sock)
		returnedList = blescan.parse_events(sock, 10)
		newStateAvailable = -1
		print "isNewNukiStateAvailable() -> search through %d received beacons..." % len(returnedList)
		for beacon in returnedList:
			beaconElements = beacon.split(',')
			if beaconElements[0] == self.macAddress.lower() and beaconElements[1] == "a92ee200550111e4916c0800200c9a66":
				print "Nuki beacon found, new state element: %s" % beaconElements[4]
				if beaconElements[4] == '-60':
					newStateAvailable = 0
				else:
					newStateAvailable = 1
				break
			else:
				print "non-Nuki beacon found: mac=%s, signature=%s" % (beaconElements[0],beaconElements[1])
		print "isNewNukiStateAvailable() -> result=%d" % newStateAvailable
		return newStateAvailable
	
	# private method to handle responses coming back from the Nuki Lock over the BLE connection
	def _handleCharWriteResponse(self, handle, value):
		self._charWriteResponse += "".join(format(x, '02x') for x in value)
	
	# method to authenticate yourself (only needed the very first time) to the Nuki Lock
	#	-publicKeyHex: a public key (as hex string) you created to talk with the Nuki Lock
	#	-privateKeyHex: a private key (complementing the public key, described above) you created to talk with the Nuki Lock
	#	-ID : a unique number to identify yourself to the Nuki Lock
	#	-IDType : '00' for 'app', '01' for 'bridge' and '02' for 'fob'
	#	-name : a unique name to identify yourself to the Nuki Lock (will also appear in the logs of the Nuki Lock)
	def authenticateUser(self, publicKeyHex, privateKeyHex, ID, IDType, name):
		self._makeBLEConnection()
		self.config.remove_section(self.macAddress)
		self.config.add_section(self.macAddress)
		pairingHandle = self.device.get_handle('a92ee101-5501-11e4-916c-0800200c9a66')
		print "Nuki Pairing UUID handle created: %04x" % pairingHandle
		publicKeyReq = nuki_messages.Nuki_REQ('0003')
		self.device.subscribe('a92ee101-5501-11e4-916c-0800200c9a66', self._handleCharWriteResponse, indication=True))
		publicKeyReqCommand = publicKeyReq.generate()
		self._charWriteResponse = ""
		print "Requesting Nuki Public Key using command: %s" % publicKeyReq.show()
		self.device.char_write_handle(pairingHandle,publicKeyReqCommand,True,2)
		print "Nuki Public key requested" 
		time.sleep(2)
		commandParsed = self.parser.parse(self._charWriteResponse)
		if self.parser.isNukiCommand(self._charWriteResponse) == False:
			sys.exit("Error while requesting public key: %s" % commandParsed)
		if commandParsed.command != '0003':
			sys.exit("Nuki returned unexpected response (expecting PUBLIC_KEY): %s" % commandParsed.show())
		publicKeyNuki = commandParsed.publicKey
		self.config.set(self.macAddress,'publicKeyNuki',publicKeyNuki)
		self.config.set(self.macAddress,'publicKeyHex',publicKeyHex)
		self.config.set(self.macAddress,'privateKeyHex',privateKeyHex)
		self.config.set(self.macAddress,'ID',ID)
		self.config.set(self.macAddress,'IDType',IDType)
		self.config.set(self.macAddress,'Name',name)
		print "Public key received: %s" % commandParsed.publicKey
		publicKeyPush = nuki_messages.Nuki_PUBLIC_KEY(publicKeyHex)
		publicKeyPushCommand = publicKeyPush.generate()
		print "Pushing Public Key using command: %s" % publicKeyPush.show()
		self._charWriteResponse = ""
		self.device.char_write_handle(pairingHandle,publicKeyPushCommand,True,5)
		print "Public key pushed" 
		time.sleep(2)
		commandParsed = self.parser.parse(self._charWriteResponse)
		if self.parser.isNukiCommand(self._charWriteResponse) == False:
			sys.exit("Error while pushing public key: %s" % commandParsed)
		if commandParsed.command != '0004':
			sys.exit("Nuki returned unexpected response (expecting CHALLENGE): %s" % commandParsed.show())
		print "Challenge received: %s" % commandParsed.nonce
		nonceNuki = commandParsed.nonce
		authAuthenticator = nuki_messages.Nuki_AUTH_AUTHENTICATOR()
		authAuthenticator.createPayload(nonceNuki, privateKeyHex, publicKeyHex, publicKeyNuki)
		authAuthenticatorCommand = authAuthenticator.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(pairingHandle,authAuthenticatorCommand,True,5)
		print "Authorization Authenticator sent: %s" % authAuthenticator.show() 
		time.sleep(2)
		commandParsed = self.parser.parse(self._charWriteResponse)
		if self.parser.isNukiCommand(self._charWriteResponse) == False:
			sys.exit("Error while sending Authorization Authenticator: %s" % commandParsed)
		if commandParsed.command != '0004':
			sys.exit("Nuki returned unexpected response (expecting CHALLENGE): %s" % commandParsed.show())
		print "Challenge received: %s" % commandParsed.nonce
		nonceNuki = commandParsed.nonce
		authData = nuki_messages.Nuki_AUTH_DATA()
		authData.createPayload(publicKeyNuki, privateKeyHex, publicKeyHex, nonceNuki, ID, IDType, name)
		authDataCommand = authData.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(pairingHandle,authDataCommand,True,7)
		print "Authorization Data sent: %s" % authData.show() 
		time.sleep(2)
		commandParsed = self.parser.parse(self._charWriteResponse)
		if self.parser.isNukiCommand(self._charWriteResponse) == False:
			sys.exit("Error while sending Authorization Data: %s" % commandParsed)
		if commandParsed.command != '0007':
			sys.exit("Nuki returned unexpected response (expecting AUTH_ID): %s" % commandParsed.show())
		print "Authorization ID received: %s" % commandParsed.show()
		nonceNuki = commandParsed.nonce
		authorizationID = commandParsed.authID
		self.config.set(self.macAddress,'authorizationID',authorizationID)
		authId = int(commandParsed.authID,16)
		authIDConfirm = nuki_messages.Nuki_AUTH_ID_CONFIRM()
		authIDConfirm.createPayload(publicKeyNuki, privateKeyHex, publicKeyHex, nonceNuki, authId)
		authIDConfirmCommand = authIDConfirm.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(pairingHandle,authIDConfirmCommand,True,7)
		print "Authorization ID Confirmation sent: %s" % authIDConfirm.show() 
		time.sleep(2)
		commandParsed = self.parser.parse(self._charWriteResponse)
		if self.parser.isNukiCommand(self._charWriteResponse) == False:
			sys.exit("Error while sending Authorization ID Confirmation: %s" % commandParsed)
		if commandParsed.command != '000E':
			sys.exit("Nuki returned unexpected response (expecting STATUS): %s" % commandParsed.show())
		print "STATUS received: %s" % commandParsed.status
		with open('/home/pi/nuki/nuki.cfg', 'wb') as configfile:
			self.config.write(configfile)
		return commandParsed.status
	
	# method to read the current lock state of the Nuki Lock
	def readLockState(self):
		self._makeBLEConnection()
		keyturnerUSDIOHandle = self.device.get_handle("a92ee202-5501-11e4-916c-0800200c9a66")
		self.device.subscribe('a92ee202-5501-11e4-916c-0800200c9a66', self._handleCharWriteResponse, indication=True))
		stateReq = nuki_messages.Nuki_REQ('000C')
		stateReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=stateReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		stateReqEncryptedCommand = stateReqEncrypted.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,stateReqEncryptedCommand,True,3)
		print "Nuki State Request sent: %s\nresponse received: %s" % (stateReq.show(),self._charWriteResponse) 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki STATES: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '000C':
			sys.exit("Nuki returned unexpected response (expecting Nuki STATES): %s" % commandParsed.show())
		print "%s" % commandParsed.show()
		return commandParsed
		
	# method to perform a lock action on the Nuki Lock:
	#	-lockAction: 'UNLOCK', 'LOCK', 'UNLATCH', 'LOCKNGO', 'LOCKNGO_UNLATCH', 'FOB_ACTION_1', 'FOB_ACTION_2' or 'FOB_ACTION_3'
	def lockAction(self,lockAction):
		self._makeBLEConnection()
		keyturnerUSDIOHandle = self.device.get_handle("a92ee202-5501-11e4-916c-0800200c9a66")
		self.device.subscribe('a92ee202-5501-11e4-916c-0800200c9a66', self._handleCharWriteResponse, indication=True))
		challengeReq = nuki_messages.Nuki_REQ('0004')
		challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=challengeReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		challengeReqEncryptedCommand = challengeReqEncrypted.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,challengeReqEncryptedCommand,True,4)
		print "Nuki CHALLENGE Request sent: %s" % challengeReq.show() 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki CHALLENGE: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '0004':
			sys.exit("Nuki returned unexpected response (expecting Nuki CHALLENGE): %s" % commandParsed.show())
		print "Challenge received: %s" % commandParsed.nonce
		lockActionReq = nuki_messages.Nuki_LOCK_ACTION()
		lockActionReq.createPayload(self.config.getint(self.macAddress, 'ID'), lockAction, commandParsed.nonce)
		lockActionReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=lockActionReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		lockActionReqEncryptedCommand = lockActionReqEncrypted.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,lockActionReqEncryptedCommand,True,4)
		print "Nuki Lock Action Request sent: %s" % lockActionReq.show() 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki Lock Action: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '000C' and commandParsed.command != '000E':
			sys.exit("Nuki returned unexpected response (expecting Nuki STATUS/STATES): %s" % commandParsed.show())
		print "%s" % commandParsed.show()
	
	# method to fetch the number of log entries from your Nuki Lock
	#	-pinHex : a 2-byte hex string representation of the PIN code you have set on your Nuki Lock (default is 0000)
	def getLogEntriesCount(self, pinHex):
		self._makeBLEConnection()
		keyturnerUSDIOHandle = self.device.get_handle("a92ee202-5501-11e4-916c-0800200c9a66")
		self.device.subscribe('a92ee202-5501-11e4-916c-0800200c9a66', self._handleCharWriteResponse, indication=True))
		challengeReq = nuki_messages.Nuki_REQ('0004')
		challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=challengeReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		challengeReqEncryptedCommand = challengeReqEncrypted.generate()
		self._charWriteResponse = ""
		print "Requesting CHALLENGE: %s" % challengeReqEncrypted.generate("HEX")
		self.device.char_write_handle(keyturnerUSDIOHandle,challengeReqEncryptedCommand,True,5)
		print "Nuki CHALLENGE Request sent: %s" % challengeReq.show() 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki CHALLENGE: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '0004':
			sys.exit("Nuki returned unexpected response (expecting Nuki CHALLENGE): %s" % commandParsed.show())
		print "Challenge received: %s" % commandParsed.nonce
		logEntriesReq = nuki_messages.Nuki_LOG_ENTRIES_REQUEST()
		logEntriesReq.createPayload(0, commandParsed.nonce, self.byteSwapper.swap(pinHex))
		logEntriesReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=logEntriesReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		logEntriesReqEncryptedCommand = logEntriesReqEncrypted.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,logEntriesReqEncryptedCommand,True,4)
		print "Nuki Log Entries Request sent: %s" % logEntriesReq.show() 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki Log Entries: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '0026':
			sys.exit("Nuki returned unexpected response (expecting Nuki LOG ENTRY): %s" % commandParsed.show())
		print "%s" % commandParsed.show()
		return int(commandParsed.logCount, 16)
	
	# method to fetch the most recent log entries from your Nuki Lock
	#	-count: the number of entries you would like to fetch (if available)
	#	-pinHex : a 2-byte hex string representation of the PIN code you have set on your Nuki Lock (default is 0000)
	def getLogEntries(self,count,pinHex):
		self._makeBLEConnection()
		keyturnerUSDIOHandle = self.device.get_handle("a92ee202-5501-11e4-916c-0800200c9a66")
		self.device.subscribe('a92ee202-5501-11e4-916c-0800200c9a66', self._handleCharWriteResponse, indication=True))
		challengeReq = nuki_messages.Nuki_REQ('0004')
		challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=challengeReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		challengeReqEncryptedCommand = challengeReqEncrypted.generate()
		print "Requesting CHALLENGE: %s" % challengeReqEncrypted.generate("HEX")
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,challengeReqEncryptedCommand,True,5)
		print "Nuki CHALLENGE Request sent: %s" % challengeReq.show() 
		time.sleep(2)
		commandParsed = self.parser.decrypt(self._charWriteResponse,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
		if self.parser.isNukiCommand(commandParsed) == False:
			sys.exit("Error while requesting Nuki CHALLENGE: %s" % commandParsed)
		commandParsed = self.parser.parse(commandParsed)
		if commandParsed.command != '0004':
			sys.exit("Nuki returned unexpected response (expecting Nuki CHALLENGE): %s" % commandParsed.show())
		print "Challenge received: %s" % commandParsed.nonce
		logEntriesReq = nuki_messages.Nuki_LOG_ENTRIES_REQUEST()
		logEntriesReq.createPayload(count, commandParsed.nonce, self.byteSwapper.swap(pinHex))
		logEntriesReqEncrypted = nuki_messages.Nuki_EncryptedCommand(authID=self.config.get(self.macAddress, 'authorizationID'), nukiCommand=logEntriesReq, publicKey=self.config.get(self.macAddress, 'publicKeyNuki'), privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
		logEntriesReqEncryptedCommand = logEntriesReqEncrypted.generate()
		self._charWriteResponse = ""
		self.device.char_write_handle(keyturnerUSDIOHandle,logEntriesReqEncryptedCommand,True,6)
		print "Nuki Log Entries Request sent: %s" % logEntriesReq.show()
		time.sleep(2)
		messages = self.parser.splitEncryptedMessages(self._charWriteResponse)
		print "Received %d messages" % len(messages)
		logMessages = []
		for message in messages:
			print "Decrypting message %s" % message
			try:
				commandParsed = self.parser.decrypt(message,self.config.get(self.macAddress, 'publicKeyNuki'),self.config.get(self.macAddress, 'privateKeyHex'))[8:]
				if self.parser.isNukiCommand(commandParsed) == False:
					sys.exit("Error while requesting Nuki Log Entries: %s" % commandParsed)
				commandParsed = self.parser.parse(commandParsed)
				if commandParsed.command != '0024' and commandParsed.command != '0026' and commandParsed.command != '000E':
					sys.exit("Nuki returned unexpected response (expecting Nuki LOG ENTRY): %s" % commandParsed.show())
				print "%s" % commandParsed.show()
				if commandParsed.command == '0024':
					logMessages.append(commandParsed)
			except:
				print "Unable to decrypt message"
		return logMessages
Example #10
0
 def __init__(self):
     self.byteSwapper = ByteSwapper()
Example #11
0
class NukiCommandParser:
    def __init__(self):
        self.byteSwapper = ByteSwapper()

    def isNukiCommand(self, commandString):
        command = self.byteSwapper.swap(commandString[:4])
        return command.upper() in NukiCommandText

    def getNukiCommandText(self, command):
        return NukiCommandText.get(
            command.upper(),
            'UNKNOWN').__name__  # UNKNOWN is default if command not found

    def parse(self, commandString):
        if self.isNukiCommand(commandString):
            command = self.byteSwapper.swap(commandString[:4]).upper()
            payload = commandString[4:-4]
            crc = self.byteSwapper.swap(commandString[-4:])
            #print(f"command = {command}, payload = {payload}, crc = {crc}")
            commandClass = NukiCommandText[command]
            if commandClass:
                return commandClass(payload)
            # pucgenie: check if the following thing changed since cleanup...
            if command == '0001':
                return Nuki_REQ(payload)
            elif command == '0003':
                return Nuki_PUBLIC_KEY(payload)
            elif command == '0004':
                return Nuki_CHALLENGE(payload)
            elif command == '0005':
                return Nuki_AUTH_AUTHENTICATOR(payload)
            elif command == '0006':
                return Nuki_AUTH_DATA(payload)
            elif command == '0007':
                return Nuki_AUTH_ID(payload)
            elif command == '000C':
                return Nuki_STATES(payload)
            elif command == '001E':
                return Nuki_AUTH_ID_CONFIRM(payload)
            elif command == '000E':
                return Nuki_STATUS(payload)
            elif command == '0023':
                return Nuki_LOG_ENTRIES_REQUEST(payload)
            elif command == '0024':
                return Nuki_LOG_ENTRY(payload)
            elif command == '0026':
                return Nuki_LOG_ENTRY_COUNT(payload)
            elif command == '0012':
                return Nuki_ERROR(payload)
        else:
            return f"{commandString} does not seem to be a valid Nuki command"

    def splitEncryptedMessages(self, msg):
        msgList = []
        offset = 0
        while offset < len(msg):
            nonce = msg[offset:offset + 48]
            authID = msg[offset + 48:offset + 56]
            length = int(self.byteSwapper.swap(msg[offset + 56:offset + 60]),
                         16)
            singleMsg = msg[offset:offset + 60 + (length * 2)]
            msgList.append(singleMsg)
            offset = offset + 60 + (length * 2)
        return msgList

    def decrypt(self, msg, publicKey, privateKey):
        print("msg: %s" % msg)
        nonce = msg[:48]
        #print "nonce: %s" % nonce
        authID = msg[48:56]
        #print "authID: %s" % authID
        length = int(self.byteSwapper.swap(msg[56:60]), 16)
        #print "length: %d" % length
        encrypted = nonce + msg[60:60 + (length * 2)]
        #print "encrypted: %s" % encrypted
        sharedKey = crypto_box_beforenm(
            bytes(publicKey), bytes(bytearray.fromhex(privateKey))).hex()
        box = nacl.secret.SecretBox(bytes(bytearray.fromhex(sharedKey)))
        decrypted = box.decrypt(bytes(bytearray.fromhex(encrypted))).hex()
        #print "decrypted: %s" % decrypted
        return decrypted
Example #12
0
class Nuki():
    # creates BLE connection with NUKI
    #	-macAddress: bluetooth mac-address of your Nuki Lock
    def __init__(self, macAddress, cfg):
        self._charWriteResponse = ""
        self.parser = nuki_messages.NukiCommandParser()
        self.crcCalculator = CrcCalculator()
        self.byteSwapper = ByteSwapper()
        self.macAddress = macAddress
        self.config = configparser.RawConfigParser()
        self.config.read(cfg)
        self.configfile = cfg
        self.device = None

    def _makeBLEConnection(self, retries=3):
        if self.device == None:
            currentTries = 0
            adapter = pygatt.backends.GATTToolBackend()
            nukiBleConnectionReady = False
            while (nukiBleConnectionReady == False and currentTries < retries):
                print("Starting BLE adapter...")
                adapter.start()
                print("Init Nuki BLE connection...")
                try:
                    self.device = adapter.connect(self.macAddress)
                    nukiBleConnectionReady = True
                except:
                    currentTries += 1
                    print("Unable to connect, retrying..., retry count: " +
                          str(currentTries))
            if self.device == None:
                print("Could not connect after " + str(currentTries) +
                      " tries")
            else:
                print("Nuki BLE connection established")

    def isNewNukiStateAvailable(self):
        if self.device != None:
            self.device.disconnect()
            self.device = None
        dev_id = 0
        try:
            sock = bluez.hci_open_dev(dev_id)
        except:
            print("error accessing bluetooth device...")
            sys.exit(1)
        blescan.hci_le_set_scan_parameters(sock)
        blescan.hci_enable_le_scan(sock)
        returnedList = blescan.parse_events(sock, 10)
        newStateAvailable = -1
        print(
            "isNewNukiStateAvailable() -> search through %d received beacons..."
            % len(returnedList))
        for beacon in returnedList:
            beaconElements = beacon.split(',')
            if beaconElements[0] == self.macAddress.lower(
            ) and beaconElements[1] == "a92ee200550111e4916c0800200c9a66":
                print("Nuki beacon found, new state element: %s" %
                      beaconElements[4])
                if beaconElements[4] == '-60':
                    newStateAvailable = 0
                else:
                    newStateAvailable = 1
                break
            else:
                print(
                    f"non-Nuki beacon found: mac={beaconElements[0]}, signature={beaconElements[1]}"
                )
        print(f"isNewNukiStateAvailable() -> result={newStateAvailable}")
        return newStateAvailable

    # private method to handle responses coming back from the Nuki Lock over the BLE connection
    def _handleCharWriteResponse(self, handle, value):
        self._charWriteResponse += "".join(format(x, '02x') for x in value)

    # method to authenticate yourself (only needed the very first time) to the Nuki Lock
    #	-publicKeyHex: a public key (as hex string) you created to talk with the Nuki Lock
    #	-privateKeyHex: a private key (complementing the public key, described above) you created to talk with the Nuki Lock
    #	-ID : a unique number to identify yourself to the Nuki Lock
    #	-IDType : '00' for 'app', '01' for 'bridge' and '02' for 'fob'
    #	-name : a unique name to identify yourself to the Nuki Lock (will also appear in the logs of the Nuki Lock)
    def authenticateUser(self, publicKey, privateKeyHex, ID, IDType, name):
        self._makeBLEConnection()
        if self.device == None:
            return
        self.config.remove_section(self.macAddress)
        self.config.add_section(self.macAddress)
        pairingHandle = self.device.get_handle(DEVICE_HANDLEID1)
        print("Nuki Pairing UUID handle created: %04x" % pairingHandle)
        publicKeyReq = nuki_messages.Nuki_REQ(
            nuki_messages.Nuki_PUBLIC_KEY.command)
        self.device.subscribe(DEVICE_HANDLEID1,
                              self._handleCharWriteResponse,
                              indication=True)
        publicKeyReqCommand = publicKeyReq.generate()
        self._charWriteResponse = ""
        print(
            f"Requesting Nuki Public Key using command: {publicKeyReq.show()}")
        self.device.char_write_handle(pairingHandle, publicKeyReqCommand, True,
                                      2)
        print("Nuki Public key requested")
        # wtf
        time.sleep(2)
        commandParsed = self.parser.parse(self._charWriteResponse)
        if self.parser.isNukiCommand(self._charWriteResponse) == False:
            sys.exit(f"Error while requesting public key: {commandParsed}")
        if type(commandParsed) != nuki_messages.Nuki_PUBLIC_KEY:
            sys.exit(
                f"Nuki returned unexpected response (expecting PUBLIC_KEY): {commandParsed.show()}"
            )
        publicKeyNuki = commandParsed.publicKey
        self.config.set(self.macAddress, 'publicKeyNuki', publicKeyNuki)
        self.config.set(self.macAddress, 'publicKeyHex', publicKey.hex())
        self.config.set(self.macAddress, 'privateKeyHex', privateKeyHex)
        self.config.set(self.macAddress, 'ID', ID)
        self.config.set(self.macAddress, 'IDType', IDType)
        self.config.set(self.macAddress, 'Name', name)
        print(f"Public key received: {commandParsed.publicKey}")
        publicKeyPush = nuki_messages.Nuki_PUBLIC_KEY(publicKey)
        publicKeyPushCommand = publicKeyPush.generate()
        print(f"Pushing Public Key using command: {publicKeyPush.show()}")
        self._charWriteResponse = ""
        self.device.char_write_handle(pairingHandle, publicKeyPushCommand,
                                      True, 5)
        print("Public key pushed")
        time.sleep(2)
        commandParsed = self.parser.parse(self._charWriteResponse)
        if self.parser.isNukiCommand(self._charWriteResponse) == False:
            sys.exit(f"Error while pushing public key: {commandParsed}")
        if type(commandParsed) != nuki_messages.Nuki_CHALLENGE:
            sys.exit(
                f"Nuki returned unexpected response (expecting CHALLENGE): {commandParsed.show()}"
            )
        print(f"Challenge received: {commandParsed.nonce}")
        nonceNuki = commandParsed.nonce
        authAuthenticator = nuki_messages.Nuki_AUTH_AUTHENTICATOR()
        authAuthenticator.createPayload(nonceNuki, privateKeyHex, publicKey,
                                        publicKeyNuki)
        authAuthenticatorCommand = authAuthenticator.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(pairingHandle, authAuthenticatorCommand,
                                      True, 5)
        print(f"Authorization Authenticator sent: {authAuthenticator.show()}")
        time.sleep(2)
        commandParsed = self.parser.parse(self._charWriteResponse)
        if self.parser.isNukiCommand(self._charWriteResponse) == False:
            sys.exit(
                f"Error while sending Authorization Authenticator: {commandParsed}"
            )
        if type(commandParsed) != nuki_messages.Nuki_CHALLENGE:
            sys.exit(
                f"Nuki returned unexpected response (expecting CHALLENGE): {commandParsed.show()}"
            )
        print(f"Challenge received: {commandParsed.nonce}")
        nonceNuki = commandParsed.nonce
        authData = nuki_messages.Nuki_AUTH_DATA()
        authData.createPayload(publicKeyNuki, privateKeyHex, nonceNuki, ID,
                               IDType, name)
        authDataCommand = authData.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(pairingHandle, authDataCommand, True, 7)
        print(f"Authorization Data sent: {authData.show()}")
        time.sleep(2)
        commandParsed = self.parser.parse(self._charWriteResponse)
        if self.parser.isNukiCommand(self._charWriteResponse) == False:
            sys.exit(
                f"Error while sending Authorization Data: {commandParsed}")
        if type(commandParsed) != nuki_messages.Nuki_AUTH_ID:
            sys.exit(
                f"Nuki returned unexpected response (expecting AUTH_ID): {commandParsed.show()}"
            )
        print(f"Authorization ID received: {commandParsed.show()}")
        nonceNuki = commandParsed.nonce
        authorizationID = commandParsed.authID
        self.config.set(self.macAddress, 'authorizationID', authorizationID)
        authId = int(commandParsed.authID, 16)
        authIDConfirm = nuki_messages.Nuki_AUTH_ID_CONFIRM()
        authIDConfirm.createPayload(publicKeyNuki, privateKeyHex, nonceNuki,
                                    authId)
        authIDConfirmCommand = authIDConfirm.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(pairingHandle, authIDConfirmCommand,
                                      True, 7)
        print(f"Authorization ID Confirmation sent: {authIDConfirm.show()}")
        time.sleep(2)
        commandParsed = self.parser.parse(self._charWriteResponse)
        if self.parser.isNukiCommand(self._charWriteResponse) == False:
            sys.exit(
                f"Error while sending Authorization ID Confirmation: {commandParsed}"
            )
        if commandParsed.command != '000E':
            sys.exit(
                f"Nuki returned unexpected response (expecting STATUS): {commandParsed.show()}"
            )
        print(f"STATUS received: {commandParsed.status}")
        with open(self.configfile, 'w') as configfile:
            self.config.write(configfile)
        return commandParsed.status

    # method to read the current lock state of the Nuki Lock
    def readLockState(self):
        self._makeBLEConnection()
        if self.device == None:
            return

        keyturnerUSDIOHandle = self.getHandle()
        self.executeChallenge('000C', keyturnerUSDIOHandle)
        commandParsed = self.parseChallengeResponse('000C')
        return commandParsed

    # method to perform a lock action on the Nuki Lock:
    #	-lockAction: 'UNLOCK', 'LOCK', 'UNLATCH', 'LOCKNGO', 'LOCKNGO_UNLATCH', 'FOB_ACTION_1', 'FOB_ACTION_2' or 'FOB_ACTION_3'
    def lockAction(self, lockAction):
        epoch_time = int(time.time())
        self._makeBLEConnection()
        if self.device == None:
            return

        keyturnerUSDIOHandle = self.getHandle()
        self.executeChallenge('0004', keyturnerUSDIOHandle)
        commandParsed = self.parseChallengeResponse('0004')
        self.executeLockAction(keyturnerUSDIOHandle, lockAction, commandParsed)
        response = self.checkLockActionResponse()
        print("Done in {} seconds".format((int(time.time()) - epoch_time)))
        return response

    @retry(Exception, tries=8, delay=0.5)
    def getHandle(self):
        print("Retrieving handle")
        keyturnerUSDIOHandle = self.device.get_handle(
            "a92ee202-5501-11e4-916c-0800200c9a66")
        print("Handle retrieved")
        self.device.subscribe('a92ee202-5501-11e4-916c-0800200c9a66',
                              self._handleCharWriteResponse,
                              indication=True)
        print("Subscribed to device")
        return keyturnerUSDIOHandle

    @retry(Exception, tries=8, delay=0.5)
    def executeChallenge(self, request, keyturnerUSDIOHandle):
        print("Going to execute challenge")
        challengeReq = nuki_messages.Nuki_REQ(request)
        challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=challengeReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
        challengeReqEncryptedCommand = challengeReqEncrypted.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      challengeReqEncryptedCommand, True, 4)
        print("Nuki CHALLENGE Request sent: %s" % challengeReq.show())

    @retry(Exception, tries=8, delay=0.5)
    def parseChallengeResponse(self, request):
        commandParsed = self.parser.decrypt(
            self._charWriteResponse,
            self.config.get(self.macAddress, 'publicKeyNuki'),
            self.config.get(self.macAddress, 'privateKeyHex'))[8:]
        if self.parser.isNukiCommand(commandParsed) == False:
            raise Exception("Error while checking challenge response")
        commandParsed = self.parser.parse(commandParsed)
        if commandParsed.command != request:
            raise Exception("Parsed command is not equal to the request")
        return commandParsed

    @retry(Exception, tries=8, delay=0.5)
    def executeLockAction(self, keyturnerUSDIOHandle, lockAction,
                          commandParsed):
        lockActionReq = nuki_messages.Nuki_LOCK_ACTION()
        lockActionReq.createPayload(self.config.getint(self.macAddress, 'ID'),
                                    lockAction, commandParsed.nonce)
        lockActionReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=lockActionReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'),
        )
        lockActionReqEncryptedCommand = lockActionReqEncrypted.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      lockActionReqEncryptedCommand, True, 4)
        print("Nuki Lock Action Request sent: %s" % lockActionReq.show())

    @retry(Exception, tries=8, delay=0.5)
    def checkLockActionResponse(self):
        commandParsed = self.parser.decrypt(
            self._charWriteResponse,
            self.config.get(self.macAddress, 'publicKeyNuki'),
            self.config.get(self.macAddress, 'privateKeyHex'))[8:]
        if self.parser.isNukiCommand(commandParsed) == False:
            raise Exception("Error while request lock action")
        return self.parser.parse(commandParsed)

    # method to fetch the number of log entries from your Nuki Lock
    #	-pinHex : a 2-byte hex string representation of the PIN code you have set on your Nuki Lock (default is 0000)
    def getLogEntriesCount(self, pinHex):
        self._makeBLEConnection()
        keyturnerUSDIOHandle = self.device.get_handle(DEVICE_HANDLEID2)
        self.device.subscribe(DEVICE_HANDLEID2,
                              self._handleCharWriteResponse,
                              indication=True)
        challengeReq = nuki_messages.Nuki_REQ(
            nuki_messages.Nuki_CHALLENGE.command)
        challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=challengeReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
        challengeReqEncryptedCommand = challengeReqEncrypted.generate()
        self._charWriteResponse = ""
        print(f"Requesting CHALLENGE: {challengeReqEncrypted.generate('HEX')}")
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      challengeReqEncryptedCommand, True, 5)
        print("Nuki CHALLENGE Request sent: %s" % challengeReq.show())
        # time.sleep(2)
        commandParsed = self.parser.decrypt(
            self._charWriteResponse,
            self.config.get(self.macAddress, 'publicKeyNuki'),
            self.config.get(self.macAddress, 'privateKeyHex'))[8:]
        if self.parser.isNukiCommand(commandParsed) == False:
            sys.exit(f"Error while requesting Nuki CHALLENGE: {commandParsed}")
        commandParsed = self.parser.parse(commandParsed)
        if type(commandParsed) != nuki_messages.Nuki_CHALLENGE:
            sys.exit(
                f"Nuki returned unexpected response (expecting Nuki CHALLENGE): {commandParsed.show()}"
            )
        print(f"Challenge received: {commandParsed.nonce}")
        logEntriesReq = nuki_messages.Nuki_LOG_ENTRIES_REQUEST()
        logEntriesReq.createPayload(0, commandParsed.nonce,
                                    self.byteSwapper.swap(pinHex))
        logEntriesReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=logEntriesReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'),
        )
        logEntriesReqEncryptedCommand = logEntriesReqEncrypted.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      logEntriesReqEncryptedCommand, True, 4)
        print(f"Nuki Log Entries Request sent: {logEntriesReq.show()}")
        # time.sleep(2)
        commandParsed = self.parser.decrypt(
            self._charWriteResponse,
            self.config.get(self.macAddress, 'publicKeyNuki'),
            self.config.get(self.macAddress, 'privateKeyHex'))[8:]
        if self.parser.isNukiCommand(commandParsed) == False:
            sys.exit(
                f"Error while requesting Nuki Log Entries: {commandParsed}")
        commandParsed = self.parser.parse(commandParsed)
        if type(commandParsed) != nuki_messages.Nuki_LOG_ENTRY_COUNT:
            sys.exit(
                f"Nuki returned unexpected response (expecting Nuki LOG ENTRY): {commandParsed.show()}"
            )
        print("%s" % commandParsed.show())
        return int(commandParsed.logCount, 16)

    # method to fetch the most recent log entries from your Nuki Lock
    #	-count: the number of entries you would like to fetch (if available)
    #	-pinHex : a 2-byte hex string representation of the PIN code you have set on your Nuki Lock (default is 0000)
    def getLogEntries(self, count, pinHex):
        self._makeBLEConnection()
        keyturnerUSDIOHandle = self.device.get_handle(DEVICE_HANDLEID2)
        self.device.subscribe(DEVICE_HANDLEID2,
                              self._handleCharWriteResponse,
                              indication=True)
        challengeReq = nuki_messages.Nuki_REQ(
            nuki_messages.Nuki_CHALLENGE.command)
        challengeReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=challengeReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
        challengeReqEncryptedCommand = challengeReqEncrypted.generate()
        print(f"Requesting CHALLENGE: {challengeReqEncrypted.generate('HEX')}")
        self._charWriteResponse = ""
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      challengeReqEncryptedCommand, True, 5)
        print("Nuki CHALLENGE Request sent: %s" % challengeReq.show())
        # time.sleep(2)
        commandParsed = self.parser.decrypt(
            self._charWriteResponse,
            self.config.get(self.macAddress, 'publicKeyNuki'),
            self.config.get(self.macAddress, 'privateKeyHex'))[8:]
        if self.parser.isNukiCommand(commandParsed) == False:
            sys.exit("Error while requesting Nuki CHALLENGE: %s" %
                     commandParsed)
        commandParsed = self.parser.parse(commandParsed)
        if type(commandParsed) != nuki_messages.Nuki_CHALLENGE:
            sys.exit(
                "Nuki returned unexpected response (expecting Nuki CHALLENGE): %s"
                % commandParsed.show())
        print("Challenge received: %s" % commandParsed.nonce)
        logEntriesReq = nuki_messages.Nuki_LOG_ENTRIES_REQUEST()
        logEntriesReq.createPayload(count, commandParsed.nonce,
                                    self.byteSwapper.swap(pinHex))
        logEntriesReqEncrypted = nuki_messages.Nuki_EncryptedCommand(
            authID=self.config.get(self.macAddress, 'authorizationID'),
            nukiCommand=logEntriesReq,
            publicKey=self.config.get(self.macAddress, 'publicKeyNuki'),
            privateKey=self.config.get(self.macAddress, 'privateKeyHex'))
        logEntriesReqEncryptedCommand = logEntriesReqEncrypted.generate()
        self._charWriteResponse = ""
        self.device.char_write_handle(keyturnerUSDIOHandle,
                                      logEntriesReqEncryptedCommand, True, 6)
        print("Nuki Log Entries Request sent: %s" % logEntriesReq.show())
        # time.sleep(2)
        messages = self.parser.splitEncryptedMessages(self._charWriteResponse)
        print("Received %d messages" % len(messages))
        logMessages = []
        for message in messages:
            print(f"Decrypting message {message}")
            try:
                commandParsed = self.parser.decrypt(
                    message, self.config.get(self.macAddress, 'publicKeyNuki'),
                    self.config.get(self.macAddress, 'privateKeyHex'))[8:]
                if self.parser.isNukiCommand(commandParsed) == False:
                    sys.exit(
                        f"Error while requesting Nuki Log Entries: {commandParsed}"
                    )
                commandParsed = self.parser.parse(commandParsed)
                if type(commandParsed) not in [
                        nuki_messages.Nuki_LOG_ENTRY,
                        nuki_messages.Nuki_LOG_ENTRY_COUNT,
                        nuki_messages.Nuki_STATUS
                ]:
                    sys.exit(
                        "Nuki returned unexpected response (expecting Nuki LOG ENTRY): %s"
                        % commandParsed.show())
                print("%s" % commandParsed.show())
                if type(commandParsed) == nuki_messages.Nuki_LOG_ENTRY:
                    logMessages.append(commandParsed)
            except:
                print("Unable to decrypt message")
        return logMessages