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])
Beispiel #3
0
    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
Beispiel #4
0
    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]
Beispiel #6
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))
Beispiel #7
0
    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
            })