Esempio n. 1
0
    def task(self):
        acme_domain = settings.app.acme_domain

        if not acme_domain:
            return

        if not settings.app.acme_timestamp:
            logger.exception(
                'Failed to update acme certificate. Timestamp not set',
                'tasks',
                acme_domain=acme_domain,
            )
            return

        if not settings.app.acme_key:
            logger.exception(
                'Failed to update acme certificate. Account key not set',
                'tasks',
                acme_domain=acme_domain,
            )
            return

        if utils.time_now() - settings.app.acme_timestamp < \
                settings.app.acme_renew:
            return

        logger.info(
            'Updating acme certificate',
            'tasks',
            acme_domain=acme_domain,
        )

        acme.update_acme_cert()
        app.update_server()
Esempio n. 2
0
def auth_session_post():
    username = flask.request.json["username"]
    password = flask.request.json["password"]
    otp_code = flask.request.json.get("otp_code")
    remote_addr = utils.get_remote_addr()

    admin = auth.get_by_username(username, remote_addr)
    if not admin:
        if settings.app.sso == RADIUS_AUTH:
            return _auth_radius(username, password)

        time.sleep(random.randint(0, 100) / 1000.0)
        return utils.jsonify({"error": AUTH_INVALID, "error_msg": AUTH_INVALID_MSG}, 401)

    if not otp_code and admin.otp_auth:
        return utils.jsonify({"error": AUTH_OTP_REQUIRED, "error_msg": AUTH_OTP_REQUIRED_MSG}, 402)

    if not admin.auth_check(password, otp_code, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.0)
        return utils.jsonify({"error": AUTH_INVALID, "error_msg": AUTH_INVALID_MSG}, 401)

    flask.session["session_id"] = admin.new_session()
    flask.session["admin_id"] = str(admin.id)
    flask.session["timestamp"] = int(utils.time_now())
    if not settings.conf.ssl:
        flask.session["source"] = remote_addr

    return utils.jsonify({"authenticated": True, "default": admin.default or False})
Esempio n. 3
0
File: key.py Progetto: kknet/pritunl
def key_sync_get(org_id, user_id, server_id, key_hash):
    utils.rand_sleep()

    if not settings.local.sub_active:
        return utils.response('', status_code=480)

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    org = organization.get_by_id(org_id)
    if not org:
        return flask.abort(404)

    user = org.get_user(id=user_id)
    if not user:
        return flask.abort(404)
    elif not user.sync_secret:
        return flask.abort(404)

    auth_string = '&'.join([
        auth_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path] +
        ([flask.request.data] if flask.request.data else []))

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(
        user.sync_secret.encode(), auth_string,
        hashlib.sha256).digest())
    if auth_signature != auth_test_signature:
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        }, w=0)
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    key_conf = user.sync_conf(server_id, key_hash)
    if key_conf:
        return utils.response(key_conf['conf'])
    return utils.response('')
Esempio n. 4
0
    def verify_otp_code(self, code):
        otp_secret = self.otp_secret
        padding = 8 - len(otp_secret) % 8
        if padding != 8:
            otp_secret = otp_secret.ljust(len(otp_secret) + padding, '=')
        otp_secret = base64.b32decode(otp_secret.upper())
        valid_codes = []
        epoch = int(utils.time_now() / 30)
        for epoch_offset in range(-1, 2):
            value = struct.pack('>q', epoch + epoch_offset)
            hmac_hash = hmac.new(otp_secret, value, hashlib.sha1).digest()
            offset = ord(hmac_hash[-1]) & 0x0F
            truncated_hash = hmac_hash[offset:offset + 4]
            truncated_hash = struct.unpack('>L', truncated_hash)[0]
            truncated_hash &= 0x7FFFFFFF
            truncated_hash %= 1000000
            valid_codes.append('%06d' % truncated_hash)

        if code not in valid_codes:
            return False

        try:
            self.otp_collection.insert({
                '_id': {
                    'user_id': self.id,
                    'code': code,
                },
                'timestamp': utils.now(),
            })
        except pymongo.errors.DuplicateKeyError:
            return False

        return True
Esempio n. 5
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    utils.rand_sleep()

    if not settings.local.sub_active:
        return utils.response('', status_code=480)

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    org = organization.get_by_id(org_id)
    if not org:
        return flask.abort(401)

    user = org.get_user(id=user_id)
    if not user:
        return flask.abort(401)
    elif not user.sync_secret:
        return flask.abort(401)

    auth_string = '&'.join([
        auth_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path] +
        ([flask.request.data] if flask.request.data else []))

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(
        user.sync_secret.encode(), auth_string,
        hashlib.sha256).digest())
    if auth_signature != auth_test_signature:
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        }, w=0)
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    key_conf = user.sync_conf(server_id, key_hash)
    if key_conf:
        return utils.response(key_conf['conf'])
    return utils.response('')
Esempio n. 6
0
    def task(self):
        acme_domain = settings.app.acme_domain

        if not acme_domain:
            return

        if not settings.app.acme_timestamp:
            logger.exception(
                'Failed to update acme certificate. Timestamp not set',
                'tasks',
                acme_domain=acme_domain,
            )
            return

        if not settings.app.acme_key:
            logger.exception(
                'Failed to update acme certificate. Account key not set',
                'tasks',
                acme_domain=acme_domain,
            )
            return

        if utils.time_now() - settings.app.acme_timestamp < \
                settings.app.acme_renew:
            return

        logger.info(
            'Updating acme certificate', 'tasks',
            acme_domain=acme_domain,
        )

        acme.update_acme_cert()
        app.update_server()
Esempio n. 7
0
    def verify_otp_code(self, code):
        otp_secret = self.otp_secret
        padding = 8 - len(otp_secret) % 8
        if padding != 8:
            otp_secret = otp_secret.ljust(len(otp_secret) + padding, '=')
        otp_secret = base64.b32decode(otp_secret.upper())
        valid_codes = []
        epoch = int(utils.time_now() / 30)
        for epoch_offset in range(-1, 2):
            value = struct.pack('>q', epoch + epoch_offset)
            hmac_hash = hmac.new(otp_secret, value, hashlib.sha1).digest()
            offset = ord(hmac_hash[-1]) & 0x0F
            truncated_hash = hmac_hash[offset:offset + 4]
            truncated_hash = struct.unpack('>L', truncated_hash)[0]
            truncated_hash &= 0x7FFFFFFF
            truncated_hash %= 1000000
            valid_codes.append('%06d' % truncated_hash)

        if code not in valid_codes:
            return False

        try:
            self.otp_collection.insert({
                '_id': {
                    'user_id': self.id,
                    'code': code,
                },
                'timestamp': utils.now(),
            })
        except pymongo.errors.DuplicateKeyError:
            return False

        return True
Esempio n. 8
0
def _get_base_entry(event):
    data = {
        'event': event,
        'timestamp': utils.time_now(),
    }

    data.update(settings.local.host.journal_data)

    return data
Esempio n. 9
0
def link_state_delete():
    if settings.app.demo_mode:
        return utils.demo_blocked()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(406)
    auth_token = auth_token[:256]
    auth_timestamp = auth_timestamp[:64]
    auth_nonce = auth_nonce[:32]
    auth_signature = auth_signature[:512]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(408)
    except ValueError:
        return flask.abort(405)

    host = link.get_host(database.ObjectId(auth_token))
    if not host:
        return flask.abort(404)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(413)

    auth_test_signature = base64.b64encode(
        hmac.new(host.secret.encode(), auth_string.encode(),
                 hashlib.sha512).digest()).decode()
    if not utils.const_compare(auth_signature, auth_test_signature):
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(409)

    host.set_inactive()

    return utils.jsonify({})
Esempio n. 10
0
def get_base_entry(event):
    data = {
        'id': bson.ObjectId(),
        'event': event,
        'timestamp': utils.time_now(),
    }

    data.update(settings.local.host.journal_data)

    return data
Esempio n. 11
0
    def verify_otp_code(self, code, remote_ip=None):
        if remote_ip and settings.vpn.cache_otp_codes:
            doc = self.otp_cache_collection.find_one({"_id": self.id})

            if doc:
                _, hash_salt, cur_otp_hash = doc["otp_hash"].split("$")
                hash_salt = base64.b64decode(hash_salt)
            else:
                hash_salt = os.urandom(8)
                cur_otp_hash = None

            otp_hash = hashlib.sha512()
            otp_hash.update(code + remote_ip)
            otp_hash.update(hash_salt)
            otp_hash = base64.b64encode(otp_hash.digest())

            if otp_hash == cur_otp_hash:
                self.otp_cache_collection.update({"_id": self.id}, {"$set": {"timestamp": utils.now()}})
                return True

            otp_hash = "$".join(("1", base64.b64encode(hash_salt), otp_hash))

        otp_secret = self.otp_secret
        padding = 8 - len(otp_secret) % 8
        if padding != 8:
            otp_secret = otp_secret.ljust(len(otp_secret) + padding, "=")
        otp_secret = base64.b32decode(otp_secret.upper())
        valid_codes = []
        epoch = int(utils.time_now() / 30)
        for epoch_offset in range(-1, 2):
            value = struct.pack(">q", epoch + epoch_offset)
            hmac_hash = hmac.new(otp_secret, value, hashlib.sha1).digest()
            offset = ord(hmac_hash[-1]) & 0x0F
            truncated_hash = hmac_hash[offset : offset + 4]
            truncated_hash = struct.unpack(">L", truncated_hash)[0]
            truncated_hash &= 0x7FFFFFFF
            truncated_hash %= 1000000
            valid_codes.append("%06d" % truncated_hash)

        if code not in valid_codes:
            return False

        response = self.otp_collection.update(
            {"_id": {"user_id": self.id, "code": code}}, {"$set": {"timestamp": utils.now()}}, upsert=True
        )

        if response["updatedExisting"]:
            return False

        if remote_ip and settings.vpn.cache_otp_codes:
            self.otp_cache_collection.update(
                {"_id": self.id}, {"$set": {"otp_hash": otp_hash, "timestamp": utils.now()}}, upsert=True
            )

        return True
Esempio n. 12
0
def auth_session_post():
    username = utils.json_filter_str('username')
    password = utils.json_str('password')
    otp_code = utils.json_opt_filter_str('otp_code')
    yubico_key = utils.json_opt_filter_str('yubico_key')
    remote_addr = utils.get_remote_addr()

    time.sleep(random.randint(50, 100) / 1000.)

    admin = auth.get_by_username(username)
    if not admin:
        if settings.app.sso and RADIUS_AUTH in settings.app.sso:
            return _auth_radius(username, password)

        time.sleep(random.randint(0, 100) / 1000.)
        return _auth_plugin(username, password)

    if (not otp_code and admin.otp_auth) or \
            (not yubico_key and admin.yubikey_id):
        return utils.jsonify(
            {
                'error': AUTH_OTP_REQUIRED,
                'error_msg': AUTH_OTP_REQUIRED_MSG,
                'otp_auth': admin.otp_auth,
                'yubico_auth': bool(admin.yubikey_id),
            }, 402)

    if not limiter.auth_check(admin.id):
        return utils.jsonify(
            {
                'error': AUTH_TOO_MANY,
                'error_msg': AUTH_TOO_MANY_MSG,
            }, 400)

    if not admin.auth_check(password, otp_code, yubico_key, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.app.server_ssl:
        flask.session['source'] = remote_addr

    utils.set_flask_sig()

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 13
0
def link_state_delete():
    if settings.app.demo_mode:
        return utils.demo_blocked()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
        not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
            settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    host = link.get_host(utils.ObjectId(auth_token))
    if not host:
        return flask.abort(401)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(
        host.secret.encode(), auth_string,
        hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    host.set_inactive()

    return utils.jsonify({})
Esempio n. 14
0
File: acme.py Progetto: ijat/pritunl
def update_acme_cert():
    if not settings.app.acme_key:
        settings.app.acme_key = utils.generate_private_key()
        settings.commit()

    private_key = utils.generate_private_ec_key()
    csr = utils.generate_csr(private_key, settings.app.acme_domain)
    cert = get_acme_cert(settings.app.acme_key, csr)

    settings.app.server_key = private_key.strip()
    settings.app.server_cert = cert.strip()
    settings.app.acme_timestamp = utils.time_now()
    settings.commit()
Esempio n. 15
0
def update_acme_cert():
    if not settings.app.acme_key:
        settings.app.acme_key = utils.generate_private_key()
        settings.commit()

    private_key = utils.generate_private_ec_key()
    csr = utils.generate_csr(private_key, settings.app.acme_domain)
    cert = get_acme_cert(settings.app.acme_key, csr)

    settings.app.server_key = private_key.strip()
    settings.app.server_cert = cert.strip()
    settings.app.acme_timestamp = utils.time_now()
    settings.commit()
Esempio n. 16
0
def auth_session_post():
    username = utils.json_filter_str('username')
    password = utils.json_str('password')
    otp_code = utils.json_opt_filter_str('otp_code')
    yubico_key = utils.json_opt_filter_str('yubico_key')
    remote_addr = utils.get_remote_addr()

    time.sleep(random.randint(50, 100) / 1000.)

    admin = auth.get_by_username(username, remote_addr)
    if not admin:
        if settings.app.sso and RADIUS_AUTH in settings.app.sso:
            return _auth_radius(username, password)

        time.sleep(random.randint(0, 100) / 1000.)
        return _auth_plugin(username, password)

    if (not otp_code and admin.otp_auth) or \
            (not yubico_key and admin.yubikey_id):
        return utils.jsonify({
            'error': AUTH_OTP_REQUIRED,
            'error_msg': AUTH_OTP_REQUIRED_MSG,
            'otp_auth': admin.otp_auth,
            'yubico_auth': bool(admin.yubikey_id),
        }, 402)

    if not admin.auth_check(password, otp_code, yubico_key, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.app.server_ssl:
        flask.session['source'] = remote_addr

    utils.set_flask_sig()

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 17
0
    def _check_auth_data(self):
        if not self.auth_token and not self.auth_nonce and \
                not self.auth_timestamp:
            return

        if not self.auth_nonce:
            raise AuthError('Auth data missing nonce')

        if not self.auth_timestamp:
            raise AuthError('Auth data missing timestamp')

        if abs(int(self.auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            self.user.audit_event(
                'user_connection',
                'User connection to "%s" denied. Auth timestamp expired' % \
                    self.server.name,
                remote_addr=self.remote_ip,
            )
            raise AuthError('Auth timestamp expired')

        if self.auth_token:
            auth_token_hash = hashlib.sha512()
            auth_token_hash.update(self.auth_token)
            auth_token = base64.b64encode(auth_token_hash.digest())
        else:
            auth_token = None

        try:
            self.nonces_collection.insert({
                'token': self.user.id,
                'nonce': self.auth_nonce,
                'timestamp': utils.now(),
            })
        except pymongo.errors.DuplicateKeyError:
            self.user.audit_event(
                'user_connection',
                'User connection to "%s" denied. Duplicate nonce' % \
                self.server.name,
                remote_addr=self.remote_ip,
            )
            raise AuthError('Duplicate nonce')

        self.password = self.auth_password
        self.server_auth_token = auth_token
Esempio n. 18
0
def auth_session_post():
    username = flask.request.json['username']
    password = flask.request.json['password']
    otp_code = flask.request.json.get('otp_code')
    remote_addr = utils.get_remote_addr()

    admin = auth.get_by_username(username, remote_addr)
    if not admin:
        if settings.app.sso == RADIUS_AUTH:
            return _auth_radius(username, password)

        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    if not otp_code and admin.otp_auth:
        return utils.jsonify(
            {
                'error': AUTH_OTP_REQUIRED,
                'error_msg': AUTH_OTP_REQUIRED_MSG,
            }, 402)

    if not admin.auth_check(password, otp_code, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.conf.ssl:
        flask.session['source'] = remote_addr

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 19
0
    def task(self):
        acme_domain = settings.app.acme_domain

        if not acme_domain:
            return

        if settings.app.acme_timestamp and \
            settings.app.acme_key and \
                utils.time_now() - settings.app.acme_timestamp < \
                    settings.app.acme_renew:
            return

        logger.info(
            'Updating acme certificate',
            'tasks',
            acme_domain=acme_domain,
        )

        acme.update_acme_cert()
        app.update_server()
Esempio n. 20
0
def auth_session_post():
    username = flask.request.json['username']
    password = flask.request.json['password']
    otp_code = flask.request.json.get('otp_code')
    remote_addr = utils.get_remote_addr()

    admin = auth.get_by_username(username, remote_addr)
    if not admin:
        if settings.app.sso == RADIUS_AUTH:
            return _auth_radius(username, password)

        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    if not otp_code and admin.otp_auth:
        return utils.jsonify({
            'error': AUTH_OTP_REQUIRED,
            'error_msg': AUTH_OTP_REQUIRED_MSG,
        }, 402)

    if not admin.auth_check(password, otp_code, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.conf.ssl:
        flask.session['source'] = remote_addr

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 21
0
def auth_session_post():
    username = flask.request.json['username']
    password = flask.request.json['password']
    remote_addr = utils.get_remote_addr()
    admin = auth.check_auth(username, password, remote_addr)

    if not admin:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.conf.ssl:
        flask.session['source'] = remote_addr

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 22
0
def auth_session_post():
    username = flask.request.json['username']
    password = flask.request.json['password']
    remote_addr = utils.get_remote_addr()
    admin = auth.check_auth(username, password, remote_addr)

    if not admin:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.conf.ssl:
        flask.session['source'] = remote_addr

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 23
0
    def verify_otp_code(self, code, remote_ip=None):
        if remote_ip and settings.vpn.cache_otp_codes:
            doc = self.otp_cache_collection.find_one({
                '_id': self.id,
            })

            if doc:
                _, hash_salt, cur_otp_hash = doc['otp_hash'].split('$')
                hash_salt = base64.b64decode(hash_salt)
            else:
                hash_salt = os.urandom(8)
                cur_otp_hash = None

            otp_hash = hashlib.sha512()
            otp_hash.update(code + remote_ip)
            otp_hash.update(hash_salt)
            otp_hash = base64.b64encode(otp_hash.digest())

            if otp_hash == cur_otp_hash:
                self.otp_cache_collection.update({
                    '_id': self.id,
                }, {'$set': {
                    'timestamp': utils.now(),
                }})
                return True

            otp_hash = '$'.join((
                '1',
                base64.b64encode(hash_salt),
                otp_hash,
            ))

        otp_secret = self.otp_secret
        padding = 8 - len(otp_secret) % 8
        if padding != 8:
            otp_secret = otp_secret.ljust(len(otp_secret) + padding, '=')
        otp_secret = base64.b32decode(otp_secret.upper())
        valid_codes = []
        epoch = int(utils.time_now() / 30)
        for epoch_offset in range(-1, 2):
            value = struct.pack('>q', epoch + epoch_offset)
            hmac_hash = hmac.new(otp_secret, value, hashlib.sha1).digest()
            offset = ord(hmac_hash[-1]) & 0x0F
            truncated_hash = hmac_hash[offset:offset + 4]
            truncated_hash = struct.unpack('>L', truncated_hash)[0]
            truncated_hash &= 0x7FFFFFFF
            truncated_hash %= 1000000
            valid_codes.append('%06d' % truncated_hash)

        if code not in valid_codes:
            return False

        response = self.otp_collection.update(
            {
                '_id': {
                    'user_id': self.id,
                    'code': code,
                },
            }, {'$set': {
                'timestamp': utils.now(),
            }},
            upsert=True)

        if response['updatedExisting']:
            return False

        if remote_ip and settings.vpn.cache_otp_codes:
            self.otp_cache_collection.update({
                '_id': self.id,
            }, {'$set': {
                'otp_hash': otp_hash,
                'timestamp': utils.now(),
            }},
                                             upsert=True)

        return True
Esempio n. 24
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    remote_addr = utils.get_remote_addr()

    if not settings.user.conf_sync:
        return utils.jsonify({})

    if not settings.local.sub_active:
        return utils.jsonify({}, status_code=480)

    utils.rand_sleep()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Missing auth header',
        )
        return flask.abort(406)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            journal.entry(
                journal.USER_SYNC_FAILURE,
                remote_address=remote_addr,
                event_long='Expired auth timestamp',
            )
            return flask.abort(408)
    except ValueError:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Invalid auth timestamp',
        )
        return flask.abort(405)

    org = organization.get_by_id(org_id)
    if not org:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Organization not found',
        )
        return flask.abort(404)

    usr = org.get_user(id=user_id)
    if not usr:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='User not found',
        )
        return flask.abort(404)
    elif not usr.sync_secret:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User missing sync secret',
        )
        return flask.abort(410)

    if auth_token != usr.sync_token:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync token mismatch',
        )
        return flask.abort(410)

    if usr.disabled:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User disabled',
        )
        return flask.abort(403)

    auth_string = '&'.join([
        usr.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth string len limit exceeded',
        )
        return flask.abort(413)

    auth_test_signature = base64.b64encode(
        hmac.new(usr.sync_secret.encode(), auth_string,
                 hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync signature mismatch',
        )
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate key',
        )
        return flask.abort(409)

    key_conf = usr.sync_conf(server_id, key_hash)
    if key_conf:
        usr.audit_event(
            'user_profile',
            'User profile synced from pritunl client',
            remote_addr=remote_addr,
        )

        journal.entry(
            journal.USER_SYNC_SUCCESS,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User profile synced from pritunl client',
        )

        sync_signature = base64.b64encode(
            hmac.new(usr.sync_secret.encode(), key_conf['conf'],
                     hashlib.sha512).digest())

        return utils.jsonify({
            'signature': sync_signature,
            'conf': key_conf['conf'],
        })

    return utils.jsonify({})
Esempio n. 25
0
def check_session():
    auth_token = flask.request.headers.get('Auth-Token', None)
    if auth_token:
        auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
        auth_nonce = flask.request.headers.get('Auth-Nonce', None)
        auth_signature = flask.request.headers.get('Auth-Signature', None)
        if not auth_token or not auth_timestamp or not auth_nonce or \
                not auth_signature:
            return False
        auth_nonce = auth_nonce[:32]

        try:
            if abs(int(auth_timestamp) - int(utils.time_now())) > \
                    settings.app.auth_time_window:
                return False
        except ValueError:
            return False

        administrator = find_user(token=auth_token)
        if not administrator:
            return False

        auth_string = '&'.join([
            auth_token, auth_timestamp, auth_nonce, flask.request.method,
            flask.request.path
        ] + ([flask.request.data] if flask.request.data else []))

        if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
            return False

        auth_test_signature = base64.b64encode(
            hmac.new(administrator.secret.encode(), auth_string,
                     hashlib.sha256).digest())
        if auth_signature != auth_test_signature:
            return False

        try:
            Administrator.nonces_collection.insert(
                {
                    'token': auth_token,
                    'nonce': auth_nonce,
                    'timestamp': utils.now(),
                },
                w=0)
        except pymongo.errors.DuplicateKeyError:
            return False
    else:
        if not flask.session:
            return False

        admin_id = flask.session.get('admin_id')
        if not admin_id:
            return False
        admin_id = bson.ObjectId(admin_id)
        session_id = flask.session.get('session_id')

        administrator = get_user(admin_id, session_id)
        if not administrator:
            return False

        if not settings.conf.ssl and flask.session.get(
                'source') != utils.get_remote_addr():
            flask.session.clear()
            return False

        session_timeout = settings.app.session_timeout
        if session_timeout and int(utils.time_now()) - \
                flask.session['timestamp'] > session_timeout:
            flask.session.clear()
            return False

        flask.session['timestamp'] = int(utils.time_now())

    flask.g.administrator = administrator
    return True
Esempio n. 26
0
def key_wg_put(org_id, user_id, server_id):
    org_id = org_id
    user_id = user_id
    server_id = server_id
    remote_addr = utils.get_remote_addr()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Missing auth header',
        )
        return flask.abort(406)
    auth_token = auth_token[:256]
    auth_timestamp = auth_timestamp[:64]
    auth_nonce = auth_nonce[:32]
    auth_signature = auth_signature[:512]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            journal.entry(
                journal.USER_WG_FAILURE,
                remote_address=remote_addr,
                event_long='Expired auth timestamp',
            )
            return flask.abort(408)
    except ValueError:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Invalid auth timestamp',
        )
        return flask.abort(405)

    org = organization.get_by_id(org_id)
    if not org:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Organization not found',
        )
        return flask.abort(404)

    usr = org.get_user(id=user_id)
    if not usr:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='User not found',
        )
        return flask.abort(404)
    elif not usr.sync_secret:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User missing sync secret',
        )
        return flask.abort(410)

    if auth_token != usr.sync_token:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync token mismatch',
        )
        return flask.abort(411)

    if usr.disabled:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User disabled',
        )
        return flask.abort(403)

    cipher_data64 = flask.request.json.get('data')
    box_nonce64 = flask.request.json.get('nonce')
    public_key64 = flask.request.json.get('public_key')
    signature64 = flask.request.json.get('signature')

    auth_string = '&'.join([
        usr.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path, cipher_data64, box_nonce64, public_key64,
        signature64])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth string len limit exceeded',
        )
        return flask.abort(414)

    auth_test_signature = base64.b64encode(hmac.new(
        usr.sync_secret.encode(), auth_string.encode(),
        hashlib.sha512).digest()).decode()
    if not utils.const_compare(auth_signature, auth_test_signature):
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth signature mismatch',
        )
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate nonce',
        )
        return flask.abort(409)

    data_hash = hashlib.sha512(
        '&'.join([cipher_data64, box_nonce64, public_key64]).encode(),
    ).digest()
    try:
        usr.verify_sig(
            data_hash,
            base64.b64decode(signature64),
        )
    except InvalidSignature:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Invalid rsa signature',
        )
        return flask.abort(412)

    svr = usr.get_server(server_id)

    sender_pub_key = nacl.public.PublicKey(
        base64.b64decode(public_key64))
    box_nonce = base64.b64decode(box_nonce64)

    priv_key = nacl.public.PrivateKey(
        base64.b64decode(svr.auth_box_private_key))

    cipher_data = base64.b64decode(cipher_data64)
    nacl_box = nacl.public.Box(priv_key, sender_pub_key)
    plaintext = nacl_box.decrypt(cipher_data, box_nonce).decode()

    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': box_nonce64,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate secondary nonce',
        )
        return flask.abort(412)

    key_data = json.loads(plaintext)

    client_wg_public_key = utils.filter_str(key_data['wg_public_key'])

    if len(client_wg_public_key) < 32:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Public key too short',
        )
        return flask.abort(415)

    try:
        client_wg_public_key = base64.b64decode(client_wg_public_key)
        if len(client_wg_public_key) != 32:
            raise ValueError('Invalid length')
        client_wg_public_key = base64.b64encode(client_wg_public_key)
    except:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Public key invalid',
        )
        return flask.abort(416)

    wg_keys_collection = mongo.get_collection('wg_keys')
    wg_key_doc = wg_keys_collection.find_one({
        '_id': client_wg_public_key,
    })
    if not wg_key_doc:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Public key not found',
        )
        return flask.abort(417)

    instance = server.get_instance(server_id)
    if not instance or instance.state != 'running':
        return flask.abort(429)

    if not instance.server.wg:
        return flask.abort(429)

    clients = instance.instance_com.clients

    status = clients.ping_wg(
        user=usr,
        org=org,
        wg_public_key=client_wg_public_key,
        remote_ip=remote_addr,
    )

    send_data = {
        'status': status,
        'timestamp': int(utils.time_now()),
    }

    send_nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE)
    nacl_box = nacl.public.Box(priv_key, sender_pub_key)
    send_cipher_data = nacl_box.encrypt(
        json.dumps(send_data).encode(), send_nonce)
    send_cipher_data = send_cipher_data[nacl.public.Box.NONCE_SIZE:]

    send_nonce64 = base64.b64encode(send_nonce).decode()
    send_cipher_data64 = base64.b64encode(send_cipher_data).decode()

    journal.entry(
        journal.USER_WG_SUCCESS,
        usr.journal_data,
        remote_address=remote_addr,
        event_long='User wg ping from pritunl client',
    )

    sync_signature = base64.b64encode(hmac.new(
        usr.sync_secret.encode(),
        (send_cipher_data64 + '&' + send_nonce64).encode(),
        hashlib.sha512).digest()).decode()

    return utils.jsonify({
        'data': send_cipher_data64,
        'nonce': send_nonce64,
        'signature': sync_signature,
    })
Esempio n. 27
0
def auth_user_post():
    if settings.app.demo_mode:
        return utils.demo_blocked()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)

    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return utils.jsonify(
                {
                    'error': AUTH_INVALID,
                    'error_msg': AUTH_INVALID_MSG,
                }, 401)
    except ValueError:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    org = organization.get_by_token(auth_token)
    if not org:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ] + ([flask.request.data] if flask.request.data else []))

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    if not org.auth_secret or len(org.auth_secret) < 8:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    auth_test_signature = base64.b64encode(
        hmac.new(org.auth_secret.encode(), auth_string,
                 hashlib.sha256).digest())
    if auth_signature != auth_test_signature:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    try:
        org.nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    username = flask.request.json['username']
    network_links = flask.request.json.get('network_links')

    usr = org.find_user(name=username)
    if usr:
        usr.remove()

    usr = org.new_user(name=username, type=CERT_CLIENT)
    usr.audit_event('user_created',
                    'User created with authentication token',
                    remote_addr=utils.get_remote_addr())

    if network_links:
        for network_link in network_links:
            try:
                usr.add_network_link(network_link, force=True)
            except (ipaddress.AddressValueError, ValueError):
                return _network_link_invalid()

    event.Event(type=ORGS_UPDATED)
    event.Event(type=USERS_UPDATED, resource_id=org.id)
    event.Event(type=SERVERS_UPDATED)

    keys = {}
    for svr in org.iter_servers():
        key = usr.build_key_conf(svr.id)
        keys[key['name']] = key['conf']

    return utils.jsonify(keys)
Esempio n. 28
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    if not settings.user.conf_sync:
        return utils.jsonify({})

    if not settings.local.sub_active:
        return utils.jsonify({}, status_code=480)

    utils.rand_sleep()

    auth_token = flask.request.headers.get("Auth-Token", None)
    auth_timestamp = flask.request.headers.get("Auth-Timestamp", None)
    auth_nonce = flask.request.headers.get("Auth-Nonce", None)
    auth_signature = flask.request.headers.get("Auth-Signature", None)
    if not auth_token or not auth_timestamp or not auth_nonce or not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    org = organization.get_by_id(org_id)
    if not org:
        return flask.abort(401)

    user = org.get_user(id=user_id)
    if not user:
        return flask.abort(401)
    elif not user.sync_secret:
        return flask.abort(401)

    if auth_token != user.sync_token:
        return flask.abort(401)

    if user.disabled:
        return flask.abort(403)

    auth_string = "&".join(
        [user.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path]
        + ([flask.request.data] if flask.request.data else [])
    )

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(user.sync_secret.encode(), auth_string, hashlib.sha512).digest())
    if auth_signature != auth_test_signature:
        return flask.abort(401)

    nonces_collection = mongo.get_collection("auth_nonces")
    try:
        nonces_collection.insert({"token": auth_token, "nonce": auth_nonce, "timestamp": utils.now()})
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    key_conf = user.sync_conf(server_id, key_hash)
    if key_conf:
        user.audit_event("user_profile", "User profile synced from pritunl client", remote_addr=utils.get_remote_addr())

        sync_signature = base64.b64encode(
            hmac.new(user.sync_secret.encode(), key_conf["conf"], hashlib.sha512).digest()
        )

        return utils.jsonify({"signature": sync_signature, "conf": key_conf["conf"]})

    return utils.jsonify({})
Esempio n. 29
0
    def _check_auth_data(self):
        if not self.auth_token and not self.auth_nonce and \
                not self.auth_timestamp:
            return

        if not self.auth_nonce:
            self.user.audit_event(
                'user_connection',
                'User connection to "%s" denied. Auth data missing nonce' % \
                    self.server.name,
                remote_addr=self.remote_ip,
            )

            journal.entry(
                journal.USER_CONNECT_FAILURE,
                self.journal_data,
                self.user.journal_data,
                self.server.journal_data,
                event_long='Auth data missing nonce',
            )

            raise AuthError('Auth data missing nonce')

        if not self.auth_timestamp:
            self.user.audit_event(
                'user_connection',
                ('User connection to "%s" denied. ' +
                    'Auth data missing timestamp') % \
                    self.server.name,
                remote_addr=self.remote_ip,
            )

            journal.entry(
                journal.USER_CONNECT_FAILURE,
                self.journal_data,
                self.user.journal_data,
                self.server.journal_data,
                event_long='Auth data missing timestamp',
            )

            raise AuthError('Auth data missing timestamp')

        if abs(int(self.auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            self.user.audit_event(
                'user_connection',
                'User connection to "%s" denied. Auth timestamp expired' % \
                    self.server.name,
                remote_addr=self.remote_ip,
            )

            journal.entry(
                journal.USER_CONNECT_FAILURE,
                self.journal_data,
                self.user.journal_data,
                self.server.journal_data,
                event_long='Auth timestamp expired',
            )

            raise AuthError('Auth timestamp expired')

        if self.auth_token:
            auth_token_hash = hashlib.sha512()
            auth_token_hash.update(self.auth_token)
            auth_token = base64.b64encode(auth_token_hash.digest())
        else:
            auth_token = None

        try:
            self.nonces_collection.insert({
                'token': self.user.id,
                'nonce': self.auth_nonce,
                'timestamp': utils.now(),
            })
        except pymongo.errors.DuplicateKeyError:
            self.user.audit_event(
                'user_connection',
                'User connection to "%s" denied. Duplicate nonce' % \
                self.server.name,
                remote_addr=self.remote_ip,
            )

            journal.entry(
                journal.USER_CONNECT_FAILURE,
                self.journal_data,
                self.user.journal_data,
                self.server.journal_data,
                event_long='Duplicate nonce',
            )

            raise AuthError('Duplicate nonce')

        self.password = self.auth_password
        self.server_auth_token = auth_token
Esempio n. 30
0
def check_session():
    auth_token = flask.request.headers.get('Auth-Token', None)
    if auth_token:
        auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
        auth_nonce = flask.request.headers.get('Auth-Nonce', None)
        auth_signature = flask.request.headers.get('Auth-Signature', None)
        if not auth_token or not auth_timestamp or not auth_nonce or \
                not auth_signature:
            return False
        auth_nonce = auth_nonce[:32]

        try:
            if abs(int(auth_timestamp) - int(utils.time_now())) > \
                    settings.app.auth_time_window:
                return False
        except ValueError:
            return False

        administrator = find_user(token=auth_token)
        if not administrator:
            return False

        auth_string = '&'.join([
            auth_token, auth_timestamp, auth_nonce, flask.request.method,
            flask.request.path] +
            ([flask.request.data] if flask.request.data else []))

        if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
            return False

        auth_test_signature = base64.b64encode(hmac.new(
            administrator.secret.encode(), auth_string,
            hashlib.sha256).digest())
        if auth_signature != auth_test_signature:
            return False

        try:
            Administrator.nonces_collection.insert({
                'token': auth_token,
                'nonce': auth_nonce,
                'timestamp': utils.now(),
            }, w=0)
        except pymongo.errors.DuplicateKeyError:
            return False
    else:
        if not flask.session:
            return False

        admin_id = flask.session.get('admin_id')
        if not admin_id:
            return False
        admin_id = bson.ObjectId(admin_id)
        session_id = flask.session.get('session_id')

        administrator = get_user(admin_id, session_id)
        if not administrator:
            return False

        if not settings.conf.ssl and flask.session.get(
                'source') != utils.get_remote_addr():
            flask.session.clear()
            return False

        session_timeout = settings.app.session_timeout
        if session_timeout and int(utils.time_now()) - \
                flask.session['timestamp'] > session_timeout:
            flask.session.clear()
            return False

        flask.session['timestamp'] = int(utils.time_now())

    flask.g.administrator = administrator
    return True
Esempio n. 31
0
def auth_onelogin_secondary(username, passcode, remote_ip, onelogin_mode):
    access_token = _get_access_token()
    if not access_token:
        return False

    if 'passcode' in onelogin_mode and not passcode:
        logger.error(
            'OneLogin passcode empty',
            'sso',
            username=username,
        )
        return False

    response = requests.get(
        _get_base_url() + '/api/1/users',
        headers={
            'Authorization': 'bearer:%s' % access_token,
        },
        params={
            'username': username,
        },
    )

    if response.status_code != 200:
        logger.error(
            'OneLogin api error',
            'sso',
            username=username,
            status_code=response.status_code,
            response=response.content,
        )
        return False

    users = response.json()['data']
    if not users:
        logger.error(
            'OneLogin user not found',
            'sso',
            username=username,
        )
        return False

    user = users[0]
    if user['status'] != 1:
        logger.error(
            'OneLogin user disabled',
            'sso',
            username=username,
        )
        return False

    user_id = user['id']

    response = requests.get(
        _get_base_url() + '/api/1/users/%d/otp_devices' % user_id,
        headers={
            'Authorization': 'bearer:%s' % access_token,
        },
    )

    if response.status_code != 200:
        logger.error(
            'OneLogin api error',
            'sso',
            username=username,
            onelogin_mode=onelogin_mode,
            status_code=response.status_code,
            response=response.content,
        )
        return False

    device_id = None
    devices = response.json()['data']['otp_devices']
    needs_trigger = False
    for device in devices:
        if device['auth_factor_name'] != 'OneLogin Protect':
            continue

        if device['default']:
            device_id = device['id']
            needs_trigger = bool(device.get('needs_trigger'))
            break
        elif not device_id:
            device_id = device['id']
            needs_trigger = bool(device.get('needs_trigger'))

    if not device_id:
        if 'none' in onelogin_mode:
            logger.info(
                'OneLogin secondary not available, skipped',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return True

        logger.error(
            'OneLogin secondary not available',
            'sso',
            username=username,
            onelogin_mode=onelogin_mode,
        )

        return False

    state_token = None
    if needs_trigger or 'push' in onelogin_mode:
        response = requests.post(
            _get_base_url() + '/api/1/users/%d/otp_devices/%d/trigger' %
            (user_id, device_id),
            headers={
                'Authorization': 'bearer:%s' % access_token,
                'Content-Type': 'application/json',
                'X-Forwarded-For': remote_ip,
            },
            json={
                'ipaddr': remote_ip,
            },
        )

        if response.status_code != 200:
            logger.error(
                'OneLogin api error',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        activate = response.json()['data']
        if not activate:
            logger.error(
                'OneLogin activate empty',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return False

        state_token = activate[0]['state_token']

    start = utils.time_now()
    while True:
        if utils.time_now() - start > 45:
            logger.error(
                'OneLogin secondary timed out',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                user_id=user_id,
            )
            return False

        response = requests.post(
            _get_base_url() + '/api/1/users/%d/otp_devices/%d/verify' %
            (user_id, device_id),
            headers={
                'Authorization': 'bearer:%s' % access_token,
                'Content-Type': 'application/json',
                'X-Forwarded-For': remote_ip,
            },
            json={
                'state_token': state_token,
                'otp_token': passcode,
            },
        )

        if response.status_code != 200 and response.status_code != 401:
            logger.error(
                'OneLogin api error',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        verify = response.json()['status']
        if not verify:
            logger.error(
                'OneLogin verify empty',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return False

        if response.status_code == 401:
            if 'Authentication pending' in verify['message']:
                time.sleep(0.5)
                continue

            logger.error(
                'OneLogin secondary rejected',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                user_id=user_id,
            )
            return False

        if response.status_code != 200 or verify['type'] != "success":
            logger.error(
                'OneLogin verify bad data',
                'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        return True
Esempio n. 32
0
def link_state_put():
    if settings.app.demo_mode:
        return utils.demo_blocked()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(406)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(408)
    except ValueError:
        return flask.abort(405)

    host = link.get_host(utils.ObjectId(auth_token))
    if not host:
        return flask.abort(404)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(413)

    auth_test_signature = base64.b64encode(
        hmac.new(host.secret.encode(), auth_string, hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(409)

    host.load_link()

    host.version = flask.request.json.get('version')
    host.public_address = flask.request.json.get('public_address')
    host.local_address = flask.request.json.get('local_address')
    host.address6 = flask.request.json.get('address6')

    data = json.dumps(host.get_state(), default=lambda x: str(x))
    data += (16 - len(data) % 16) * '\x00'

    iv = os.urandom(16)
    key = hashlib.sha256(host.secret).digest()
    cipher = Cipher(algorithms.AES(key),
                    modes.CBC(iv),
                    backend=default_backend()).encryptor()
    enc_data = base64.b64encode(cipher.update(data) + cipher.finalize())

    enc_signature = base64.b64encode(
        hmac.new(host.secret.encode(), enc_data, hashlib.sha512).digest())

    resp = flask.Response(response=enc_data, mimetype='application/base64')
    resp.headers.add('Cache-Control', 'no-cache, no-store, must-revalidate')
    resp.headers.add('Pragma', 'no-cache')
    resp.headers.add('Expires', 0)
    resp.headers.add('Cipher-IV', base64.b64encode(iv))
    resp.headers.add('Cipher-Signature', enc_signature)

    return resp
Esempio n. 33
0
def link_state_put():
    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    host = link.get_host(utils.ObjectId(auth_token))
    if not host:
        return flask.abort(401)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(
        hmac.new(host.secret.encode(), auth_string, hashlib.sha512).digest())
    if auth_signature != auth_test_signature:
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    data = json.dumps(host.get_state(), default=lambda x: str(x))
    data += (16 - len(data) % 16) * '\x00'

    iv = Crypto.Random.new().read(16)
    key = hashlib.sha256(host.secret).digest()
    cipher = Crypto.Cipher.AES.new(
        key,
        Crypto.Cipher.AES.MODE_CBC,
        iv,
    )

    enc_data = base64.b64encode(cipher.encrypt(data))

    resp = flask.Response(response=enc_data, mimetype='application/base64')
    resp.headers.add('Cache-Control', 'no-cache, no-store, must-revalidate')
    resp.headers.add('Pragma', 'no-cache')
    resp.headers.add('Expires', 0)
    resp.headers.add('Cipher-IV', base64.b64encode(iv))

    return resp
Esempio n. 34
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    if not settings.user.conf_sync:
        return utils.jsonify({})

    if not settings.local.sub_active:
        return utils.jsonify({}, status_code=480)

    utils.rand_sleep()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    org = organization.get_by_id(org_id)
    if not org:
        return flask.abort(401)

    user = org.get_user(id=user_id)
    if not user:
        return flask.abort(401)
    elif not user.sync_secret:
        return flask.abort(401)

    if auth_token != user.sync_token:
        return flask.abort(401)

    if user.disabled:
        return flask.abort(403)

    auth_string = '&'.join([
        user.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(
        user.sync_secret.encode(), auth_string,
        hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    key_conf = user.sync_conf(server_id, key_hash)
    if key_conf:
        user.audit_event('user_profile',
            'User profile synced from pritunl client',
            remote_addr=utils.get_remote_addr(),
        )

        sync_signature = base64.b64encode(hmac.new(
            user.sync_secret.encode(), key_conf['conf'],
            hashlib.sha512).digest())

        return utils.jsonify({
            'signature': sync_signature,
            'conf': key_conf['conf'],
        })

    return utils.jsonify({})
Esempio n. 35
0
    def verify_otp_code(self, code, remote_ip=None):
        if remote_ip and settings.vpn.cache_otp_codes:
            doc = self.otp_cache_collection.find_one({
                '_id': self.id,
            })

            if doc:
                _, hash_salt, cur_otp_hash = doc['otp_hash'].split('$')
                hash_salt = base64.b64decode(hash_salt)
            else:
                hash_salt = os.urandom(8)
                cur_otp_hash = None

            otp_hash = hashlib.sha512()
            otp_hash.update(code + remote_ip)
            otp_hash.update(hash_salt)
            otp_hash = base64.b64encode(otp_hash.digest())

            if otp_hash == cur_otp_hash:
                self.otp_cache_collection.update({
                    '_id': self.id,
                }, {'$set': {
                    'timestamp': utils.now(),
                }})
                return True

            otp_hash = '$'.join((
                '1',
                base64.b64encode(hash_salt),
                otp_hash,
            ))

        otp_secret = self.otp_secret
        padding = 8 - len(otp_secret) % 8
        if padding != 8:
            otp_secret = otp_secret.ljust(len(otp_secret) + padding, '=')
        otp_secret = base64.b32decode(otp_secret.upper())
        valid_codes = []
        epoch = int(utils.time_now() / 30)
        for epoch_offset in range(-1, 2):
            value = struct.pack('>q', epoch + epoch_offset)
            hmac_hash = hmac.new(otp_secret, value, hashlib.sha1).digest()
            offset = ord(hmac_hash[-1]) & 0x0F
            truncated_hash = hmac_hash[offset:offset + 4]
            truncated_hash = struct.unpack('>L', truncated_hash)[0]
            truncated_hash &= 0x7FFFFFFF
            truncated_hash %= 1000000
            valid_codes.append('%06d' % truncated_hash)

        if code not in valid_codes:
            return False

        response = self.otp_collection.update({
            '_id': {
                'user_id': self.id,
                'code': code,
            },
        }, {'$set': {
            'timestamp': utils.now(),
        }}, upsert=True)

        if response['updatedExisting']:
            return False

        if remote_ip and settings.vpn.cache_otp_codes:
            self.otp_cache_collection.update({
                '_id': self.id,
            }, {'$set': {
                'otp_hash': otp_hash,
                'timestamp': utils.now(),
            }}, upsert=True)

        return True
Esempio n. 36
0
def auth_user_post():
    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)

    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return utils.jsonify({
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)
    except ValueError:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    org = organization.get_by_token(auth_token)
    if not org:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ] + ([flask.request.data] if flask.request.data else []))

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    auth_test_signature = base64.b64encode(hmac.new(
        org.auth_secret.encode(), auth_string,
        hashlib.sha256).digest())
    if auth_signature != auth_test_signature:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    try:
        org.nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return utils.jsonify({
            'error': AUTH_INVALID,
            'error_msg': AUTH_INVALID_MSG,
        }, 401)

    username = flask.request.json['username']
    network_links = flask.request.json.get('network_links')

    usr = org.find_user(name=username)
    if not usr:
        usr = org.new_user(name=username, type=CERT_CLIENT)
        usr.audit_event('user_created',
            'User created with authentication token',
            remote_addr=utils.get_remote_addr())

        if network_links:
            for network_link in network_links:
                try:
                    usr.add_network_link(network_link, force=True)
                except (ipaddress.AddressValueError, ValueError):
                    return _network_link_invalid()

        event.Event(type=ORGS_UPDATED)
        event.Event(type=USERS_UPDATED, resource_id=org.id)
        event.Event(type=SERVERS_UPDATED)

    keys = {}
    for svr in org.iter_servers():
        key = usr.build_key_conf(svr.id)
        keys[key['name']] = key['conf']

    return utils.jsonify(keys)
Esempio n. 37
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    remote_addr = utils.get_remote_addr()

    if not settings.user.conf_sync:
        return utils.jsonify({})

    if not settings.local.sub_active:
        return utils.jsonify({}, status_code=480)

    utils.rand_sleep()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Missing auth header',
        )
        return flask.abort(406)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            journal.entry(
                journal.USER_SYNC_FAILURE,
                remote_address=remote_addr,
                event_long='Expired auth timestamp',
            )
            return flask.abort(408)
    except ValueError:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Invalid auth timestamp',
        )
        return flask.abort(405)

    org = organization.get_by_id(org_id)
    if not org:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='Organization not found',
        )
        return flask.abort(404)

    usr = org.get_user(id=user_id)
    if not usr:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            remote_address=remote_addr,
            event_long='User not found',
        )
        return flask.abort(404)
    elif not usr.sync_secret:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User missing sync secret',
        )
        return flask.abort(410)

    if auth_token != usr.sync_token:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync token mismatch',
        )
        return flask.abort(410)

    if usr.disabled:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User disabled',
        )
        return flask.abort(403)

    auth_string = '&'.join([
        usr.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth string len limit exceeded',
        )
        return flask.abort(413)

    auth_test_signature = base64.b64encode(hmac.new(
        usr.sync_secret.encode(), auth_string,
        hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync signature mismatch',
        )
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_SYNC_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate key',
        )
        return flask.abort(409)

    key_conf = usr.sync_conf(server_id, key_hash)
    if key_conf:
        usr.audit_event('user_profile',
            'User profile synced from pritunl client',
            remote_addr=remote_addr,
        )

        journal.entry(
            journal.USER_SYNC_SUCCESS,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User profile synced from pritunl client',
        )

        sync_signature = base64.b64encode(hmac.new(
            usr.sync_secret.encode(), key_conf['conf'],
            hashlib.sha512).digest())

        return utils.jsonify({
            'signature': sync_signature,
            'conf': key_conf['conf'],
        })

    return utils.jsonify({})
Esempio n. 38
0
def auth_onelogin_secondary(username, passcode, remote_ip, onelogin_mode):
    access_token = _get_access_token()
    if not access_token:
        return False

    if 'passcode' in onelogin_mode and not passcode:
        logger.error('OneLogin passcode empty', 'sso',
            username=username,
        )
        return False

    response = requests.get(
        _get_base_url() + '/api/1/users',
        headers={
            'Authorization': 'bearer:%s' % access_token,
        },
        params={
            'username': username,
        },
    )

    if response.status_code != 200:
        logger.error('OneLogin api error', 'sso',
            username=username,
            status_code=response.status_code,
            response=response.content,
        )
        return False

    users = response.json()['data']
    if not users:
        logger.error('OneLogin user not found', 'sso',
            username=username,
        )
        return False

    user = users[0]
    if user['status'] != 1:
        logger.error('OneLogin user disabled', 'sso',
            username=username,
        )
        return False

    user_id = user['id']

    response = requests.get(
        _get_base_url() + '/api/1/users/%d/otp_devices' % user_id,
        headers={
            'Authorization': 'bearer:%s' % access_token,
        },
    )

    if response.status_code != 200:
        logger.error('OneLogin api error', 'sso',
            username=username,
            onelogin_mode=onelogin_mode,
            status_code=response.status_code,
            response=response.content,
        )
        return False

    device_id = None
    devices = response.json()['data']['otp_devices']
    needs_trigger = False
    for device in devices:
        if device['auth_factor_name'] != 'OneLogin Protect':
            continue

        if device['default']:
            device_id = device['id']
            needs_trigger = bool(device.get('needs_trigger'))
            break
        elif not device_id:
            device_id = device['id']
            needs_trigger = bool(device.get('needs_trigger'))

    if not device_id:
        if 'none' in onelogin_mode:
            logger.info('OneLogin secondary not available, skipped', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return True

        logger.error('OneLogin secondary not available', 'sso',
            username=username,
            onelogin_mode=onelogin_mode,
        )

        return False

    state_token = None
    if needs_trigger or 'push' in onelogin_mode:
        response = requests.post(
            _get_base_url() + '/api/1/users/%d/otp_devices/%d/trigger' % (
                user_id, device_id),
            headers={
                'Authorization': 'bearer:%s' % access_token,
                'Content-Type': 'application/json',
                'X-Forwarded-For': remote_ip,
            },
            json={
                'ipaddr': remote_ip,
            },
        )

        if response.status_code != 200:
            logger.error('OneLogin api error', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        activate = response.json()['data']
        if not activate:
            logger.error('OneLogin activate empty', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return False

        state_token = activate[0]['state_token']

    start = utils.time_now()
    while True:
        if utils.time_now() - start > 45:
            logger.error('OneLogin secondary timed out', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                user_id=user_id,
            )
            return False

        response = requests.post(
            _get_base_url() + '/api/1/users/%d/otp_devices/%d/verify' % (
                user_id, device_id),
            headers={
                'Authorization': 'bearer:%s' % access_token,
                'Content-Type': 'application/json',
                'X-Forwarded-For': remote_ip,
            },
            json={
                'state_token': state_token,
                'otp_token': passcode,
            },
        )

        if response.status_code != 200 and response.status_code != 401:
            logger.error('OneLogin api error', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        verify = response.json()['status']
        if not verify:
            logger.error('OneLogin verify empty', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
            )
            return False

        if response.status_code == 401:
            if 'Authentication pending' in verify['message']:
                time.sleep(0.5)
                continue

            logger.error('OneLogin secondary rejected', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                user_id=user_id,
            )
            return False

        if response.status_code != 200 or verify['type'] != "success":
            logger.error('OneLogin verify bad data', 'sso',
                username=username,
                onelogin_mode=onelogin_mode,
                status_code=response.status_code,
                response=response.content,
            )
            return False

        return True
Esempio n. 39
0
def auth_session_post():
    username = utils.json_filter_str('username')[:128]
    password = flask.request.json['password']
    if password:
        password = password[:128]
    otp_code = utils.json_opt_filter_str('otp_code')
    if otp_code:
        otp_code = otp_code[:64]
    yubico_key = utils.json_opt_filter_str('yubico_key')
    if yubico_key:
        yubico_key = yubico_key[:128]
    remote_addr = utils.get_remote_addr()

    time.sleep(random.randint(50, 100) / 1000.)

    admin = auth.get_by_username(username)
    if not admin:
        if settings.app.sso and RADIUS_AUTH in settings.app.sso:
            return _auth_radius(username, password, remote_addr)

        time.sleep(random.randint(0, 100) / 1000.)
        return _auth_plugin(username, password, remote_addr)

    if (not otp_code and admin.otp_auth) or \
            (not yubico_key and admin.yubikey_id):
        return utils.jsonify(
            {
                'error': AUTH_OTP_REQUIRED,
                'error_msg': AUTH_OTP_REQUIRED_MSG,
                'otp_auth': admin.otp_auth,
                'yubico_auth': bool(admin.yubikey_id),
            }, 402)

    if not limiter.auth_check(admin.id):
        journal.entry(
            journal.ADMIN_AUTH_FAILURE,
            admin.journal_data,
            remote_address=remote_addr,
            reason=journal.ADMIN_AUTH_REASON_RATE_LIMIT,
            reason_long='Too many authentication attempts',
        )

        return utils.jsonify(
            {
                'error': AUTH_TOO_MANY,
                'error_msg': AUTH_TOO_MANY_MSG,
            }, 400)

    if not admin.auth_check(password, otp_code, yubico_key, remote_addr):
        time.sleep(random.randint(0, 100) / 1000.)
        return utils.jsonify(
            {
                'error': AUTH_INVALID,
                'error_msg': AUTH_INVALID_MSG,
            }, 401)

    flask.session['session_id'] = admin.new_session()
    flask.session['admin_id'] = str(admin.id)
    flask.session['timestamp'] = int(utils.time_now())
    if not settings.app.server_ssl:
        flask.session['source'] = remote_addr

    journal.entry(
        journal.ADMIN_SESSION_START,
        admin.journal_data,
        remote_address=remote_addr,
        session_id=flask.session['session_id'],
    )

    utils.set_flask_sig()

    return utils.jsonify({
        'authenticated': True,
        'default': admin.default or False,
    })
Esempio n. 40
0
def key_wg_post(org_id, user_id, server_id):
    org_id = org_id
    user_id = user_id
    server_id = server_id
    remote_addr = utils.get_remote_addr()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Missing auth header',
        )
        return flask.abort(406)
    auth_token = auth_token[:256]
    auth_timestamp = auth_timestamp[:64]
    auth_nonce = auth_nonce[:32]
    auth_signature = auth_signature[:512]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            journal.entry(
                journal.USER_WG_FAILURE,
                remote_address=remote_addr,
                event_long='Expired auth timestamp',
            )
            return flask.abort(408)
    except ValueError:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Invalid auth timestamp',
        )
        return flask.abort(405)

    org = organization.get_by_id(org_id)
    if not org:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='Organization not found',
        )
        return flask.abort(404)

    usr = org.get_user(id=user_id)
    if not usr:
        journal.entry(
            journal.USER_WG_FAILURE,
            remote_address=remote_addr,
            event_long='User not found',
        )
        return flask.abort(404)
    elif not usr.sync_secret:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User missing sync secret',
        )
        return flask.abort(410)

    if auth_token != usr.sync_token:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Sync token mismatch',
        )
        return flask.abort(411)

    if usr.disabled:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='User disabled',
        )
        return flask.abort(403)

    cipher_data64 = flask.request.json.get('data')
    box_nonce64 = flask.request.json.get('nonce')
    public_key64 = flask.request.json.get('public_key')
    signature64 = flask.request.json.get('signature')

    auth_string = '&'.join([
        usr.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path, cipher_data64, box_nonce64, public_key64,
        signature64
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth string len limit exceeded',
        )
        return flask.abort(414)

    auth_test_signature = base64.b64encode(
        hmac.new(usr.sync_secret.encode(), auth_string,
                 hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Auth signature mismatch',
        )
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate nonce',
        )
        return flask.abort(409)

    data_hash = hashlib.sha512('&'.join(
        [cipher_data64, box_nonce64, public_key64])).digest()
    try:
        usr.verify_sig(
            data_hash,
            base64.b64decode(signature64),
        )
    except InvalidSignature:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Invalid rsa signature',
        )
        return flask.abort(412)

    svr = usr.get_server(server_id)

    sender_pub_key = nacl.public.PublicKey(base64.b64decode(public_key64))
    box_nonce = base64.b64decode(box_nonce64)

    priv_key = nacl.public.PrivateKey(
        base64.b64decode(svr.auth_box_private_key))

    cipher_data = base64.b64decode(cipher_data64)
    nacl_box = nacl.public.Box(priv_key, sender_pub_key)
    plaintext = nacl_box.decrypt(cipher_data, box_nonce).decode('utf-8')

    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': box_nonce64,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Duplicate secondary nonce',
        )
        return flask.abort(415)

    key_data = json.loads(plaintext)

    client_platform = utils.filter_str_uni(key_data['platform'])
    client_device_id = utils.filter_str_uni(key_data['device_id'])
    client_device_name = utils.filter_str_uni(key_data['device_name'])
    client_mac_addr = utils.filter_str_uni(key_data['mac_addr'])
    client_mac_addrs = key_data['mac_addrs']
    if client_mac_addrs:
        client_mac_addrs = [utils.filter_str_uni(x) for x in client_mac_addrs]
    else:
        client_mac_addrs = None
    client_auth_token = key_data['token'].decode('utf-8')
    client_auth_nonce = utils.filter_str_uni(key_data['nonce'])
    client_auth_password = key_data['password'].decode('utf-8')
    client_auth_timestamp = int(key_data['timestamp'])
    client_wg_public_key = key_data['wg_public_key'].decode('utf-8')

    if len(client_wg_public_key) < 32:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Public key too short',
        )
        return flask.abort(416)

    try:
        client_wg_public_key = base64.b64decode(client_wg_public_key)
        if len(client_wg_public_key) != 32:
            raise ValueError('Invalid length')
        client_wg_public_key = base64.b64encode(client_wg_public_key)
    except:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            event_long='Public key invalid',
        )
        return flask.abort(417)

    instance = server.get_instance(server_id)
    if not instance or instance.state != 'running':
        return flask.abort(429)

    if not instance.server.wg:
        return flask.abort(429)

    wg_keys_collection = mongo.get_collection('wg_keys')
    try:
        wg_keys_collection.insert({
            '_id': client_wg_public_key,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        journal.entry(
            journal.USER_WG_FAILURE,
            usr.journal_data,
            remote_address=remote_addr,
            wg_public_key=client_wg_public_key,
            event_long='Duplicate wg public key',
        )
        return flask.abort(413)

    clients = instance.instance_com.clients

    event = threading.Event()
    send_data = {
        'allow': None,
        'configuration': None,
        'reason': None,
    }

    def callback(allow, data):
        send_data['allow'] = allow
        if allow:
            send_data['configuration'] = data
        else:
            send_data['reason'] = data
        event.set()

    clients.connect_wg(
        user=usr,
        org=org,
        wg_public_key=client_wg_public_key,
        auth_password=client_auth_password,
        auth_token=client_auth_token,
        auth_nonce=client_auth_nonce,
        auth_timestamp=client_auth_timestamp,
        platform=client_platform,
        device_id=client_device_id,
        device_name=client_device_name,
        mac_addr=client_mac_addr,
        mac_addrs=client_mac_addrs,
        remote_ip=remote_addr,
        connect_callback=callback,
    )

    event.wait()

    send_nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE)
    nacl_box = nacl.public.Box(priv_key, sender_pub_key)
    send_cipher_data = nacl_box.encrypt(json.dumps(send_data), send_nonce)
    send_cipher_data = send_cipher_data[nacl.public.Box.NONCE_SIZE:]

    send_nonce64 = base64.b64encode(send_nonce)
    send_cipher_data64 = base64.b64encode(send_cipher_data)

    usr.audit_event(
        'user_profile',
        'User retrieved wg public key from pritunl client',
        remote_addr=remote_addr,
    )

    journal.entry(
        journal.USER_WG_SUCCESS,
        usr.journal_data,
        remote_address=remote_addr,
        event_long='User retrieved wg public key from pritunl client',
    )

    sync_signature = base64.b64encode(
        hmac.new(usr.sync_secret.encode(),
                 send_cipher_data64 + '&' + send_nonce64,
                 hashlib.sha512).digest())

    return utils.jsonify({
        'data': send_cipher_data64,
        'nonce': send_nonce64,
        'signature': sync_signature,
    })
Esempio n. 41
0
def link_state_put():
    if settings.app.demo_mode:
        return utils.demo_blocked()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    host = link.get_host(utils.ObjectId(auth_token))
    if not host:
        return flask.abort(401)

    auth_string = '&'.join([
        auth_token,
        auth_timestamp,
        auth_nonce,
        flask.request.method,
        flask.request.path,
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(hmac.new(
        host.secret.encode(), auth_string,
        hashlib.sha512).digest())
    if not utils.const_compare(auth_signature, auth_test_signature):
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    host.load_link()

    host.version = flask.request.json.get('version')
    host.public_address = flask.request.json.get('public_address')

    data = json.dumps(host.get_state(), default=lambda x: str(x))
    data += (16 - len(data) % 16) * '\x00'

    iv = Crypto.Random.new().read(16)
    key = hashlib.sha256(host.secret).digest()
    cipher = Crypto.Cipher.AES.new(
        key,
        Crypto.Cipher.AES.MODE_CBC,
        iv,
    )

    enc_data = base64.b64encode(cipher.encrypt(data))
    enc_signature = base64.b64encode(hmac.new(
        host.secret.encode(), enc_data,
        hashlib.sha512).digest())

    resp = flask.Response(response=enc_data, mimetype='application/base64')
    resp.headers.add('Cache-Control',
        'no-cache, no-store, must-revalidate')
    resp.headers.add('Pragma', 'no-cache')
    resp.headers.add('Expires', 0)
    resp.headers.add('Cipher-IV', base64.b64encode(iv))
    resp.headers.add('Cipher-Signature', enc_signature)

    return resp
Esempio n. 42
0
def key_sync_get(org_id, user_id, server_id, key_hash):
    if not settings.user.conf_sync:
        return utils.jsonify({})

    if not settings.local.sub_active:
        return utils.jsonify({}, status_code=480)

    utils.rand_sleep()

    auth_token = flask.request.headers.get('Auth-Token', None)
    auth_timestamp = flask.request.headers.get('Auth-Timestamp', None)
    auth_nonce = flask.request.headers.get('Auth-Nonce', None)
    auth_signature = flask.request.headers.get('Auth-Signature', None)
    if not auth_token or not auth_timestamp or not auth_nonce or \
            not auth_signature:
        return flask.abort(401)
    auth_nonce = auth_nonce[:32]

    try:
        if abs(int(auth_timestamp) - int(utils.time_now())) > \
                settings.app.auth_time_window:
            return flask.abort(401)
    except ValueError:
        return flask.abort(401)

    org = organization.get_by_id(org_id)
    if not org:
        return flask.abort(401)

    user = org.get_user(id=user_id)
    if not user:
        return flask.abort(401)
    elif not user.sync_secret:
        return flask.abort(401)

    if auth_token != user.sync_token:
        return flask.abort(401)

    if user.disabled:
        return flask.abort(403)

    auth_string = '&'.join([
        user.sync_token, auth_timestamp, auth_nonce, flask.request.method,
        flask.request.path
    ])

    if len(auth_string) > AUTH_SIG_STRING_MAX_LEN:
        return flask.abort(401)

    auth_test_signature = base64.b64encode(
        hmac.new(user.sync_secret.encode(), auth_string,
                 hashlib.sha512).digest())
    if auth_signature != auth_test_signature:
        return flask.abort(401)

    nonces_collection = mongo.get_collection('auth_nonces')
    try:
        nonces_collection.insert({
            'token': auth_token,
            'nonce': auth_nonce,
            'timestamp': utils.now(),
        })
    except pymongo.errors.DuplicateKeyError:
        return flask.abort(401)

    key_conf = user.sync_conf(server_id, key_hash)
    if key_conf:
        user.audit_event(
            'user_profile',
            'User profile synced from pritunl client',
            remote_addr=utils.get_remote_addr(),
        )

        sync_signature = base64.b64encode(
            hmac.new(user.sync_secret.encode(), key_conf['conf'],
                     hashlib.sha512).digest())

        return utils.jsonify({
            'signature': sync_signature,
            'conf': key_conf['conf'],
        })

    return utils.jsonify({})