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)
def get_encryption_keys(self): """Getter for encryption keys Returns: [EncryptionKeys]: Wrapper object. Also the same object that the constructor expects. The user should persist this object and load it into the constructor on future sessions. """ return EncryptionKeys(self._key_cache[SIGN_PUBLIC_KEY], self._key_cache[SIGN_PRIVATE_KEY], self._key_cache[ENCRYPT_PUBLIC_KEY], self._key_cache[ENCRYPT_PRIVATE_KEY])
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 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__
def _run(job_contexts, sodium_client): errors = [] LOGGER.debug("Running search process, searches=%s", len(job_contexts)) for job in job_contexts: LOGGER.debug("Processing search job. search_key=%s", job.search_context.search.key()) encryption_keys = EncryptionKeys.from_json(job.encryption_keys) encryption_context = EncryptionContext(encryption_keys, sodium_client) async_client_factory = AsyncClientFactory(job.splunk_uri) try: yield process_pubsub_subscription( job.auth_header, encryption_context, async_client_factory.spacebridge_client(), async_client_factory.kvstore_client(), async_client_factory.splunk_client(), job.search_context) except Exception as e: LOGGER.exception("Failed to process search, search_key=%s", job.search_context.search.key()) errors.append(e) if len(errors) > 0: raise errors[0]
async def send_push_notifications(request_context, notification, recipient_devices, async_kvstore_client, async_spacebridge_client, async_splunk_client): """ Given a notification object and a list of device ids, sends a post request to the Spacebridge notifications api for each device id :param request_context: :param notification: notification object to be sent :param recipient_devices: list of device id strings :param async_kvstore_client: AsyncKVStoreClient :param async_spacebridge_client: AsyncSpacebridgeClient :param async_splunk_client: AsyncSpacebridgeClient :return: """ sodium_client = SodiumClient(LOGGER.getChild('sodium_client')) sign_keys_response = await async_splunk_client.async_get_sign_credentials(request_context.auth_header) if sign_keys_response[0] == HTTPStatus.OK: encryption_keys = EncryptionKeys(b64decode(sign_keys_response[1]['sign_public_key']), b64decode(sign_keys_response[1]['sign_private_key']), None, None) encryption_context = EncryptionContext(encryption_keys, sodium_client) else: LOGGER.exception("Unable to fetch encryption keys with error_code=%s", str(sign_keys_response[0])) raise EncryptionKeyError(sign_keys_response[1], sign_keys_response[0]) sender_id = encryption_context.sign_public_key(transform=encryption_context.generichash_raw) sender_id_hex = py23.encode_hex_str(sender_id) headers = {'Content-Type': 'application/x-protobuf', 'Authorization': sender_id_hex} recipient_devices = [device.encode("utf8") for device in recipient_devices] deferred_responses = [] signer = partial(sign_detached, sodium_client, encryption_context.sign_private_key()) for device_id in recipient_devices: device_id_raw = b64decode(device_id) try: _, receiver_encrypt_public_key = await public_keys_for_device(device_id_raw, request_context.auth_header, async_kvstore_client) encryptor = partial(encrypt_for_send, sodium_client, receiver_encrypt_public_key) LOGGER.info("Sending notification alert_id=%s, device_id=%s", notification.alert_id, device_id) notification_request = notifications.build_notification_request(device_id, device_id_raw, sender_id, notification, encryptor, signer) # Send post request asynchronously deferred_responses.append(async_spacebridge_client.async_send_notification_request( auth_header=SpacebridgeAuthHeader(sender_id), data=notification_request.SerializeToString(), headers=headers)) except KeyNotFoundError: LOGGER.info("Public key not found for device_id=%s", device_id) except SodiumOperationError: LOGGER.warn("Sodium operation failed! device_id=%s", device_id) # Wait until all the post requests have returned responses = await asyncio.gather(*deferred_responses, return_exceptions=True) successes = [] exceptions = [] # Parse responses and split up exceptions from successful results for i in range(len(responses)): if isinstance(responses[i], Exception): exceptions.append((recipient_devices[i], responses[i])) else: code = responses[i].code msg = await responses[i].text() successes.append((recipient_devices[i], code, msg)) LOGGER.info("Finished sending push notifications with responses=%s", str(successes)) if exceptions: LOGGER.error("Encountered exceptions sending pushing notifications to devices=%s", str(exceptions))
def handle_saml_mdm_request(self, user, session_token, system_authtoken, mdm_signing_bundle, body): """ Handles the MDM SAML Registration Request. Validates signature sent from client, validates session token, generates a JWT token, and sends it encrypted using splapp's keys and the client public key :param user: string provided by rest handler :param session_token: string :param system_authtoken: string :param mdm_signing_bundle: Object :param body: JSON :return: Reponse object with payload and status """ public_key = base64.b64decode( extract_parameter(body, PUBLIC_KEY_LABEL, BODY_LABEL)) mdm_signature = base64.b64decode( extract_parameter(body, MDM_SIGNATURE_LABEL, BODY_LABEL)) client_keys = EncryptionKeys(None, None, public_key, None) client_encryption_context = EncryptionContext(client_keys) try: valid_signature = yield sign_verify( SodiumClient(LOGGER.getChild("sodium_client")), base64.b64decode( mdm_signing_bundle['sign_public_key'].encode('utf8')), client_encryption_context.encrypt_public_key(), mdm_signature) except Exception as e: LOGGER.exception( "Exception verifying signature from client for user={}".format( user)) defer.returnValue({ 'payload': { 'token': "", 'user': user, 'status': http.UNAUTHORIZED }, 'status': http.OK }) async_splunk_client = self.async_client_factory.splunk_client() valid_request = yield valid_session_token(user, session_token, async_splunk_client) LOGGER.info( "Received new mdm registration request by user={}".format(user)) if valid_signature and valid_request: try: credentials = SplunkJWTCredentials(user) credentials.load_jwt_token(SplunkAuthHeader(system_authtoken)) LOGGER.info("Successfully fetched jwt token") except Exception as e: LOGGER.exception( "Exception fetching jwt token for user={} with message={}". format(user, e)) defer.returnValue({ 'payload': { 'token': "", 'user': user, 'status': 422 }, 'status': http.OK }) splapp_encryption_context = SplunkEncryptionContext( system_authtoken, constants.SPACEBRIDGE_APP_NAME, SodiumClient(LOGGER.getChild("sodium_client"))) # Encrypt session token using splapp keys secured_session_token = splapp_encryption_context.secure_session_token( credentials.get_credentials()) # Encrypt session token using client's given public key encrypted_jwt_token = yield encrypt_for_send( SodiumClient(LOGGER.getChild("sodium_client")), client_encryption_context.encrypt_public_key(), secured_session_token) base64_encrypted_jwt_token = base64.b64encode(encrypted_jwt_token) defer.returnValue({ 'payload': { 'token': base64_encrypted_jwt_token, 'user': user, 'status': http.OK }, 'status': http.OK }) else: LOGGER.info( "Error: Mismatched user={} and session token".format(user)) defer.returnValue({ 'payload': { 'token': "", 'user': user, 'status': http.UNAUTHORIZED }, 'status': http.OK })