def __init__(self): # TODO: Maybe we should add dict as ParticleProtocol inheritance? self._transport = None self._step = None self._device_id = None self._device_id_hex = None self._device_key = None self._nonce = None self._aes_key = None self._iv = None self._salt = None self._counter = 0 self._crypto = CryptoOsCrypto() # Load server key logging.debug("Loading server key from: %s" % args.private_key_file) self._crypto.load_server_key(args.private_key_file) super().__init__()
class ParticleProtocol(asyncio.Protocol): """ This is wrapper that implement: * handshake as describe here: https://github.com/spark/spark-protocol/blob/master/js/lib/Handshake.js#L29 """ def __init__(self): # TODO: Maybe we should add dict as ParticleProtocol inheritance? self._transport = None self._step = None self._device_id = None self._device_id_hex = None self._device_key = None self._nonce = None self._aes_key = None self._iv = None self._salt = None self._counter = 0 self._crypto = CryptoOsCrypto() # Load server key logging.debug("Loading server key from: %s" % args.private_key_file) self._crypto.load_server_key(args.private_key_file) super().__init__() def connection_made(self, transport): # Handshake step 1: Socket opens. logging.debug("Client connection from %s" % transport.get_extra_info('peername')[0]) self._transport = transport self._step = 0 self._nonce = self._crypto.random(40) # Handshake step 2: Server responds with 40 bytes of random data as a nonce. if self._step == 0: logging.debug("Generated nonce: %s" % self._nonce) self.write(self._nonce) self._step += 1 def connection_lost(self, exc): logging.debug("Connection lost with device ID: %s (%s)" % (self._device_id_hex, self._transport.get_extra_info('peername')[0])) def data_received(self, data): logging.debug("Data received (%s) %s: %s" % (self._step, len(data), data)) if self._step == 1: cipher = None # Handshake step 3: Server should read exactly 256 bytes from the socket. if len(data) != 256: logging.critical("Not enough data received from core in handshake step #3") self._transport.close() try: cipher = self._crypto.rsa_pkcs1v15_decrypt(data) except ValueError: self._transport.close() # The first 40 bytes of the message must match the previously sent nonce, # otherwise Server must close the connection. # # Remaining 12 bytes of message represent STM32 ID. (_client_nonce, self._device_id) = (cipher[:40], cipher[40:52]) if _client_nonce != self._nonce: logging.critical("Invalid nonce received, closing connection.") self._transport.close() # Store an hexadecimal string of device ID self._device_id_hex = binascii.hexlify(self._device_id).decode() logging.debug("Loading device public key from %s/%s.pem" % (args.devices_keys, self._device_id_hex)) # Server looks up STM32 ID, retrieving the Core's public RSA key. try: self._crypto.load_device_key("%s/%s.pem" % (args.devices_keys, self._device_id_hex)) # TODO: Check key size # If the public key is not found, Server must close the connection. except Exception: logging.critical("Device ID %s public key not found, closing connection" % self._device_id_hex) self._transport.close() logging.info("Device ID %s connected" % self._device_id_hex) # Handshake step 4: Server creates secure session key # Server generates 40 bytes of secure random data to serve # as components of a session key for AES-128-CBC encryption. session_key = self._crypto.random(40) # The first 16 bytes (MSB first) will be the key, the next 16 bytes (MSB first) will be the # initialization vector (IV), and the final 8 bytes (MSB first) will be the salt. (self._aes_key, self._iv, self._salt) = (session_key[:16], session_key[16:32], session_key[-8:]) logging.debug("AES KEY: %s, IV: %s, SALT: %s" % (self._aes_key, self._iv, self._salt)) # Server RSA encrypts this 40-byte message using the Core's public key to create a 128-byte ciphertext. cipher = self._crypto.rsa_pkcs1v15_encrypt(session_key) logging.debug("Cipher size: %s" % len(cipher)) # Server creates a 20-byte HMAC of the ciphertext using SHA1 and the 40 bytes generated # in the previous step as the HMAC key. hmac_result = self._crypto.hmac(session_key, cipher, CryptographicMeta.SHA1) logging.debug("HMAC (size: %s): %s" % (len(hmac_result), hmac_result)) # Server signs the HMAC with its RSA private key generating a 256-byte signature. signature = self._crypto.rsa_pkcs1v15_sign(hmac_result) # Server sends 384 bytes to Core: the ciphertext then the signature. self.write(cipher + signature) self._step += 1 elif self._step == 2: logging.debug("hello received from %s" % self._device_id_hex) # Decrypt data from hello (a, b, cipherdata) = (data[0], data[1], data[2::]) hello_message = self._crypto.aes_decrypt(self._aes_key, cipherdata, self._iv) logging.debug("Hello message: %s %s %s" % (a, b, binascii.hexlify(hello_message))) # Send our hello self._step += 1 elif self._step > 3: return self.protocol_received(data) def write(self, data): logging.debug("Sending %s bytes to %s (%s), step: %s" % ( len(data), self._device_id_hex, self._transport.get_extra_info('peername')[0], self._step )) self._transport.write(data) def protocol_received(self, data): raise NotImplementedError