def generate_keys(): """ Generates the public and private keys necessary for encryption and signing. """ sodium_client = SodiumClient() [sign_pk, sign_sk] = sodium_client.sign_generate_keypair() [box_pk, box_sk] = sodium_client.box_generate_keypair() return EncryptionKeys(sign_pk, sign_sk, box_pk, box_sk)
class SplunkEncryptionContext(EncryptionContext): """ Context object for handling generating and fetching public and private keys. (Currently only public key is supported) """ def __init__(self, session_key, app_name, sodium_client=None): """ Pass a session token and create a KV Store Handler to be able to write and fetch public keys from KV Store. The session token itself is not exposed, only the handler. """ self.mode = SdkMode.SPLUNK if sodium_client: self.sodium_client = sodium_client else: self.sodium_client = SodiumClient() self._key_cache = {} self.session_key = session_key self.app_name = app_name self.generate_keys() def set_encryption_keys(self, keys_dict): self._key_cache = keys_dict def _cache_keys(self): result = False raw_data = splunk_client.fetch_sensitive_data(self.session_key, ENCRYPTION_KEYS, self.app_name) try: self._key_cache = EncryptionKeys.from_json(json.loads(raw_data)).__dict__ except Exception: self._key_cache = {} if SIGN_PUBLIC_KEY in self._key_cache: result = True return result def _create_key_bucket(self): try: splunk_client.create_sensitive_data(self.session_key, ENCRYPTION_KEYS, '{}', self.app_name) except RESTException as e: if e.statusCode != 409: raise e def generate_keys(self): """ Stores the required signing and encryption keys to KV Store in the meta collection. If the public key already exists, then this is a no op. There is a synchronization issue where when you call generate keys, KV Store might not yet have been initialized. The way we handle this is if we get a 503 HTTP error back from KV Store, this means the store is not yet available, in which case we callback generate keys to run in 5 seconds. """ self._create_key_bucket() keys_cached = self._cache_keys() if not keys_cached: [sign_pk, sign_sk] = [k for k in self.sodium_client.sign_generate_keypair()] [box_pk, box_sk] = [k for k in self.sodium_client.box_generate_keypair()] encryption_keys = EncryptionKeys(sign_pk, sign_sk, box_pk, box_sk) key_data = json.dumps(encryption_keys.to_json()) splunk_client.update_sensitive_data(self.session_key, ENCRYPTION_KEYS, key_data, self.app_name) self._key_cache = encryption_keys.__dict__
class DeploymentBundle(BaseRestHandler, PersistentServerConnectionApplication): """ Main class for handling the deployment bundle endpoint. Subclasses the spacebridge_app BaseRestHandler. """ def __init__(self, command_line, command_arg): BaseRestHandler.__init__(self) self.sodium_client = SodiumClient() def get(self, request): """ Handler which returns mdm signing public key """ response = {} try: kvstore_service = kvstore(collection=USER_META_COLLECTION_NAME, session_key=request[SESSION][AUTHTOKEN], owner=request[SESSION][USER]) result = json.loads( kvstore_service.get_item_by_key(MDM_KEYPAIR_GENERATION_TIME) [1]) response.update({TIMESTAMP: result[TIMESTAMP]}) except Exception as e: # If key not in kvstore if hasattr(e, 'statusCode') and e.statusCode == HTTPStatus.NOT_FOUND: return { 'payload': { 'message': 'Could not find mdm keypair update time in kvstore', 'status': HTTPStatus.NOT_FOUND } } return { 'payload': { 'message': str(e), 'status': HTTPStatus.BAD_REQUEST } } try: public_key = fetch_sensitive_data(request[SESSION][AUTHTOKEN], MDM_SIGN_PUBLIC_KEY) private_key = fetch_sensitive_data(request[SESSION][AUTHTOKEN], MDM_SIGN_PRIVATE_KEY) response.update({ 'sign_public_key': public_key, 'sign_private_key': private_key }) except Exception as e: # If key not in storage/passwords if hasattr(e, 'statusCode') and e.statusCode == HTTPStatus.NOT_FOUND: return { 'payload': { 'message': 'Could not find one or both of key={} and key={} in /storage/passwords' .format(MDM_SIGN_PUBLIC_KEY, MDM_SIGN_PRIVATE_KEY), 'status': HTTPStatus.NOT_FOUND } } return { 'payload': { 'message': str(e), 'status': HTTPStatus.BAD_REQUEST } } return {'payload': response, 'status': HTTPStatus.OK} def post(self, request): """ Handler which generates and returns an mdm keypair """ # generate mdm credentials LOGGER.info("Generating MDM Credentials") system_authtoken = request[SYSTEM_AUTHTOKEN] key_bundle = _load_key_bundle(system_authtoken) [public_key, private_key] = self.sodium_client.sign_generate_keypair() now = int(datetime.now().strftime('%s')) response = {} response['message'] = [] status = HTTPStatus.OK try: # send public signing key to spacebridge send_mdm_signing_key_to_spacebridge(request[SESSION][AUTHTOKEN], public_key, key_bundle) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR LOGGER.warn( "Failed to register mdm keys with spacebridge. error=%s", e) return { 'payload': { 'failed_save': True, 'message': e }, 'status': status, } # update key generation timestamp try: kvstore_service = kvstore(collection=USER_META_COLLECTION_NAME, session_key=request[SESSION][AUTHTOKEN], owner=request[SESSION][USER]) entry = {KEY: MDM_KEYPAIR_GENERATION_TIME, TIMESTAMP: now} kvstore_service.insert_or_update_item_containing_key(entry) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_timesave'] = True response['message'].append(e.message) # store to storage/passwords try: [_, created_public_key] = update_or_create_sensitive_data( request[SESSION][AUTHTOKEN], MDM_SIGN_PUBLIC_KEY, py23.b64encode_to_str(public_key)) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_public_localsave'] = True response['message'].append(str(e)) try: [_, created_private_key] = update_or_create_sensitive_data( request[SESSION][AUTHTOKEN], MDM_SIGN_PRIVATE_KEY, py23.b64encode_to_str(private_key)) except Exception as e: status = HTTPStatus.INTERNAL_SERVER_ERROR response['failed_private_localsave'] = True response['message'].append(str(e)) # don't pass back the message if we have no errors if not response['message']: del response['message'] response[SIGN_PUBLIC_KEY] = py23.b64encode_to_str(public_key) response[SIGN_PRIVATE_KEY] = py23.b64encode_to_str(private_key) response[TIMESTAMP] = now return { 'payload': response, 'status': status, }