def _get_key(cls, device_id): """Attempt to get a user key from an environment variable """ var_name = "USER_KEY_{0:08X}".format(device_id) if var_name not in os.environ: raise NotFoundError("No user key could be found for devices", device_id=device_id, expected_variable_name=var_name) key_var = os.environ[var_name] if len(key_var) != 64: raise NotFoundError( "User key in variable is not the correct length, should be 64 hex characters", device_id=device_id, key_value=key_var) try: key = binascii.unhexlify(key_var) except ValueError: raise NotFoundError( "User key in variable could not be decoded from hex", device_id=device_id, key_value=key_var) if len(key) != 32: raise NotFoundError( "User key in variable is not the correct length, should be 64 hex characters", device_id=device_id, key_value=key_var) return key
def verify_key(self, root_key_type): """Verify that the root key type is known to us. Args: root_key_type: requested key type Raises: NotFoundError: If the key type is not known. """ if root_key_type not in self.KnownKeyRoots: raise NotFoundError("Unknown key type", key=root_key_type) if root_key_type not in self.supported_keys: raise NotFoundError("Not supported key type", key=root_key_type)
def sign_report(self, device_id, root, data, **kwargs): """Sign a buffer of report data on behalf of a device. Args: device_id (int): The id of the device that we should encrypt for root (int): The root key type that should be used to generate the report data (bytearray): The data that we should sign **kwargs: There are additional specific keyword args that are required depending on the root key used. Typically, you must specify - report_id (int): The report id - sent_timestamp (int): The sent timestamp of the report These two bits of information are used to construct the per report signing and encryption key from the specific root key type. Returns: dict: The signature and any associated metadata about the signature. The signature itself must always be a bytearray stored under the 'signature' key, however additional keys may be present depending on the signature method used. Raises: NotFoundError: If the auth provider is not able to sign the data. """ for _priority, provider in self.providers: try: return provider.sign_report(device_id, root, data, **kwargs) except NotFoundError: pass raise NotFoundError( "sign_report method is not implemented in any sub_providers")
def sign_report(self, device_id, root, data, **kwargs): """Sign a buffer of report data on behalf of a device. Args: device_id (int): The id of the device that we should encrypt for root (int): The root key type that should be used to generate the report data (bytearray): The data that we should sign **kwargs: There are additional specific keyword args that are required depending on the root key used. Typically, you must specify - report_id (int): The report id - sent_timestamp (int): The sent timestamp of the report These two bits of information are used to construct the per report signing and encryption key from the specific root key type. Returns: dict: The signature and any associated metadata about the signature. The signature itself must always be a bytearray stored under the 'signature' key, however additional keys may be present depending on the signature method used. Raises: NotFoundError: If the auth provider is not able to sign the data. """ AuthProvider.VerifyRoot(root) if root != AuthProvider.NoKey: raise NotFoundError('unsupported root key in BasicAuthProvider', root_key=root) result = bytearray(hashlib.sha256(data).digest()) return {'signature': result, 'root_key': root}
def encrypt_report(self, device_id, root, data, **kwargs): """Encrypt a buffer of report data on behalf of a device. Args: device_id (int): The id of the device that we should encrypt for root (int): The root key type that should be used to generate the report data (bytearray): The data that we should encrypt. **kwargs: There are additional specific keyword args that are required depending on the root key used. Typically, you must specify - report_id (int): The report id - sent_timestamp (int): The sent timestamp of the report These two bits of information are used to construct the per report signing and encryption key from the specific root key type. Returns: dict: The encrypted data and any associated metadata about the data. The data itself must always be a bytearray stored under the 'data' key, however additional keys may be present depending on the encryption method used. Raises: NotFoundError: If the auth provider is not able to encrypt the data. """ raise NotFoundError("encrypt_report method is not implemented")
def get_serialized_key(self, key_type, device_id, **key_info): """Get a serialized key such for signing a streamer report. These keys are designed to only be used once and only provide access to the object of key_type with the given serial_number Args: key_type (int): no key, user key or device key device_id (int): UUID of the device key_info (dict): data required for key generation. It may be report_id and sent_timestamp Returns: bytearray: the key """ report_id = key_info.get('report_id', None) sent_timestamp = key_info.get('sent_timestamp', None) if report_id is None or sent_timestamp is None: raise NotFoundError('report_id or sent_timestamp is not provided') self.verify_key(key_type) root_key = self.get_root_key(key_type, device_id) report_key = self.DeriveReportKey(root_key, report_id, sent_timestamp) return report_key
def verify_report(self, device_id, root, data, signature, **kwargs): """Verify a buffer of report data on behalf of a device. Args: device_id (int): The id of the device that we should encrypt for root (int): The root key type that should be used to generate the report data (bytearray): The data that we should verify signature (bytearray): The signature attached to data that we should verify **kwargs: There are additional specific keyword args that are required depending on the root key used. Typically, you must specify - report_id (int): The report id - sent_timestamp (int): The sent timestamp of the report These two bits of information are used to construct the per report signing and encryption key from the specific root key type. Returns: dict: The result of the verification process must always be a bool under the 'verified' key, however additional keys may be present depending on the signature method used. Raises: NotFoundError: If the auth provider is not able to verify the data due to an error. If the data is simply not valid, then the function returns normally. """ raise NotFoundError("verify method is not implemented")
def DeriveRotatedKey(cls, reboot_key, current_timestamp, rotation_interval_power): """Derive an ephemeral key every 2^rotation_interval_power seconds, The same key will be return if timeout of rotation is not elapsed AES-128-ECB(Reboot Key, current_timestamp with low X bits masked to 0) Args: reboot_key (bytearray): 16 bytes key current_timestamp (int): current time rotation_interval_power (int): X in 2^X Returns: bytearray: the rotated key """ timestamp = current_timestamp & (~(2**rotation_interval_power - 1)) try: from Crypto.Cipher import AES except ImportError as error: raise NotFoundError( "Cryptographic library is not available") from error cipher = AES.new(reboot_key, AES.MODE_ECB) msg = struct.pack("<LLLL", timestamp, 0, 0, 0) return cipher.encrypt(msg)
def HeaderLength(cls): """Return the length of a header needed to calculate this report's length Returns: int: the length of the needed report header """ raise NotFoundError("IOTileReport HeaderLength needs to be overridden")
def VerifyRoot(cls, root): """Verify that the root key type is known to us. Raises: NotFoundError: If the key type is not known. """ if root in cls.KnownKeyRoots: return raise NotFoundError("Unknown key type", key=root)
def _verify_derive_key(cls, device_id, root, **kwargs): report_id = kwargs.get('report_id', None) sent_timestamp = kwargs.get('sent_timestamp', None) if report_id is None or sent_timestamp is None: raise NotFoundError( 'report_id or sent_timestamp not provided in EnvAuthProvider.sign_report' ) AuthProvider.VerifyRoot(root) if root != AuthProvider.UserKey: raise NotFoundError('unsupported root key in EnvAuthProvider', root_key=root) root_key = cls._get_key(device_id) report_key = AuthProvider.DeriveReportKey(root_key, report_id, sent_timestamp) return report_key
def get_rotated_key(self, key_type, device_id, **rotation_info): """Get a key that is only valid for a limit period of time. Args: key_type (int): no key, user key or device key device_id (int): UUID of the device key_info (dict): data that describes the conditions when the key is rotated. For example, for a broadcast report key it may be the reboot counter of the device, the current uptime and the rotation interval of the key. Returns: bytearray: the rotated key """ counter = rotation_info.get("reboot_counter", None) interval_power = rotation_info.get("rotation_interval_power", None) timestamp = rotation_info.get("current_timestamp", None) if not counter: raise NotFoundError( 'reboot_counter is not provided in EnvAuthProvider get_rotated_key' ) if not interval_power: raise NotFoundError( 'rotation_interval_power is not provided in EnvAuthProvider get_rotated_key' ) if not timestamp: raise NotFoundError( 'timestamp is not provided in EnvAuthProvider get_rotated_key') self.verify_key(key_type) root_key = self.get_root_key(key_type, device_id) reboot_key = self.DeriveRebootKey(root_key, 0, counter) temp_key = self.DeriveRotatedKey(reboot_key, timestamp, interval_power) return temp_key
def get_password(cls, device_id): """Returns the password from the class Args: device_id (int): uuid or mac of the device Returns: bytes: the root key """ if device_id in cls._shared_passwords: return cls._shared_passwords[device_id] else: raise NotFoundError("No key could be found for device", device_id=device_id)
def check(self, depinfo, deptile, depsettings): """Check if this dependency is the latest version in a subclass defined way Args: depinfo (dict): a dictionary returned by IOTile.dependencies describing the dependency deptile (IOTile): an IOTile object for the installed dependency depsettings (dict): a dictionary that was previously stored with this dependency by resolve Returns: bool: True meaning the dependency is up-to-date or False if it is not. """ raise NotFoundError( "DependencyResolver did not implement check method")
def verify_report(self, device_id, root, data, signature, **kwargs): """Verify a buffer of report data on behalf of a device. Args: device_id (int): The id of the device that we should encrypt for root (int): The root key type that should be used to generate the report data (bytearray): The data that we should verify signature (bytearray): The signature attached to data that we should verify **kwargs: There are additional specific keyword args that are required depending on the root key used. Typically, you must specify - report_id (int): The report id - sent_timestamp (int): The sent timestamp of the report These two bits of information are used to construct the per report signing and encryption key from the specific root key type. Returns: dict: The result of the verification process must always be a bool under the 'verified' key, however additional keys may be present depending on the signature method used. Raises: NotFoundError: If the auth provider is not able to verify the data due to an error. If the data is simply not valid, then the function returns normally. """ AuthProvider.VerifyRoot(root) if root != AuthProvider.NoKey: raise NotFoundError('unsupported root key in BasicAuthProvider', root_key=root) result = bytearray(hashlib.sha256(data).digest()) if len(signature) == 0: verified = False elif len(signature) > len(result): verified = False elif len(signature) < len(result): trunc_result = result[:len(signature)] verified = hmac.compare_digest(signature, trunc_result) else: verified = hmac.compare_digest(signature, result) return {'verified': verified, 'bit_length': 8 * len(signature)}
def get_rotated_key(self, key_type, device_id, **rotation_info): """Deligates call to auth providers in the chain Args: key_type (int): see KnownKeyRoots device_id (int): uuid of the device rotation_info (dict): specific value for every auth provider Returns: bytes: the rotated key """ for _priority, provider in self.providers: try: return provider.get_rotated_key(key_type, device_id, **rotation_info) except NotFoundError: pass raise NotFoundError( "get_rotated_key method is not implemented in any sub_providers")
def resolve(self, depinfo, destdir): """Attempt to resolve this dependency using a subclass defined method Args: depinfo (dict): a dictionary returned by IOTile.dependencies describing the dependency destdir (string): the directory that the dependency should be copied into Returns: dict: The function returns a dictionary that has required and optional keys. The required keys are: found: boolean if this resolver found a matching dependency optional keys are: stop: boolean if this resolver is suggesting that we should stop looking for this dependency. This is useful for making a resolver that stops a resolver chain info: a dictionary that contains information that this resolver wants to store with the dependency for future reference. """ raise NotFoundError( "DependencyResolver did not implement resolve method")
def get_root_key(self, key_type, device_id): """Deligates call to auth providers in the chain. This function will attempt to use a device alias if it exists. This allows support of using both UUID and device MAC. Args: key_type (int): see KnownKeyRoots device_id (int): ID of the device Returns: bytes: the root key """ for _priority, provider in self.providers: try: return provider.get_root_key(key_type, device_id) except NotFoundError: pass raise NotFoundError( "get_serialized_key method is not implemented in any sub_providers" )
def ReportLength(cls, header): """Given a header of HeaderLength bytes, calculate the size of this report """ raise NotFoundError("IOTileReport ReportLength needs to be overriden")
def decode(self): """Decode a raw report into a series of readings """ raise NotFoundError("IOTileReport decode needs to be overriden")