From 40243edfceb41346d8acef48d6c0fc270a6bff7b Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Tue, 30 Jun 2026 17:22:35 +0200 Subject: [PATCH] MINIFICPP-2749 Add EncryptContentPGP and DecryptContentPGP --- .../ubuntu_22_04_clang_arm_manifest.json | 184 +++++++++++++ .../extensions/minifi_pgp/.cargo/config.toml | 5 + minifi_rust/extensions/minifi_pgp/.gitignore | 7 + minifi_rust/extensions/minifi_pgp/Cargo.toml | 15 ++ .../features/encrypt_decrypt.feature | 54 ++++ .../minifi_pgp/features/environment.py | 73 +++++ .../minifi_pgp/features/steps/steps.py | 100 +++++++ .../extensions/minifi_pgp/minifi_pgp.md | 120 +++++++++ .../src/controller_services/key_lookup.rs | 56 ++++ .../minifi_pgp/src/controller_services/mod.rs | 3 + .../private_key_service.rs | 94 +++++++ .../controller_service_definition.rs | 10 + .../private_key_service/properties.rs | 32 +++ .../private_key_service/tests.rs | 214 +++++++++++++++ .../controller_services/public_key_service.rs | 72 +++++ .../controller_service_definition.rs | 10 + .../public_key_service/properties.rs | 22 ++ .../public_key_service/tests.rs | 245 +++++++++++++++++ minifi_rust/extensions/minifi_pgp/src/lib.rs | 23 ++ .../src/processors/decrypt_content.rs | 150 +++++++++++ .../decrypt_content/output_attributes.rs | 13 + .../decrypt_content/processor_definition.rs | 22 ++ .../processors/decrypt_content/properties.rs | 36 +++ .../decrypt_content/relationships.rs | 11 + .../src/processors/decrypt_content/tests.rs | 253 ++++++++++++++++++ .../src/processors/encrypt_content.rs | 152 +++++++++++ .../encrypt_content/output_attributes.rs | 7 + .../encrypt_content/processor_definition.rs | 20 ++ .../processors/encrypt_content/properties.rs | 46 ++++ .../encrypt_content/relationships.rs | 11 + .../src/processors/encrypt_content/tests.rs | 137 ++++++++++ .../minifi_pgp/src/processors/mod.rs | 2 + .../minifi_pgp/src/test_utils/mod.rs | 15 ++ .../extensions/minifi_pgp/src/utils.rs | 11 + .../minifi_pgp/test_keys/README.txt | 8 + .../extensions/minifi_pgp/test_keys/alice.asc | 50 ++++ .../extensions/minifi_pgp/test_keys/alice.gpg | Bin 0 -> 2175 bytes .../minifi_pgp/test_keys/alice_private.asc | 92 +++++++ .../minifi_pgp/test_keys/alice_private.gpg | Bin 0 -> 4220 bytes .../minifi_pgp/test_keys/bob_private.asc | 71 +++++ .../minifi_pgp/test_keys/bob_private.gpg | Bin 0 -> 3207 bytes .../minifi_pgp/test_keys/garbage.gpg | Bin 0 -> 1024 bytes .../minifi_pgp/test_keys/keyring.asc | 89 ++++++ .../minifi_pgp/test_keys/keyring.gpg | Bin 0 -> 4080 bytes .../minifi_pgp/test_keys/secret_keyring.asc | 159 +++++++++++ .../minifi_pgp/test_keys/secret_keyring.gpg | Bin 0 -> 7427 bytes .../minifi_pgp/test_keys/truncated.asc | 10 + .../test_keys/truncated_private.asc | 17 ++ .../test_messages/foo_for_alice.asc | 12 + .../test_messages/foo_for_alice.gpg | Bin 0 -> 346 bytes .../test_messages/password_encrypted_foo.asc | 6 + .../test_messages/password_encrypted_foo.gpg | 1 + .../minifi_rs_behave/Dockerfile.alpine | 2 +- .../minifi_rs_behave/Dockerfile.debian | 2 +- 54 files changed, 2742 insertions(+), 2 deletions(-) create mode 100644 minifi_rust/extensions/minifi_pgp/.cargo/config.toml create mode 100644 minifi_rust/extensions/minifi_pgp/.gitignore create mode 100644 minifi_rust/extensions/minifi_pgp/Cargo.toml create mode 100644 minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature create mode 100644 minifi_rust/extensions/minifi_pgp/features/environment.py create mode 100644 minifi_rust/extensions/minifi_pgp/features/steps/steps.py create mode 100644 minifi_rust/extensions/minifi_pgp/minifi_pgp.md create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/lib.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/mod.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/utils.rs create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/README.txt create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/alice.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index 8e48815439..e9772d44c1 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -1159,6 +1159,66 @@ "supportsDynamicProperties": "false", "type": "minifi_rs_playground.processors.count_actual_logging.CountActualLogging" }, + { + "propertyDescriptors": { + "Decryption Strategy": { + "name": "Decryption Strategy", + "description": "Strategy for writing files to success after decryption", + "validator": "VALID", + "required": "true", + "sensitive": "false", + "expressionLanguageScope": "NONE", + "defaultValue": "DECRYPTED", + "allowableValues": [ + { + "value": "DECRYPTED", + "displayName": "DECRYPTED" + }, + { + "value": "PACKAGED", + "displayName": "PACKAGED" + } + ] + }, + "Private Key Service": { + "typeProvidedByValue": { + "type": "minifi_pgp.controller_services.private_key_service.PGPPrivateKeyService", + "group": "minifi_pgp.controller_services.private_key_service", + "artifact": "minifi_pgp" + }, + "name": "Private Key Service", + "description": "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "NONE" + }, + "Symmetric Password": { + "name": "Symmetric Password", + "description": "Password used for decrypting data encrypted with Password-Based Encryption", + "validator": "VALID", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + } + }, + "inputRequirement": "INPUT_REQUIRED", + "isSingleThreaded": "false", + "supportedRelationships": [ + { + "name": "failure", + "description": "Decryption Failed" + }, + { + "name": "success", + "description": "Decryption Succeeded" + } + ], + "typeDescription": "Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification.", + "supportsDynamicRelationships": "false", + "supportsDynamicProperties": "false", + "type": "minifi_pgp.processors.decrypt_content.DecryptContentPGP" + }, { "propertyDescriptors": { "Max Buffer Age": { @@ -1911,6 +1971,74 @@ "supportsDynamicProperties": "false", "type": "minifi_rs_playground.processors.duplicate_text.DuplicateStreamText" }, + { + "propertyDescriptors": { + "File Encoding": { + "name": "File Encoding", + "description": "File Encoding for encryption", + "validator": "VALID", + "required": "true", + "sensitive": "false", + "expressionLanguageScope": "NONE", + "defaultValue": "BINARY", + "allowableValues": [ + { + "value": "ASCII", + "displayName": "ASCII" + }, + { + "value": "BINARY", + "displayName": "BINARY" + } + ] + }, + "Public Key Search": { + "name": "Public Key Search", + "description": "PGP Public Key Search will be used to match against the User ID or Key ID when formatted as uppercase hexadecimal string of 16 characters", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "FLOWFILE_ATTRIBUTES" + }, + "Public Key Service": { + "typeProvidedByValue": { + "type": "minifi_pgp.controller_services.public_key_service.PGPPublicKeyService", + "group": "minifi_pgp.controller_services.public_key_service", + "artifact": "minifi_pgp" + }, + "name": "Public Key Service", + "description": "PGP Public Key Service for encrypting data with Public Key Encryption", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "NONE" + }, + "Symmetric Password": { + "name": "Symmetric Password", + "description": "Password used for encrypting data with Password-Based Encryption", + "validator": "VALID", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + } + }, + "inputRequirement": "INPUT_REQUIRED", + "isSingleThreaded": "false", + "supportedRelationships": [ + { + "name": "failure", + "description": "Encryption Failed" + }, + { + "name": "success", + "description": "Encryption Succeeded" + } + ], + "typeDescription": "Encrypt contents using OpenPGP.", + "supportsDynamicRelationships": "false", + "supportsDynamicProperties": "false", + "type": "minifi_pgp.processors.encrypt_content.EncryptContentPGP" + }, { "propertyDescriptors": { "Destination": { @@ -12046,6 +12174,62 @@ "supportsDynamicProperties": "false", "type": "org.apache.nifi.minifi.controllers.PersistentMapStateStorage" }, + { + "propertyDescriptors": { + "Key": { + "name": "Key", + "description": "Secret Key encoded in ASCII Armor", + "validator": "VALID", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + }, + "Key File": { + "name": "Key File", + "description": "File path to PGP Secret Key encoded in binary or ASCII Armor", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "FLOWFILE_ATTRIBUTES" + }, + "Key Passphrase": { + "name": "Key Passphrase", + "description": "Passphrase used for decrypting Private Keys", + "validator": "VALID", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + } + }, + "typeDescription": "PGP Private Key Service provides Private Keys loaded from files or properties", + "supportsDynamicRelationships": "false", + "supportsDynamicProperties": "false", + "type": "minifi_pgp.controller_services.private_key_service.PGPPrivateKeyService" + }, + { + "propertyDescriptors": { + "Keyring": { + "name": "Keyring", + "description": "PGP Keyring or Secret Key encoded in ASCII Armor", + "validator": "VALID", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + }, + "Keyring File": { + "name": "Keyring File", + "description": "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "FLOWFILE_ATTRIBUTES" + } + }, + "typeDescription": "PGP Public Key Service providing Public Keys loaded from files", + "supportsDynamicRelationships": "false", + "supportsDynamicProperties": "false", + "type": "minifi_pgp.controller_services.public_key_service.PGPPublicKeyService" + }, { "propertyDescriptors": { "Proxy Server Host": { diff --git a/minifi_rust/extensions/minifi_pgp/.cargo/config.toml b/minifi_rust/extensions/minifi_pgp/.cargo/config.toml new file mode 100644 index 0000000000..cb8c02ddc4 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.aarch64-apple-darwin] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] + +[target.x86_64-apple-darwin] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] diff --git a/minifi_rust/extensions/minifi_pgp/.gitignore b/minifi_rust/extensions/minifi_pgp/.gitignore new file mode 100644 index 0000000000..f9f6d205fa --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/.gitignore @@ -0,0 +1,7 @@ +target +output +features/.venv +features/output +integration_tests/features/.venv +integration_tests/features/linux_so +integration_tests/.venv \ No newline at end of file diff --git a/minifi_rust/extensions/minifi_pgp/Cargo.toml b/minifi_rust/extensions/minifi_pgp/Cargo.toml new file mode 100644 index 0000000000..af2c6e1749 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "minifi_pgp" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +minifi_native = { path = "../../minifi_native" } +strum_macros = "0.28.0" +strum = "0.28.0" +pgp = "0.20.0" +rand = "0.8.6" # pgp 0.20.0 doesnt support >= 0.9 rand yet + diff --git a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature new file mode 100644 index 0000000000..3914e71308 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature @@ -0,0 +1,54 @@ +@SUPPORTS_WINDOWS +Feature: Test PGP extension's encryption and decryption capabilities + + Background: The pgp library is successfully built on linux + + Scenario: The pgp library is loaded into minifi + Given log property "logger.org::apache::nifi::minifi::core::extension::ExtensionManager" is set to "TRACE,stderr" + And log property "logger.org::apache::nifi::minifi::core::ClassLoader" is set to "TRACE,stderr" + + When the MiNiFi instance starts up + + Then the Minifi logs contain the following message: "Registering class 'EncryptContentPGP' at '/minifi_pgp'" in less than 10 seconds + And the Minifi logs contain the following message: "Registering class 'DecryptContentPGP' at '/minifi_pgp'" in less than 1 seconds + And the Minifi logs contain the following message: "Registering class 'PGPPublicKeyService' at '/minifi_pgp'" in less than 1 seconds + And the Minifi logs contain the following message: "Registering class 'PGPPrivateKeyService' at '/minifi_pgp'" in less than 1 seconds + And the Minifi logs do not contain errors + And the Minifi logs do not contain warnings + + Scenario: Encrypted for Alice but not for Bob + Given log property "logger.minifi_pgp::processors::decrypt_content::DecryptContentPGP" is set to "TRACE,stderr" + And log property "logger.minifi_pgp::processors::encrypt_content::EncryptContentPGP" is set to "TRACE,stderr" + + And a GetFile processor with the "Input Directory" property set to "/tmp/input" + And an EncryptContentPGP processor with a PGPPublicKeyService is set up + And a DecryptContentPGP processor named DecryptAlice with a PGPPrivateKeyService is set up for Alice + And a DecryptContentPGP processor named DecryptBob with a PGPPrivateKeyService is set up for Bob + And a PutFile processor with the name "AliceSuccess" + And a PutFile processor with the name "BobFailure" + + And these processor properties are set + | processor name | property name | property value | + | EncryptContentPGP | File Encoding | ASCII | + | EncryptContentPGP | Public Key Search | Alice | + | AliceSuccess | Directory | /tmp/output/alice_ok | + | BobFailure | Directory | /tmp/output/bob_fail | + + And the processors are connected up as described here + | source name | relationship name | destination name | + | GetFile | success | EncryptContentPGP | + | EncryptContentPGP | success | DecryptAlice | + | EncryptContentPGP | success | DecryptBob | + | DecryptAlice | success | AliceSuccess | + | DecryptBob | failure | BobFailure | + + And AliceSuccess's success relationship is auto-terminated + And BobFailure's success relationship is auto-terminated + + And a directory at "/tmp/input" has a file "test_file.log" with the content "test content" + + When the MiNiFi instance starts up + + Then at least one file with the content "test content" is placed in the "/tmp/output/alice_ok" directory in less than 5 seconds + And an encrypted armored pgp file is placed in the "/tmp/output/bob_fail" directory in less than 5 seconds + And the Minifi logs do not contain errors diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py new file mode 100644 index 0000000000..cdec449069 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -0,0 +1,73 @@ +import os +from typing import List + +from minifi_behave.containers.docker_image_builder import DockerImageBuilder +from minifi_behave.core.hooks import common_after_scenario +from minifi_behave.core.hooks import common_before_scenario, get_minifi_container_image +from minifi_behave.core.minifi_test_context import MinifiTestContext + + +def add_extension_to_minifi_container( + extension_name: str, possible_paths: List[str], context: MinifiTestContext +): + new_container_name = f"apacheminificpp:{extension_name}" + is_windows = os.name == "nt" + if is_windows: + lib_filename = f"{extension_name}.dll" + container_extension_dir = ( + "C:/Program Files/ApacheNiFiMiNiFi/nifi-minifi-cpp/extensions" + ) + else: + lib_filename = f"lib{extension_name}.so" + container_extension_dir = "/opt/minifi/minifi-current/extensions/" + + host_path = None + for path in possible_paths: + if os.path.exists(os.path.join(path, lib_filename)): + host_path = os.path.join(path, lib_filename) + break + + assert host_path is not None, ( + f"Could not find {lib_filename} in {[p for p in possible_paths]}" + ) + + with open(host_path, "rb") as f: + lib_content = f.read() + + base_img = get_minifi_container_image() + + if is_windows: + dockerfile = f""" +FROM {base_img} +COPY ["{lib_filename}", "{container_extension_dir}/{lib_filename}"] +""" + else: + dockerfile = f""" +FROM {base_img} +COPY --chown=minificpp:minificpp {lib_filename} {container_extension_dir} +RUN chmod 755 {container_extension_dir}{lib_filename} +""" + + builder = DockerImageBuilder( + image_tag=new_container_name, + dockerfile_content=dockerfile, + files_on_context={lib_filename: lib_content}, + ) + + builder.build() + return new_container_name + + +def before_all(context): + dir_path = os.path.dirname(os.path.realpath(__file__)) + build_path = os.path.normpath(os.path.join(dir_path, "../../../target/release/")) + add_extension_to_minifi_container("minifi_pgp", [build_path], context) + + +def before_scenario(context, scenario): + context.minifi_container_image = "apacheminificpp:minifi_pgp" + common_before_scenario(context, scenario) + + +def after_scenario(context, scenario): + common_after_scenario(context, scenario) diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py new file mode 100644 index 0000000000..b2a4d6e632 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -0,0 +1,100 @@ +import os +from pathlib import Path + +import humanfriendly +from behave import step, then + +from minifi_behave.steps import checking_steps # noqa: F401 +from minifi_behave.steps import configuration_steps # noqa: F401 +from minifi_behave.steps import core_steps # noqa: F401 +from minifi_behave.steps import flow_building_steps # noqa: F401 +from minifi_behave.core.helpers import wait_for_condition +from minifi_behave.core.minifi_test_context import MinifiTestContext +from minifi_behave.minifi.controller_service import ControllerService +from minifi_behave.minifi.processor import Processor + + +@step("an EncryptContentPGP processor with a PGPPublicKeyService is set up") +def step_encrypt_content_with_service(context: MinifiTestContext): + dir_path = os.path.dirname(os.path.realpath(__file__)) + + public_key_service = ControllerService( + class_name="PGPPublicKeyService", service_name="my_public_keys" + ) + alice_public_key = Path(f"{dir_path}/../../test_keys/keyring.asc").read_text() + public_key_service.add_property("Keyring", alice_public_key) + context.get_or_create_default_minifi_container().flow_definition.controller_services.append( + public_key_service + ) + + processor = Processor("EncryptContentPGP", "EncryptContentPGP") + processor.add_property("Public Key Service", "my_public_keys") + context.get_or_create_default_minifi_container().flow_definition.processors.append( + processor + ) + + +@step( + "a DecryptContentPGP processor named DecryptAlice with a PGPPrivateKeyService is set up for Alice" +) +def step_decrypt_content_for_alice(context: MinifiTestContext): + dir_path = os.path.dirname(os.path.realpath(__file__)) + + private_key_service = ControllerService( + class_name="PGPPrivateKeyService", service_name="alice_private_key" + ) + alice_private_key = Path( + f"{dir_path}/../../test_keys/alice_private.asc" + ).read_text() + private_key_service.add_property("Key", alice_private_key) + private_key_service.add_property("Key Passphrase", "whiterabbit") + context.get_or_create_default_minifi_container().flow_definition.controller_services.append( + private_key_service + ) + + processor = Processor("DecryptContentPGP", "DecryptAlice") + processor.add_property("Private Key Service", "alice_private_key") + context.get_or_create_default_minifi_container().flow_definition.processors.append( + processor + ) + + +@step( + "a DecryptContentPGP processor named DecryptBob with a PGPPrivateKeyService is set up for Bob" +) +def step_decrypt_content_for_bob(context: MinifiTestContext): + dir_path = os.path.dirname(os.path.realpath(__file__)) + + private_key_service = ControllerService( + class_name="PGPPrivateKeyService", service_name="bob_private_key" + ) + bob_private_key = Path(f"{dir_path}/../../test_keys/bob_private.asc").read_text() + private_key_service.add_property("Key", bob_private_key) + context.get_or_create_default_minifi_container().flow_definition.controller_services.append( + private_key_service + ) + + processor = Processor("DecryptContentPGP", "DecryptBob") + processor.add_property("Private Key Service", "bob_private_key") + context.get_or_create_default_minifi_container().flow_definition.processors.append( + processor + ) + + +@then( + 'an encrypted armored pgp file is placed in the "{directory}" directory in less than {duration}' +) +def then_armored_pgp_file_in_dir( + context: MinifiTestContext, directory: str, duration: str +): + duration_seconds = humanfriendly.parse_timespan(duration) + assert wait_for_condition( + condition=lambda: ( + context.get_or_create_default_minifi_container().directory_contains_file_with_regex( + directory, "-----BEGIN PGP MESSAGE-----" + ) + ), + timeout_seconds=duration_seconds, + bail_condition=lambda: False, + context=context, + ) diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md new file mode 100644 index 0000000000..f2d9cbeab7 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -0,0 +1,120 @@ + + +## Table of Contents + +### Processors + +- [DecryptContentPGP](#DecryptContentPGP) +- [EncryptContentPGP](#EncryptContentPGP) +### Controller Services + +- [PGPPrivateKeyService](#PGPPrivateKeyService) +- [PGPPublicKeyService](#PGPPublicKeyService) + + +## DecryptContentPGP + +### Description + +Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification. + +### Properties + +In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language. + +| Name | Default Value | Allowable Values | Description | +|---------------------|---------------|------------------------|-------------------------------------------------------------------------------------------------------------| +| Decryption Strategy | DECRYPTED | DECRYPTED
PACKAGED | Strategy for writing files to success after decryption | +| Symmetric Password | | | Password used for decrypting data encrypted with Password-Based Encryption
**Sensitive Property: true** | +| Private Key Service | | | PGP Private Key Service for decrypting data encrypted with Public Key Encryption | + +### Relationships + +| Name | Description | +|---------|----------------------| +| success | Decryption Succeeded | +| failure | Decryption Failed | + +### Output Attributes + +| Attribute | Relationship | Description | +|---------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| pgp.literal.data.filename | success | Filename from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementation omit these inherently malleable metadata) | +| pgp.literal.data.modified | success | Modified Date from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementation omit these inherently malleable metadata) | + + +## EncryptContentPGP + +### Description + +Encrypt contents using OpenPGP. + +### Properties + +In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language. + +| Name | Default Value | Allowable Values | Description | +|--------------------|---------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **File Encoding** | BINARY | ASCII
BINARY | File Encoding for encryption | +| Symmetric Password | | | Password used for encrypting data with Password-Based Encryption
**Sensitive Property: true** | +| Public Key Search | | | PGP Public Key Search will be used to match against the User ID or Key ID when formatted as uppercase hexadecimal string of 16 characters
**Supports Expression Language: true** | +| Public Key Service | | | PGP Public Key Service for encrypting data with Public Key Encryption | + +### Relationships + +| Name | Description | +|---------|----------------------| +| success | Encryption Succeeded | +| failure | Encryption Failed | + +### Output Attributes + +| Attribute | Relationship | Description | +|-------------------|--------------|---------------| +| pgp.file.encoding | success | File Encoding | + + +## PGPPrivateKeyService + +### Description + +PGP Private Key Service provides Private Keys loaded from files or properties + +### Properties + +In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language. + +| Name | Default Value | Allowable Values | Description | +|----------------|---------------|------------------|---------------------------------------------------------------------------------------------------------| +| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Key Passphrase | | | Passphrase used for decrypting Private Keys
**Sensitive Property: true** | + + +## PGPPublicKeyService + +### Description + +PGP Public Key Service providing Public Keys loaded from files + +### Properties + +In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language. + +| Name | Default Value | Allowable Values | Description | +|--------------|---------------|------------------|--------------------------------------------------------------------------------------------------------------------| +| Keyring File | | | File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Keyring | | | PGP Keyring or Secret Key encoded in ASCII Armor
**Sensitive Property: true** | diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs new file mode 100644 index 0000000000..cff723a718 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs @@ -0,0 +1,56 @@ +use pgp::composed::SignedKeyDetails; +use pgp::types::KeyId; + +/// Returns true when `target_id` matches either: +/// - the key's Key ID formatted as 16-character hex (case-insensitive), or +/// - any of its User IDs as a case-insensitive substring match. +pub(crate) fn key_matches(key_id: &KeyId, details: &SignedKeyDetails, target_id: &str) -> bool { + let target = target_id.trim(); + if target.is_empty() { + return false; + } + + if key_id.to_string().eq_ignore_ascii_case(target) { + return true; + } + + let target_lower = target.to_ascii_lowercase(); + details.users.iter().any(|user| { + user.id + .as_str() + .map(|user_id| user_id.to_ascii_lowercase().contains(&target_lower)) + .unwrap_or(false) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn key_id_from_hex(hex: &str) -> KeyId { + let mut bytes = [0u8; 8]; + for (i, chunk) in hex.as_bytes().chunks(2).take(8).enumerate() { + bytes[i] = u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap(); + } + KeyId::from(bytes) + } + + #[test] + fn empty_target_never_matches() { + let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]); + let key_id = key_id_from_hex("1122334455667788"); + assert!(!key_matches(&key_id, &details, "")); + assert!(!key_matches(&key_id, &details, " ")); + } + + #[test] + fn matches_key_id_case_insensitive() { + let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]); + let key_id = key_id_from_hex("11ABcdEF33445566"); + + assert!(key_matches(&key_id, &details, "11abcdef33445566")); + assert!(key_matches(&key_id, &details, "11ABCDEF33445566")); + assert!(!key_matches(&key_id, &details, "11abcdef3344556")); // 15 chars + assert!(!key_matches(&key_id, &details, "abcdef33445566")); + } +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs new file mode 100644 index 0000000000..d2c83be438 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod key_lookup; +pub(crate) mod private_key_service; +pub(crate) mod public_key_service; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs new file mode 100644 index 0000000000..bef5342362 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs @@ -0,0 +1,94 @@ +mod controller_service_definition; +mod properties; + +#[cfg(test)] +use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::private_key_service::properties::KEY_PASSPHRASE; +use crate::utils; +use minifi_native::macros::ComponentIdentifier; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; +use pgp::composed::{Deserializable, SignedSecretKey, TheRing}; +#[cfg(test)] +use pgp::types::KeyDetails; +use std::path::PathBuf; + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct PGPPrivateKeyService { + private_keys: Vec, + passphrase: pgp::types::Password, +} + +impl EnableControllerService for PGPPrivateKeyService { + fn enable(context: &P, logger: &L) -> Result + where + Self: Sized, + { + let mut private_keys = vec![]; + if let Some(keyring_file_path) = context.get_property::(&properties::KEY_FILE)? { + if let Ok((keys, _headers)) = SignedSecretKey::from_armor_file_many(&keyring_file_path) + { + collect_keys(keys, &mut private_keys, logger); + } else if let Ok(keys) = SignedSecretKey::from_file_many(keyring_file_path) { + collect_keys(keys, &mut private_keys, logger); + } + } + if let Some(keyring_ascii) = context.get_property::(&properties::KEY)? + && let Ok((keys, _headers)) = SignedSecretKey::from_armor_many(keyring_ascii.as_bytes()) + { + collect_keys(keys, &mut private_keys, logger); + } + + let passphrase = context + .get_property::(&KEY_PASSPHRASE)? + .unwrap_or_default(); + + if private_keys.is_empty() { + return Err(MinifiError::controller_service_err( + "Could not load any valid keys", + )); + } + Ok(Self { + private_keys, + passphrase, + }) + } +} + +impl PGPPrivateKeyService { + pub fn get_the_ring(&'_ self) -> TheRing<'_> { + TheRing { + secret_keys: self.private_keys.iter().collect(), + key_passwords: vec![&self.passphrase], + message_password: vec![], + session_keys: vec![], + decrypt_options: Default::default(), + } + } + + #[cfg(test)] + pub fn get_secret_key(&self, target_id: &str) -> Option<&SignedSecretKey> { + self.private_keys.iter().find(|private_key| { + key_matches( + &private_key.primary_key.legacy_key_id(), + &private_key.details, + target_id, + ) + }) + } +} + +fn collect_keys(keys: I, out: &mut Vec, logger: &L) +where + I: Iterator>, + L: Logger, +{ + for key in keys { + match key { + Ok(k) => out.push(k), + Err(e) => warn!(logger, "Skipping unparseable private key: {}", e), + } + } +} + +#[cfg(test)] +mod tests; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs new file mode 100644 index 0000000000..11f80a911d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs @@ -0,0 +1,10 @@ +use super::PGPPrivateKeyService; +use super::properties::*; +use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface}; + +impl ControllerServiceDefinition for PGPPrivateKeyService { + const DESCRIPTION: &'static str = + "PGP Private Key Service provides Private Keys loaded from files or properties"; + const PROPERTIES: &'static [Property] = &[KEY_FILE, KEY, KEY_PASSPHRASE]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs new file mode 100644 index 0000000000..6cabc0086b --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs @@ -0,0 +1,32 @@ +use minifi_native::Property; +use minifi_native::PropertyConstraints::NoConstraints; + +pub(crate) const KEY_FILE: Property = Property { + name: "Key File", + description: "File path to PGP Secret Key encoded in binary or ASCII Armor", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEY: Property = Property { + name: "Key", + description: "Secret Key encoded in ASCII Armor", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEY_PASSPHRASE: Property = Property { + name: "Key Passphrase", + description: "Passphrase used for decrypting Private Keys", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs new file mode 100644 index 0000000000..efdb6e39af --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs @@ -0,0 +1,214 @@ +use super::PGPPrivateKeyService; +use crate::test_utils::get_test_key_path; +use minifi_native::MinifiError::ControllerServiceError; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, MockControllerServiceContext, MockLogger, +}; + +fn assert_private_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, +) { + if let Err(ControllerServiceError(error)) = + PGPPrivateKeyService::enable(context, &MockLogger::new()) + { + assert_eq!(error, "Could not load any valid keys"); + } else { + panic!("Didnt fail with no_valid_keys"); + } +} + +#[test] +fn test_component_id() { + assert_eq!( + PGPPrivateKeyService::CLASS_NAME, + "minifi_pgp::controller_services::private_key_service::PGPPrivateKeyService" + ); + assert_eq!(PGPPrivateKeyService::GROUP_NAME, "minifi_pgp"); + assert_eq!(PGPPrivateKeyService::VERSION, "0.1.0"); +} + +#[test] +fn default_fails() { + let context = MockControllerServiceContext::new(); + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_binary_keyring_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Key File".to_string(), get_test_key_path("garbage.gpg")); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn armored_public_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("private_mistake.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("truncated_private.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn non_existent_keyfile() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("non_existent.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn single_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("alice_private.asc"), + ); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!( + controller_service + .get_secret_key("alice@example.com") + .is_some() + ); + + assert!(controller_service.get_secret_key("Bob").is_none()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn single_binary_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("alice_private.gpg"), + ); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("A").is_some()); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!( + controller_service + .get_secret_key("Alice ") + .is_some() + ); + + assert!(controller_service.get_secret_key("").is_none()); + + assert!(controller_service.get_secret_key("Bob").is_none()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn armored_keyring_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("secret_keyring.asc"), + ); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!(controller_service.get_secret_key("Bob").is_some()); + assert!(controller_service.get_secret_key("bob@home.io").is_some()); + assert!(controller_service.get_secret_key("bob@work.com").is_some()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn binary_keyring_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("secret_keyring.gpg"), + ); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!(controller_service.get_secret_key("Bob").is_some()); + assert!(controller_service.get_secret_key("bob@home.io").is_some()); + assert!(controller_service.get_secret_key("bob@work.com").is_some()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn armored_keyring() { + let mut context = MockControllerServiceContext::new(); + + let file_content = std::fs::read_to_string(get_test_key_path("secret_keyring.asc")) + .expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!(controller_service.get_secret_key("Bob").is_some()); + assert!(controller_service.get_secret_key("bob@home.io").is_some()); + assert!(controller_service.get_secret_key("bob@work.com").is_some()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn armored_single_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!(controller_service.get_secret_key("Bob").is_none()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn corrupted_armored_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = std::fs::read_to_string(get_test_key_path("truncated_private.asc")) + .expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn public_ascii_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs new file mode 100644 index 0000000000..93f03d20e9 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs @@ -0,0 +1,72 @@ +mod controller_service_definition; +mod properties; + +use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::public_key_service::properties::{KEYRING, KEYRING_FILE}; +use minifi_native::macros::ComponentIdentifier; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; +use pgp::composed::{Deserializable, SignedPublicKey}; +use pgp::types::KeyDetails; +use std::path::PathBuf; + +#[derive(Debug, ComponentIdentifier, PartialEq)] +pub(crate) struct PGPPublicKeyService { + public_keys: Vec, +} + +impl EnableControllerService for PGPPublicKeyService { + fn enable(context: &P, logger: &L) -> Result + where + Self: Sized, + { + let mut public_keys = vec![]; + if let Some(keyring_file_path) = context.get_property::(&KEYRING_FILE)? { + if let Ok((keys, _headers)) = SignedPublicKey::from_armor_file_many(&keyring_file_path) + { + collect_keys(keys, &mut public_keys, logger); + } else if let Ok(keys) = SignedPublicKey::from_file_many(keyring_file_path) { + collect_keys(keys, &mut public_keys, logger); + } + } + if let Some(keyring_ascii) = context.get_property::(&KEYRING)? + && let Ok((keys, _headers)) = SignedPublicKey::from_armor_many(keyring_ascii.as_bytes()) + { + collect_keys(keys, &mut public_keys, logger); + } + + if public_keys.is_empty() { + return Err(MinifiError::controller_service_err( + "Could not load any valid keys", + )); + } + Ok(Self { public_keys }) + } +} + +fn collect_keys(keys: I, out: &mut Vec, logger: &L) +where + I: Iterator>, + L: Logger, +{ + for key in keys { + match key { + Ok(k) => out.push(k), + Err(e) => warn!(logger, "Skipping unparseable public key: {}", e), + } + } +} + +impl PGPPublicKeyService { + pub fn get(&self, target_id: &str) -> Option<&SignedPublicKey> { + self.public_keys.iter().find(|public_key| { + key_matches( + &public_key.primary_key.legacy_key_id(), + &public_key.details, + target_id, + ) + }) + } +} + +#[cfg(test)] +mod tests; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs new file mode 100644 index 0000000000..77fb1c34a2 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs @@ -0,0 +1,10 @@ +use super::PGPPublicKeyService; +use super::properties::*; +use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface}; + +impl ControllerServiceDefinition for PGPPublicKeyService { + const DESCRIPTION: &'static str = + "PGP Public Key Service providing Public Keys loaded from files"; + const PROPERTIES: &'static [Property] = &[KEYRING_FILE, KEYRING]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs new file mode 100644 index 0000000000..2087163ea6 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs @@ -0,0 +1,22 @@ +use minifi_native::Property; +use minifi_native::PropertyConstraints::NoConstraints; + +pub(crate) const KEYRING_FILE: Property = Property { + name: "Keyring File", + description: "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEYRING: Property = Property { + name: "Keyring", + description: "PGP Keyring or Secret Key encoded in ASCII Armor", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs new file mode 100644 index 0000000000..4adbb14b4e --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs @@ -0,0 +1,245 @@ +use super::PGPPublicKeyService; +use crate::test_utils::get_test_key_path; +use minifi_native::MinifiError::ControllerServiceError; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, MockControllerServiceContext, MockLogger, +}; +use pgp::types::KeyDetails; + +fn assert_public_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, +) { + if let Err(ControllerServiceError(error)) = + PGPPublicKeyService::enable(context, &MockLogger::new()) + { + assert_eq!(error, "Could not load any valid keys"); + } else { + panic!("Didnt fail with no_valid_keys"); + } +} + +#[test] +fn test_component_id() { + assert_eq!( + PGPPublicKeyService::CLASS_NAME, + "minifi_pgp::controller_services::public_key_service::PGPPublicKeyService" + ); + assert_eq!(PGPPublicKeyService::GROUP_NAME, "minifi_pgp"); + assert_eq!(PGPPublicKeyService::VERSION, "0.1.0"); +} + +#[test] +fn default_fails() { + let context = MockControllerServiceContext::new(); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_binary_keyring_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("garbage.gpg")); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn armored_private_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("alice_private.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("truncated.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn non_existent_keyfile() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("non_existent.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn single_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("alice.asc")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("alice@example.com").is_some()); + + assert!(controller_service.get("Bob").is_none()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn single_binary_key_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("alice.gpg")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("A").is_some()); + assert!(controller_service.get("Alice").is_some()); + assert!( + controller_service + .get("Alice ") + .is_some() + ); + + assert!(controller_service.get("").is_none()); + + assert!(controller_service.get("Bob").is_none()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn armored_keyring_key_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("keyring.asc")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("Bob").is_some()); + assert!(controller_service.get("bob@home.io").is_some()); + assert!(controller_service.get("bob@work.com").is_some()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn binary_keyring_key_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("keyring.gpg")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("Bob").is_some()); + assert!(controller_service.get("bob@home.io").is_some()); + assert!(controller_service.get("bob@work.com").is_some()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn armored_keyring() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("keyring.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("Bob").is_some()); + assert!(controller_service.get("bob@home.io").is_some()); + assert!(controller_service.get("bob@work.com").is_some()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn armored_single_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("Bob").is_none()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn corrupted_armored_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("truncated.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn private_ascii_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn looks_up_by_key_id_hex() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("alice.asc")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + + // Get Alice's Key ID from the loaded key so the test doesn't hard-code hex bytes. + let alice = controller_service.get("Alice").expect("Alice should exist"); + let key_id_hex = alice.primary_key.legacy_key_id().to_string(); + assert_eq!(key_id_hex.len(), 16); + + // Full 16-char hex, both cases, should match. + assert!(controller_service.get(&key_id_hex).is_some()); + assert!( + controller_service + .get(&key_id_hex.to_ascii_uppercase()) + .is_some() + ); + + // A partial or unrelated hex string should not. + assert!(controller_service.get(&key_id_hex[..8]).is_none()); + assert!(controller_service.get("0123456789abcdef").is_none()); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/lib.rs b/minifi_rust/extensions/minifi_pgp/src/lib.rs new file mode 100644 index 0000000000..6c01256874 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/lib.rs @@ -0,0 +1,23 @@ +mod controller_services; +mod processors; + +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::decrypt_content::DecryptContentPGP; +use crate::processors::encrypt_content::EncryptContentPGP; +use minifi_native::{FlowFileStreamTransformProcessorType, MultiThreaded}; + +minifi_native::declare_minifi_extension!( + processors: [ + (FlowFileStreamTransformProcessorType, MultiThreaded, EncryptContentPGP), + (FlowFileStreamTransformProcessorType, MultiThreaded, DecryptContentPGP), + ], + controllers: [ + PGPPublicKeyService, + PGPPrivateKeyService, + ] +); + +#[cfg(test)] +mod test_utils; +mod utils; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs new file mode 100644 index 0000000000..e97b15e54a --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -0,0 +1,150 @@ +mod output_attributes; +pub(crate) mod properties; +mod relationships; + +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::properties::{ + DECRYPTION_STRATEGY, PRIVATE_KEY_SERVICE, SYMMETRIC_PASSWORD, +}; +use crate::processors::decrypt_content::relationships::{FAILURE, SUCCESS}; +use crate::utils; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use minifi_native::{ + FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, Logger, MinifiError, + OutputStream, Schedule, TransformStreamResult, warn, +}; +use pgp::composed::{Message, TheRing}; +use std::collections::HashMap; +use std::fmt::Debug; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; + +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] +#[strum(serialize_all = "UPPERCASE", const_into_str)] +enum DecryptionStrategy { + Decrypted, + Packaged, +} + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct DecryptContentPGP { + decompress_data: bool, + symmetric_password: Option, +} + +impl Schedule for DecryptContentPGP { + fn schedule(context: &P, _logger: &L) -> Result + where + Self: Sized, + L: Logger, + { + let decryption_strategy = + context.get_req_property::(&DECRYPTION_STRATEGY)?; + + let symmetric_password = context.get_property::(&SYMMETRIC_PASSWORD)?; + let has_context_service = context + .get_property::(&PRIVATE_KEY_SERVICE)? + .is_some(); + if !has_context_service && symmetric_password.is_none() { + Err(MinifiError::schedule_err( + "Either Symmetric Password or Private Key Service must be set", + )) + } else { + Ok(DecryptContentPGP { + decompress_data: decryption_strategy == DecryptionStrategy::Decrypted, + symmetric_password, + }) + } + } +} + +impl DecryptContentPGP { + fn decrypt_msg<'a>( + &'a self, + msg: Message<'a>, + private_key_service: Option<&'a PGPPrivateKeyService>, + ) -> pgp::errors::Result> { + let mut ring = if let Some(pks) = private_key_service { + pks.get_the_ring() + } else { + TheRing::default() + }; + + ring.decrypt_options = ring.decrypt_options.enable_gnupg_aead(); + + if let Some(sym_passwd) = &self.symmetric_password { + ring.message_password.push(sym_passwd); + } + let (decrypted_msg, _ring_result) = msg.decrypt_the_ring(ring, false)?; + Ok(decrypted_msg) + } + + fn extract_attributes_from_decrypted_message( + decrypted_msg: &Message, + ) -> HashMap { + let mut attributes_to_add = HashMap::new(); + if let Some(literal_data_header) = decrypted_msg.literal_data_header() { + if let Ok(file_name) = str::from_utf8(literal_data_header.file_name()) { + attributes_to_add.insert( + output_attributes::LITERAL_DATA_FILENAME.name.to_string(), + file_name.to_string(), + ); + } + attributes_to_add.insert( + output_attributes::LITERAL_DATA_MODIFIED.name.to_string(), + (1000u64 * literal_data_header.created().as_secs() as u64).to_string(), // Nifi uses ms timestamp + ); + } + attributes_to_add + } +} + +impl FlowFileStreamTransform for DecryptContentPGP { + fn transform( + &self, + context: &Ctx, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + logger: &LoggerImpl, + ) -> Result { + let Ok(msg) = Message::from_reader(input_stream).map(|(msg, _header)| msg) else { + warn!(logger, "No valid PGP message found"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + let private_key_service = + context.get_controller_service::(&PRIVATE_KEY_SERVICE)?; + + let Ok(mut decrypted_msg) = self.decrypt_msg(msg, private_key_service) else { + warn!(logger, "Failed to decrypt data"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + if self.decompress_data && decrypted_msg.is_compressed() { + match decrypted_msg.decompress() { + Ok(decompressed_data) => { + decrypted_msg = decompressed_data; + } + Err(e) => { + warn!(logger, "Failed to decompress data: {}", e); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + } + } + }; + + let attributes_to_add = Self::extract_attributes_from_decrypted_message(&decrypted_msg); + let Ok(_written_bytes) = std::io::copy(&mut decrypted_msg.into_inner(), output_stream) + else { + warn!(logger, "Failed to extract raw data from decrypted message"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + Ok(TransformStreamResult::new(&SUCCESS, attributes_to_add)) + } +} + +#[cfg(test)] +mod tests; + +pub(crate) mod processor_definition; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs new file mode 100644 index 0000000000..e4a894a1cc --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs @@ -0,0 +1,13 @@ +use minifi_native::OutputAttribute; + +pub(crate) const LITERAL_DATA_FILENAME: OutputAttribute = OutputAttribute { + name: "pgp.literal.data.filename", + relationships: &["success"], + description: "Filename from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementations omit these inherently malleable metadata)", +}; + +pub(crate) const LITERAL_DATA_MODIFIED: OutputAttribute = OutputAttribute { + name: "pgp.literal.data.modified", + relationships: &["success"], + description: "Modified Date from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementations omit these inherently malleable metadata)", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs new file mode 100644 index 0000000000..c9c6fddf61 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs @@ -0,0 +1,22 @@ +use super::{DecryptContentPGP, output_attributes, properties, relationships}; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, +}; + +impl ProcessorDefinition for DecryptContentPGP { + const DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[ + output_attributes::LITERAL_DATA_FILENAME, + output_attributes::LITERAL_DATA_MODIFIED, + ]; + const RELATIONSHIPS: &'static [Relationship] = + &[relationships::SUCCESS, relationships::FAILURE]; + const PROPERTIES: &'static [Property] = &[ + properties::DECRYPTION_STRATEGY, + properties::SYMMETRIC_PASSWORD, + properties::PRIVATE_KEY_SERVICE, + ]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs new file mode 100644 index 0000000000..d642e7f35d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs @@ -0,0 +1,36 @@ +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::DecryptionStrategy; +use minifi_native::ComponentIdentifier; +use minifi_native::Property; +use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, NoConstraints}; +use strum::VariantNames; + +pub(crate) const DECRYPTION_STRATEGY: Property = Property { + name: "Decryption Strategy", + description: "Strategy for writing files to success after decryption", + is_required: true, + is_sensitive: false, + supports_expr_lang: false, + default_value: Some(DecryptionStrategy::Decrypted.into_str()), + constraints: AllowedValues(DecryptionStrategy::VARIANTS), +}; + +pub(crate) const SYMMETRIC_PASSWORD: Property = Property { + name: "Symmetric Password", + description: "Password used for decrypting data encrypted with Password-Based Encryption", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PRIVATE_KEY_SERVICE: Property = Property { + name: "Private Key Service", + description: "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", + is_required: false, + is_sensitive: false, + supports_expr_lang: false, + default_value: None, + constraints: AllowedType(PGPPrivateKeyService::CLASS_NAME), +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs new file mode 100644 index 0000000000..a4403da17d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs @@ -0,0 +1,11 @@ +use minifi_native::Relationship; + +pub(crate) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Decryption Succeeded", +}; + +pub(crate) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Decryption Failed", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs new file mode 100644 index 0000000000..65d4167dcb --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs @@ -0,0 +1,253 @@ +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::{DecryptContentPGP, output_attributes}; +use crate::test_utils; +use crate::test_utils::get_test_message; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, FlowFileStreamTransform, IoState, + MockControllerServiceContext, MockLogger, MockProcessContext, Schedule, +}; + +#[test] +fn test_ids() { + assert_eq!( + DecryptContentPGP::CLASS_NAME, + "minifi_pgp::processors::decrypt_content::DecryptContentPGP" + ); + assert_eq!(DecryptContentPGP::GROUP_NAME, "minifi_pgp"); + assert_eq!(DecryptContentPGP::VERSION, "0.1.0"); +} + +#[test] +fn fails_to_schedule_by_default() { + let decrypt_content = + DecryptContentPGP::schedule(&MockProcessContext::new(), &MockLogger::new()); + assert!(decrypt_content.is_err()); +} + +#[test] +fn schedules_with_password() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + "my_secret_password".to_string(), + ); + let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(decrypt_content.is_ok()); +} + +#[test] +fn schedules_with_controller() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(decrypt_content.is_ok()); +} + +#[test] +fn schedule_rejects_invalid_strategy_without_panicking() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::DECRYPTION_STRATEGY.name.to_string(), + "NOT_A_STRATEGY".to_string(), + ); + context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + "my_secret_password".to_string(), + ); + // Must return Err, not panic. + let result = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(result.is_err(), "expected schedule to fail on bad strategy"); +} + +#[derive(Copy, Clone)] +struct PrivateKeyData { + key_filename: &'static str, + passphrase: Option<&'static str>, +} + +impl PrivateKeyData { + fn into_controller(self) -> PGPPrivateKeyService { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Key File", test_utils::get_test_key_path(self.key_filename)); + + if let Some(passphrase) = self.passphrase { + context.properties.insert("Key Passphrase", passphrase); + } + + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable") + } +} + +fn test_decryption( + message_file_name: &str, + private_key_data: Option, + symmetric_password: Option<&'static str>, + expected_result: Result<&[u8], ()>, +) { + let mut processor_context = MockProcessContext::new(); + if let Some(private_key) = private_key_data { + processor_context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(private_key.into_controller()), + ); + processor_context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + } + if let Some(symmetric_password) = symmetric_password { + processor_context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + symmetric_password.to_string(), + ); + } + + let decrypt_content = DecryptContentPGP::schedule(&processor_context, &MockLogger::new()) + .expect("Should schedule with the configured properties"); + let mut output: Vec = Vec::new(); + let mut flow_file_stream = std::io::Cursor::new(get_test_message(message_file_name)); + let res = decrypt_content + .transform( + &processor_context, + &mut flow_file_stream, + &mut output, + &MockLogger::new(), + ) + .expect("Should be able to transform"); + + match expected_result { + Ok(_result_bytes) => { + assert_eq!( + res.target_relationship_name(), + super::relationships::SUCCESS.name + ); + assert_eq!(res.write_status(), IoState::Ok); + let data_modified = res + .get_attribute(output_attributes::LITERAL_DATA_MODIFIED.name) + .unwrap() + .parse::() + .expect("Should be u64"); + assert!(data_modified > 1770000000000); + assert!(data_modified < 1780000000000); + assert!( + res.get_attribute(output_attributes::LITERAL_DATA_FILENAME.name) + .is_some() + ); + } + Err(_) => { + assert_eq!( + res.target_relationship_name(), + super::relationships::FAILURE.name + ); + assert_eq!(res.write_status(), IoState::Cancel); + } + } +} + +#[test] +fn decrypts_with_password() { + test_decryption( + "password_encrypted_foo.gpg", + None, + Some("my_secret_password"), + Ok("foo\n".as_bytes()), + ); + test_decryption( + "password_encrypted_foo.asc", + None, + Some("my_secret_password"), + Ok("foo\n".as_bytes()), + ); + test_decryption( + "foo_for_alice.gpg", + None, + Some("my_secret_password"), + Err(()), + ); + test_decryption( + "foo_for_alice.asc", + None, + Some("my_secret_password"), + Err(()), + ); +} + +#[test] +fn decrypts_for_alice() { + let alice_private_key_data = PrivateKeyData { + key_filename: "alice_private.asc", + passphrase: Some("whiterabbit"), + }; + + test_decryption( + "foo_for_alice.asc", + Some(alice_private_key_data), + None, + Ok("foo\n".as_bytes()), + ); + + test_decryption( + "foo_for_alice.gpg", + Some(alice_private_key_data), + None, + Ok("foo\n".as_bytes()), + ); + + test_decryption( + "password_encrypted_foo.gpg", + Some(alice_private_key_data), + None, + Err(()), + ); + + test_decryption( + "password_encrypted_foo.asc", + Some(alice_private_key_data), + None, + Err(()), + ); +} + +#[test] +fn decryption_of_not_encrypted_data() { + let alice_private_key = PrivateKeyData { + key_filename: "alice_private.asc", + passphrase: Some("whiterabbit"), + }; + + let mut processor_context = MockProcessContext::new(); + processor_context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(alice_private_key.into_controller()), + ); + processor_context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + + let logger = MockLogger::new(); + + let decrypt_content = DecryptContentPGP::schedule(&processor_context, &logger) + .expect("Should schedule without any properties"); + let mut result: Vec = vec![]; + let mut flow_file_stream = std::io::Cursor::new("something not encrypted".as_bytes()); + let res = decrypt_content + .transform( + &processor_context, + &mut flow_file_stream, + &mut result, + &logger, + ) + .expect("Should be able to transform"); + + assert_eq!( + res.target_relationship_name(), + super::relationships::FAILURE.name + ); + assert_eq!(res.write_status(), IoState::Cancel); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs new file mode 100644 index 0000000000..1c1d47d7dc --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -0,0 +1,152 @@ +use minifi_native::{ + FlowFileStreamTransform, GetAttribute, GetControllerService, GetProperty, InputStream, Logger, + MinifiError, OutputStream, Schedule, TransformStreamResult, warn, +}; +use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey}; +use pgp::types::StringToKey; +use std::collections::HashMap; + +mod output_attributes; +mod properties; +mod relationships; + +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::encrypt_content::output_attributes::FILE_ENCODING; +use crate::processors::encrypt_content::properties::{ + PASSWORD, PUBLIC_KEY_SEARCH, PUBLIC_KEY_SERVICE, +}; +use crate::processors::encrypt_content::relationships::{FAILURE, SUCCESS}; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; + +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] +#[strum(serialize_all = "UPPERCASE", const_into_str)] +enum FileEncoding { + Ascii, + Binary, +} + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct EncryptContentPGP { + file_encoding: FileEncoding, +} + +#[cfg(not(test))] +fn string_to_key() -> StringToKey { + StringToKey::new_argon2(rand::thread_rng(), 3, 4, 16) // 64 MiB with rpgp's recommended parameter choice +} + +#[cfg(test)] +fn string_to_key() -> StringToKey { + StringToKey::new_argon2(rand::thread_rng(), 1, 1, 10) // fast for unit tests +} + +impl EncryptContentPGP { + fn encrypt_bytes( + &self, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + pub_key: Option<&SignedPublicKey>, + passphrase: Option<&str>, + file_name: String, + ) -> pgp::errors::Result<()> { + let mut builder = MessageBuilder::from_reader(file_name, input_stream).seipd_v1( + rand::thread_rng(), + pgp::crypto::sym::SymmetricKeyAlgorithm::AES256, + ); + + if let Some(pub_key) = pub_key { + builder.encrypt_to_key(rand::thread_rng(), pub_key)?; + } + + if let Some(passphrase) = passphrase { + builder.encrypt_with_password(string_to_key(), &passphrase.into())?; + } + + match self.file_encoding { + FileEncoding::Ascii => builder.to_armored_writer( + rand::thread_rng(), + ArmorOptions::default(), + output_stream, + ), + FileEncoding::Binary => builder.to_writer(rand::thread_rng(), output_stream), + } + } +} + +impl Schedule for EncryptContentPGP { + fn schedule(context: &P, _logger: &L) -> Result + where + Self: Sized, + { + let file_encoding = context.get_req_property::(&properties::FILE_ENCODING)?; + + let has_password = context.get_property::(&PASSWORD)?.is_some(); + let has_public_key = context + .get_property::(&PUBLIC_KEY_SERVICE)? + .is_some() + && context + .get_property::(&PUBLIC_KEY_SEARCH)? + .is_some(); + + if !has_password && !has_public_key { + Err(MinifiError::schedule_err( + "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", + )) + } else { + Ok(EncryptContentPGP { file_encoding }) + } + } +} + +impl FlowFileStreamTransform for EncryptContentPGP { + fn transform( + &self, + context: &Ctx, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + logger: &LoggerImpl, + ) -> Result { + let file_name = context.get_attribute("filename")?.unwrap_or_default(); + let public_key = if let (Some(pub_key_search), Some(public_key_service)) = ( + context.get_property::(&PUBLIC_KEY_SEARCH)?, + context.get_controller_service::(&PUBLIC_KEY_SERVICE)?, + ) { + public_key_service.get(&pub_key_search) + } else { + None + }; + let password = context.get_property::(&PASSWORD)?; + if public_key.is_none() && password.is_none() { + warn!(logger, "No password or public key to encrypt with"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + } + + match self.encrypt_bytes( + input_stream, + output_stream, + public_key, + password.as_deref(), + file_name, + ) { + Ok(_) => Ok(TransformStreamResult::new( + &SUCCESS, + HashMap::from([( + FILE_ENCODING.name.to_string(), + self.file_encoding.to_string(), + )]), + )), + Err(e) => { + warn!(logger, "Failed to encrypt content {:?}", e); + Ok(TransformStreamResult::route_without_changes(&FAILURE)) + } + } + } +} + +#[cfg(test)] +mod tests; + +pub(crate) mod processor_definition; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs new file mode 100644 index 0000000000..07f3396788 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs @@ -0,0 +1,7 @@ +use minifi_native::OutputAttribute; + +pub(crate) const FILE_ENCODING: OutputAttribute = OutputAttribute { + name: "pgp.file.encoding", + relationships: &["success"], + description: "File Encoding", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs new file mode 100644 index 0000000000..4ac06faa7a --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs @@ -0,0 +1,20 @@ +use super::{EncryptContentPGP, output_attributes, properties, relationships}; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, +}; + +impl ProcessorDefinition for EncryptContentPGP { + const DESCRIPTION: &'static str = "Encrypt contents using OpenPGP."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[output_attributes::FILE_ENCODING]; + const RELATIONSHIPS: &'static [Relationship] = + &[relationships::SUCCESS, relationships::FAILURE]; + const PROPERTIES: &'static [Property] = &[ + properties::FILE_ENCODING, + properties::PASSWORD, + properties::PUBLIC_KEY_SEARCH, + properties::PUBLIC_KEY_SERVICE, + ]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs new file mode 100644 index 0000000000..70ebdb404d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs @@ -0,0 +1,46 @@ +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::encrypt_content::FileEncoding; +use minifi_native::ComponentIdentifier; +use minifi_native::Property; +use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, NoConstraints}; +use strum::VariantNames; + +pub(crate) const FILE_ENCODING: Property = Property { + name: "File Encoding", + description: "File Encoding for encryption", + is_required: true, + is_sensitive: false, + supports_expr_lang: false, + default_value: Some(FileEncoding::Binary.into_str()), + constraints: AllowedValues(FileEncoding::VARIANTS), +}; + +pub(crate) const PASSWORD: Property = Property { + name: "Symmetric Password", + description: "Password used for encrypting data with Password-Based Encryption", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PUBLIC_KEY_SEARCH: Property = Property { + name: "Public Key Search", + description: "PGP Public Key Search will be used to match against the User ID or Key ID when formatted as uppercase hexadecimal string of 16 characters", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PUBLIC_KEY_SERVICE: Property = Property { + name: "Public Key Service", + description: "PGP Public Key Service for encrypting data with Public Key Encryption", + is_required: false, + is_sensitive: false, + supports_expr_lang: false, + default_value: None, + constraints: AllowedType(PGPPublicKeyService::CLASS_NAME), +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs new file mode 100644 index 0000000000..fcdda9921c --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs @@ -0,0 +1,11 @@ +use minifi_native::Relationship; + +pub(crate) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Encryption Succeeded", +}; + +pub(crate) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Encryption Failed", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs new file mode 100644 index 0000000000..2e961dd7ee --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs @@ -0,0 +1,137 @@ +use super::*; +use crate::test_utils; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, IoState, MockControllerServiceContext, + MockLogger, MockProcessContext, +}; + +#[test] +fn test_ids() { + assert_eq!( + EncryptContentPGP::CLASS_NAME, + "minifi_pgp::processors::encrypt_content::EncryptContentPGP" + ); + assert_eq!(EncryptContentPGP::GROUP_NAME, "minifi_pgp"); + assert_eq!(EncryptContentPGP::VERSION, "0.1.0"); +} + +#[test] +fn cannot_schedule_without_password_or_public_key() { + assert!(EncryptContentPGP::schedule(&MockProcessContext::new(), &MockLogger::new()).is_err()); +} + +fn assert_content(transform_result: &TransformStreamResult, is_ascii: bool) { + assert_eq!(transform_result.target_relationship_name(), SUCCESS.name); + assert_eq!(transform_result.write_status(), IoState::Ok); + assert_eq!( + transform_result.get_attribute("pgp.file.encoding").unwrap(), + if is_ascii { "ASCII" } else { "BINARY" } + ); +} + +#[test] +fn encrypts_via_passphrase() { + let mut context = MockProcessContext::new(); + context.properties.insert(PASSWORD.name, "password"); + context + .attributes + .insert("filename".to_owned(), "mammut".to_owned()); + + let mut result: Vec = Vec::new(); + let mut input_stream = std::io::Cursor::new("foo".as_bytes()); + let processor = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let transformed_ff = processor + .transform(&context, &mut input_stream, &mut result, &MockLogger::new()) + .expect("should transform"); + + assert!(!result.is_ascii()); + assert_content(&transformed_ff, false); +} + +fn public_key_service() -> PGPPublicKeyService { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + test_utils::get_test_key_path("keyring.asc"), + ); + + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("should enable") +} + +#[test] +fn encrypts_ascii_for_alice() { + let mut context = MockProcessContext::new(); + context.properties.extend([ + ("Public Key Service", "my_controller_service"), + ("Public Key Search", "Alice"), + ("File Encoding", "ASCII"), + ]); + + context.controller_services.insert( + "my_controller_service".to_string(), + Box::new(public_key_service()), + ); + + let mut result: Vec = Vec::new(); + let mut input_stream = std::io::Cursor::new("foo".as_bytes()); + let processor = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let transformed_ff = processor + .transform(&context, &mut input_stream, &mut result, &MockLogger::new()) + .expect("should transform"); + + assert!(result.is_ascii()); + assert_content(&transformed_ff, true); +} + +#[test] +fn encrypts_binary_for_bob() { + let mut context = MockProcessContext::new(); + context.properties.extend([ + ("Public Key Service", "my_controller_service"), + ("Public Key Search", "Bob"), + ("File Encoding", "BINARY"), + ]); + + context.controller_services.insert( + "my_controller_service".to_string(), + Box::new(public_key_service()), + ); + + let mut result: Vec = Vec::new(); + let mut input_stream = std::io::Cursor::new("foo".as_bytes()); + let processor = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let transformed_ff = processor + .transform(&context, &mut input_stream, &mut result, &MockLogger::new()) + .expect("should transform"); + + assert!(!result.is_ascii()); + assert_content(&transformed_ff, false); +} + +#[test] +fn cannot_encrypt_for_carol() { + let mut context = MockProcessContext::new(); + context.properties.extend([ + ("Public Key Service", "my_controller_service"), + ("Public Key Search", "Carol"), + ]); + + context.controller_services.insert( + "my_controller_service".to_string(), + Box::new(public_key_service()), + ); + + let mut result: Vec = Vec::new(); + let mut input_stream = std::io::Cursor::new("foo".as_bytes()); + let processor = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let transformed_ff = processor + .transform(&context, &mut input_stream, &mut result, &MockLogger::new()) + .expect("should transform"); + + assert_eq!(transformed_ff.target_relationship_name(), FAILURE.name); + assert_eq!(transformed_ff.write_status(), IoState::Cancel); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs new file mode 100644 index 0000000000..ac45357fe3 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod decrypt_content; +pub(crate) mod encrypt_content; diff --git a/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs b/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs new file mode 100644 index 0000000000..963af71617 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs @@ -0,0 +1,15 @@ +use std::path::PathBuf; + +pub fn get_test_key_path(filename: &str) -> String { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("test_keys"); + path.push(filename); + path.display().to_string() +} + +pub fn get_test_message(filename: &str) -> Vec { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("test_messages"); + path.push(filename); + std::fs::read(path).expect("test message should be readable") +} diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs b/minifi_rust/extensions/minifi_pgp/src/utils.rs new file mode 100644 index 0000000000..95b3462380 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -0,0 +1,11 @@ +use minifi_native::{MinifiError, PropertyType}; + +pub(crate) struct Password {} + +impl PropertyType for Password { + type Output = pgp::types::Password; + + fn parse(s: &str) -> Result { + Ok(pgp::types::Password::from(s)) + } +} diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt new file mode 100644 index 0000000000..7b8ad1a612 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt @@ -0,0 +1,8 @@ +Testing keys v2 +------------------------ +uid [ultimate] Alice +passphrase whiterabbit + +uid [ultimate] Bob Personal +uid [ultimate] Bob Primary +no passphrase \ No newline at end of file diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc new file mode 100644 index 0000000000..8ae976d7b2 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc @@ -0,0 +1,50 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +q+zIM+a7/I/TcOmDRO5s +=EEA6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg new file mode 100644 index 0000000000000000000000000000000000000000..71af439899c8034d0cc23dbeda6ea6c2dd8fdb8d GIT binary patch literal 2175 zcmb7^do&XaAIG;D<~Ae~Q#KJsZn+iZZYWVM$s$^4p$1mZ0|{NWsBu`@NzLXJ)O4 z-Ixy?zKFd|Tc$i^Lp!1ACt^DYM$_&HcUo%oz_^Ef=%oA{Yu}(ZlAf%Fy)Vd4F$Bys zX?;1`E|zOjiK1V2HnL(QEmH=8~IN zC>2sRGW9WBsYiG)kxi@oZ6!4pn*H9kD8mS0Uyj50%~ zXQ+ptzptk%_NLz{GVnZDY%eDR7!oY{adm_H#d>II+V}vxjCFd&UY^4Z`W++)uEs6E z2jK;A$%249;{1FN0T37l;sSyW@q#1>0DeA^KS0kC?~bhDib7Gv&!rOW4Q@8G$e!{= zYf_9bq}}2d@bheYEyR63FK;y^70}UGWIm_dXlC-jmo`o0ga#Y}=jq@E@9! z5~haCAI1mo9?r0~*GDc1j>ZbxBX_7f(h1Py+TFa(kw~|X8XRdx1ZpWIRL>aEg{52CfrC zElOZb*k>QIYE=tEIp`&ksuo~XN`3 z<@R+m1qCospT9hNQ(?Gf*&g?{m08R@BNXbTBwUb|V3l#NDk`Yk2s16+NGA zyWO>Mt@v1yYBEtZfw#~NX?%C3Cs+5U#nNJ&#k|2sROP7dj%)S7owOhu3|~agnCp~< zOFF90m))Wq8u)FUM;LMdO0?cyKn>W&aw+`n9~tBeKCmYl;=f3y3N-phq^4j5SaJ_K zxCafFsx>5?5`nSfZC&*S8e0sjrg;8i=5O`>e6R%&K%@EK%7$sqSJQht=;Jj^>k}to zB1nbc!hx)o!yUV60zYEVEF5_hXUWD78E8Tu-Ps=bp&OlFM(*AAUZ6yYz#{Sky+rk^ zf;+opTbaH2c&7sf=kbmwpEyL-8bxBz2#J*F9oI#Ku!&Ro==tW|gbp1`C7i2_Rs^Y# zkd+|E6W#wEyqrto7l_XJ6iri6k)5VJvJ1Ws*Ga?0F;Sexyd%Mwu`ukf>q=CG4=~4Y zn>nhalv+}+kU%@w_H~MswKiMM&$!fBvE%JN)}U$&+eSNJT3N~MVPbS-jv2}+G-Ipqfl{w;fsv9|@?ZE4$&1|L zngYrXie0xebNb7?kIJo1#?jB{J-O5xwh}|AcwRPT>z)z;td^{`7@5tUJ#&x}4-@Lc znB6MRWM+hHxA|}FZxH8o3$@JHw1y;hHujh4m4@2~_bX~TndnqkUsybMoDw0KB~RT@qy?;(`{>$?ptNFGQzAIN-ellqOj*qb#d&z>(vsUGnA?Vvqc}G2@&jkckh{8yN$;*4{A`&bSRT zo4-&NyV^j!b@j85RTrDYyRAoBdXToszjAHe?RAK~h1DkUXP^G#D^Aq?TW>a!^h@VU zjvHF(rDyf3(r7w-h3v+zaV>ng^ zd=l}yWRuwcVgCOB{pWbMxYO@m-Msj=Qp&qs5LecaIpv6p>2Z#4FFZkBz{k#E)f;sQ zqws|&t{749upe4|Bhgd$W1my44VUaihbwklo)T12f943HUD0e;rZgbM zH+@toPQ!D6#UdxQHbVTWuJ(BFcgd9=JwdH(1iq5VsV6Vu-zi=(6}H!|5(`@ArnBB3 Ro2lQv_rd>PL^HPUzX43W{7(P? literal 0 HcmV?d00001 diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc new file mode 100644 index 0000000000..955e525841 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc @@ -0,0 +1,92 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=6nxq +-----END PGP PRIVATE KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg new file mode 100644 index 0000000000000000000000000000000000000000..5f385d9eead5a808cd55709b0e48595536214738 GIT binary patch literal 4220 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 literal 0 HcmV?d00001 diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc new file mode 100644 index 0000000000..09fc1bc992 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.asc @@ -0,0 +1,71 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=ePbg +-----END PGP PRIVATE KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg new file mode 100644 index 0000000000000000000000000000000000000000..a3937024300cf4a5a3ea028737d35e7490ba81ff GIT binary patch literal 3207 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 ZW?au-@Kq-xoCh7x$=}_6hD)6}{THHJ&@BJ} literal 0 HcmV?d00001 diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc b/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc new file mode 100644 index 0000000000..d25ddfb620 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/truncated.asc @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGmJ4OIBCACz9RXNN6lFaUi0b4V6PTyjc27g9G0OCBMy6H/lcjROMGupqPm1 +9QzEzTIrxkc1LlPx31qzb6SwzQWkKiDnmObcZzG43Yiz1aD0YOqJsHBb9klrdWFx +VbGTtaDmZg/xAS+VseYTijiucydURPzIKDb25vWl7r+iAdhZY3eo8Zif7g7LDpU6 +hsqAQOVgIGCokbbS4GFTeOIl6uwS1Gchq40vY5AM7o4/AObANNstyROgQrQqq19Y +QEjnLT6GsxF6jpbrcb+8No6JWJSaqhDjIVug+psaeuqruQkN6o3B85izGk0fu4QD +kSYGW3/A9ArGrLhGMtFnTyo/fg9sEGqWxLoJABEBAAG0GUFsaWNlIDxhbGljZUBl +eGFtcGxlLmNvbT6JAVIEEwEIADwWIQQR1fT4Ba73eK2U4NIbsOxL81Ml9gUCaYng +4gIbLwU \ No newline at end of file diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc b/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc new file mode 100644 index 0000000000..9cc58539ae --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/truncated_private.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQPGBGmJ4OIBCACz9RXNN6lFaUi0b4V6PTyjc27g9G0OCBMy6H/lcjROMGupqPm1 +9QzEzTIrxkc1LlPx31qzb6SwzQWkKiDnmObcZzG43Yiz1aD0YOqJsHBb9klrdWFx +VbGTtaDmZg/xAS+VseYTijiucydURPzIKDb25vWl7r+iAdhZY3eo8Zif7g7LDpU6 +hsqAQOVgIGCokbbS4GFTeOIl6uwS1Gchq40vY5AM7o4/AObANNstyROgQrQqq19Y +QEjnLT6GsxF6jpbrcb+8No6JWJSaqhDjIVug+psaeuqruQkN6o3B85izGk0fu4QD +kSYGW3/A9ArGrLhGMtFnTyo/fg9sEGqWxLoJABEBAAH+BwMCYkH6w6nO395gtdQ6 +zQJvZ1itO9NCbRtI20iVkqWmwtr2FwgN8AZ9sGZss6zdpfxh85Ef1kHvP1nkbedk +/8OmBljPooqKB7MTwCCxOC53Mf6wNMijlBYsY8YUyi4dwaHoxnDFnaCeITSHHehY +07ifnInvrTkbJ41JzfP124xQ804voehm7merA91Vtpvg/hoYqJ/Sxo22UpTwvuw/ +aKqoetlJWqRk8VmBpcuuVFYcF9jaOPB51WG8fRDj66eINg2zXL49WRvwlUtbAHvS +cbglkBzMFHqljx0KJWX/QMO64X894eFafVFvSiYf+fn80wv9h7IKjj413itlbF0r +X+DckGQ9b50XAD3kZgDOMr5dKTGQ9ytChl7hpy38ucQqJM0qRlTSG2mp05qBO+XZ +CNNE7qvVNJNP5nD/3xkWD8oi+nhmqqg4bZ/QDUcoTVrWW7L1er2fgREJg7xL7xuD +QYu/T0A6N9PxWefYdEN/jjcLqks/Pjdy3DfGtlDysj88GpLh3diNgQ2EnNgQ32pq +JmxwEWIQ4VV30Ms1D0Uh7g4Ksq0lq1/LjOll3FSyjr3Ihpesopj \ No newline at end of file diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc new file mode 100644 index 0000000000..94b8561c9a --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.asc @@ -0,0 +1,12 @@ +-----BEGIN PGP MESSAGE----- + +hQEMA7zOP9+6AZ1+AQf/WlAEDriFTKHJfn5KXAi123WGeDJBoRi/etl7GJ8MO5+8 +crdou58wMcqRJ8u3wNgKWDYm+QknLhQK5+3dJajwQeKH18uruTkEmFQB/wArHsOX +62UhFf2qbAzvUuTH5kPyt1d/Wt51T9+K/xlEPJr+DiK0uHlXZPu7rEnqk9pcKikC +/dYAuljnkNigDoykHwEBRcBfQu5t/hIe/Bii3wTZPm2w0YneyjOtd7Yq3mDlfmDW +cy3bdDjuwP4npCxcnHi7WkbElTyCJMybKVwwLjugihGI+4r8itO2wknAT5GDGMQ8 +u2FfOfGnIYTK2mBAQgyM7gtBX2qS28uWYlj5gehngdRJAQkCEFMqwWDAaUXAi6QU +xhn/O+wEEpUVYnGuEpIqG9KTW3qYl+vTxLkeNzg2NL255QP9gLbdlKFJYkC60c4I +JJXhkwkPTSC+Xw== +=UAju +-----END PGP MESSAGE----- diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg new file mode 100644 index 0000000000000000000000000000000000000000..bd3d9b476ed0a78b622b8d614490e4f5d454cbbe GIT binary patch literal 346 zcmV-g0j2(h0Sp7Y&OhI}0iAvU2mph{vt~MrQX`tB{ur()(~EA(MfcM`jgrLIqF3e* zbJbgqOC0R$&pthX0bU{O4%)W~y^FdsCjmQmY~(c;VosT3r%PBm%7OD#?Fuf$%bL8v zoNNN7X$EZ{@g!~CV}-_>9;!EG@-XJ2^Lcb;TB=t*2IJ6wr7dbl|HXz4-Fb--%rwBC zEboyEgNe5kdN-$c9+i69tM?TP@_RiR>88lrl|oZ_bvN{*PSm#}2+N1~pstspM;NK2 z%trg?Y(--#WAMp<*ceLiL-!aG{Qz}7&hrA4pvN$8>jtvND_~k9*gUAxnoYk459})@ z3JqWeq4Il>9WhFOHuHfO^8VCG0SN*Sh9?-c*moa7H2#;tH0?v%mh+1u_D26;BRycH s)uU8#WvaUvLuSJ^tmAYZih8NH1(KNqR61kd>y_6YVHEM!o#Z_3H`V>8nE(I) literal 0 HcmV?d00001 diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc new file mode 100644 index 0000000000..22d639e711 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.asc @@ -0,0 +1,6 @@ +-----BEGIN PGP MESSAGE----- + +jA0ECQMCU2B2LnRTkyNg0jkBhgVPotvo6S9iLOTWhzglgsjR/6QB2v7vUNImzkh7 +fjhd17fG5tjB1RPRgW3bR12BidV6TQKuwLs= +=AXoJ +-----END PGP MESSAGE----- diff --git a/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg new file mode 100644 index 0000000000..40ab6a354e --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_messages/password_encrypted_foo.gpg @@ -0,0 +1 @@ +  t?`9B2;Œg]N)!r2!Fnl./a9._}_2,ɴ \ No newline at end of file diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.alpine b/minifi_rust/minifi_rs_behave/Dockerfile.alpine index 032c26ec57..e3c1557797 100644 --- a/minifi_rust/minifi_rs_behave/Dockerfile.alpine +++ b/minifi_rust/minifi_rs_behave/Dockerfile.alpine @@ -22,4 +22,4 @@ RUN cargo build --release # Export Stage FROM scratch AS bin-export -COPY --from=builder /app/target/release/libminifi_rs_playground.so / +COPY --from=builder /app/target/release/libminifi_*.so / diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.debian b/minifi_rust/minifi_rs_behave/Dockerfile.debian index 274ed3cb5e..a3c4aa03d6 100644 --- a/minifi_rust/minifi_rs_behave/Dockerfile.debian +++ b/minifi_rust/minifi_rs_behave/Dockerfile.debian @@ -22,4 +22,4 @@ RUN cargo build --release # Export Stage FROM scratch AS bin-export -COPY --from=builder /app/target/release/libminifi_rs_playground.so / +COPY --from=builder /app/target/release/libminifi_*.so /