def test_encrypt_stores_encrypted_secret(uuid_env_var, patch_get_store_path): enc = Encryption({'name': 'secret_thing', 'user': '******'}) enc_string = enc.encrypt('testing123') assert enc_string != 'testing123' with open(QUSER_DB_STORE) as db_file: store_json = json.load(db_file) assert store_json['secret_thing']['secret'] == enc_string
def test_init_strips_leading_and_trailing_whitespace(uuid_env_var, patch_get_store_path): enc = Encryption({'name': ' secret_thing ', 'user': '******'}) enc_string = enc.encrypt('testing123') with open(QUSER_DB_STORE) as db_file: store_json = json.load(db_file) assert store_json['secret_thing']['secret'] == enc_string
def test_encryption_stores_encrypted_secret_in_config( set_unset_qradar_app_uuid_env_var, patch_get_store_path): enc = Encryption({"name": "test_name", "user": "******"}) enc_string = enc.encrypt('testing123') assert enc_string != 'testing123' with open(DB_STORE) as db_file: file_json = json.load(db_file) assert file_json.get('test_name').get('secret') == enc_string
def encrypt(key): # Get '?val=' query param value = request.args.get('val') # Encrypt the value with the key provided enc = Encryption({'name': key, 'user': '******'}) encrypted = enc.encrypt(value) return render_template('encrypt.html', key=key, value=value, encrypted=encrypted)
def test_decrypt_raises_error_when_secret_not_in_config_db( uuid_env_var, tmpdir): db_store = 'missing_secret_e.db' db_store_path = os.path.join(tmpdir.strpath, db_store) with open(db_store_path, 'w') as db_file: db_file.write('{"secret_thing": {"version": 3}}') with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = db_store_path enc = Encryption({'name': 'secret_thing', 'user': '******'}) with pytest.raises(EncryptionError, match='No secret found for name secret_thing'): enc.decrypt()
def test_encryption_raises_value_error_on_missing_name_and_user_fields(): with pytest.raises(ValueError) as ex: Encryption({}) assert str(ex.value) == "Encryption : name and user are mandatory fields!" with pytest.raises(ValueError) as ex: Encryption({"name": "test_name"}) assert str(ex.value) == "Encryption : name and user are mandatory fields!" with pytest.raises(ValueError) as ex: Encryption({"user": "******"}) assert str(ex.value) == "Encryption : name and user are mandatory fields!"
def test_decrypt_raises_error_on_decryption_failure(uuid_env_var, tmpdir): db_store = 'badsecret_e.db' copy_dbstore(db_store, tmpdir.strpath) with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = os.path.join( tmpdir.strpath, db_store) enc = Encryption({'name': 'secret_thing', 'user': '******'}) with pytest.raises( EncryptionError, match= 'Failed to decrypt secret for name secret_thing: InvalidToken' ): enc.decrypt()
def decrypt(key): # Set up encdec encryption enc = Encryption({'name': key, 'user': '******'}) try: # Attempt to decrypt the value decrypted = enc.decrypt() return render_template('decrypt.html', key=key, decrypted=decrypted, key_found=True) except EncryptionError: # EncryptionError raised, handle the error with a template describing # that the decryption failed (for example if the key doesn't exist) return render_template('decrypt.html', key=key, key_found=False)
def save(self): """ Save to the secure storage """ try: from qpylib.encdec import Encryption Encryption({'name': self.name, 'user': self.simpleHash(self.user)}).encrypt(self.pswd) except Exception as ex: pass
def load(self): """ Load from the secure storage """ try: from qpylib.encdec import Encryption self.pswd = Encryption({'name': self.name, 'user': self.simpleHash(self.user)}).decrypt() except Exception as ex: pass
def test_init_raises_error_on_invalid_config_file_json(uuid_env_var, tmpdir): db_store = 'bad_content_e.db' db_store_path = os.path.join(tmpdir.strpath, db_store) with open(db_store_path, 'w') as db_file: db_file.write('{invalid json}') with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = db_store_path with pytest.raises(EncryptionError, match='Unable to load config store'): Encryption({'name': 'secret_thing', 'user': '******'})
def test_decrypt_raises_error_on_invalid_config_engine_version( uuid_env_var, tmpdir): db_store = 'bad_engine_version_e.db' db_store_path = os.path.join(tmpdir.strpath, db_store) with open(db_store_path, 'w') as db_file: db_file.write( '{"secret_thing": {"version": 1, "secret": "522ae11b6b2b88cb1bb16adea76f7b96"}}' ) with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = db_store_path enc = Encryption({ 'name': 'secret_thing', 'user': '******' }) with pytest.raises(EncryptionError) as ex: enc.decrypt() assert str( ex.value ) == 'Config for name secret_thing contains invalid engine version 1'
def test_decrypt_raises_error_when_config_missing( set_unset_qradar_app_uuid_env_var, patch_get_store_path): enc = Encryption({"name": "test_name", "user": "******"}) enc_string = enc.encrypt('testing123') assert enc_string != 'testing123' os.remove(DB_STORE) enc = Encryption({"name": "test_name", "user": "******"}) with pytest.raises(ValueError) as ex: enc.decrypt() assert str(ex.value) == "Encryption : no secret to decrypt"
def repeatable_encrypt(): e = Encryption({"name": "test_name", "user": "******"}) e.config["test_name"]['salt'] = 'Lk&RBjmg,22Xcs`!' e.config["test_name"]['UUID'] = '6599ba78-4896-11e8-842f-0ed5f89f718b' e.config["test_name"]['ivz'] = 'AXN(=,ix7=s,e}g\\' e.config["test_name"]['iterations'] = 100000 return e
def create_app(): # Create a Flask instance. qflask = Flask(__name__) csrf = CSRFProtect() csrf.init_app(qflask) # Retrieve QRadar app id. qradar_app_id = qpylib.get_app_id() # Create unique session cookie name for this app. qflask.config['SESSION_COOKIE_NAME'] = 'session_{0}'.format(qradar_app_id) secret_key = "" try: # Read in secret key secret_key_store = Encryption({'name': 'secret_key', 'user': '******'}) secret_key = secret_key_store.decrypt() except EncryptionError: # If secret key file doesn't exist/fail to decrypt it, # generate a new random password for it and encrypt it secret_key = secrets.token_urlsafe(64) secret_key_store = Encryption({'name': 'secret_key', 'user': '******'}) secret_key_store.encrypt(secret_key) qflask.config["SECRET_KEY"] = secret_key # Hide server details in endpoint responses. # pylint: disable=unused-variable @qflask.after_request def obscure_server_header(resp): resp.headers['Server'] = 'QRadar App {0}'.format(qradar_app_id) return resp # Register q_url_for function for use with Jinja2 templates. qflask.add_template_global(qpylib.q_url_for, 'q_url_for') # Initialize logging. qpylib.create_log() # To enable app health checking, the QRadar App Framework # requires every Flask app to define a /debug endpoint. # The endpoint function should contain a trivial implementation # that returns a simple confirmation response message. @qflask.route('/debug') def debug(): return 'Pong!' # Import additional endpoints. # For more information see: # https://flask.palletsprojects.com/en/1.1.x/tutorial/views from . import views qflask.register_blueprint(views.viewsbp) return qflask
def test_decrypt_handles_enginev3_secrets(v3_uuid_env_var, tmpdir): db_store = 'v3user_e.db' copy_dbstore(db_store, tmpdir.strpath) with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = os.path.join( tmpdir.strpath, db_store) mykey = Encryption({'name': 'mykey', 'user': '******'}) assert mykey.decrypt() == '12345678' mytoken = Encryption({'name': 'mytoken', 'user': '******'}) assert mytoken.decrypt() == 'abcdefghij'
def test_encrypt_raises_error_when_config_db_not_writable( uuid_env_var, tmpdir): db_store = QUSER_DB_STORE db_store_path = os.path.join(tmpdir.strpath, db_store) with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: mock_get_store_path.return_value = db_store_path enc = Encryption({'name': 'secret_thing', 'user': '******'}) enc.encrypt('xyz') os.chmod(db_store_path, 0o400) with pytest.raises(EncryptionError, match='Unable to save config'): enc.encrypt('xyz')
def test_decrypt_raise_value_error_on_engine_version_mismatch( set_unset_qradar_app_uuid_env_var, patch_get_store_path): enc = Encryption({"name": "test_name", "user": "******"}) enc_string = enc.encrypt('testing123') assert enc_string != 'testing123' with open(DB_STORE) as db_file: file_json = json.load(db_file) file_json['test_name']['version'] = -1 with open(DB_STORE, 'w') as db_file: json.dump(file_json, db_file) enc = Encryption({"name": "test_name", "user": "******"}) with pytest.raises(ValueError) as ex: enc.decrypt() assert "Encryption : secret engine mismatch." in str(ex.value)
def test_encrypt_raises_error_on_encryption_failure(uuid_env_var, patch_get_store_path): enc = Encryption({'name': 'secret_thing', 'user': '******'}) enc.encrypt('mypassword') with patch('qpylib.encdec.Encryption.latest_engine_class' ) as mock_latest_engine: mock_latest_engine.return_value = DummyEngine with pytest.raises( EncryptionError, match= 'Failed to encrypt secret for name secret_thing: ValueError'): enc.encrypt('mypassword')
def test_decrypt_returns_incorrect_plaintext_with_altered_salt( set_unset_qradar_app_uuid_env_var, patch_get_store_path): enc = Encryption({"name": "test_name", "user": "******"}) enc_string = enc.encrypt('testing123') assert enc_string != 'testing123' with open(DB_STORE) as db_file: file_json = json.load(db_file) file_json['test_name']['salt'] = 'incorrect' with open(DB_STORE, 'w') as db_file: json.dump(file_json, db_file) enc = Encryption({"name": "test_name", "user": "******"}) assert enc.decrypt() != 'testing123'
def test_decrypt_v3_secret_reencrypts_with_latest_engine( v3_uuid_env_var, tmpdir): db_store = 'v3user_e.db' copy_dbstore(db_store, tmpdir.strpath) with patch('qpylib.qpylib.get_store_path') as mock_get_store_path: db_store_path = os.path.join(tmpdir.strpath, db_store) mock_get_store_path.return_value = db_store_path mytoken = Encryption({'name': 'mytoken', 'user': '******'}) assert mytoken.config['mytoken']['version'] == 3 assert mytoken.decrypt() == 'abcdefghij' # DB config for 'mytoken' has now been updated with latest engine version details. mytoken2 = Encryption({'name': 'mytoken', 'user': '******'}) assert mytoken2.config['mytoken'][ 'version'] == Encryption.latest_engine_version assert mytoken2.decrypt() == 'abcdefghij' with open(db_store_path) as db_file: store_json = json.load(db_file) assert store_json['mytoken'][ 'version'] == Encryption.latest_engine_version
def test_init_raises_error_on_missing_uuid_env_var(): with pytest.raises( EncryptionError, match='Environment variable QRADAR_APP_UUID is missing'): Encryption({'name': 'secret_thing', 'user': '******'})
def test_init_raises_error_on_whitespace_user_field(): with pytest.raises(EncryptionError, match='Supplied name and user cannot be empty'): Encryption({'name': 'secret_thing', 'user': '******'})
def test_init_raises_error_on_empty_name_field(): with pytest.raises(EncryptionError, match='Supplied name and user cannot be empty'): Encryption({'name': '', 'user': '******'})
def test_init_raises_error_on_missing_user_field(): with pytest.raises(EncryptionError, match='You must supply name and user strings'): Encryption({'name': 'secret_thing'})
def test_init_raises_error_on_missing_name_field(): with pytest.raises(EncryptionError, match='You must supply name and user strings'): Encryption({'user': '******'})
def test_init_raises_error_on_missing_data_fields(): with pytest.raises(EncryptionError, match='You must supply name and user strings'): Encryption({})
def test_encrypt_decrypt_whitespace(uuid_env_var, patch_get_store_path): enc = Encryption({'name': 'secret_thing', 'user': '******'}) assert enc.encrypt(' \n \t ') assert enc.decrypt() == ' \n \t '
def test_encrypt_decrypt_empty_string(uuid_env_var, patch_get_store_path): enc = Encryption({'name': 'secret_thing', 'user': '******'}) enc.encrypt('') assert enc.decrypt() == ''
def test_encrypt_decrypt_null_char(uuid_env_var, patch_get_store_path): enc = Encryption({'name': 'secret_thing', 'user': '******'}) enc.encrypt('\x00') assert enc.decrypt() == '\x00'