From ebdc9d7e8c4ea9be95c8fada19739db290e5230e Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Tue, 30 Jun 2026 17:22:35 +0200 Subject: [PATCH 01/25] MINIFICPP-2749 Add EncryptContentPGP and DecryptContentPGP --- .../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 | 298 +++++++++++++++ .../controller_service_definition.rs | 30 ++ .../controller_services/public_key_service.rs | 309 +++++++++++++++ .../controller_service_definition.rs | 25 ++ minifi_rust/extensions/minifi_pgp/src/lib.rs | 24 ++ .../src/processors/decrypt_content.rs | 356 ++++++++++++++++++ .../decrypt_content/output_attributes.rs | 2 + .../decrypt_content/processor_definition.rs | 62 +++ .../processors/decrypt_content/properties.rs | 4 + .../decrypt_content/relationships.rs | 2 + .../src/processors/decrypt_content/tests.rs | 9 + .../src/processors/encrypt_content.rs | 286 ++++++++++++++ .../encrypt_content/processor_definition.rs | 61 +++ .../minifi_pgp/src/processors/mod.rs | 2 + .../minifi_pgp/src/test_utils/mod.rs | 15 + .../extensions/minifi_pgp/src/utils.rs | 16 + .../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_playground.md | 1 + .../src/processors/asciify_german/tests.rs | 7 +- .../flow_file_stream_transform.rs | 6 +- minifi_rust/minifi_native/src/lib.rs | 2 + minifi_rust/minifi_native/src/test_utils.rs | 16 + 48 files changed, 2475 insertions(+), 6 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/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/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/processor_definition.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 create mode 100644 minifi_rust/minifi_native/src/test_utils.rs 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..0f8ca9eca1 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs @@ -0,0 +1,298 @@ +mod controller_service_definition; +use controller_service_definition::*; + +#[cfg(test)] +use crate::controller_services::key_lookup::key_matches; +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; + +#[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(&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(&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::custom("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 { + use super::*; + use crate::test_utils::get_test_key_path; + use minifi_native::MinifiError::CustomError; + use minifi_native::{ComponentIdentifier, MockControllerServiceContext, MockLogger}; + + fn assert_private_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, + ) { + if let Err(CustomError(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/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..585ad82145 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs @@ -0,0 +1,30 @@ +use super::PGPPrivateKeyService; +use crate::utils; +use minifi_native::{ + ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, + property_definitions, +}; +use std::path::PathBuf; + +pub(super) const KEY_FILE: Property> = Property::new( + "Key File", + "File path to PGP Secret Key encoded in binary or ASCII Armor", +) +.supports_expression_language(); + +pub(super) const KEY: Property> = + Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); + +pub(super) const KEY_PASSPHRASE: Property> = Property::new( + "Key Passphrase", + "Passphrase used for decrypting Private Keys", +) +.sensitive(); + +impl ControllerServiceDefinition for PGPPrivateKeyService { + const DESCRIPTION: &'static str = + "PGP Private Key Service provides Private Keys loaded from files or properties"; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![KEY_FILE, KEY, KEY_PASSPHRASE]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; +} 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..007ca6f94c --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs @@ -0,0 +1,309 @@ +mod controller_service_definition; +use controller_service_definition::*; + +use crate::controller_services::key_lookup::key_matches; +use minifi_native::macros::ComponentIdentifier; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; +use pgp::composed::{Deserializable, SignedPublicKey}; +use pgp::types::KeyDetails; + +#[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::custom("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 { + use super::*; + use crate::test_utils::get_test_key_path; + use minifi_native::MinifiError::CustomError; + use minifi_native::{ComponentIdentifier, MockControllerServiceContext, MockLogger}; + + fn assert_public_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, + ) { + if let Err(CustomError(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/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..580b3e0079 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs @@ -0,0 +1,25 @@ +use super::PGPPublicKeyService; +use minifi_native::{ + ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, + property_definitions, +}; +use std::path::PathBuf; + +pub(crate) const KEYRING_FILE: Property> = Property::new( + "Keyring File", + "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", +) +.supports_expression_language(); + +pub(crate) const KEYRING: Property> = Property::new( + "Keyring", + "PGP Keyring or Secret Key encoded in ASCII Armor", +) +.sensitive(); + +impl ControllerServiceDefinition for PGPPublicKeyService { + const DESCRIPTION: &'static str = + "PGP Public Key Service providing Public Keys loaded from files"; + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![KEYRING_FILE, KEYRING]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; +} 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..f8085bb072 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/lib.rs @@ -0,0 +1,24 @@ +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!( + group_name: "org.apache.nifi.minifi.rust", + 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..7ea0dc78b7 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -0,0 +1,356 @@ +mod processor_definition; + +use processor_definition::*; + +use crate::controller_services::private_key_service::PGPPrivateKeyService; + +use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use minifi_native::{ + FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, Logger, MinifiError, + OutputStream, ProcessError, RouteErrorExt, Schedule, TransformStreamResult, +}; +use pgp::composed::{Message, TheRing}; +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_property(&DECRYPTION_STRATEGY)?; + + let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; + let has_context_service = context.get_raw_property(&PRIVATE_KEY_SERVICE)?.is_some(); + if !has_context_service && symmetric_password.is_none() { + Err(MinifiError::custom( + "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, + ) -> Vec<(&'static str, String)> { + let mut res = Vec::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()) { + res.push((LITERAL_DATA_FILENAME.name, file_name.to_string())); + } + // NiFi uses ms timestamp + res.push(( + LITERAL_DATA_MODIFIED.name, + (1000u64 * literal_data_header.created().as_secs() as u64).to_string(), + )); + } + res + } +} + +impl FlowFileStreamTransform for DecryptContentPGP { + fn transform( + &self, + context: &Ctx, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + _logger: &LoggerImpl, + ) -> Result { + let private_key_service = context.get_controller_service(&PRIVATE_KEY_SERVICE)?; + + let msg = Message::from_reader(input_stream) + .map(|(msg, _header)| msg) + .route_err_to_failure()?; + + let mut decrypted_msg = self + .decrypt_msg(msg, private_key_service) + .route_err_to_failure()?; + + if self.decompress_data && decrypted_msg.is_compressed() { + decrypted_msg = decrypted_msg + .decompress() + .map_err(MinifiError::other) + .route_err_to_failure()? + }; + + let attributes_to_add = Self::extract_attributes_from_decrypted_message(&decrypted_msg); + let _written_bytes = + std::io::copy(&mut decrypted_msg.into_inner(), output_stream).route_err_to_failure()?; + + Ok(TransformStreamResult::new(&SUCCESS).with_attributes(attributes_to_add)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils; + use crate::test_utils::get_test_message; + use minifi_native::{ + ComponentIdentifier, EnableControllerService, IoState, MockControllerServiceContext, + MockLogger, MockProcessContext, test, + }; + #[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(SYMMETRIC_PASSWORD.name(), "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( + PRIVATE_KEY_SERVICE.name(), + "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(DECRYPTION_STRATEGY.name(), "NOT_A_STRATEGY".to_string()); + context + .properties + .insert(SYMMETRIC_PASSWORD.name(), "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( + PRIVATE_KEY_SERVICE.name(), + "my_private_key_service".to_string(), + ); + } + if let Some(symmetric_password) = symmetric_password { + processor_context + .properties + .insert(SYMMETRIC_PASSWORD.name(), 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(), + ); + + match expected_result { + Ok(_result_bytes) => { + let res = res.expect("Should be able to transform"); + assert_eq!(res.target_relationship_name(), SUCCESS.name); + assert_eq!(res.write_status(), IoState::Ok); + let data_modified = res + .get_attribute(LITERAL_DATA_MODIFIED.name) + .unwrap() + .parse::() + .expect("Should be u64"); + assert!(data_modified > 1770000000000); + assert!(data_modified < 1780000000000); + assert!(res.get_attribute(LITERAL_DATA_FILENAME.name).is_some()); + } + Err(_) => test::assert_routed_to(res, &FAILURE), + } + } + + #[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( + PRIVATE_KEY_SERVICE.name(), + "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, + ); + + test::assert_routed_to(res, &FAILURE); + } +} 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..5511193e82 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs @@ -0,0 +1,2 @@ +use minifi_native::OutputAttribute; + 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..62b4126925 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs @@ -0,0 +1,62 @@ +use super::{DecryptContentPGP, DecryptionStrategy}; +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::utils; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, PropertyDefinition, + Relationship, property_definitions, +}; + +pub(super) 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(super) 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)", +}; + +pub(super) const DECRYPTION_STRATEGY: Property = Property::new( + "Decryption Strategy", + "Strategy for writing files to success after decryption", +) +.with_default(DecryptionStrategy::Decrypted.into_str()); + +pub(super) const SYMMETRIC_PASSWORD: Property> = Property::new( + "Symmetric Password", + "Password used for decrypting data encrypted with Password-Based Encryption", +) +.sensitive(); + +pub(super) const PRIVATE_KEY_SERVICE: Property> = Property::new( + "Private Key Service", + "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", +); + +pub(super) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Decryption Succeeded", +}; + +pub(super) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Decryption Failed", +}; + +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] = + &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; + const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; + + fn properties() -> &'static [PropertyDefinition] { + const PROPERTIES: &[PropertyDefinition] = + property_definitions![DECRYPTION_STRATEGY, SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; + PROPERTIES + } +} 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..d2c0be7860 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs @@ -0,0 +1,4 @@ +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::DecryptionStrategy; +use crate::utils; +use minifi_native::Property; 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..ed61a1165e --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs @@ -0,0 +1,2 @@ +use minifi_native::Relationship; + 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..87b6179d04 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs @@ -0,0 +1,9 @@ +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, +}; + 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..6d2352136b --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -0,0 +1,286 @@ +use minifi_native::{ + FlowFileStreamTransform, GetAttribute, GetControllerService, GetId, GetProperty, InputStream, + Logger, MinifiError, OutputStream, ProcessError, RouteErrorExt, Schedule, + TransformStreamResult, +}; +use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey}; +use pgp::types::{Password, StringToKey}; + +mod processor_definition; + +use processor_definition::*; + +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, + symmetric_password: Option, +} + +#[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>, + file_name: String, + ) -> Result<(), MinifiError> { + if pub_key.is_none() && self.symmetric_password.is_none() { + return Err(MinifiError::custom( + "No password or public key to encrypt with", + )); + } + + 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) + .map_err(MinifiError::other)?; + } + + if let Some(password) = &self.symmetric_password { + builder + .encrypt_with_password(string_to_key(), password) + .map_err(MinifiError::other)?; + } + + match self.file_encoding { + FileEncoding::Ascii => builder + .to_armored_writer(rand::thread_rng(), ArmorOptions::default(), output_stream) + .map_err(MinifiError::other), + FileEncoding::Binary => builder + .to_writer(rand::thread_rng(), output_stream) + .map_err(MinifiError::other), + } + } + + fn check_validity(password: &Option, has_pub_key: bool) -> Result<(), MinifiError> { + if password.is_none() && !has_pub_key { + Err(MinifiError::custom( + "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", + )) + } else { + Ok(()) + } + } +} + +impl Schedule for EncryptContentPGP { + fn schedule(context: &P, _logger: &L) -> Result + where + Self: Sized, + { + let file_encoding = context.get_property::(&FILE_ENCODING)?; + let symmetric_password = context.get_property(&PASSWORD)?; + + let has_public_key = context.get_raw_property(&PUBLIC_KEY_SERVICE)?.is_some() + && context.get_property(&PUBLIC_KEY_SEARCH)?.is_some(); + + Self::check_validity(&symmetric_password, has_public_key)?; + Ok(EncryptContentPGP { + file_encoding, + symmetric_password, + }) + } +} + +impl EncryptContentPGP { + fn get_public_key( + context: &Ctx, + ) -> Result, MinifiError> { + if let (Some(pub_key_search), Some(public_key_service)) = ( + context.get_property(&PUBLIC_KEY_SEARCH)?, + context.get_controller_service(&PUBLIC_KEY_SERVICE)?, + ) { + Ok(public_key_service.get(&pub_key_search)) + } else { + Ok(None) + } + } +} + +impl FlowFileStreamTransform for EncryptContentPGP { + fn transform< + Ctx: GetProperty + GetControllerService + GetAttribute + GetId, + LoggerImpl: Logger, + >( + &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(context.get_id()?); + let public_key = Self::get_public_key(context)?; + + self.encrypt_bytes(input_stream, output_stream, public_key, file_name) + .route_err_to_failure()?; + + Ok(TransformStreamResult::new(&SUCCESS) + .with_attribute(FILE_ENCODING_ATTR.name, self.file_encoding.into_str())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::controller_services::public_key_service::PGPPublicKeyService; + use crate::test_utils; + use minifi_native::{ + ComponentIdentifier, EnableControllerService, IoState, MockControllerServiceContext, + MockLogger, MockProcessContext, test, + }; + #[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"); + + 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 res = processor.transform(&context, &mut input_stream, &mut result, &MockLogger::new()); + + test::assert_routed_to(res, &FAILURE); + } +} 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..62b5d07e42 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs @@ -0,0 +1,61 @@ +use super::{EncryptContentPGP, FileEncoding}; +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::utils; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, PropertyDefinition, + Relationship, property_definitions, +}; + +pub(crate) const FILE_ENCODING: Property = + Property::new("File Encoding", "File Encoding for encryption") + .with_default(FileEncoding::Binary.into_str()); +pub(crate) const PASSWORD: Property> = Property::new( + "Symmetric Password", + "Password used for encrypting data with Password-Based Encryption", +) +.sensitive(); + +pub(crate) const PUBLIC_KEY_SEARCH: Property> = Property::new( + "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(); + +pub(crate) const PUBLIC_KEY_SERVICE: Property> = Property::new( + "Public Key Service", + "PGP Public Key Service for encrypting data with Public Key Encryption", +); + +pub(super) const FILE_ENCODING_ATTR: OutputAttribute = OutputAttribute { + name: "pgp.file.encoding", + relationships: &["success"], + description: "File Encoding", +}; + +pub(super) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Encryption Succeeded", +}; + +pub(super) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Encryption Failed", +}; + +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] = &[FILE_ENCODING_ATTR]; + const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; + + fn properties() -> &'static [PropertyDefinition] { + const PROPERTIES: &[PropertyDefinition] = property_definitions![ + FILE_ENCODING, + PASSWORD, + PUBLIC_KEY_SEARCH, + PUBLIC_KEY_SERVICE, + ]; + PROPERTIES + } +} 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..f222127ef5 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -0,0 +1,16 @@ +use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; + +pub(crate) struct Password {} + +impl PropertySchema for Password { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = false; +} + +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à´?`Ò9ƒŽ˜B«2;²åÂŒ´ƒ�«g]N)‹!r2¥!F–šžnl./ó‚’a˜9._ª}_2¡,’É´ ý \ No newline at end of file diff --git a/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md b/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md index e05adf0e0e..74ecd008fd 100644 --- a/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md +++ b/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md @@ -218,6 +218,7 @@ In the list below, the names of required properties appear in bold. Any other pr | Name | Default Value | Allowable Values | Description | |------------------------------------|---------------|-------------------|--------------------------------------------| +| Dummy Controller Service | | | Optional dummy controller service | | **Lorem Ipsum Controller Service** | | | Name of the lorem ipsum controller service | | **Write Method** | Buffer | Buffer
Stream | Which API to test | diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/tests.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/tests.rs index 52863ac6c2..619d40d3d9 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/tests.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/tests.rs @@ -17,7 +17,7 @@ use super::*; use crate::processors::asciify_german::relationships::SUCCESS; -use minifi_native::{IoState, MockLogger, MockProcessContext}; +use minifi_native::{IoState, MockLogger, MockProcessContext, test}; use std::io::BufReader; #[test] @@ -84,8 +84,5 @@ fn truncated_umlaut_at_eof_routes_to_failure() { let mut output_vec: Vec = Vec::new(); let result = asciify_german.transform(&context, &mut input_stream, &mut output_vec, &logger); - match result { - Err(ProcessError::Route(route)) => assert_eq!(route.relationship, FAILURE.name), - other => panic!("expected a route error to failure, got {other:?}"), - } + test::assert_routed_to(result, &FAILURE); } diff --git a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_stream_transform.rs b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_stream_transform.rs index f300f99b83..1d1bb6765a 100644 --- a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_stream_transform.rs +++ b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_stream_transform.rs @@ -24,6 +24,7 @@ use crate::{ MultiThreaded, OnTriggerResult, OutputStream, ProcessContext, ProcessError, ProcessSession, Processor, Relationship, Schedule, SingleThreaded, }; +use minifi_native::GetId; #[derive(Debug)] pub struct TransformStreamResult { @@ -72,7 +73,10 @@ impl TransformStreamResult { impl_with_attributes!(TransformStreamResult); pub trait FlowFileStreamTransform { - fn transform( + fn transform< + Ctx: GetProperty + GetControllerService + GetAttribute + GetId, + LoggerImpl: Logger, + >( &self, context: &Ctx, input_stream: &mut dyn InputStream, diff --git a/minifi_rust/minifi_native/src/lib.rs b/minifi_rust/minifi_native/src/lib.rs index b855291a29..4ae26f9c14 100644 --- a/minifi_rust/minifi_native/src/lib.rs +++ b/minifi_rust/minifi_native/src/lib.rs @@ -19,6 +19,7 @@ extern crate self as minifi_native; mod api; pub mod c_ffi; pub mod mock; +pub mod test_utils; pub use api::errors::{MinifiError, ProcessError, RouteError, RouteErrorExt}; @@ -69,6 +70,7 @@ pub use mock::{ MockControllerServiceContext, MockFlowFile, MockLogger, MockProcessContext, MockProcessSession, StdLogger, }; +pub use test_utils as test; #[unsafe(no_mangle)] #[allow(non_upper_case_globals)] diff --git a/minifi_rust/minifi_native/src/test_utils.rs b/minifi_rust/minifi_native/src/test_utils.rs new file mode 100644 index 0000000000..089860498a --- /dev/null +++ b/minifi_rust/minifi_native/src/test_utils.rs @@ -0,0 +1,16 @@ +use crate::{ProcessError, Relationship, TransformStreamResult}; + +pub fn assert_routed_to( + res: Result, + expected_relationship: &Relationship, +) { + match res { + Err(ProcessError::Route(route)) => { + assert_eq!(route.relationship, expected_relationship.name) + } + Err(other) => { + panic!("expected route to '{expected_relationship}', got fatal error: {other:?}") + } + Ok(_) => panic!("expected route to '{expected_relationship}', got Ok"), + } +} From 161dd6112b2b7e49a048b40200ea4e1cea5591d0 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 14 Aug 2026 09:56:52 +0200 Subject: [PATCH 02/25] update manifest --- .../ubuntu_22_04_clang_arm_manifest.json | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index 139b3854d7..aebcc45651 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12104,6 +12104,203 @@ "version": "1.0.0" } }, +{ + "bundles": { + "componentManifest": { + "processors": [ + { + "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": "org.apache.nifi.minifi.rust", + "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": { + "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": "org.apache.nifi.minifi.rust", + "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" + } + ], + "controllerServices": [ + { + "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" + } + ] + }, + "group": "org.apache.nifi.minifi.rust", + "artifact": "minifi_pgp", + "version": "0.1.0" + } +}, { "bundles": { "componentManifest": { From 7b85c6ef6d74b46f5a80afd4f2a89fa7184b52f1 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 14 Aug 2026 09:58:47 +0200 Subject: [PATCH 03/25] minifi_pgp set version to 1.0.0 --- .github/references/ubuntu_22_04_clang_arm_manifest.json | 2 +- minifi_rust/extensions/minifi_pgp/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index aebcc45651..61f2bea9b3 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12298,7 +12298,7 @@ }, "group": "org.apache.nifi.minifi.rust", "artifact": "minifi_pgp", - "version": "0.1.0" + "version": "1.0.0" } }, { diff --git a/minifi_rust/extensions/minifi_pgp/Cargo.toml b/minifi_rust/extensions/minifi_pgp/Cargo.toml index af2c6e1749..6edd4d3b72 100644 --- a/minifi_rust/extensions/minifi_pgp/Cargo.toml +++ b/minifi_rust/extensions/minifi_pgp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minifi_pgp" -version = "0.1.0" +version = "1.0.0" edition = "2024" [lib] @@ -12,4 +12,3 @@ 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 - From 78861f542fd4efe1fe71be7bfbab47d18c2319a7 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 14 Aug 2026 13:41:37 +0200 Subject: [PATCH 04/25] refactor --- .../controller_services/key_file_property.rs | 124 ++++++++++++ .../src/controller_services/key_property.rs | 49 +++++ .../minifi_pgp/src/controller_services/mod.rs | 4 +- .../private_key_service.rs | 181 ++++------------- .../controller_service_definition.rs | 7 +- .../controller_services/public_key_service.rs | 187 +++++------------- .../controller_service_definition.rs | 7 +- .../src/processors/decrypt_content.rs | 4 +- .../decrypt_content/output_attributes.rs | 2 - .../processors/decrypt_content/properties.rs | 4 - .../decrypt_content/relationships.rs | 2 - .../src/processors/decrypt_content/tests.rs | 9 - .../src/processors/encrypt_content.rs | 2 +- 13 files changed, 276 insertions(+), 306 deletions(-) create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs new file mode 100644 index 0000000000..4e0c876f01 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs @@ -0,0 +1,124 @@ +use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; +use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; + +pub(crate) struct SecretKeyFile {} + +impl PropertySchema for SecretKeyFile { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = false; +} + +impl PropertyType for SecretKeyFile { + type Output = Vec; + + fn parse(s: &str) -> Result { + let mut result: Vec = Vec::new(); + if let Ok((keys, _headers)) = SignedSecretKey::from_armor_file_many(s) { + result.extend(keys.filter_map(Result::ok)); + } else if let Ok(keys) = SignedSecretKey::from_file_many(s) { + result.extend(keys.filter_map(Result::ok)); + } + if result.is_empty() { + Err(MinifiError::validation( + "Couldnt load any valid secret keys", + )) + } else { + Ok(result) + } + } +} + +pub(crate) struct PublicKeyFile {} +impl PropertySchema for PublicKeyFile { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = false; +} + +impl PropertyType for PublicKeyFile { + type Output = Vec; + + fn parse(s: &str) -> Result { + let mut result: Vec = Vec::new(); + if let Ok((keys, _headers)) = SignedPublicKey::from_armor_file_many(s) { + result.extend(keys.filter_map(Result::ok)); + } else if let Ok(keys) = SignedPublicKey::from_file_many(s) { + result.extend(keys.filter_map(Result::ok)); + } + if result.is_empty() { + Err(MinifiError::validation( + "Couldnt load any valid public keys", + )) + } else { + Ok(result) + } + } +} + +#[cfg(test)] +mod secret_key_file_tests { + use super::*; + use crate::test_utils::get_test_key_path; + + fn assert_invalid_secret_key_file(file_name: &str) { + assert!(SecretKeyFile::parse(&get_test_key_path(file_name)).is_err()) + } + fn assert_valid_secret_key_file(file_name: &str) { + assert!( + !SecretKeyFile::parse(&get_test_key_path(file_name)) + .unwrap() + .is_empty() + ) + } + #[test] + fn test_invalid_secret_keyfiles() { + assert_invalid_secret_key_file("alice.asc"); + assert_invalid_secret_key_file("alice.gpg"); + assert_invalid_secret_key_file("garbage.gpg"); + assert_invalid_secret_key_file("truncated_private.asc"); + assert_invalid_secret_key_file("non_existent.asc"); + } + + #[test] + fn test_valid_secret_keyfiles() { + assert_valid_secret_key_file("alice_private.asc"); + assert_valid_secret_key_file("alice_private.gpg"); + assert_valid_secret_key_file("bob_private.asc"); + assert_valid_secret_key_file("bob_private.gpg"); + assert_valid_secret_key_file("secret_keyring.asc"); + assert_valid_secret_key_file("secret_keyring.gpg"); + } +} + +#[cfg(test)] +mod public_key_file_tests { + use crate::controller_services::key_file_property::PublicKeyFile; + use crate::test_utils::get_test_key_path; + use minifi_native::PropertyType; + + fn assert_invalid_public_key_file(file_name: &str) { + assert!(PublicKeyFile::parse(&get_test_key_path(file_name)).is_err()) + } + fn assert_valid_public_key_file(file_name: &str) { + assert!( + !PublicKeyFile::parse(&get_test_key_path(file_name)) + .unwrap() + .is_empty() + ) + } + #[test] + fn test_invalid_public_keyfiles() { + assert_invalid_public_key_file("alice_private.asc"); + assert_invalid_public_key_file("alice_private.gpg"); + assert_invalid_public_key_file("garbage.gpg"); + assert_invalid_public_key_file("truncated.asc"); + assert_invalid_public_key_file("non_existent.asc"); + } + + #[test] + fn test_valid_public_keyfiles() { + assert_valid_public_key_file("alice.asc"); + assert_valid_public_key_file("alice.gpg"); + assert_valid_public_key_file("keyring.asc"); + assert_valid_public_key_file("keyring.gpg"); + } +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs new file mode 100644 index 0000000000..b3d9aa07d0 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs @@ -0,0 +1,49 @@ +use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; +use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; + +pub(crate) struct SecretKey {} + +impl PropertySchema for SecretKey { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = false; +} + +impl PropertyType for SecretKey { + type Output = Vec; + + fn parse(s: &str) -> Result { + let mut secret_keys: Vec = Vec::new(); + if let Ok((keys, _headers)) = SignedSecretKey::from_armor_many(s.as_bytes()) { + secret_keys.extend(keys.filter_map(Result::ok)); + } + if secret_keys.is_empty() { + return Err(MinifiError::validation( + "Couldnt load any valid secrey keys", + )); + } + Ok(secret_keys) + } +} + +pub(crate) struct PublicKey {} +impl PropertySchema for PublicKey { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = false; +} + +impl PropertyType for PublicKey { + type Output = Vec; + + fn parse(s: &str) -> Result { + let mut public_keys: Vec = Vec::new(); + if let Ok((keys, _headers)) = SignedPublicKey::from_armor_many(s.as_bytes()) { + public_keys.extend(keys.filter_map(Result::ok)); + } + if public_keys.is_empty() { + return Err(MinifiError::validation( + "Couldnt load any valid public keys", + )); + } + Ok(public_keys) + } +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs index d2c83be438..930207f53f 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs @@ -1,3 +1,5 @@ -pub(crate) mod key_lookup; +mod key_file_property; +mod key_lookup; +mod key_property; 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 index 0f8ca9eca1..848fa5603c 100644 --- 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 @@ -4,8 +4,8 @@ use controller_service_definition::*; #[cfg(test)] use crate::controller_services::key_lookup::key_matches; use minifi_native::macros::ComponentIdentifier; -use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; -use pgp::composed::{Deserializable, SignedSecretKey, TheRing}; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; +use pgp::composed::{SignedSecretKey, TheRing}; #[cfg(test)] use pgp::types::KeyDetails; @@ -16,29 +16,17 @@ pub(crate) struct PGPPrivateKeyService { } impl EnableControllerService for PGPPrivateKeyService { - fn enable(context: &P, logger: &L) -> Result + fn enable(context: &P, _logger: &L) -> Result where Self: Sized, { - let mut private_keys = vec![]; - if let Some(keyring_file_path) = context.get_property(&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(&KEY)? - && let Ok((keys, _headers)) = SignedSecretKey::from_armor_many(keyring_ascii.as_bytes()) - { - collect_keys(keys, &mut private_keys, logger); - } + let mut private_keys = context.get_property(&KEY_FILE)?.unwrap_or_default(); + private_keys.extend(context.get_property(&KEY)?.unwrap_or_default()); let passphrase = context.get_property(&KEY_PASSPHRASE)?.unwrap_or_default(); if private_keys.is_empty() { - return Err(MinifiError::custom("Could not load any valid keys")); + return Err(MinifiError::validation("Could not load any valid keys")); } Ok(Self { private_keys, @@ -70,36 +58,12 @@ impl PGPPrivateKeyService { } } -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 { use super::*; use crate::test_utils::get_test_key_path; - use minifi_native::MinifiError::CustomError; use minifi_native::{ComponentIdentifier, MockControllerServiceContext, MockLogger}; - fn assert_private_key_service_enable_fails_with_no_valid_keys( - context: &MockControllerServiceContext, - ) { - if let Err(CustomError(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!( @@ -107,56 +71,13 @@ mod tests { "minifi_pgp::controller_services::private_key_service::PGPPrivateKeyService" ); assert_eq!(PGPPrivateKeyService::GROUP_NAME, "minifi_pgp"); - assert_eq!(PGPPrivateKeyService::VERSION, "0.1.0"); + assert_eq!(PGPPrivateKeyService::VERSION, "1.0.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); + assert!(PGPPrivateKeyService::enable(&context, &MockLogger::new()).is_err()); } #[test] @@ -167,17 +88,13 @@ mod tests { get_test_key_path("alice_private.asc"), ); - let controller_service = + let 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!(service.get_secret_key("Alice").is_some()); + assert!(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()); + assert!(service.get_secret_key("Bob").is_none()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -188,20 +105,20 @@ mod tests { get_test_key_path("alice_private.gpg"), ); - let controller_service = + let 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!(service.get_secret_key("A").is_some()); + assert!(service.get_secret_key("Alice").is_some()); assert!( - controller_service + service .get_secret_key("Alice ") .is_some() ); - assert!(controller_service.get_secret_key("").is_none()); + assert!(service.get_secret_key("").is_none()); - assert!(controller_service.get_secret_key("Bob").is_none()); - assert!(controller_service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Bob").is_none()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -212,13 +129,13 @@ mod tests { get_test_key_path("secret_keyring.asc"), ); - let controller_service = + let 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()); + assert!(service.get_secret_key("Alice").is_some()); + assert!(service.get_secret_key("Bob").is_some()); + assert!(service.get_secret_key("bob@home.io").is_some()); + assert!(service.get_secret_key("bob@work.com").is_some()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -229,13 +146,13 @@ mod tests { get_test_key_path("secret_keyring.gpg"), ); - let controller_service = + let 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()); + assert!(service.get_secret_key("Alice").is_some()); + assert!(service.get_secret_key("Bob").is_some()); + assert!(service.get_secret_key("bob@home.io").is_some()); + assert!(service.get_secret_key("bob@work.com").is_some()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -247,13 +164,13 @@ mod tests { context.properties.insert("Key".to_string(), file_content); - let controller_service = + let 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()); + assert!(service.get_secret_key("Alice").is_some()); + assert!(service.get_secret_key("Bob").is_some()); + assert!(service.get_secret_key("bob@home.io").is_some()); + assert!(service.get_secret_key("bob@work.com").is_some()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -265,23 +182,11 @@ mod tests { context.properties.insert("Key".to_string(), file_content); - let controller_service = + let 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); + assert!(service.get_secret_key("Alice").is_some()); + assert!(service.get_secret_key("Bob").is_none()); + assert!(service.get_secret_key("Carol").is_none()); } #[test] @@ -293,6 +198,6 @@ mod tests { context.properties.insert("Key".to_string(), file_content); - assert_private_key_service_enable_fails_with_no_valid_keys(&context); + assert!(PGPPrivateKeyService::enable(&context, &MockLogger::new()).is_err()); } } 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 index 585ad82145..fe421553ad 100644 --- 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 @@ -1,18 +1,19 @@ use super::PGPPrivateKeyService; +use crate::controller_services::key_file_property::SecretKeyFile; +use crate::controller_services::key_property::SecretKey; use crate::utils; use minifi_native::{ ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, property_definitions, }; -use std::path::PathBuf; -pub(super) const KEY_FILE: Property> = Property::new( +pub(super) const KEY_FILE: Property> = Property::new( "Key File", "File path to PGP Secret Key encoded in binary or ASCII Armor", ) .supports_expression_language(); -pub(super) const KEY: Property> = +pub(super) const KEY: Property> = Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); pub(super) const KEY_PASSPHRASE: Property> = Property::new( 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 index 007ca6f94c..117abd1198 100644 --- 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 @@ -3,8 +3,8 @@ use controller_service_definition::*; use crate::controller_services::key_lookup::key_matches; use minifi_native::macros::ComponentIdentifier; -use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; -use pgp::composed::{Deserializable, SignedPublicKey}; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; +use pgp::composed::SignedPublicKey; use pgp::types::KeyDetails; #[derive(Debug, ComponentIdentifier, PartialEq)] @@ -13,45 +13,20 @@ pub(crate) struct PGPPublicKeyService { } impl EnableControllerService for PGPPublicKeyService { - fn enable(context: &P, logger: &L) -> Result + 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); - } + let mut public_keys = context.get_property(&KEYRING_FILE)?.unwrap_or_default(); + public_keys.extend(context.get_property(&KEYRING)?.unwrap_or_default()); if public_keys.is_empty() { - return Err(MinifiError::custom("Could not load any valid keys")); + return Err(MinifiError::validation("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| { @@ -68,18 +43,8 @@ impl PGPPublicKeyService { mod tests { use super::*; use crate::test_utils::get_test_key_path; - use minifi_native::MinifiError::CustomError; - use minifi_native::{ComponentIdentifier, MockControllerServiceContext, MockLogger}; - fn assert_public_key_service_enable_fails_with_no_valid_keys( - context: &MockControllerServiceContext, - ) { - if let Err(CustomError(error)) = PGPPublicKeyService::enable(context, &MockLogger::new()) { - assert_eq!(error, "Could not load any valid keys"); - } else { - panic!("Didnt fail with no_valid_keys"); - } - } + use minifi_native::{ComponentIdentifier, MockControllerServiceContext, MockLogger}; #[test] fn test_component_id() { @@ -88,24 +53,13 @@ mod tests { "minifi_pgp::controller_services::public_key_service::PGPPublicKeyService" ); assert_eq!(PGPPublicKeyService::GROUP_NAME, "minifi_pgp"); - assert_eq!(PGPPublicKeyService::VERSION, "0.1.0"); + assert_eq!(PGPPublicKeyService::VERSION, "1.0.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); + assert!(PGPPublicKeyService::enable(&context, &MockLogger::new()).is_err()); } #[test] @@ -116,29 +70,7 @@ mod tests { 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); + assert!(PGPPublicKeyService::enable(&context, &MockLogger::new()).is_err()); } #[test] @@ -165,20 +97,16 @@ mod tests { .properties .insert("Keyring File".to_string(), get_test_key_path("alice.gpg")); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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!(service.get("A").is_some()); + assert!(service.get("Alice").is_some()); + assert!(service.get("Alice ").is_some()); - assert!(controller_service.get("").is_none()); + assert!(service.get("").is_none()); - assert!(controller_service.get("Bob").is_none()); - assert!(controller_service.get("Carol").is_none()); + assert!(service.get("Bob").is_none()); + assert!(service.get("Carol").is_none()); } #[test] @@ -188,13 +116,13 @@ mod tests { .properties .insert("Keyring File".to_string(), get_test_key_path("keyring.asc")); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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()); + assert!(service.get("Alice").is_some()); + assert!(service.get("Bob").is_some()); + assert!(service.get("bob@home.io").is_some()); + assert!(service.get("bob@work.com").is_some()); + assert!(service.get("Carol").is_none()); } #[test] @@ -204,13 +132,13 @@ mod tests { .properties .insert("Keyring File".to_string(), get_test_key_path("keyring.gpg")); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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()); + assert!(service.get("Alice").is_some()); + assert!(service.get("Bob").is_some()); + assert!(service.get("bob@home.io").is_some()); + assert!(service.get("bob@work.com").is_some()); + assert!(service.get("Carol").is_none()); } #[test] @@ -224,13 +152,13 @@ mod tests { .properties .insert("Keyring".to_string(), file_content); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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()); + assert!(service.get("Alice").is_some()); + assert!(service.get("Bob").is_some()); + assert!(service.get("bob@home.io").is_some()); + assert!(service.get("bob@work.com").is_some()); + assert!(service.get("Carol").is_none()); } #[test] @@ -244,25 +172,11 @@ mod tests { .properties .insert("Keyring".to_string(), file_content); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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); + assert!(service.get("Alice").is_some()); + assert!(service.get("Bob").is_none()); + assert!(service.get("Carol").is_none()); } #[test] @@ -276,7 +190,7 @@ mod tests { .properties .insert("Keyring".to_string(), file_content); - assert_public_key_service_enable_fails_with_no_valid_keys(&context); + assert!(PGPPublicKeyService::enable(&context, &MockLogger::new()).is_err()); } #[test] @@ -286,24 +200,15 @@ mod tests { .properties .insert("Keyring File".to_string(), get_test_key_path("alice.asc")); - let controller_service = PGPPublicKeyService::enable(&context, &MockLogger::new()) + let 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 alice = 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()); + assert!(service.get(&key_id_hex).is_some()); + assert!(service.get(&key_id_hex.to_ascii_uppercase()).is_some()); + assert!(service.get(&key_id_hex[..8]).is_none()); + assert!(service.get("0123456789abcdef").is_none()); } } 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 index 580b3e0079..099cd5fc1e 100644 --- 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 @@ -1,17 +1,18 @@ use super::PGPPublicKeyService; +use crate::controller_services::key_file_property::PublicKeyFile; +use crate::controller_services::key_property::PublicKey; use minifi_native::{ ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, property_definitions, }; -use std::path::PathBuf; -pub(crate) const KEYRING_FILE: Property> = Property::new( +pub(crate) const KEYRING_FILE: Property> = Property::new( "Keyring File", "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", ) .supports_expression_language(); -pub(crate) const KEYRING: Property> = Property::new( +pub(crate) const KEYRING: Property> = Property::new( "Keyring", "PGP Keyring or Secret Key encoded in ASCII Armor", ) diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index 7ea0dc78b7..506bc24ba6 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -39,7 +39,7 @@ impl Schedule for DecryptContentPGP { let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; let has_context_service = context.get_raw_property(&PRIVATE_KEY_SERVICE)?.is_some(); if !has_context_service && symmetric_password.is_none() { - Err(MinifiError::custom( + Err(MinifiError::validation( "Either Symmetric Password or Private Key Service must be set", )) } else { @@ -139,7 +139,7 @@ mod tests { "minifi_pgp::processors::decrypt_content::DecryptContentPGP" ); assert_eq!(DecryptContentPGP::GROUP_NAME, "minifi_pgp"); - assert_eq!(DecryptContentPGP::VERSION, "0.1.0"); + assert_eq!(DecryptContentPGP::VERSION, "1.0.0"); } #[test] 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 deleted file mode 100644 index 5511193e82..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs +++ /dev/null @@ -1,2 +0,0 @@ -use minifi_native::OutputAttribute; - 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 deleted file mode 100644 index d2c0be7860..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::controller_services::private_key_service::PGPPrivateKeyService; -use crate::processors::decrypt_content::DecryptionStrategy; -use crate::utils; -use minifi_native::Property; 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 deleted file mode 100644 index ed61a1165e..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs +++ /dev/null @@ -1,2 +0,0 @@ -use minifi_native::Relationship; - 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 deleted file mode 100644 index 87b6179d04..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -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, -}; - diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 6d2352136b..07b7b70dc0 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -164,7 +164,7 @@ mod tests { "minifi_pgp::processors::encrypt_content::EncryptContentPGP" ); assert_eq!(EncryptContentPGP::GROUP_NAME, "minifi_pgp"); - assert_eq!(EncryptContentPGP::VERSION, "0.1.0"); + assert_eq!(EncryptContentPGP::VERSION, "1.0.0"); } #[test] From 825a32b264361fa9ad545e9ef7943def1ed8303f Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Mon, 17 Aug 2026 14:43:34 +0200 Subject: [PATCH 05/25] no public key should route to failure instead of rollback --- .../extensions/minifi_pgp/src/processors/encrypt_content.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 07b7b70dc0..4c0c391565 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -138,7 +138,7 @@ impl FlowFileStreamTransform for EncryptContentPGP { let file_name = context .get_attribute("filename")? .unwrap_or(context.get_id()?); - let public_key = Self::get_public_key(context)?; + let public_key = Self::get_public_key(context).route_err_to_failure()?; self.encrypt_bytes(input_stream, output_stream, public_key, file_name) .route_err_to_failure()?; From c3bb10c1bb080671a4199966394a13881fb9ae2f Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 26 Aug 2026 13:44:12 +0200 Subject: [PATCH 06/25] ruff format, ruff check --fix --- .../minifi_pgp/features/environment.py | 16 +--- .../minifi_pgp/features/steps/steps.py | 73 ++++++------------- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py index cdec449069..a55d665af8 100644 --- a/minifi_rust/extensions/minifi_pgp/features/environment.py +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -1,22 +1,16 @@ 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.hooks import common_after_scenario, 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 -): +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" - ) + 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/" @@ -27,9 +21,7 @@ def add_extension_to_minifi_container( 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]}" - ) + 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() diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py index b2a4d6e632..56cbf4e3b1 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -3,96 +3,67 @@ 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 +from minifi_behave.steps import ( + checking_steps, # noqa: F401 + configuration_steps, # noqa: F401 + core_steps, # noqa: F401 + flow_building_steps, # noqa: F401 +) @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" - ) + 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 - ) + 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 - ) + 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" -) +@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 = 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 - ) + 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 - ) + 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" -) +@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" - ) + 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 - ) + 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 - ) + 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 -): +@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-----" - ) + 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, From a0a1ecf6fb9ef34762714c33a9eae179140dedcb Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Tue, 1 Sep 2026 12:33:57 +0200 Subject: [PATCH 07/25] rebase --- .../decrypt_content/processor_definition.rs | 8 ++------ .../encrypt_content/processor_definition.rs | 15 ++++++--------- 2 files changed, 8 insertions(+), 15 deletions(-) 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 index 62b4126925..f93e2c117b 100644 --- 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 @@ -53,10 +53,6 @@ impl ProcessorDefinition for DecryptContentPGP { const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; - - fn properties() -> &'static [PropertyDefinition] { - const PROPERTIES: &[PropertyDefinition] = - property_definitions![DECRYPTION_STRATEGY, SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; - PROPERTIES - } + const PROPERTIES: &[PropertyDefinition] = + property_definitions![DECRYPTION_STRATEGY, SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; } 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 index 62b5d07e42..9a062480f2 100644 --- 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 @@ -49,13 +49,10 @@ impl ProcessorDefinition for EncryptContentPGP { const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[FILE_ENCODING_ATTR]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; - fn properties() -> &'static [PropertyDefinition] { - const PROPERTIES: &[PropertyDefinition] = property_definitions![ - FILE_ENCODING, - PASSWORD, - PUBLIC_KEY_SEARCH, - PUBLIC_KEY_SERVICE, - ]; - PROPERTIES - } + const PROPERTIES: &[PropertyDefinition] = property_definitions![ + FILE_ENCODING, + PASSWORD, + PUBLIC_KEY_SEARCH, + PUBLIC_KEY_SERVICE, + ]; } From 32fccc917a0bd99aaf1f4705c37b40acbfd6231c Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Thu, 3 Sep 2026 16:08:11 +0200 Subject: [PATCH 08/25] review changes --- .../features/encrypt_decrypt.feature | 15 +++++ .../minifi_pgp/features/environment.py | 17 ++++++ .../minifi_pgp/features/steps/steps.py | 17 ++++++ .../extensions/minifi_pgp/minifi_pgp.md | 11 ++-- .../controller_services/key_file_property.rs | 21 ++++++- .../src/controller_services/key_lookup.rs | 17 ++++++ .../src/controller_services/key_property.rs | 21 ++++++- .../minifi_pgp/src/controller_services/mod.rs | 17 ++++++ .../private_key_service.rs | 17 ++++++ .../controller_service_definition.rs | 17 ++++++ .../controller_services/public_key_service.rs | 17 ++++++ .../controller_service_definition.rs | 21 ++++++- minifi_rust/extensions/minifi_pgp/src/lib.rs | 17 ++++++ .../src/processors/decrypt_content.rs | 57 +++++++------------ .../decrypt_content/processor_definition.rs | 29 +++++++--- .../src/processors/encrypt_content.rs | 49 +++++++++++++++- .../encrypt_content/processor_definition.rs | 17 ++++++ .../minifi_pgp/src/processors/mod.rs | 17 ++++++ .../minifi_pgp/src/test_utils/mod.rs | 17 ++++++ .../extensions/minifi_pgp/src/utils.rs | 25 +++++++- 20 files changed, 376 insertions(+), 60 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature index 3914e71308..f7190e6375 100644 --- a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature +++ b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature @@ -1,3 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + @SUPPORTS_WINDOWS Feature: Test PGP extension's encryption and decryption capabilities diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py index a55d665af8..293295a354 100644 --- a/minifi_rust/extensions/minifi_pgp/features/environment.py +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import os from minifi_behave.containers.docker_image_builder import DockerImageBuilder diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py index 56cbf4e3b1..8a0c4cc0e9 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import os from pathlib import Path diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md index f2d9cbeab7..03480f480c 100644 --- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -29,17 +29,16 @@ limitations under the License. ### Description -Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification. +Decrypt contents of OpenPGP messages. ### 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 | +| Name | Default Value | Allowable Values | Description | +|---------------------|---------------|------------------|-------------------------------------------------------------------------------------------------------------| +| 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 diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs index 4e0c876f01..92a32ebe3f 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; @@ -20,7 +37,7 @@ impl PropertyType for SecretKeyFile { } if result.is_empty() { Err(MinifiError::validation( - "Couldnt load any valid secret keys", + "Couldn't load any valid secret keys", )) } else { Ok(result) @@ -46,7 +63,7 @@ impl PropertyType for PublicKeyFile { } if result.is_empty() { Err(MinifiError::validation( - "Couldnt load any valid public keys", + "Couldn't load any valid public keys", )) } else { Ok(result) 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 index cff723a718..9205ff0a74 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use pgp::composed::SignedKeyDetails; use pgp::types::KeyId; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs index b3d9aa07d0..8bfcf485e2 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; @@ -18,7 +35,7 @@ impl PropertyType for SecretKey { } if secret_keys.is_empty() { return Err(MinifiError::validation( - "Couldnt load any valid secrey keys", + "Couldn't load any valid secret keys", )); } Ok(secret_keys) @@ -41,7 +58,7 @@ impl PropertyType for PublicKey { } if public_keys.is_empty() { return Err(MinifiError::validation( - "Couldnt load any valid public keys", + "Couldn't load any valid public keys", )); } Ok(public_keys) diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs index 930207f53f..bf3fe01e38 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod key_file_property; mod key_lookup; mod key_property; 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 index 848fa5603c..fe1d5b6f5e 100644 --- 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 @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod controller_service_definition; use controller_service_definition::*; 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 index fe421553ad..80b7f7fdd6 100644 --- 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 @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use super::PGPPrivateKeyService; use crate::controller_services::key_file_property::SecretKeyFile; use crate::controller_services::key_property::SecretKey; 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 index 117abd1198..63442678d9 100644 --- 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 @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod controller_service_definition; use controller_service_definition::*; 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 index 099cd5fc1e..3f36bca234 100644 --- 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 @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use super::PGPPublicKeyService; use crate::controller_services::key_file_property::PublicKeyFile; use crate::controller_services::key_property::PublicKey; @@ -8,13 +25,13 @@ use minifi_native::{ pub(crate) const KEYRING_FILE: Property> = Property::new( "Keyring File", - "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", + "File path to PGP Keyring or Public Key encoded in binary or ASCII Armor", ) .supports_expression_language(); pub(crate) const KEYRING: Property> = Property::new( "Keyring", - "PGP Keyring or Secret Key encoded in ASCII Armor", + "PGP Keyring or Public Key encoded in ASCII Armor", ) .sensitive(); diff --git a/minifi_rust/extensions/minifi_pgp/src/lib.rs b/minifi_rust/extensions/minifi_pgp/src/lib.rs index f8085bb072..eba818524f 100644 --- a/minifi_rust/extensions/minifi_pgp/src/lib.rs +++ b/minifi_rust/extensions/minifi_pgp/src/lib.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod controller_services; mod processors; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index 506bc24ba6..c3dc7c53e3 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -1,30 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + mod processor_definition; use processor_definition::*; use crate::controller_services::private_key_service::PGPPrivateKeyService; -use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use minifi_native::macros::ComponentIdentifier; use minifi_native::{ FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, Logger, MinifiError, OutputStream, ProcessError, RouteErrorExt, Schedule, TransformStreamResult, }; use pgp::composed::{Message, TheRing}; -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, } @@ -34,8 +39,6 @@ impl Schedule for DecryptContentPGP { Self: Sized, L: Logger, { - let decryption_strategy = context.get_property(&DECRYPTION_STRATEGY)?; - let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; let has_context_service = context.get_raw_property(&PRIVATE_KEY_SERVICE)?.is_some(); if !has_context_service && symmetric_password.is_none() { @@ -43,10 +46,7 @@ impl Schedule for DecryptContentPGP { "Either Symmetric Password or Private Key Service must be set", )) } else { - Ok(DecryptContentPGP { - decompress_data: decryption_strategy == DecryptionStrategy::Decrypted, - symmetric_password, - }) + Ok(DecryptContentPGP { symmetric_password }) } } } @@ -108,7 +108,7 @@ impl FlowFileStreamTransform for DecryptContentPGP { .decrypt_msg(msg, private_key_service) .route_err_to_failure()?; - if self.decompress_data && decrypted_msg.is_compressed() { + if decrypted_msg.is_compressed() { decrypted_msg = decrypted_msg .decompress() .map_err(MinifiError::other) @@ -170,20 +170,6 @@ mod tests { assert!(decrypt_content.is_ok()); } - #[test] - fn schedule_rejects_invalid_strategy_without_panicking() { - let mut context = MockProcessContext::new(); - context - .properties - .insert(DECRYPTION_STRATEGY.name(), "NOT_A_STRATEGY".to_string()); - context - .properties - .insert(SYMMETRIC_PASSWORD.name(), "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, @@ -240,10 +226,11 @@ mod tests { ); match expected_result { - Ok(_result_bytes) => { + Ok(result_bytes) => { let res = res.expect("Should be able to transform"); assert_eq!(res.target_relationship_name(), SUCCESS.name); assert_eq!(res.write_status(), IoState::Ok); + assert_eq!(output, result_bytes); let data_modified = res .get_attribute(LITERAL_DATA_MODIFIED.name) .unwrap() 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 index f93e2c117b..b9d1cf4698 100644 --- 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 @@ -1,4 +1,21 @@ -use super::{DecryptContentPGP, DecryptionStrategy}; +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use super::DecryptContentPGP; use crate::controller_services::private_key_service::PGPPrivateKeyService; use crate::utils; use minifi_native::{ @@ -18,12 +35,6 @@ pub(super) const LITERAL_DATA_MODIFIED: OutputAttribute = OutputAttribute { 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)", }; -pub(super) const DECRYPTION_STRATEGY: Property = Property::new( - "Decryption Strategy", - "Strategy for writing files to success after decryption", -) -.with_default(DecryptionStrategy::Decrypted.into_str()); - pub(super) const SYMMETRIC_PASSWORD: Property> = Property::new( "Symmetric Password", "Password used for decrypting data encrypted with Password-Based Encryption", @@ -46,7 +57,7 @@ pub(super) const FAILURE: Relationship = 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 DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages."; const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; @@ -54,5 +65,5 @@ impl ProcessorDefinition for DecryptContentPGP { &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; const PROPERTIES: &[PropertyDefinition] = - property_definitions![DECRYPTION_STRATEGY, SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; + property_definitions![SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; } diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 4c0c391565..bd5fd0f7aa 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use minifi_native::{ FlowFileStreamTransform, GetAttribute, GetControllerService, GetId, GetProperty, InputStream, Logger, MinifiError, OutputStream, ProcessError, RouteErrorExt, Schedule, @@ -99,7 +116,7 @@ impl Schedule for EncryptContentPGP { let symmetric_password = context.get_property(&PASSWORD)?; let has_public_key = context.get_raw_property(&PUBLIC_KEY_SERVICE)?.is_some() - && context.get_property(&PUBLIC_KEY_SEARCH)?.is_some(); + && context.get_raw_property(&PUBLIC_KEY_SEARCH)?.is_some(); Self::check_validity(&symmetric_password, has_public_key)?; Ok(EncryptContentPGP { @@ -117,7 +134,12 @@ impl EncryptContentPGP { context.get_property(&PUBLIC_KEY_SEARCH)?, context.get_controller_service(&PUBLIC_KEY_SERVICE)?, ) { - Ok(public_key_service.get(&pub_key_search)) + match public_key_service.get(&pub_key_search) { + Some(public_key) => Ok(Some(public_key)), + None => Err(MinifiError::custom(format!( + "No public key matching '{pub_key_search}' found in the configured Public Key Service" + ))), + } } else { Ok(None) } @@ -283,4 +305,27 @@ mod tests { test::assert_routed_to(res, &FAILURE); } + + #[test] + fn configured_public_key_miss_fails_even_with_password() { + let mut context = MockProcessContext::new(); + context.properties.extend([ + ("Public Key Service", "my_controller_service"), + ("Public Key Search", "Carol"), + ("Symmetric Password", "password"), + ]); + + 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 res = processor.transform(&context, &mut input_stream, &mut result, &MockLogger::new()); + + test::assert_routed_to(res, &FAILURE); + } } 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 index 9a062480f2..602cffc2e3 100644 --- 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 @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use super::{EncryptContentPGP, FileEncoding}; use crate::controller_services::public_key_service::PGPPublicKeyService; use crate::utils; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs index ac45357fe3..a22c7b1b82 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs @@ -1,2 +1,19 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + 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 index 963af71617..1ceb14cab8 100644 --- a/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs +++ b/minifi_rust/extensions/minifi_pgp/src/test_utils/mod.rs @@ -1,3 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + use std::path::PathBuf; pub fn get_test_key_path(filename: &str) -> String { diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs b/minifi_rust/extensions/minifi_pgp/src/utils.rs index f222127ef5..2abc09751c 100644 --- a/minifi_rust/extensions/minifi_pgp/src/utils.rs +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -1,9 +1,30 @@ -use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use minifi_native::{ + MinifiError, PropertyConstraints, PropertySchema, PropertyType, StandardPropertyValidator, +}; pub(crate) struct Password {} impl PropertySchema for Password { - const CONSTRAINT: Option = None; + const CONSTRAINT: Option = Some(PropertyConstraints::Validator( + StandardPropertyValidator::NonBlankValidator, + )); const IS_REQUIRED: bool = false; } From 400669fbfe7c7764bdf37a96f6b577f82da25405 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Thu, 3 Sep 2026 17:36:59 +0200 Subject: [PATCH 09/25] refresh manifest --- .../ubuntu_22_04_clang_arm_manifest.json | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index 61f2bea9b3..f5d357e015 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12110,25 +12110,6 @@ "processors": [ { "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", @@ -12145,7 +12126,7 @@ "Symmetric Password": { "name": "Symmetric Password", "description": "Password used for decrypting data encrypted with Password-Based Encryption", - "validator": "VALID", + "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "true", "expressionLanguageScope": "NONE" @@ -12163,7 +12144,7 @@ "description": "Decryption Succeeded" } ], - "typeDescription": "Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification.", + "typeDescription": "Decrypt contents of OpenPGP messages.", "supportsDynamicRelationships": "false", "supportsDynamicProperties": "false", "type": "minifi_pgp.processors.decrypt_content.DecryptContentPGP" @@ -12213,7 +12194,7 @@ "Symmetric Password": { "name": "Symmetric Password", "description": "Password used for encrypting data with Password-Based Encryption", - "validator": "VALID", + "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "true", "expressionLanguageScope": "NONE" @@ -12259,7 +12240,7 @@ "Key Passphrase": { "name": "Key Passphrase", "description": "Passphrase used for decrypting Private Keys", - "validator": "VALID", + "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "true", "expressionLanguageScope": "NONE" @@ -12274,7 +12255,7 @@ "propertyDescriptors": { "Keyring": { "name": "Keyring", - "description": "PGP Keyring or Secret Key encoded in ASCII Armor", + "description": "PGP Keyring or Public Key encoded in ASCII Armor", "validator": "VALID", "required": "false", "sensitive": "true", @@ -12282,7 +12263,7 @@ }, "Keyring File": { "name": "Keyring File", - "description": "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", + "description": "File path to PGP Keyring or Public Key encoded in binary or ASCII Armor", "validator": "VALID", "required": "false", "sensitive": "false", From 8c63d4807d5a95445dd06845740962407d1a2722 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Mon, 14 Sep 2026 15:47:55 +0200 Subject: [PATCH 10/25] review changes --- .../controller_services/key_file_property.rs | 4 +-- .../src/controller_services/key_property.rs | 4 +-- .../src/processors/decrypt_content.rs | 9 ++++-- .../src/processors/encrypt_content.rs | 29 +++++++++---------- .../extensions/minifi_pgp/src/utils.rs | 2 +- .../minifi_native/src/api/processor.rs | 3 +- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs index 92a32ebe3f..3f59bc357c 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs @@ -22,7 +22,7 @@ pub(crate) struct SecretKeyFile {} impl PropertySchema for SecretKeyFile { const CONSTRAINT: Option = None; - const IS_REQUIRED: bool = false; + const IS_REQUIRED: bool = true; } impl PropertyType for SecretKeyFile { @@ -48,7 +48,7 @@ impl PropertyType for SecretKeyFile { pub(crate) struct PublicKeyFile {} impl PropertySchema for PublicKeyFile { const CONSTRAINT: Option = None; - const IS_REQUIRED: bool = false; + const IS_REQUIRED: bool = true; } impl PropertyType for PublicKeyFile { diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs index 8bfcf485e2..62fa932e0e 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs @@ -22,7 +22,7 @@ pub(crate) struct SecretKey {} impl PropertySchema for SecretKey { const CONSTRAINT: Option = None; - const IS_REQUIRED: bool = false; + const IS_REQUIRED: bool = true; } impl PropertyType for SecretKey { @@ -45,7 +45,7 @@ impl PropertyType for SecretKey { pub(crate) struct PublicKey {} impl PropertySchema for PublicKey { const CONSTRAINT: Option = None; - const IS_REQUIRED: bool = false; + const IS_REQUIRED: bool = true; } impl PropertyType for PublicKey { diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index c3dc7c53e3..d5a0c6c404 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -34,14 +34,17 @@ pub(crate) struct DecryptContentPGP { } impl Schedule for DecryptContentPGP { - fn schedule(context: &P, _logger: &L) -> Result + fn schedule( + context: &P, + _logger: &L, + ) -> Result where Self: Sized, L: Logger, { let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; - let has_context_service = context.get_raw_property(&PRIVATE_KEY_SERVICE)?.is_some(); - if !has_context_service && symmetric_password.is_none() { + let private_key_service = context.get_controller_service(&PRIVATE_KEY_SERVICE)?; + if private_key_service.is_none() && symmetric_password.is_none() { Err(MinifiError::validation( "Either Symmetric Password or Private Key Service must be set", )) diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index bd5fd0f7aa..f4f8e07d20 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -95,30 +95,29 @@ impl EncryptContentPGP { .map_err(MinifiError::other), } } - - fn check_validity(password: &Option, has_pub_key: bool) -> Result<(), MinifiError> { - if password.is_none() && !has_pub_key { - Err(MinifiError::custom( - "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", - )) - } else { - Ok(()) - } - } } impl Schedule for EncryptContentPGP { - fn schedule(context: &P, _logger: &L) -> Result + fn schedule( + context: &P, + _logger: &L, + ) -> Result where Self: Sized, { - let file_encoding = context.get_property::(&FILE_ENCODING)?; + let file_encoding = context.get_property(&FILE_ENCODING)?; let symmetric_password = context.get_property(&PASSWORD)?; - let has_public_key = context.get_raw_property(&PUBLIC_KEY_SERVICE)?.is_some() - && context.get_raw_property(&PUBLIC_KEY_SEARCH)?.is_some(); + let public_key_service = context.get_controller_service(&PUBLIC_KEY_SERVICE)?; + let public_key_search = context.get_raw_property(&PUBLIC_KEY_SEARCH)?; - Self::check_validity(&symmetric_password, has_public_key)?; + if symmetric_password.is_none() + && (public_key_search.is_none() || public_key_service.is_none()) + { + return Err(MinifiError::custom( + "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", + )); + } Ok(EncryptContentPGP { file_encoding, symmetric_password, diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs b/minifi_rust/extensions/minifi_pgp/src/utils.rs index 2abc09751c..877cc7005f 100644 --- a/minifi_rust/extensions/minifi_pgp/src/utils.rs +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -25,7 +25,7 @@ impl PropertySchema for Password { const CONSTRAINT: Option = Some(PropertyConstraints::Validator( StandardPropertyValidator::NonBlankValidator, )); - const IS_REQUIRED: bool = false; + const IS_REQUIRED: bool = true; } impl PropertyType for Password { diff --git a/minifi_rust/minifi_native/src/api/processor.rs b/minifi_rust/minifi_native/src/api/processor.rs index 8ac4bfb12f..413524e29f 100644 --- a/minifi_rust/minifi_native/src/api/processor.rs +++ b/minifi_rust/minifi_native/src/api/processor.rs @@ -15,12 +15,13 @@ // specific language governing permissions and limitations // under the License. +use crate::GetControllerService; use crate::api::{RawProcessor, ThreadingModel}; use crate::{GetProperty, LogLevel, Logger, MinifiError, ProcessContext}; use std::marker::PhantomData; pub trait Schedule { - fn schedule( + fn schedule( context: &Ctx, logger: &L, ) -> Result From 9c04d2bd7436360aff92f4a7f7090170d5af7ffc Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 10:52:51 +0200 Subject: [PATCH 11/25] renames --- .../src/controller_services/private_key_service.rs | 4 ++-- ...r_service_definition.rs => private_key_service_def.rs} | 0 .../src/controller_services/public_key_service.rs | 4 ++-- ...er_service_definition.rs => public_key_service_def.rs} | 0 .../minifi_pgp/src/processors/decrypt_content.rs | 4 ++-- .../{processor_definition.rs => decrypt_content_def.rs} | 0 .../minifi_pgp/src/processors/encrypt_content.rs | 8 ++++---- .../{processor_definition.rs => encrypt_content_def.rs} | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) rename minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/{controller_service_definition.rs => private_key_service_def.rs} (100%) rename minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/{controller_service_definition.rs => public_key_service_def.rs} (100%) rename minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/{processor_definition.rs => decrypt_content_def.rs} (100%) rename minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/{processor_definition.rs => encrypt_content_def.rs} (96%) 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 index fe1d5b6f5e..e6b772224c 100644 --- 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 @@ -15,8 +15,8 @@ // specific language governing permissions and limitations // under the License. -mod controller_service_definition; -use controller_service_definition::*; +mod private_key_service_def; +use private_key_service_def::*; #[cfg(test)] use crate::controller_services::key_lookup::key_matches; 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/private_key_service_def.rs similarity index 100% rename from minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs rename to minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/private_key_service_def.rs 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 index 63442678d9..f761bd9ada 100644 --- 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 @@ -15,8 +15,8 @@ // specific language governing permissions and limitations // under the License. -mod controller_service_definition; -use controller_service_definition::*; +mod public_key_service_def; +use public_key_service_def::*; use crate::controller_services::key_lookup::key_matches; use minifi_native::macros::ComponentIdentifier; 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/public_key_service_def.rs similarity index 100% rename from minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs rename to minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/public_key_service_def.rs diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index d5a0c6c404..92615a06b5 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -15,9 +15,9 @@ // specific language governing permissions and limitations // under the License. -mod processor_definition; +mod decrypt_content_def; -use processor_definition::*; +use decrypt_content_def::*; use crate::controller_services::private_key_service::PGPPrivateKeyService; 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/decrypt_content_def.rs similarity index 100% rename from minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs rename to minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/decrypt_content_def.rs diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index f4f8e07d20..a3557fb722 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -23,9 +23,9 @@ use minifi_native::{ use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey}; use pgp::types::{Password, StringToKey}; -mod processor_definition; +mod encrypt_content_def; -use processor_definition::*; +use encrypt_content_def::*; use minifi_native::macros::{ComponentIdentifier, PropertyType}; use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; @@ -106,7 +106,7 @@ impl Schedule for EncryptContentPGP { Self: Sized, { let file_encoding = context.get_property(&FILE_ENCODING)?; - let symmetric_password = context.get_property(&PASSWORD)?; + let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; let public_key_service = context.get_controller_service(&PUBLIC_KEY_SERVICE)?; let public_key_search = context.get_raw_property(&PUBLIC_KEY_SEARCH)?; @@ -207,7 +207,7 @@ mod tests { #[test] fn encrypts_via_passphrase() { let mut context = MockProcessContext::new(); - context.properties.insert(PASSWORD.name(), "password"); + context.properties.insert(SYMMETRIC_PASSWORD.name(), "password"); let mut result: Vec = Vec::new(); let mut input_stream = std::io::Cursor::new("foo".as_bytes()); 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/encrypt_content_def.rs similarity index 96% rename from minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs rename to minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs index 602cffc2e3..9ee844805e 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs @@ -26,7 +26,7 @@ use minifi_native::{ pub(crate) const FILE_ENCODING: Property = Property::new("File Encoding", "File Encoding for encryption") .with_default(FileEncoding::Binary.into_str()); -pub(crate) const PASSWORD: Property> = Property::new( +pub(crate) const SYMMETRIC_PASSWORD: Property> = Property::new( "Symmetric Password", "Password used for encrypting data with Password-Based Encryption", ) @@ -68,7 +68,7 @@ impl ProcessorDefinition for EncryptContentPGP { const PROPERTIES: &[PropertyDefinition] = property_definitions![ FILE_ENCODING, - PASSWORD, + SYMMETRIC_PASSWORD, PUBLIC_KEY_SEARCH, PUBLIC_KEY_SERVICE, ]; From b67dcace81395e31f2e318fe0f083e72f8de1d1a Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 10:59:31 +0200 Subject: [PATCH 12/25] move defs into super files --- .../private_key_service.rs | 38 +++++++++- .../private_key_service_def.rs | 48 ------------ .../controller_services/public_key_service.rs | 34 ++++++++- .../public_key_service_def.rs | 43 ----------- .../src/processors/decrypt_content.rs | 65 ++++++++++++++-- .../decrypt_content/decrypt_content_def.rs | 69 ----------------- .../src/processors/encrypt_content.rs | 69 ++++++++++++++++- .../encrypt_content/encrypt_content_def.rs | 75 ------------------- 8 files changed, 190 insertions(+), 251 deletions(-) delete mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/private_key_service_def.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/public_key_service_def.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/decrypt_content_def.rs delete mode 100644 minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs 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 index e6b772224c..666d864c73 100644 --- 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 @@ -15,9 +15,6 @@ // specific language governing permissions and limitations // under the License. -mod private_key_service_def; -use private_key_service_def::*; - #[cfg(test)] use crate::controller_services::key_lookup::key_matches; use minifi_native::macros::ComponentIdentifier; @@ -25,6 +22,7 @@ use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; use pgp::composed::{SignedSecretKey, TheRing}; #[cfg(test)] use pgp::types::KeyDetails; +use service_def::*; #[derive(Debug, ComponentIdentifier)] pub(crate) struct PGPPrivateKeyService { @@ -75,6 +73,40 @@ impl PGPPrivateKeyService { } } +mod service_def { + use crate::controller_services::key_file_property::SecretKeyFile; + use crate::controller_services::key_property::SecretKey; + use crate::controller_services::private_key_service::PGPPrivateKeyService; + use crate::utils; + use minifi_native::{ + ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, + property_definitions, + }; + + pub(super) const KEY_FILE: Property> = Property::new( + "Key File", + "File path to PGP Secret Key encoded in binary or ASCII Armor", + ) + .supports_expression_language(); + + pub(super) const KEY: Property> = + Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); + + pub(super) const KEY_PASSPHRASE: Property> = Property::new( + "Key Passphrase", + "Passphrase used for decrypting Private Keys", + ) + .sensitive(); + + impl ControllerServiceDefinition for PGPPrivateKeyService { + const DESCRIPTION: &'static str = + "PGP Private Key Service provides Private Keys loaded from files or properties"; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![KEY_FILE, KEY, KEY_PASSPHRASE]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/private_key_service_def.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/private_key_service_def.rs deleted file mode 100644 index 80b7f7fdd6..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/private_key_service_def.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use super::PGPPrivateKeyService; -use crate::controller_services::key_file_property::SecretKeyFile; -use crate::controller_services::key_property::SecretKey; -use crate::utils; -use minifi_native::{ - ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, - property_definitions, -}; - -pub(super) const KEY_FILE: Property> = Property::new( - "Key File", - "File path to PGP Secret Key encoded in binary or ASCII Armor", -) -.supports_expression_language(); - -pub(super) const KEY: Property> = - Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); - -pub(super) const KEY_PASSPHRASE: Property> = Property::new( - "Key Passphrase", - "Passphrase used for decrypting Private Keys", -) -.sensitive(); - -impl ControllerServiceDefinition for PGPPrivateKeyService { - const DESCRIPTION: &'static str = - "PGP Private Key Service provides Private Keys loaded from files or properties"; - const PROPERTIES: &'static [PropertyDefinition] = - property_definitions![KEY_FILE, KEY, KEY_PASSPHRASE]; - const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; -} 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 index f761bd9ada..8c634b29c8 100644 --- 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 @@ -15,14 +15,12 @@ // specific language governing permissions and limitations // under the License. -mod public_key_service_def; -use public_key_service_def::*; - use crate::controller_services::key_lookup::key_matches; use minifi_native::macros::ComponentIdentifier; use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; use pgp::composed::SignedPublicKey; use pgp::types::KeyDetails; +use service_def::*; #[derive(Debug, ComponentIdentifier, PartialEq)] pub(crate) struct PGPPublicKeyService { @@ -56,6 +54,36 @@ impl PGPPublicKeyService { } } +mod service_def { + use crate::controller_services::key_file_property::PublicKeyFile; + use crate::controller_services::key_property::PublicKey; + use crate::controller_services::public_key_service::PGPPublicKeyService; + use minifi_native::{ + ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, + property_definitions, + }; + + pub(crate) const KEYRING_FILE: Property> = Property::new( + "Keyring File", + "File path to PGP Keyring or Public Key encoded in binary or ASCII Armor", + ) + .supports_expression_language(); + + pub(crate) const KEYRING: Property> = Property::new( + "Keyring", + "PGP Keyring or Public Key encoded in ASCII Armor", + ) + .sensitive(); + + impl ControllerServiceDefinition for PGPPublicKeyService { + const DESCRIPTION: &'static str = + "PGP Public Key Service providing Public Keys loaded from files"; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![KEYRING_FILE, KEYRING]; + const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/public_key_service_def.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/public_key_service_def.rs deleted file mode 100644 index 3f36bca234..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/public_key_service_def.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use super::PGPPublicKeyService; -use crate::controller_services::key_file_property::PublicKeyFile; -use crate::controller_services::key_property::PublicKey; -use minifi_native::{ - ControllerServiceDefinition, Property, PropertyDefinition, ProvidedInterface, - property_definitions, -}; - -pub(crate) const KEYRING_FILE: Property> = Property::new( - "Keyring File", - "File path to PGP Keyring or Public Key encoded in binary or ASCII Armor", -) -.supports_expression_language(); - -pub(crate) const KEYRING: Property> = Property::new( - "Keyring", - "PGP Keyring or Public Key encoded in ASCII Armor", -) -.sensitive(); - -impl ControllerServiceDefinition for PGPPublicKeyService { - const DESCRIPTION: &'static str = - "PGP Public Key Service providing Public Keys loaded from files"; - const PROPERTIES: &'static [PropertyDefinition] = property_definitions![KEYRING_FILE, KEYRING]; - const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; -} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index 92615a06b5..d70454ccfa 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -15,9 +15,7 @@ // specific language governing permissions and limitations // under the License. -mod decrypt_content_def; - -use decrypt_content_def::*; +use proc_def::*; use crate::controller_services::private_key_service::PGPPrivateKeyService; @@ -126,6 +124,61 @@ impl FlowFileStreamTransform for DecryptContentPGP { } } +mod proc_def { + use super::*; + use crate::controller_services::private_key_service::PGPPrivateKeyService; + use crate::utils; + use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, + PropertyDefinition, Relationship, property_definitions, + }; + + pub(super) 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(super) 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)", + }; + + pub(super) const SYMMETRIC_PASSWORD: Property> = Property::new( + "Symmetric Password", + "Password used for decrypting data encrypted with Password-Based Encryption", + ) + .sensitive(); + + pub(super) const PRIVATE_KEY_SERVICE: Property> = Property::new( + "Private Key Service", + "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", + ); + + pub(super) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Decryption Succeeded", + }; + + pub(super) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Decryption Failed", + }; + + impl ProcessorDefinition for DecryptContentPGP { + const DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = + &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; + const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; + const PROPERTIES: &[PropertyDefinition] = + property_definitions![SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; + } +} + #[cfg(test)] mod tests { use super::*; @@ -163,14 +216,14 @@ mod tests { } #[test] - fn schedules_with_controller() { + fn schedule_fails_with_invalid_controller() { let mut context = MockProcessContext::new(); context.properties.insert( PRIVATE_KEY_SERVICE.name(), - "my_private_key_service".to_string(), + "invalid_private_key_service".to_string(), ); let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); - assert!(decrypt_content.is_ok()); + assert!(decrypt_content.is_err()); } #[derive(Copy, Clone)] diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/decrypt_content_def.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/decrypt_content_def.rs deleted file mode 100644 index b9d1cf4698..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/decrypt_content_def.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use super::DecryptContentPGP; -use crate::controller_services::private_key_service::PGPPrivateKeyService; -use crate::utils; -use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, PropertyDefinition, - Relationship, property_definitions, -}; - -pub(super) 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(super) 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)", -}; - -pub(super) const SYMMETRIC_PASSWORD: Property> = Property::new( - "Symmetric Password", - "Password used for decrypting data encrypted with Password-Based Encryption", -) -.sensitive(); - -pub(super) const PRIVATE_KEY_SERVICE: Property> = Property::new( - "Private Key Service", - "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", -); - -pub(super) const SUCCESS: Relationship = Relationship { - name: "success", - description: "Decryption Succeeded", -}; - -pub(super) const FAILURE: Relationship = Relationship { - name: "failure", - description: "Decryption Failed", -}; - -impl ProcessorDefinition for DecryptContentPGP { - const DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages."; - const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; - const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; - const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; - const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = - &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; - const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; - const PROPERTIES: &[PropertyDefinition] = - property_definitions![SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; -} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index a3557fb722..90ae72ad44 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -23,9 +23,7 @@ use minifi_native::{ use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey}; use pgp::types::{Password, StringToKey}; -mod encrypt_content_def; - -use encrypt_content_def::*; +use proc_def::*; use minifi_native::macros::{ComponentIdentifier, PropertyType}; use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; @@ -169,6 +167,67 @@ impl FlowFileStreamTransform for EncryptContentPGP { } } +mod proc_def { + use super::*; + use crate::controller_services::public_key_service::PGPPublicKeyService; + use crate::utils; + use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, + PropertyDefinition, Relationship, property_definitions, + }; + + pub(crate) const FILE_ENCODING: Property = + Property::new("File Encoding", "File Encoding for encryption") + .with_default(FileEncoding::Binary.into_str()); + pub(crate) const SYMMETRIC_PASSWORD: Property> = Property::new( + "Symmetric Password", + "Password used for encrypting data with Password-Based Encryption", + ) + .sensitive(); + + pub(crate) const PUBLIC_KEY_SEARCH: Property> = Property::new( + "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(); + + pub(crate) const PUBLIC_KEY_SERVICE: Property> = Property::new( + "Public Key Service", + "PGP Public Key Service for encrypting data with Public Key Encryption", + ); + + pub(super) const FILE_ENCODING_ATTR: OutputAttribute = OutputAttribute { + name: "pgp.file.encoding", + relationships: &["success"], + description: "File Encoding", + }; + + pub(super) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Encryption Succeeded", + }; + + pub(super) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Encryption Failed", + }; + + 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] = &[FILE_ENCODING_ATTR]; + const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; + + const PROPERTIES: &[PropertyDefinition] = property_definitions![ + FILE_ENCODING, + SYMMETRIC_PASSWORD, + PUBLIC_KEY_SEARCH, + PUBLIC_KEY_SERVICE, + ]; + } +} + #[cfg(test)] mod tests { use super::*; @@ -207,7 +266,9 @@ mod tests { #[test] fn encrypts_via_passphrase() { let mut context = MockProcessContext::new(); - context.properties.insert(SYMMETRIC_PASSWORD.name(), "password"); + context + .properties + .insert(SYMMETRIC_PASSWORD.name(), "password"); let mut result: Vec = Vec::new(); let mut input_stream = std::io::Cursor::new("foo".as_bytes()); diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs deleted file mode 100644 index 9ee844805e..0000000000 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/encrypt_content_def.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use super::{EncryptContentPGP, FileEncoding}; -use crate::controller_services::public_key_service::PGPPublicKeyService; -use crate::utils; -use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, PropertyDefinition, - Relationship, property_definitions, -}; - -pub(crate) const FILE_ENCODING: Property = - Property::new("File Encoding", "File Encoding for encryption") - .with_default(FileEncoding::Binary.into_str()); -pub(crate) const SYMMETRIC_PASSWORD: Property> = Property::new( - "Symmetric Password", - "Password used for encrypting data with Password-Based Encryption", -) -.sensitive(); - -pub(crate) const PUBLIC_KEY_SEARCH: Property> = Property::new( - "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(); - -pub(crate) const PUBLIC_KEY_SERVICE: Property> = Property::new( - "Public Key Service", - "PGP Public Key Service for encrypting data with Public Key Encryption", -); - -pub(super) const FILE_ENCODING_ATTR: OutputAttribute = OutputAttribute { - name: "pgp.file.encoding", - relationships: &["success"], - description: "File Encoding", -}; - -pub(super) const SUCCESS: Relationship = Relationship { - name: "success", - description: "Encryption Succeeded", -}; - -pub(super) const FAILURE: Relationship = Relationship { - name: "failure", - description: "Encryption Failed", -}; - -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] = &[FILE_ENCODING_ATTR]; - const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; - - const PROPERTIES: &[PropertyDefinition] = property_definitions![ - FILE_ENCODING, - SYMMETRIC_PASSWORD, - PUBLIC_KEY_SEARCH, - PUBLIC_KEY_SERVICE, - ]; -} From de1bd8900c97e8269d71cbf08053db950733ac7b Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 14:36:21 +0200 Subject: [PATCH 13/25] python changes --- .../minifi_pgp/features/encrypt_decrypt.feature | 2 -- .../extensions/minifi_pgp/features/environment.py | 2 ++ .../extensions/minifi_pgp/features/steps/steps.py | 14 +++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature index f7190e6375..202a013075 100644 --- a/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature +++ b/minifi_rust/extensions/minifi_pgp/features/encrypt_decrypt.feature @@ -16,8 +16,6 @@ @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" diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py index 293295a354..19f97a7d79 100644 --- a/minifi_rust/extensions/minifi_pgp/features/environment.py +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -16,6 +16,7 @@ # under the License. import os +from pathlib import Path from minifi_behave.containers.docker_image_builder import DockerImageBuilder from minifi_behave.core.hooks import common_after_scenario, common_before_scenario, get_minifi_container_image @@ -71,6 +72,7 @@ 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) + context.resource_dir = Path(f"{dir_path}/../..") def before_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 index 8a0c4cc0e9..ab80998090 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -15,8 +15,6 @@ # specific language governing permissions and limitations # under the License. -import os -from pathlib import Path import humanfriendly from behave import step, then @@ -34,10 +32,8 @@ @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() + alice_public_key = context.resource_dir / "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) @@ -48,10 +44,8 @@ def step_encrypt_content_with_service(context: MinifiTestContext): @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() + alice_private_key = context.resource_dir / "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) @@ -63,10 +57,8 @@ def step_decrypt_content_for_alice(context: MinifiTestContext): @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() + bob_private_key = context.resource_dir / "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) From 27d679e162a3028c389789800ace138716e00652 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 15:20:57 +0200 Subject: [PATCH 14/25] add_minifi_dependent_option MINIFI_EXTENSION_PGP --- cmake/MiNiFiOptions.cmake | 1 + minifi_rust/CMakeLists.txt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/cmake/MiNiFiOptions.cmake b/cmake/MiNiFiOptions.cmake index 7a898a7658..dcd3a2b265 100644 --- a/cmake/MiNiFiOptions.cmake +++ b/cmake/MiNiFiOptions.cmake @@ -120,6 +120,7 @@ add_minifi_option(ENABLE_CONTROLLER "Enables the build of MiNiFi controller bina add_minifi_option(ENABLE_LLAMACPP "Enables llama.cpp support." ON) add_minifi_option(ENABLE_OPC "Instructs the build system to enable the OPC extension" ON) add_minifi_option(MINIFI_RUST "Enables the build of rust based extensions." OFF) +add_minifi_dependent_option(MINIFI_EXTENSION_PGP "Enables the PGP rust extension." ON "MINIFI_RUST" OFF) add_minifi_option(MINIFI_LMDB "Enables the LMDB extension." OFF) set_minifi_cache_variable(CUSTOM_MALLOC OFF "Overwrite malloc implementation.") diff --git a/minifi_rust/CMakeLists.txt b/minifi_rust/CMakeLists.txt index 63b8a407e1..3166ec5178 100644 --- a/minifi_rust/CMakeLists.txt +++ b/minifi_rust/CMakeLists.txt @@ -39,6 +39,10 @@ if (NOT ENABLE_TEST_PROCESSORS) set_target_properties(cargo-build_minifi_rs_playground PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() +if (NOT MINIFI_EXTENSION_PGP) + set_target_properties(cargo-build_minifi_pgp PROPERTIES EXCLUDE_FROM_ALL TRUE) +endif() + include(CTest) add_test( From 844bf603cf8814e3b2fc2576bfebba098aa8e0eb Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 15:21:14 +0200 Subject: [PATCH 15/25] add pgp documentation to root docs --- CONTROLLERS.md | 36 ++++++++++++++++++++++++++++- PROCESSORS.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 53 +++++++++++++++++++++--------------------- 3 files changed, 125 insertions(+), 27 deletions(-) diff --git a/CONTROLLERS.md b/CONTROLLERS.md index 28fce43e1e..5b639ad6dc 100644 --- a/CONTROLLERS.md +++ b/CONTROLLERS.md @@ -27,6 +27,8 @@ limitations under the License. - [NetworkPrioritizerService](#NetworkPrioritizerService) - [ODBCService](#ODBCService) - [PersistentMapStateStorage](#PersistentMapStateStorage) +- [PGPPrivateKeyService](#PGPPrivateKeyService) +- [PGPPublicKeyService](#PGPPublicKeyService) - [ProxyConfigurationService](#ProxyConfigurationService) - [RocksDbStateStorage](#RocksDbStateStorage) - [SmbConnectionControllerService](#SmbConnectionControllerService) @@ -245,6 +247,39 @@ In the list below, the names of required properties appear in bold. Any other pr | **File** | | | Path to a file to store state | +## 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 Public Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Keyring | | | PGP Keyring or Public Key encoded in ASCII Armor
**Sensitive Property: true** | + + ## ProxyConfigurationService ### Description @@ -393,4 +428,3 @@ In the list below, the names of required properties appear in bold. Any other pr | **Pretty Print XML** | false | true
false | Specifies whether or not the XML should be pretty printed | | **Name of Record Tag** | | | Specifies the name of the XML record tag wrapping the record fields. | | **Name of Root Tag** | | | Specifies the name of the XML root tag wrapping the record set. | - diff --git a/PROCESSORS.md b/PROCESSORS.md index 4c7705b2a6..4d0cac7f95 100644 --- a/PROCESSORS.md +++ b/PROCESSORS.md @@ -26,11 +26,13 @@ limitations under the License. - [ConsumeMQTT](#ConsumeMQTT) - [ConsumeWindowsEventLog](#ConsumeWindowsEventLog) - [ConvertRecord](#ConvertRecord) +- [DecryptContentPGP](#DecryptContentPGP) - [DefragmentText](#DefragmentText) - [DeleteAzureBlobStorage](#DeleteAzureBlobStorage) - [DeleteAzureDataLakeStorage](#DeleteAzureDataLakeStorage) - [DeleteGCSObject](#DeleteGCSObject) - [DeleteS3Object](#DeleteS3Object) +- [EncryptContentPGP](#EncryptContentPGP) - [EvaluateJsonPath](#EvaluateJsonPath) - [ExecuteProcess](#ExecuteProcess) - [ExecuteScript](#ExecuteScript) @@ -436,6 +438,36 @@ In the list below, the names of required properties appear in bold. Any other pr | record.error.message | failure | This attribute provides on failure the error message encountered by the Reader or Writer. | +## DecryptContentPGP + +### Description + +Decrypt contents of OpenPGP messages. + +### 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 | +|---------------------|---------------|------------------|-------------------------------------------------------------------------------------------------------------| +| 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 implementations 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 implementations omit these inherently malleable metadata) | + + ## DefragmentText ### Description @@ -594,6 +626,37 @@ In the list below, the names of required properties appear in bold. Any other pr | failure | FlowFiles are routed to failure relationship | +## 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 | + + ## EvaluateJsonPath ### Description diff --git a/README.md b/README.md index a26a995c5e..b7a1b959ed 100644 --- a/README.md +++ b/README.md @@ -75,32 +75,33 @@ The following table lists the base set of processors. The next table outlines CMAKE flags that correspond with MiNiFi extensions. Extensions that are enabled by default ( such as RocksDB ), can be disabled with the respective CMAKE flag on the command line. -| Extension Set | Processors and Controller Services | CMAKE Flag | -|----------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------| -| Archive Extensions | [ApplyTemplate](PROCESSORS.md#applytemplate)
[CompressContent](PROCESSORS.md#compresscontent)
[ManipulateArchive](PROCESSORS.md#manipulatearchive)
[MergeContent](PROCESSORS.md#mergecontent)
[FocusArchiveEntry](PROCESSORS.md#focusarchiveentry)
[UnfocusArchiveEntry](PROCESSORS.md#unfocusarchiveentry) | -DBUILD_LIBARCHIVE=ON | -| AWS | [AWSCredentialsService](CONTROLLERS.md#awscredentialsservice)
[PutS3Object](PROCESSORS.md#puts3object)
[DeleteS3Object](PROCESSORS.md#deletes3object)
[FetchS3Object](PROCESSORS.md#fetchs3object)
[ListS3](PROCESSORS.md#lists3)
[PutKinesisStream](PROCESSORS.md#putkinesisstream) | -DENABLE_AWS=ON | -| Azure | [AzureStorageCredentialsService](CONTROLLERS.md#azurestoragecredentialsservice)
[PutAzureBlobStorage](PROCESSORS.md#putazureblobstorage)
[DeleteAzureBlobStorage](PROCESSORS.md#deleteazureblobstorage)
[FetchAzureBlobStorage](PROCESSORS.md#fetchazureblobstorage)
[ListAzureBlobStorage](PROCESSORS.md#listazureblobstorage)
[PutAzureDataLakeStorage](PROCESSORS.md#putazuredatalakestorage)
[DeleteAzureDataLakeStorage](PROCESSORS.md#deleteazuredatalakestorage)
[FetchAzureDataLakeStorage](PROCESSORS.md#fetchazuredatalakestorage)
[ListAzureDataLakeStorage](PROCESSORS.md#listazuredatalakestorage) | -DENABLE_AZURE=ON | -| CivetWeb | [ListenHTTP](PROCESSORS.md#listenhttp) | -DENABLE_CIVET=ON | -| Couchbase | [CouchbaseClusterService](CONTROLLERS.md#couchbaseclusterservice)
[PutCouchbaseKey](PROCESSORS.md#putcouchbasekey)
[GetCouchbaseKey](PROCESSORS.md#getcouchbasekey) | -DENABLE_COUCHBASE=ON | -| Elasticsearch | [ElasticsearchCredentialsControllerService](CONTROLLERS.md#elasticsearchcredentialscontrollerservice)
[PostElasticsearch](PROCESSORS.md#postelasticsearch) | -DENABLE_ELASTICSEARCH=ON | -| ExecuteProcess (Linux and macOS) | [ExecuteProcess](PROCESSORS.md#executeprocess) | -DENABLE_EXECUTE_PROCESS=ON | -| Google Cloud Platform | [DeleteGCSObject](PROCESSORS.md#deletegcsobject)
[FetchGCSObject](PROCESSORS.md#fetchgcsobject)
[GCPCredentialsControllerService](CONTROLLERS.md#gcpcredentialscontrollerservice)
[ListGCSBucket](PROCESSORS.md#listgcsbucket)
[PutGCSObject](PROCESSORS.md#putgcsobject) | -DENABLE_GCP=ON | -| Grafana Loki | [PushGrafanaLokiREST](PROCESSORS.md#pushgrafanalokirest)
[PushGrafanaLokiGrpc](PROCESSORS.md#pushgrafanalokigrpc) | -DENABLE_GRAFANA_LOKI=ON | -| Kafka | [PublishKafka](PROCESSORS.md#publishkafka)
[ConsumeKafka](PROCESSORS.md#consumekafka) | -DENABLE_KAFKA=ON | -| Kubernetes (Linux) | [KubernetesControllerService](CONTROLLERS.md#kubernetescontrollerservice) | -DENABLE_KUBERNETES=ON | -| LlamaCpp | [RunLlamaCppInference](PROCESSORS.md#runllamacppinference) | -DENABLE_LLAMACPP=ON | -| Lua Scripting | [ExecuteScript](PROCESSORS.md#executescript) | -DENABLE_LUA_SCRIPTING=ON | -| MQTT | [ConsumeMQTT](PROCESSORS.md#consumemqtt)
[PublishMQTT](PROCESSORS.md#publishmqtt) | -DENABLE_MQTT=ON | -| OPC | [FetchOPCProcessor](PROCESSORS.md#fetchopcprocessor)
[PutOPCProcessor](PROCESSORS.md#putopcprocessor) | -DENABLE_OPC=ON | -| PDH (Windows) | [PerformanceDataMonitor](PROCESSORS.md#performancedatamonitor) | -DENABLE_PDH=ON | -| ProcFs (Linux) | [ProcFsMonitor](PROCESSORS.md#procfsmonitor) | -DENABLE_PROCFS=ON | -| Python Scripting | [ExecuteScript](PROCESSORS.md#executescript)
[**Custom Python Processors**](extensions/python/PYTHON.md) | -DENABLE_PYTHON_SCRIPTING=ON | -| SMB (Windows) | [FetchSmb](PROCESSORS.md#fetchsmb)
[ListSmb](PROCESSORS.md#listsmb)
[PutSmb](PROCESSORS.md#putsmb) | -DENABLE_SMB=ON | -| SFTP | [FetchSFTP](PROCESSORS.md#fetchsftp)
[ListSFTP](PROCESSORS.md#listsftp)
[PutSFTP](PROCESSORS.md#putsftp) | -DENABLE_SFTP=ON | -| SQL | [ExecuteSQL](PROCESSORS.md#executesql)
[PutSQL](PROCESSORS.md#putsql)
[QueryDatabaseTable](PROCESSORS.md#querydatabasetable)
| -DENABLE_SQL=ON | -| Splunk | [PutSplunkHTTP](PROCESSORS.md#putsplunkhttp)
[QuerySplunkIndexingStatus](PROCESSORS.md#querysplunkindexingstatus) | -DENABLE_SPLUNK=ON | -| Systemd (Linux) | [ConsumeJournald](PROCESSORS.md#consumejournald) | -DENABLE_SYSTEMD=ON | -| Windows Event Log (Windows) | [ConsumeWindowsEventLog](PROCESSORS.md#consumewindowseventlog)
[TailEventLog](PROCESSORS.md#taileventlog) | -DENABLE_WEL=ON | +| Extension Set | Processors and Controller Services | CMAKE Flag | +|----------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------| +| Archive Extensions | [ApplyTemplate](PROCESSORS.md#applytemplate)
[CompressContent](PROCESSORS.md#compresscontent)
[ManipulateArchive](PROCESSORS.md#manipulatearchive)
[MergeContent](PROCESSORS.md#mergecontent)
[FocusArchiveEntry](PROCESSORS.md#focusarchiveentry)
[UnfocusArchiveEntry](PROCESSORS.md#unfocusarchiveentry) | -DBUILD_LIBARCHIVE=ON | +| AWS | [AWSCredentialsService](CONTROLLERS.md#awscredentialsservice)
[PutS3Object](PROCESSORS.md#puts3object)
[DeleteS3Object](PROCESSORS.md#deletes3object)
[FetchS3Object](PROCESSORS.md#fetchs3object)
[ListS3](PROCESSORS.md#lists3)
[PutKinesisStream](PROCESSORS.md#putkinesisstream) | -DENABLE_AWS=ON | +| Azure | [AzureStorageCredentialsService](CONTROLLERS.md#azurestoragecredentialsservice)
[PutAzureBlobStorage](PROCESSORS.md#putazureblobstorage)
[DeleteAzureBlobStorage](PROCESSORS.md#deleteazureblobstorage)
[FetchAzureBlobStorage](PROCESSORS.md#fetchazureblobstorage)
[ListAzureBlobStorage](PROCESSORS.md#listazureblobstorage)
[PutAzureDataLakeStorage](PROCESSORS.md#putazuredatalakestorage)
[DeleteAzureDataLakeStorage](PROCESSORS.md#deleteazuredatalakestorage)
[FetchAzureDataLakeStorage](PROCESSORS.md#fetchazuredatalakestorage)
[ListAzureDataLakeStorage](PROCESSORS.md#listazuredatalakestorage) | -DENABLE_AZURE=ON | +| CivetWeb | [ListenHTTP](PROCESSORS.md#listenhttp) | -DENABLE_CIVET=ON | +| Couchbase | [CouchbaseClusterService](CONTROLLERS.md#couchbaseclusterservice)
[PutCouchbaseKey](PROCESSORS.md#putcouchbasekey)
[GetCouchbaseKey](PROCESSORS.md#getcouchbasekey) | -DENABLE_COUCHBASE=ON | +| Elasticsearch | [ElasticsearchCredentialsControllerService](CONTROLLERS.md#elasticsearchcredentialscontrollerservice)
[PostElasticsearch](PROCESSORS.md#postelasticsearch) | -DENABLE_ELASTICSEARCH=ON | +| ExecuteProcess (Linux and macOS) | [ExecuteProcess](PROCESSORS.md#executeprocess) | -DENABLE_EXECUTE_PROCESS=ON | +| Google Cloud Platform | [DeleteGCSObject](PROCESSORS.md#deletegcsobject)
[FetchGCSObject](PROCESSORS.md#fetchgcsobject)
[GCPCredentialsControllerService](CONTROLLERS.md#gcpcredentialscontrollerservice)
[ListGCSBucket](PROCESSORS.md#listgcsbucket)
[PutGCSObject](PROCESSORS.md#putgcsobject) | -DENABLE_GCP=ON | +| Grafana Loki | [PushGrafanaLokiREST](PROCESSORS.md#pushgrafanalokirest)
[PushGrafanaLokiGrpc](PROCESSORS.md#pushgrafanalokigrpc) | -DENABLE_GRAFANA_LOKI=ON | +| Kafka | [PublishKafka](PROCESSORS.md#publishkafka)
[ConsumeKafka](PROCESSORS.md#consumekafka) | -DENABLE_KAFKA=ON | +| Kubernetes (Linux) | [KubernetesControllerService](CONTROLLERS.md#kubernetescontrollerservice) | -DENABLE_KUBERNETES=ON | +| LlamaCpp | [RunLlamaCppInference](PROCESSORS.md#runllamacppinference) | -DENABLE_LLAMACPP=ON | +| Lua Scripting | [ExecuteScript](PROCESSORS.md#executescript) | -DENABLE_LUA_SCRIPTING=ON | +| MQTT | [ConsumeMQTT](PROCESSORS.md#consumemqtt)
[PublishMQTT](PROCESSORS.md#publishmqtt) | -DENABLE_MQTT=ON | +| OPC | [FetchOPCProcessor](PROCESSORS.md#fetchopcprocessor)
[PutOPCProcessor](PROCESSORS.md#putopcprocessor) | -DENABLE_OPC=ON | +| PDH (Windows) | [PerformanceDataMonitor](PROCESSORS.md#performancedatamonitor) | -DENABLE_PDH=ON | +| PGP | [EncryptContentPGP](PROCESSORS.md#encryptcontentpgp)
[DecryptContentPGP](PROCESSORS.md#decryptcontentpgp)
[PGPPublicKeyService](CONTROLLERS.md#pgppublickeyservice)
[PGPPrivateKeyService](CONTROLLERS.md#pgpprivatekeyservice) | -DMINIFI_EXTENSION_PGP=ON -DMINIFI_RUST=ON | +| ProcFs (Linux) | [ProcFsMonitor](PROCESSORS.md#procfsmonitor) | -DENABLE_PROCFS=ON | +| Python Scripting | [ExecuteScript](PROCESSORS.md#executescript)
[**Custom Python Processors**](extensions/python/PYTHON.md) | -DENABLE_PYTHON_SCRIPTING=ON | +| SMB (Windows) | [FetchSmb](PROCESSORS.md#fetchsmb)
[ListSmb](PROCESSORS.md#listsmb)
[PutSmb](PROCESSORS.md#putsmb) | -DENABLE_SMB=ON | +| SFTP | [FetchSFTP](PROCESSORS.md#fetchsftp)
[ListSFTP](PROCESSORS.md#listsftp)
[PutSFTP](PROCESSORS.md#putsftp) | -DENABLE_SFTP=ON | +| SQL | [ExecuteSQL](PROCESSORS.md#executesql)
[PutSQL](PROCESSORS.md#putsql)
[QueryDatabaseTable](PROCESSORS.md#querydatabasetable)
| -DENABLE_SQL=ON | +| Splunk | [PutSplunkHTTP](PROCESSORS.md#putsplunkhttp)
[QuerySplunkIndexingStatus](PROCESSORS.md#querysplunkindexingstatus) | -DENABLE_SPLUNK=ON | +| Systemd (Linux) | [ConsumeJournald](PROCESSORS.md#consumejournald) | -DENABLE_SYSTEMD=ON | +| Windows Event Log (Windows) | [ConsumeWindowsEventLog](PROCESSORS.md#consumewindowseventlog)
[TailEventLog](PROCESSORS.md#taileventlog) | -DENABLE_WEL=ON | Please see our [Python guide](extensions/python/PYTHON.md) on how to write Python processors and use them within MiNiFi C++. From 7dbbf1cda2ef2324730b3f65a1c4a2fe85ccfd56 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 16 Sep 2026 16:14:56 +0200 Subject: [PATCH 16/25] python fixes --- minifi_rust/extensions/minifi_pgp/features/environment.py | 2 +- minifi_rust/extensions/minifi_pgp/features/steps/steps.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py index 19f97a7d79..d24d3e2dba 100644 --- a/minifi_rust/extensions/minifi_pgp/features/environment.py +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -72,12 +72,12 @@ 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) - context.resource_dir = Path(f"{dir_path}/../..") def before_scenario(context, scenario): context.minifi_container_image = "apacheminificpp:minifi_pgp" common_before_scenario(context, scenario) + context.resource_dir = Path(f"{os.path.dirname(os.path.realpath(__file__))}/..") def 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 index ab80998090..1c256407d7 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -33,7 +33,7 @@ @step("an EncryptContentPGP processor with a PGPPublicKeyService is set up") def step_encrypt_content_with_service(context: MinifiTestContext): public_key_service = ControllerService(class_name="PGPPublicKeyService", service_name="my_public_keys") - alice_public_key = context.resource_dir / "test_keys" / "keyring.asc".read_text() + alice_public_key = (context.resource_dir / "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) @@ -45,7 +45,7 @@ def step_encrypt_content_with_service(context: MinifiTestContext): @step("a DecryptContentPGP processor named DecryptAlice with a PGPPrivateKeyService is set up for Alice") def step_decrypt_content_for_alice(context: MinifiTestContext): private_key_service = ControllerService(class_name="PGPPrivateKeyService", service_name="alice_private_key") - alice_private_key = context.resource_dir / "test_keys" / "alice_private.asc".read_text() + alice_private_key = (context.resource_dir / "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) @@ -58,7 +58,7 @@ def step_decrypt_content_for_alice(context: MinifiTestContext): @step("a DecryptContentPGP processor named DecryptBob with a PGPPrivateKeyService is set up for Bob") def step_decrypt_content_for_bob(context: MinifiTestContext): private_key_service = ControllerService(class_name="PGPPrivateKeyService", service_name="bob_private_key") - bob_private_key = context.resource_dir / "test_keys" / "bob_private.asc".read_text() + bob_private_key = (context.resource_dir / "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) From c353a86f5b1f620f57e5548198056c03cc0b0856 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 18 Sep 2026 12:47:19 +0200 Subject: [PATCH 17/25] review changes --- PROCESSORS.md | 21 +++---- .../extensions/minifi_pgp/minifi_pgp.md | 27 +++----- .../controller_services/public_key_service.rs | 3 +- .../src/processors/decrypt_content.rs | 62 ++++--------------- .../src/processors/encrypt_content.rs | 13 ++-- .../minifi_rs_behave/Dockerfile.alpine | 2 +- 6 files changed, 38 insertions(+), 90 deletions(-) diff --git a/PROCESSORS.md b/PROCESSORS.md index 4d0cac7f95..a2753b42fe 100644 --- a/PROCESSORS.md +++ b/PROCESSORS.md @@ -448,24 +448,17 @@ Decrypt contents of OpenPGP messages. 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 | -|---------------------|---------------|------------------|-------------------------------------------------------------------------------------------------------------| -| 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 | +| Name | Default Value | Allowable Values | Description | +|---------------------|---------------|------------------|---------------------------------------------------------------------------------------------------------------| +| Passphrase | | | Passphrase 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 implementations 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 implementations omit these inherently malleable metadata) | +| success | Decryption Succeeded | ## DefragmentText @@ -639,7 +632,7 @@ In the list below, the names of required properties appear in bold. Any other pr | 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** | +| Passphrase | | | Passphrase 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 | @@ -647,8 +640,8 @@ In the list below, the names of required properties appear in bold. Any other pr | Name | Description | |---------|----------------------| -| success | Encryption Succeeded | | failure | Encryption Failed | +| success | Encryption Succeeded | ### Output Attributes diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md index 03480f480c..c15e839505 100644 --- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -35,24 +35,17 @@ Decrypt contents of OpenPGP messages. 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 | -|---------------------|---------------|------------------|-------------------------------------------------------------------------------------------------------------| -| 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 | +| Name | Default Value | Allowable Values | Description | +|---------------------|---------------|------------------|---------------------------------------------------------------------------------------------------------------| +| Passphrase | | | Passphrase 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) | +| success | Decryption Succeeded | ## EncryptContentPGP @@ -68,7 +61,7 @@ In the list below, the names of required properties appear in bold. Any other pr | 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** | +| Passphrase | | | Passphrase 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 | @@ -76,8 +69,8 @@ In the list below, the names of required properties appear in bold. Any other pr | Name | Description | |---------|----------------------| -| success | Encryption Succeeded | | failure | Encryption Failed | +| success | Encryption Succeeded | ### Output Attributes @@ -98,8 +91,8 @@ In the list below, the names of required properties appear in bold. Any other pr | 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 File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | | Key Passphrase | | | Passphrase used for decrypting Private Keys
**Sensitive Property: true** | @@ -115,5 +108,5 @@ In the list below, the names of required properties appear in bold. Any other pr | 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** | +| Keyring | | | PGP Keyring or Public Key encoded in ASCII Armor | +| Keyring File | | | File path to PGP Keyring or Public Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | 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 index 8c634b29c8..c956ad18a5 100644 --- 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 @@ -72,8 +72,7 @@ mod service_def { pub(crate) const KEYRING: Property> = Property::new( "Keyring", "PGP Keyring or Public Key encoded in ASCII Armor", - ) - .sensitive(); + ); impl ControllerServiceDefinition for PGPPublicKeyService { const DESCRIPTION: &'static str = diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index d70454ccfa..e4ceb376e4 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -40,7 +40,7 @@ impl Schedule for DecryptContentPGP { Self: Sized, L: Logger, { - let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; + let symmetric_password = context.get_property(&SYMMETRIC_PASSPHRASE)?; let private_key_service = context.get_controller_service(&PRIVATE_KEY_SERVICE)?; if private_key_service.is_none() && symmetric_password.is_none() { Err(MinifiError::validation( @@ -72,23 +72,6 @@ impl DecryptContentPGP { let (decrypted_msg, _ring_result) = msg.decrypt_the_ring(ring, false)?; Ok(decrypted_msg) } - - fn extract_attributes_from_decrypted_message( - decrypted_msg: &Message, - ) -> Vec<(&'static str, String)> { - let mut res = Vec::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()) { - res.push((LITERAL_DATA_FILENAME.name, file_name.to_string())); - } - // NiFi uses ms timestamp - res.push(( - LITERAL_DATA_MODIFIED.name, - (1000u64 * literal_data_header.created().as_secs() as u64).to_string(), - )); - } - res - } } impl FlowFileStreamTransform for DecryptContentPGP { @@ -116,11 +99,10 @@ impl FlowFileStreamTransform for DecryptContentPGP { .route_err_to_failure()? }; - let attributes_to_add = Self::extract_attributes_from_decrypted_message(&decrypted_msg); let _written_bytes = std::io::copy(&mut decrypted_msg.into_inner(), output_stream).route_err_to_failure()?; - Ok(TransformStreamResult::new(&SUCCESS).with_attributes(attributes_to_add)) + Ok(TransformStreamResult::new(&SUCCESS)) } } @@ -133,21 +115,9 @@ mod proc_def { PropertyDefinition, Relationship, property_definitions, }; - pub(super) 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(super) 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)", - }; - - pub(super) const SYMMETRIC_PASSWORD: Property> = Property::new( - "Symmetric Password", - "Password used for decrypting data encrypted with Password-Based Encryption", + pub(super) const SYMMETRIC_PASSPHRASE: Property> = Property::new( + "Passphrase", + "Passphrase used for decrypting data encrypted with Password-Based Encryption", ) .sensitive(); @@ -171,11 +141,10 @@ mod proc_def { const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; - const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = - &[LITERAL_DATA_FILENAME, LITERAL_DATA_MODIFIED]; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; const PROPERTIES: &[PropertyDefinition] = - property_definitions![SYMMETRIC_PASSWORD, PRIVATE_KEY_SERVICE,]; + property_definitions![SYMMETRIC_PASSPHRASE, PRIVATE_KEY_SERVICE,]; } } @@ -208,9 +177,10 @@ mod tests { #[test] fn schedules_with_password() { let mut context = MockProcessContext::new(); - context - .properties - .insert(SYMMETRIC_PASSWORD.name(), "my_secret_password".to_string()); + context.properties.insert( + SYMMETRIC_PASSPHRASE.name(), + "my_secret_password".to_string(), + ); let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); assert!(decrypt_content.is_ok()); } @@ -267,7 +237,7 @@ mod tests { if let Some(symmetric_password) = symmetric_password { processor_context .properties - .insert(SYMMETRIC_PASSWORD.name(), symmetric_password.to_string()); + .insert(SYMMETRIC_PASSPHRASE.name(), symmetric_password.to_string()); } let decrypt_content = DecryptContentPGP::schedule(&processor_context, &MockLogger::new()) @@ -287,14 +257,6 @@ mod tests { assert_eq!(res.target_relationship_name(), SUCCESS.name); assert_eq!(res.write_status(), IoState::Ok); assert_eq!(output, result_bytes); - let data_modified = res - .get_attribute(LITERAL_DATA_MODIFIED.name) - .unwrap() - .parse::() - .expect("Should be u64"); - assert!(data_modified > 1770000000000); - assert!(data_modified < 1780000000000); - assert!(res.get_attribute(LITERAL_DATA_FILENAME.name).is_some()); } Err(_) => test::assert_routed_to(res, &FAILURE), } diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 90ae72ad44..6b4346ee7c 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -104,7 +104,7 @@ impl Schedule for EncryptContentPGP { Self: Sized, { let file_encoding = context.get_property(&FILE_ENCODING)?; - let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?; + let symmetric_password = context.get_property(&SYMMETRIC_PASSPHRASE)?; let public_key_service = context.get_controller_service(&PUBLIC_KEY_SERVICE)?; let public_key_search = context.get_raw_property(&PUBLIC_KEY_SEARCH)?; @@ -179,9 +179,10 @@ mod proc_def { pub(crate) const FILE_ENCODING: Property = Property::new("File Encoding", "File Encoding for encryption") .with_default(FileEncoding::Binary.into_str()); - pub(crate) const SYMMETRIC_PASSWORD: Property> = Property::new( - "Symmetric Password", - "Password used for encrypting data with Password-Based Encryption", + + pub(crate) const SYMMETRIC_PASSPHRASE: Property> = Property::new( + "Passphrase", + "Passphrase used for encrypting data with Password-Based Encryption", ) .sensitive(); @@ -221,7 +222,7 @@ mod proc_def { const PROPERTIES: &[PropertyDefinition] = property_definitions![ FILE_ENCODING, - SYMMETRIC_PASSWORD, + SYMMETRIC_PASSPHRASE, PUBLIC_KEY_SEARCH, PUBLIC_KEY_SERVICE, ]; @@ -268,7 +269,7 @@ mod tests { let mut context = MockProcessContext::new(); context .properties - .insert(SYMMETRIC_PASSWORD.name(), "password"); + .insert(SYMMETRIC_PASSPHRASE.name(), "password"); let mut result: Vec = Vec::new(); let mut input_stream = std::io::Cursor::new("foo".as_bytes()); diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.alpine b/minifi_rust/minifi_rs_behave/Dockerfile.alpine index 89f2c01987..220c8af4e8 100644 --- a/minifi_rust/minifi_rs_behave/Dockerfile.alpine +++ b/minifi_rust/minifi_rs_behave/Dockerfile.alpine @@ -1,4 +1,4 @@ -FROM rust:alpine3.22 AS chef +FROM rust:alpine3.24 AS chef RUN apk add --no-cache musl-dev gcc g++ clang-dev lld pkgconfig curl tar && cargo install cargo-chef WORKDIR /app From d32ae32377ff76ab909887cbbaa48dfbfa32feef Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 18 Sep 2026 13:04:33 +0200 Subject: [PATCH 18/25] review changes 2 --- CONTROLLERS.md | 12 ++++++------ .../extensions/minifi_pgp/features/steps/steps.py | 2 +- minifi_rust/extensions/minifi_pgp/minifi_pgp.md | 10 +++++----- .../src/controller_services/private_key_service.rs | 10 +++++----- .../minifi_pgp/src/processors/decrypt_content.rs | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CONTROLLERS.md b/CONTROLLERS.md index 5b639ad6dc..22a56add6e 100644 --- a/CONTROLLERS.md +++ b/CONTROLLERS.md @@ -257,11 +257,11 @@ PGP Private Key Service provides Private Keys loaded from files or 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** | +| Name | Default Value | Allowable Values | Description | +|--------------|---------------|------------------|---------------------------------------------------------------------------------------------------------| +| Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Key Password | | | Password used for decrypting Private Keys
**Sensitive Property: true** | ## PGPPublicKeyService @@ -276,8 +276,8 @@ In the list below, the names of required properties appear in bold. Any other pr | Name | Default Value | Allowable Values | Description | |--------------|---------------|------------------|--------------------------------------------------------------------------------------------------------------------| +| Keyring | | | PGP Keyring or Public Key encoded in ASCII Armor | | Keyring File | | | File path to PGP Keyring or Public Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | -| Keyring | | | PGP Keyring or Public Key encoded in ASCII Armor
**Sensitive Property: true** | ## ProxyConfigurationService diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py index 1c256407d7..8e3e46863e 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -47,7 +47,7 @@ def step_decrypt_content_for_alice(context: MinifiTestContext): private_key_service = ControllerService(class_name="PGPPrivateKeyService", service_name="alice_private_key") alice_private_key = (context.resource_dir / "test_keys" / "alice_private.asc").read_text() private_key_service.add_property("Key", alice_private_key) - private_key_service.add_property("Key Passphrase", "whiterabbit") + private_key_service.add_property("Key Password", "whiterabbit") context.get_or_create_default_minifi_container().flow_definition.controller_services.append(private_key_service) processor = Processor("DecryptContentPGP", "DecryptAlice") diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md index c15e839505..e0c6a0b9d9 100644 --- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -89,11 +89,11 @@ PGP Private Key Service provides Private Keys loaded from files or 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 | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | -| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | -| Key Passphrase | | | Passphrase used for decrypting Private Keys
**Sensitive Property: true** | +| Name | Default Value | Allowable Values | Description | +|--------------|---------------|------------------|---------------------------------------------------------------------------------------------------------| +| Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Key Password | | | Password used for decrypting Private Keys
**Sensitive Property: true** | ## PGPPublicKeyService 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 index 666d864c73..eb01595126 100644 --- 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 @@ -38,7 +38,7 @@ impl EnableControllerService for PGPPrivateKeyService { let mut private_keys = context.get_property(&KEY_FILE)?.unwrap_or_default(); private_keys.extend(context.get_property(&KEY)?.unwrap_or_default()); - let passphrase = context.get_property(&KEY_PASSPHRASE)?.unwrap_or_default(); + let passphrase = context.get_property(&KEY_PASSWORD)?.unwrap_or_default(); if private_keys.is_empty() { return Err(MinifiError::validation("Could not load any valid keys")); @@ -92,9 +92,9 @@ mod service_def { pub(super) const KEY: Property> = Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); - pub(super) const KEY_PASSPHRASE: Property> = Property::new( - "Key Passphrase", - "Passphrase used for decrypting Private Keys", + pub(super) const KEY_PASSWORD: Property> = Property::new( + "Key Password", + "Password used for decrypting Private Keys", ) .sensitive(); @@ -102,7 +102,7 @@ mod service_def { const DESCRIPTION: &'static str = "PGP Private Key Service provides Private Keys loaded from files or properties"; const PROPERTIES: &'static [PropertyDefinition] = - property_definitions![KEY_FILE, KEY, KEY_PASSPHRASE]; + property_definitions![KEY_FILE, KEY, KEY_PASSWORD]; const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; } } diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index e4ceb376e4..73fe83d98c 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -210,7 +210,7 @@ mod tests { .insert("Key File", test_utils::get_test_key_path(self.key_filename)); if let Some(passphrase) = self.passphrase { - context.properties.insert("Key Passphrase", passphrase); + context.properties.insert("Key Password", passphrase); } PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable") From 94f4b0f1112bfbdfe34d3d18be2331de663e2f61 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 18 Sep 2026 13:09:34 +0200 Subject: [PATCH 19/25] cargo fmt --- .../src/controller_services/private_key_service.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 index eb01595126..0fab61fb2f 100644 --- 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 @@ -92,11 +92,8 @@ mod service_def { pub(super) const KEY: Property> = Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); - pub(super) const KEY_PASSWORD: Property> = Property::new( - "Key Password", - "Password used for decrypting Private Keys", - ) - .sensitive(); + pub(super) const KEY_PASSWORD: Property> = + Property::new("Key Password", "Password used for decrypting Private Keys").sensitive(); impl ControllerServiceDefinition for PGPPrivateKeyService { const DESCRIPTION: &'static str = From f5639653b86b7d9bc25ed9ea3277813ea44f4e6d Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 18 Sep 2026 15:37:41 +0200 Subject: [PATCH 20/25] refresh manifest --- .../ubuntu_22_04_clang_arm_manifest.json | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index f5d357e015..f9b4df62b3 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12110,6 +12110,14 @@ "processors": [ { "propertyDescriptors": { + "Passphrase": { + "name": "Passphrase", + "description": "Passphrase used for decrypting data encrypted with Password-Based Encryption", + "validator": "NON_BLANK_VALIDATOR", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + }, "Private Key Service": { "typeProvidedByValue": { "type": "minifi_pgp.controller_services.private_key_service.PGPPrivateKeyService", @@ -12122,14 +12130,6 @@ "required": "false", "sensitive": "false", "expressionLanguageScope": "NONE" - }, - "Symmetric Password": { - "name": "Symmetric Password", - "description": "Password used for decrypting data encrypted with Password-Based Encryption", - "validator": "NON_BLANK_VALIDATOR", - "required": "false", - "sensitive": "true", - "expressionLanguageScope": "NONE" } }, "inputRequirement": "INPUT_REQUIRED", @@ -12170,6 +12170,14 @@ } ] }, + "Passphrase": { + "name": "Passphrase", + "description": "Passphrase used for encrypting data with Password-Based Encryption", + "validator": "NON_BLANK_VALIDATOR", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + }, "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", @@ -12190,14 +12198,6 @@ "required": "false", "sensitive": "false", "expressionLanguageScope": "NONE" - }, - "Symmetric Password": { - "name": "Symmetric Password", - "description": "Password used for encrypting data with Password-Based Encryption", - "validator": "NON_BLANK_VALIDATOR", - "required": "false", - "sensitive": "true", - "expressionLanguageScope": "NONE" } }, "inputRequirement": "INPUT_REQUIRED", @@ -12237,9 +12237,9 @@ "sensitive": "false", "expressionLanguageScope": "FLOWFILE_ATTRIBUTES" }, - "Key Passphrase": { - "name": "Key Passphrase", - "description": "Passphrase used for decrypting Private Keys", + "Key Password": { + "name": "Key Password", + "description": "Password used for decrypting Private Keys", "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "true", @@ -12258,7 +12258,7 @@ "description": "PGP Keyring or Public Key encoded in ASCII Armor", "validator": "VALID", "required": "false", - "sensitive": "true", + "sensitive": "false", "expressionLanguageScope": "NONE" }, "Keyring File": { From 2aa8dc385ac0eb9d9cb87958ab018c3ec793bcf3 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Wed, 23 Sep 2026 16:57:53 +0200 Subject: [PATCH 21/25] fixing opus 5's findings -support for encrypting when the encryption key is a sub-key and not primary key -fixing ambigious key search -allow multiple key password (new line delimited) -lazily evaulate get_id -import add_extension_to_minifi_container after rebase and remove duplicated impl -dry fixes for parse::impls -test keys regenerated for more extensive testing --- .../ubuntu_22_04_clang_arm_manifest.json | 2 +- CONTROLLERS.md | 2 +- .../minifi_pgp/features/environment.py | 53 +--- .../extensions/minifi_pgp/minifi_pgp.md | 10 +- .../src/controller_services/encryption_key.rs | 131 +++++++++ .../controller_services/key_file_property.rs | 31 +-- .../src/controller_services/key_lookup.rs | 114 ++++++-- .../src/controller_services/key_parsing.rs | 90 +++++++ .../src/controller_services/key_property.rs | 25 +- .../minifi_pgp/src/controller_services/mod.rs | 2 + .../private_key_service.rs | 94 +++---- .../controller_services/public_key_service.rs | 130 +++++---- .../src/processors/decrypt_content.rs | 112 ++++++++ .../src/processors/encrypt_content.rs | 26 +- .../extensions/minifi_pgp/src/utils.rs | 43 +++ .../minifi_pgp/test_keys/README.txt | 51 +++- .../extensions/minifi_pgp/test_keys/alice.asc | 62 ++--- .../extensions/minifi_pgp/test_keys/alice.gpg | Bin 2175 -> 640 bytes .../minifi_pgp/test_keys/alice_private.asc | 117 ++------ .../minifi_pgp/test_keys/alice_private.gpg | Bin 4220 -> 1291 bytes .../test_keys/ambiguous_keyring.gpg | Bin 0 -> 3227 bytes .../extensions/minifi_pgp/test_keys/dave.asc | 14 + .../minifi_pgp/test_keys/dave_private.asc | 18 ++ .../extensions/minifi_pgp/test_keys/erin.asc | 10 + .../minifi_pgp/test_keys/keyring.asc | 141 ++++------ .../minifi_pgp/test_keys/keyring.gpg | Bin 4080 -> 2545 bytes .../test_keys/mixed_secret_keyring.gpg | Bin 0 -> 1920 bytes .../minifi_pgp/test_keys/secret_keyring.asc | 251 +++++++----------- .../minifi_pgp/test_keys/secret_keyring.gpg | Bin 7427 -> 4498 bytes .../minifi_pgp/test_keys/spoofed_bob.asc | 19 ++ 30 files changed, 934 insertions(+), 614 deletions(-) create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs create mode 100644 minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/ambiguous_keyring.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/dave.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/erin.asc create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/mixed_secret_keyring.gpg create mode 100644 minifi_rust/extensions/minifi_pgp/test_keys/spoofed_bob.asc diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index f9b4df62b3..ed32c5ff74 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12239,7 +12239,7 @@ }, "Key Password": { "name": "Key Password", - "description": "Password used for decrypting Private Keys", + "description": "Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn", "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "true", diff --git a/CONTROLLERS.md b/CONTROLLERS.md index 22a56add6e..af57c16d13 100644 --- a/CONTROLLERS.md +++ b/CONTROLLERS.md @@ -261,7 +261,7 @@ In the list below, the names of required properties appear in bold. Any other pr |--------------|---------------|------------------|---------------------------------------------------------------------------------------------------------| | Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | | Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | -| Key Password | | | Password used for decrypting Private Keys
**Sensitive Property: true** | +| Key Password | | | Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn
**Sensitive Property: true** | ## PGPPublicKeyService diff --git a/minifi_rust/extensions/minifi_pgp/features/environment.py b/minifi_rust/extensions/minifi_pgp/features/environment.py index d24d3e2dba..4652c748a4 100644 --- a/minifi_rust/extensions/minifi_pgp/features/environment.py +++ b/minifi_rust/extensions/minifi_pgp/features/environment.py @@ -18,54 +18,11 @@ import os from pathlib import Path -from minifi_behave.containers.docker_image_builder import DockerImageBuilder -from minifi_behave.core.hooks import common_after_scenario, 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 +from minifi_behave.core.hooks import ( + add_extension_to_minifi_container, + common_after_scenario, + common_before_scenario, +) def before_all(context): diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md index e0c6a0b9d9..5898d25194 100644 --- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -89,11 +89,11 @@ PGP Private Key Service provides Private Keys loaded from files or 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 | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | -| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | -| Key Password | | | Password used for decrypting Private Keys
**Sensitive Property: true** | +| Name | Default Value | Allowable Values | Description | +|--------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | +| Key Password | | | Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn
**Sensitive Property: true** | ## PGPPublicKeyService diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs new file mode 100644 index 0000000000..173f7d4365 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/encryption_key.rs @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use minifi_native::MinifiError; +use pgp::composed::{SignedPublicKey, SignedPublicSubKey}; +use pgp::packet::{PublicKey, SignatureType}; +use pgp::types::KeyDetails; + +/// The component key of a certificate that a message should actually be encrypted to. +/// +/// `SignedPublicKey`'s own `EncryptionKey` implementation always uses the primary key and +/// ignores subkeys, which fails for the layout `gpg --gen-key` produces nowadays: a sign-only +/// primary key (Ed25519) plus a dedicated encryption subkey (Cv25519). +#[derive(Debug)] +pub(crate) enum EncryptionTarget<'a> { + Primary(&'a PublicKey), + Subkey(&'a SignedPublicSubKey), +} + +/// Picks the key of `certificate` to encrypt to. +/// +/// Encryption subkeys are preferred, newest first, the way GnuPG picks them; the primary key is +/// only used when the certificate has no usable encryption subkey. +pub(crate) fn select_encryption_target( + certificate: &SignedPublicKey, +) -> Result, MinifiError> { + let newest_encryption_subkey = certificate + .public_subkeys + .iter() + .filter(|subkey| is_encryption_subkey(subkey)) + .max_by_key(|subkey| subkey.created_at()); + + if let Some(subkey) = newest_encryption_subkey { + return Ok(EncryptionTarget::Subkey(subkey)); + } + + if certificate.primary_key.algorithm().can_encrypt() { + return Ok(EncryptionTarget::Primary(&certificate.primary_key)); + } + + Err(MinifiError::custom(format!( + "Key {} cannot be used for encryption, it has no encryption subkey and its primary key is {:?} which cannot encrypt", + certificate.primary_key.fingerprint(), + certificate.primary_key.algorithm() + ))) +} + +fn is_encryption_subkey(subkey: &SignedPublicSubKey) -> bool { + if !subkey.key.algorithm().can_encrypt() { + return false; + } + + let is_revoked = subkey + .signatures + .iter() + .any(|signature| signature.typ() == Some(SignatureType::SubkeyRevocation)); + if is_revoked { + return false; + } + + subkey + .signatures + .iter() + .filter(|signature| signature.typ() == Some(SignatureType::SubkeyBinding)) + .any(|signature| { + let key_flags = signature.key_flags(); + key_flags.encrypt_comms() || key_flags.encrypt_storage() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::get_test_key_path; + use pgp::composed::Deserializable; + + fn load_certificate(file_name: &str) -> SignedPublicKey { + let (certificate, _headers) = + SignedPublicKey::from_armor_file(get_test_key_path(file_name)).unwrap(); + certificate + } + + #[test] + fn rsa_primary_key_is_used_when_there_is_no_encryption_subkey() { + // alice.asc is an RSA key whose primary key carries the encrypt capability itself. + let certificate = load_certificate("alice.asc"); + assert!(certificate.primary_key.algorithm().can_encrypt()); + assert!(matches!( + select_encryption_target(&certificate).unwrap(), + EncryptionTarget::Primary(_) + )); + } + + #[test] + fn encryption_subkey_is_preferred_over_a_sign_only_primary_key() { + // dave.asc has an Ed25519 sign-only primary key and a Cv25519 encryption subkey, + // the layout `gpg --gen-key` produces by default. + let certificate = load_certificate("dave.asc"); + assert!(!certificate.primary_key.algorithm().can_encrypt()); + + let target = select_encryption_target(&certificate).unwrap(); + let EncryptionTarget::Subkey(subkey) = target else { + panic!("expected the encryption subkey to be selected"); + }; + assert!(subkey.key.algorithm().can_encrypt()); + } + + #[test] + fn sign_only_key_without_encryption_subkey_is_rejected() { + // erin.asc is an Ed25519 sign-only primary key with no subkeys at all. + let certificate = load_certificate("erin.asc"); + let err = select_encryption_target(&certificate) + .unwrap_err() + .to_string(); + assert!(err.contains("cannot be used for encryption"), "{err}"); + } +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs index 3f59bc357c..d367d404f2 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_file_property.rs @@ -15,8 +15,9 @@ // specific language governing permissions and limitations // under the License. +use crate::controller_services::key_parsing::{KeyKind, parse_key_file}; use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; -use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; +use pgp::composed::{SignedPublicKey, SignedSecretKey}; pub(crate) struct SecretKeyFile {} @@ -29,19 +30,7 @@ impl PropertyType for SecretKeyFile { type Output = Vec; fn parse(s: &str) -> Result { - let mut result: Vec = Vec::new(); - if let Ok((keys, _headers)) = SignedSecretKey::from_armor_file_many(s) { - result.extend(keys.filter_map(Result::ok)); - } else if let Ok(keys) = SignedSecretKey::from_file_many(s) { - result.extend(keys.filter_map(Result::ok)); - } - if result.is_empty() { - Err(MinifiError::validation( - "Couldn't load any valid secret keys", - )) - } else { - Ok(result) - } + parse_key_file(s, KeyKind::Secret) } } @@ -55,19 +44,7 @@ impl PropertyType for PublicKeyFile { type Output = Vec; fn parse(s: &str) -> Result { - let mut result: Vec = Vec::new(); - if let Ok((keys, _headers)) = SignedPublicKey::from_armor_file_many(s) { - result.extend(keys.filter_map(Result::ok)); - } else if let Ok(keys) = SignedPublicKey::from_file_many(s) { - result.extend(keys.filter_map(Result::ok)); - } - if result.is_empty() { - Err(MinifiError::validation( - "Couldn't load any valid public keys", - )) - } else { - Ok(result) - } + parse_key_file(s, KeyKind::Public) } } 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 index 9205ff0a74..26481bac92 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_lookup.rs @@ -15,34 +15,80 @@ // specific language governing permissions and limitations // under the License. +use minifi_native::MinifiError; 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 { +fn matches_key_id(key_id: &KeyId, target: &str) -> bool { + key_id.to_string().eq_ignore_ascii_case(target) +} + +fn matches_user_id(details: &SignedKeyDetails, target_lower: &str) -> bool { + details.users.iter().any(|user| { + user.id + .as_str() + .map(|user_id| user_id.to_ascii_lowercase().contains(target_lower)) + .unwrap_or(false) + }) +} + +pub(crate) fn find_unique_key<'a, K, F>( + keys: &'a [K], + target_id: &str, + key_parts: F, +) -> Result<&'a K, MinifiError> +where + F: Fn(&'a K) -> (KeyId, &'a SignedKeyDetails), +{ let target = target_id.trim(); if target.is_empty() { - return false; + return Err(MinifiError::custom("No key search string was given")); } - if key_id.to_string().eq_ignore_ascii_case(target) { - return true; + if let Some(key) = keys + .iter() + .find(|key| matches_key_id(&key_parts(key).0, target)) + { + return Ok(key); } 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) - }) + let mut matches = keys + .iter() + .filter(|key| matches_user_id(key_parts(key).1, &target_lower)); + + let Some(first_match) = matches.next() else { + return Err(MinifiError::custom(format!( + "No key matching '{target}' was found" + ))); + }; + + let ambiguous: Vec = std::iter::once(first_match) + .chain(matches) + .map(|key| key_parts(key).0.to_string()) + .collect(); + if ambiguous.len() > 1 { + return Err(MinifiError::custom(format!( + "'{target}' is ambiguous, it matches {} keys: {}", + ambiguous.len(), + ambiguous.join(", ") + ))); + } + + Ok(first_match) } #[cfg(test)] mod tests { use super::*; + use pgp::composed::SignedKeyDetails; + + /// A stand-in for a key: just the parts `find_unique_key` looks at. + #[derive(Debug)] + struct TestKey { + key_id: KeyId, + details: SignedKeyDetails, + } fn key_id_from_hex(hex: &str) -> KeyId { let mut bytes = [0u8; 8]; @@ -52,22 +98,44 @@ mod tests { KeyId::from(bytes) } + fn find<'a>(keys: &'a [TestKey], target: &str) -> Result<&'a TestKey, MinifiError> { + find_unique_key(keys, target, |key| (key.key_id, &key.details)) + } + + fn no_details() -> SignedKeyDetails { + SignedKeyDetails::new(vec![], vec![], vec![], vec![]) + } + #[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, " ")); + let keys = [TestKey { + key_id: key_id_from_hex("1122334455667788"), + details: no_details(), + }]; + assert!(find(&keys, "").is_err()); + assert!(find(&keys, " ").is_err()); } #[test] fn matches_key_id_case_insensitive() { - let details = SignedKeyDetails::new(vec![], vec![], vec![], vec![]); - let key_id = key_id_from_hex("11ABcdEF33445566"); + let keys = [TestKey { + key_id: key_id_from_hex("11ABcdEF33445566"), + details: no_details(), + }]; - 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")); + assert!(find(&keys, "11abcdef33445566").is_ok()); + assert!(find(&keys, "11ABCDEF33445566").is_ok()); + assert!(find(&keys, "11abcdef3344556").is_err()); // 15 chars + assert!(find(&keys, "abcdef33445566").is_err()); + } + + #[test] + fn missing_key_reports_the_search_string() { + let keys = [TestKey { + key_id: key_id_from_hex("1122334455667788"), + details: no_details(), + }]; + let err = find(&keys, "99aabbccddeeff00").unwrap_err().to_string(); + assert!(err.contains("99aabbccddeeff00"), "{err}"); } } diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs new file mode 100644 index 0000000000..70acdcb27b --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_parsing.rs @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use minifi_native::{GetProperty, MinifiError, Property, PropertyType}; +use pgp::composed::Deserializable; + +/// Describes which flavour of key is being loaded, used for error messages only. +#[derive(Copy, Clone)] +pub(crate) enum KeyKind { + Public, + Secret, +} + +impl KeyKind { + fn no_valid_keys(self) -> MinifiError { + match self { + KeyKind::Public => MinifiError::validation("Couldn't load any valid public keys"), + KeyKind::Secret => MinifiError::validation("Couldn't load any valid secret keys"), + } + } +} + +/// Parses every key found in ASCII Armored `input`. +pub(crate) fn parse_armored_keys( + input: &str, + kind: KeyKind, +) -> Result, MinifiError> { + let mut keys: Vec = Vec::new(); + if let Ok((parsed, _headers)) = T::from_armor_many(input.as_bytes()) { + keys.extend(parsed.filter_map(Result::ok)); + } + non_empty(keys, kind) +} + +/// Parses every key found in the file at `path`, which may be ASCII Armored or binary. +pub(crate) fn parse_key_file( + path: &str, + kind: KeyKind, +) -> Result, MinifiError> { + let mut keys: Vec = Vec::new(); + if let Ok((parsed, _headers)) = T::from_armor_file_many(path) { + keys.extend(parsed.filter_map(Result::ok)); + } else if let Ok(parsed) = T::from_file_many(path) { + keys.extend(parsed.filter_map(Result::ok)); + } + non_empty(keys, kind) +} + +/// Loads the keys of a controller service from its file property and its inline property, +/// failing when neither yields a usable key. +pub(crate) fn load_service_keys( + context: &Ctx, + file_property: &Property>, + inline_property: &Property>, +) -> Result, MinifiError> +where + Ctx: GetProperty, + File: PropertyType>, + Inline: PropertyType>, +{ + let mut keys = context.get_property(file_property)?.unwrap_or_default(); + keys.extend(context.get_property(inline_property)?.unwrap_or_default()); + + if keys.is_empty() { + return Err(MinifiError::validation("Could not load any valid keys")); + } + Ok(keys) +} + +fn non_empty(keys: Vec, kind: KeyKind) -> Result, MinifiError> { + if keys.is_empty() { + Err(kind.no_valid_keys()) + } else { + Ok(keys) + } +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs index 62fa932e0e..fc5c629a23 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs @@ -15,8 +15,9 @@ // specific language governing permissions and limitations // under the License. +use crate::controller_services::key_parsing::{KeyKind, parse_armored_keys}; use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; -use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; +use pgp::composed::{SignedPublicKey, SignedSecretKey}; pub(crate) struct SecretKey {} @@ -29,16 +30,7 @@ impl PropertyType for SecretKey { type Output = Vec; fn parse(s: &str) -> Result { - let mut secret_keys: Vec = Vec::new(); - if let Ok((keys, _headers)) = SignedSecretKey::from_armor_many(s.as_bytes()) { - secret_keys.extend(keys.filter_map(Result::ok)); - } - if secret_keys.is_empty() { - return Err(MinifiError::validation( - "Couldn't load any valid secret keys", - )); - } - Ok(secret_keys) + parse_armored_keys(s, KeyKind::Secret) } } @@ -52,15 +44,6 @@ impl PropertyType for PublicKey { type Output = Vec; fn parse(s: &str) -> Result { - let mut public_keys: Vec = Vec::new(); - if let Ok((keys, _headers)) = SignedPublicKey::from_armor_many(s.as_bytes()) { - public_keys.extend(keys.filter_map(Result::ok)); - } - if public_keys.is_empty() { - return Err(MinifiError::validation( - "Couldn't load any valid public keys", - )); - } - Ok(public_keys) + parse_armored_keys(s, KeyKind::Public) } } diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs index bf3fe01e38..3aa314bc0d 100644 --- a/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/mod.rs @@ -15,8 +15,10 @@ // specific language governing permissions and limitations // under the License. +pub(crate) mod encryption_key; mod key_file_property; mod key_lookup; +mod key_parsing; mod key_property; 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 index 0fab61fb2f..cf363da2b4 100644 --- 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 @@ -16,7 +16,8 @@ // under the License. #[cfg(test)] -use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::key_lookup::find_unique_key; +use crate::controller_services::key_parsing::load_service_keys; use minifi_native::macros::ComponentIdentifier; use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; use pgp::composed::{SignedSecretKey, TheRing}; @@ -27,7 +28,7 @@ use service_def::*; #[derive(Debug, ComponentIdentifier)] pub(crate) struct PGPPrivateKeyService { private_keys: Vec, - passphrase: pgp::types::Password, + passphrases: Vec, } impl EnableControllerService for PGPPrivateKeyService { @@ -35,17 +36,12 @@ impl EnableControllerService for PGPPrivateKeyService { where Self: Sized, { - let mut private_keys = context.get_property(&KEY_FILE)?.unwrap_or_default(); - private_keys.extend(context.get_property(&KEY)?.unwrap_or_default()); + let private_keys = load_service_keys(context, &KEY_FILE, &KEY)?; + let passphrases = context.get_property(&KEY_PASSWORD)?.unwrap_or_default(); - let passphrase = context.get_property(&KEY_PASSWORD)?.unwrap_or_default(); - - if private_keys.is_empty() { - return Err(MinifiError::validation("Could not load any valid keys")); - } Ok(Self { private_keys, - passphrase, + passphrases, }) } } @@ -54,7 +50,7 @@ impl PGPPrivateKeyService { pub fn get_the_ring(&'_ self) -> TheRing<'_> { TheRing { secret_keys: self.private_keys.iter().collect(), - key_passwords: vec![&self.passphrase], + key_passwords: self.passphrases.iter().collect(), message_password: vec![], session_keys: vec![], decrypt_options: Default::default(), @@ -62,12 +58,11 @@ impl PGPPrivateKeyService { } #[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(), + pub fn get_secret_key(&self, target_id: &str) -> Result<&SignedSecretKey, MinifiError> { + find_unique_key(&self.private_keys, target_id, |private_key| { + ( + private_key.primary_key.legacy_key_id(), &private_key.details, - target_id, ) }) } @@ -92,8 +87,11 @@ mod service_def { pub(super) const KEY: Property> = Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); - pub(super) const KEY_PASSWORD: Property> = - Property::new("Key Password", "Password used for decrypting Private Keys").sensitive(); + pub(super) const KEY_PASSWORD: Property> = Property::new( + "Key Password", + "Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn", + ) + .sensitive(); impl ControllerServiceDefinition for PGPPrivateKeyService { const DESCRIPTION: &'static str = @@ -136,11 +134,11 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("Alice").is_some()); - assert!(service.get_secret_key("alice@example.com").is_some()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("alice@example.com").is_ok()); - assert!(service.get_secret_key("Bob").is_none()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Bob").is_err()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] @@ -153,18 +151,14 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("A").is_some()); - assert!(service.get_secret_key("Alice").is_some()); - assert!( - service - .get_secret_key("Alice ") - .is_some() - ); + assert!(service.get_secret_key("A").is_ok()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("Alice ").is_ok()); - assert!(service.get_secret_key("").is_none()); + assert!(service.get_secret_key("").is_err()); - assert!(service.get_secret_key("Bob").is_none()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Bob").is_err()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] @@ -177,11 +171,11 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("Alice").is_some()); - assert!(service.get_secret_key("Bob").is_some()); - assert!(service.get_secret_key("bob@home.io").is_some()); - assert!(service.get_secret_key("bob@work.com").is_some()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("Bob").is_ok()); + assert!(service.get_secret_key("bob@home.io").is_ok()); + assert!(service.get_secret_key("bob@work.com").is_ok()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] @@ -194,11 +188,11 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("Alice").is_some()); - assert!(service.get_secret_key("Bob").is_some()); - assert!(service.get_secret_key("bob@home.io").is_some()); - assert!(service.get_secret_key("bob@work.com").is_some()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("Bob").is_ok()); + assert!(service.get_secret_key("bob@home.io").is_ok()); + assert!(service.get_secret_key("bob@work.com").is_ok()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] @@ -212,11 +206,11 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("Alice").is_some()); - assert!(service.get_secret_key("Bob").is_some()); - assert!(service.get_secret_key("bob@home.io").is_some()); - assert!(service.get_secret_key("bob@work.com").is_some()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("Bob").is_ok()); + assert!(service.get_secret_key("bob@home.io").is_ok()); + assert!(service.get_secret_key("bob@work.com").is_ok()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] @@ -230,9 +224,9 @@ mod tests { let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); - assert!(service.get_secret_key("Alice").is_some()); - assert!(service.get_secret_key("Bob").is_none()); - assert!(service.get_secret_key("Carol").is_none()); + assert!(service.get_secret_key("Alice").is_ok()); + assert!(service.get_secret_key("Bob").is_err()); + assert!(service.get_secret_key("Carol").is_err()); } #[test] 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 index c956ad18a5..1fc0987c5f 100644 --- 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 @@ -15,7 +15,8 @@ // specific language governing permissions and limitations // under the License. -use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::key_lookup::find_unique_key; +use crate::controller_services::key_parsing::load_service_keys; use minifi_native::macros::ComponentIdentifier; use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError}; use pgp::composed::SignedPublicKey; @@ -32,24 +33,15 @@ impl EnableControllerService for PGPPublicKeyService { where Self: Sized, { - let mut public_keys = context.get_property(&KEYRING_FILE)?.unwrap_or_default(); - public_keys.extend(context.get_property(&KEYRING)?.unwrap_or_default()); - - if public_keys.is_empty() { - return Err(MinifiError::validation("Could not load any valid keys")); - } + let public_keys = load_service_keys(context, &KEYRING_FILE, &KEYRING)?; Ok(Self { public_keys }) } } 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, - ) + pub fn get(&self, target_id: &str) -> Result<&SignedPublicKey, MinifiError> { + find_unique_key(&self.public_keys, target_id, |public_key| { + (public_key.primary_key.legacy_key_id(), &public_key.details) }) } } @@ -127,11 +119,11 @@ mod tests { 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("Alice").is_ok()); + assert!(controller_service.get("alice@example.com").is_ok()); - assert!(controller_service.get("Bob").is_none()); - assert!(controller_service.get("Carol").is_none()); + assert!(controller_service.get("Bob").is_err()); + assert!(controller_service.get("Carol").is_err()); } #[test] @@ -143,14 +135,14 @@ mod tests { let service = PGPPublicKeyService::enable(&context, &MockLogger::new()) .expect("enable should succeed"); - assert!(service.get("A").is_some()); - assert!(service.get("Alice").is_some()); - assert!(service.get("Alice ").is_some()); + assert!(service.get("A").is_ok()); + assert!(service.get("Alice").is_ok()); + assert!(service.get("Alice ").is_ok()); - assert!(service.get("").is_none()); + assert!(service.get("").is_err()); - assert!(service.get("Bob").is_none()); - assert!(service.get("Carol").is_none()); + assert!(service.get("Bob").is_err()); + assert!(service.get("Carol").is_err()); } #[test] @@ -162,11 +154,11 @@ mod tests { let service = PGPPublicKeyService::enable(&context, &MockLogger::new()) .expect("enable should succeed"); - assert!(service.get("Alice").is_some()); - assert!(service.get("Bob").is_some()); - assert!(service.get("bob@home.io").is_some()); - assert!(service.get("bob@work.com").is_some()); - assert!(service.get("Carol").is_none()); + assert!(service.get("Alice").is_ok()); + assert!(service.get("Bob").is_ok()); + assert!(service.get("bob@home.io").is_ok()); + assert!(service.get("bob@work.com").is_ok()); + assert!(service.get("Carol").is_err()); } #[test] @@ -178,11 +170,11 @@ mod tests { let service = PGPPublicKeyService::enable(&context, &MockLogger::new()) .expect("enable should succeed"); - assert!(service.get("Alice").is_some()); - assert!(service.get("Bob").is_some()); - assert!(service.get("bob@home.io").is_some()); - assert!(service.get("bob@work.com").is_some()); - assert!(service.get("Carol").is_none()); + assert!(service.get("Alice").is_ok()); + assert!(service.get("Bob").is_ok()); + assert!(service.get("bob@home.io").is_ok()); + assert!(service.get("bob@work.com").is_ok()); + assert!(service.get("Carol").is_err()); } #[test] @@ -198,11 +190,11 @@ mod tests { let service = PGPPublicKeyService::enable(&context, &MockLogger::new()) .expect("enable should succeed"); - assert!(service.get("Alice").is_some()); - assert!(service.get("Bob").is_some()); - assert!(service.get("bob@home.io").is_some()); - assert!(service.get("bob@work.com").is_some()); - assert!(service.get("Carol").is_none()); + assert!(service.get("Alice").is_ok()); + assert!(service.get("Bob").is_ok()); + assert!(service.get("bob@home.io").is_ok()); + assert!(service.get("bob@work.com").is_ok()); + assert!(service.get("Carol").is_err()); } #[test] @@ -218,9 +210,9 @@ mod tests { let service = PGPPublicKeyService::enable(&context, &MockLogger::new()) .expect("enable should succeed"); - assert!(service.get("Alice").is_some()); - assert!(service.get("Bob").is_none()); - assert!(service.get("Carol").is_none()); + assert!(service.get("Alice").is_ok()); + assert!(service.get("Bob").is_err()); + assert!(service.get("Carol").is_err()); } #[test] @@ -250,9 +242,55 @@ mod tests { let alice = 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); - assert!(service.get(&key_id_hex).is_some()); - assert!(service.get(&key_id_hex.to_ascii_uppercase()).is_some()); - assert!(service.get(&key_id_hex[..8]).is_none()); - assert!(service.get("0123456789abcdef").is_none()); + assert!(service.get(&key_id_hex).is_ok()); + assert!(service.get(&key_id_hex.to_ascii_uppercase()).is_ok()); + assert!(service.get(&key_id_hex[..8]).is_err()); + assert!(service.get("0123456789abcdef").is_err()); + } + + fn ambiguous_keyring_service() -> PGPPublicKeyService { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("ambiguous_keyring.gpg"), + ); + + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed") + } + + /// A User ID search that matches more than one key must fail loudly instead of picking one + /// of them, otherwise a look-alike key silently becomes the recipient. + #[test] + fn a_user_id_matching_several_keys_is_reported_as_ambiguous() { + let service = ambiguous_keyring_service(); + + // "bob@home.io" is a substring of the look-alike "bob@home.io.attacker.test" too. + let err = service.get("bob@home.io").unwrap_err().to_string(); + assert!(err.contains("ambiguous"), "{err}"); + assert!(err.contains("bob@home.io"), "{err}"); + + // Unaffected searches still resolve. + assert!(service.get("Alice").is_ok()); + assert!(service.get("bob@work.com").is_ok()); + assert!(service.get("bob@home.io.attacker.test").is_ok()); + } + + /// An exact Key ID is never ambiguous, so it stays usable as the way to disambiguate. + #[test] + fn a_key_id_search_wins_over_an_ambiguous_user_id() { + let service = ambiguous_keyring_service(); + + let real_bob = service + .get("bob@work.com") + .expect("the real Bob should be found by his unique User ID"); + let real_bob_key_id = real_bob.primary_key.legacy_key_id().to_string(); + + let found = service + .get(&real_bob_key_id) + .expect("a Key ID search should never be ambiguous"); + assert_eq!( + found.primary_key.legacy_key_id(), + real_bob.primary_key.legacy_key_id() + ); } } diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index 73fe83d98c..e74d9896f1 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -326,6 +326,118 @@ mod tests { ); } + /// Encrypts `content` for `public_key_search` using `keyring_file` and returns the ciphertext. + fn encrypt_for(keyring_file: &str, public_key_search: &str, content: &[u8]) -> Vec { + use crate::controller_services::public_key_service::PGPPublicKeyService; + use crate::processors::encrypt_content::EncryptContentPGP; + + let mut key_service_context = MockControllerServiceContext::new(); + key_service_context.properties.insert( + "Keyring File".to_string(), + test_utils::get_test_key_path(keyring_file), + ); + let key_service = PGPPublicKeyService::enable(&key_service_context, &MockLogger::new()) + .expect("should enable"); + + let mut context = MockProcessContext::new(); + context.properties.extend([ + ("Public Key Service", "my_public_key_service"), + ("Public Key Search", public_key_search), + ]); + context + .controller_services + .insert("my_public_key_service".to_string(), Box::new(key_service)); + + let mut ciphertext: Vec = Vec::new(); + let mut plaintext = std::io::Cursor::new(content); + let encrypt_content = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + encrypt_content + .transform( + &context, + &mut plaintext, + &mut ciphertext, + &MockLogger::new(), + ) + .expect("should encrypt"); + ciphertext + } + + fn decrypt_with(private_key_data: PrivateKeyData, ciphertext: Vec) -> Vec { + let mut context = MockProcessContext::new(); + context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(private_key_data.into_controller()), + ); + context.properties.insert( + PRIVATE_KEY_SERVICE.name(), + "my_private_key_service".to_string(), + ); + + let decrypt_content = + DecryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let mut output: Vec = Vec::new(); + let mut ciphertext = std::io::Cursor::new(ciphertext); + let res = decrypt_content + .transform(&context, &mut ciphertext, &mut output, &MockLogger::new()) + .expect("should decrypt"); + assert_eq!(res.target_relationship_name(), SUCCESS.name); + assert_eq!(res.write_status(), IoState::Ok); + output + } + + /// Dave's primary key is Ed25519, which cannot encrypt; only his Cv25519 subkey can. This + /// round trip only works if EncryptContentPGP encrypts to the subkey. + #[test] + fn round_trip_with_a_sign_only_primary_key() { + let ciphertext = encrypt_for("dave.asc", "dave@example.com", b"for dave only"); + + let dave_private_key = PrivateKeyData { + key_filename: "dave_private.asc", + passphrase: Some("gardenparty"), + }; + assert_eq!(decrypt_with(dave_private_key, ciphertext), b"for dave only"); + } + + /// A keyring holding keys with different passphrases needs every one of those passphrases, + /// which "Key Password" accepts one per line. + #[test] + fn decrypts_with_one_of_several_key_passwords() { + let ciphertext = encrypt_for("dave.asc", "dave@example.com", b"for dave only"); + + // mixed_secret_keyring.gpg holds Alice (whiterabbit) and Dave (gardenparty). + let both_passwords = PrivateKeyData { + key_filename: "mixed_secret_keyring.gpg", + passphrase: Some("whiterabbit\ngardenparty"), + }; + assert_eq!( + decrypt_with(both_passwords, ciphertext.clone()), + b"for dave only" + ); + + // Alice's passphrase alone cannot unlock Dave's key. + let alice_password_only = PrivateKeyData { + key_filename: "mixed_secret_keyring.gpg", + passphrase: Some("whiterabbit"), + }; + let mut context = MockProcessContext::new(); + context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(alice_password_only.into_controller()), + ); + context.properties.insert( + PRIVATE_KEY_SERVICE.name(), + "my_private_key_service".to_string(), + ); + let decrypt_content = + DecryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let mut output: Vec = Vec::new(); + let mut ciphertext = std::io::Cursor::new(ciphertext); + let res = + decrypt_content.transform(&context, &mut ciphertext, &mut output, &MockLogger::new()); + test::assert_routed_to(res, &FAILURE); + } + #[test] fn decryption_of_not_encrypted_data() { let alice_private_key = PrivateKeyData { diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 6b4346ee7c..185ad79538 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use crate::controller_services::encryption_key::{EncryptionTarget, select_encryption_target}; use minifi_native::{ FlowFileStreamTransform, GetAttribute, GetControllerService, GetId, GetProperty, InputStream, Logger, MinifiError, OutputStream, ProcessError, RouteErrorExt, Schedule, @@ -73,9 +74,14 @@ impl EncryptContentPGP { ); if let Some(pub_key) = pub_key { - builder - .encrypt_to_key(rand::thread_rng(), pub_key) - .map_err(MinifiError::other)?; + match select_encryption_target(pub_key)? { + EncryptionTarget::Primary(primary_key) => builder + .encrypt_to_key(rand::thread_rng(), primary_key) + .map_err(MinifiError::other)?, + EncryptionTarget::Subkey(subkey) => builder + .encrypt_to_key(rand::thread_rng(), subkey) + .map_err(MinifiError::other)?, + }; } if let Some(password) = &self.symmetric_password { @@ -131,12 +137,7 @@ impl EncryptContentPGP { context.get_property(&PUBLIC_KEY_SEARCH)?, context.get_controller_service(&PUBLIC_KEY_SERVICE)?, ) { - match public_key_service.get(&pub_key_search) { - Some(public_key) => Ok(Some(public_key)), - None => Err(MinifiError::custom(format!( - "No public key matching '{pub_key_search}' found in the configured Public Key Service" - ))), - } + Ok(Some(public_key_service.get(&pub_key_search)?)) } else { Ok(None) } @@ -154,9 +155,10 @@ impl FlowFileStreamTransform for EncryptContentPGP { output_stream: &mut dyn OutputStream, _logger: &LoggerImpl, ) -> Result { - let file_name = context - .get_attribute("filename")? - .unwrap_or(context.get_id()?); + let file_name = match context.get_attribute("filename")? { + Some(file_name) => file_name, + None => context.get_id()?, + }; let public_key = Self::get_public_key(context).route_err_to_failure()?; self.encrypt_bytes(input_stream, output_stream, public_key, file_name) diff --git a/minifi_rust/extensions/minifi_pgp/src/utils.rs b/minifi_rust/extensions/minifi_pgp/src/utils.rs index 877cc7005f..7528e06b3c 100644 --- a/minifi_rust/extensions/minifi_pgp/src/utils.rs +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -35,3 +35,46 @@ impl PropertyType for Password { Ok(pgp::types::Password::from(s)) } } + +/// A newline separated list of passwords, each of which is tried in turn when unlocking a key. +pub(crate) struct Passwords {} + +impl PropertySchema for Passwords { + const CONSTRAINT: Option = Some(PropertyConstraints::Validator( + StandardPropertyValidator::NonBlankValidator, + )); + const IS_REQUIRED: bool = true; +} + +impl PropertyType for Passwords { + type Output = Vec; + + fn parse(s: &str) -> Result { + Ok(s.lines() + .filter(|line| !line.is_empty()) + .map(pgp::types::Password::from) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn single_password() { + assert_eq!(Passwords::parse("hunter2").unwrap().len(), 1); + } + + #[test] + fn one_password_per_line() { + let passwords = Passwords::parse("alice-pw\nbob-pw").unwrap(); + assert_eq!(passwords.len(), 2); + } + + #[test] + fn blank_lines_are_dropped() { + let passwords = Passwords::parse("alice-pw\n\nbob-pw\n").unwrap(); + assert_eq!(passwords.len(), 2); + } +} diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt index 7b8ad1a612..2b403e3275 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt +++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt @@ -1,8 +1,55 @@ -Testing keys v2 +Testing keys v3 ------------------------ uid [ultimate] Alice +keyid BCCE3FDFBA019D7E passphrase whiterabbit +RSA, the primary key carries the encrypt capability itself, no subkeys uid [ultimate] Bob Personal uid [ultimate] Bob Primary -no passphrase \ No newline at end of file +keyid A06749BA4F34B0E5 +no passphrase + +uid [ultimate] Dave +keyid 297B6A88887FB64F +passphrase gardenparty +Ed25519 sign-only primary key plus a Cv25519 encryption subkey, which is the +layout `gpg --gen-key` produces by default. Encrypting to this key only works +if the encryption subkey is selected rather than the primary key. + gpg --quick-generate-key 'Dave ' ed25519 sign never + gpg --quick-add-key cv25519 encr never + +uid [ultimate] Erin +keyid 8C96441440A22F74 +no passphrase +Ed25519 sign-only primary key with no subkeys at all, so it cannot be used for +encryption. Public key only. + gpg --quick-generate-key 'Erin ' ed25519 sign never + +uid [ultimate] Bob Personal +keyid 756FC2EBECB8747C +no passphrase +A look-alike of Bob whose User ID has Bob's own address as a prefix, so that a +"bob@home.io" search matches both keys. Only used to build +ambiguous_keyring.gpg. Public key only. + +Keyrings +------------------------ +keyring.{asc,gpg} public: Alice + Bob +secret_keyring.{asc,gpg} secret: Alice + Bob +ambiguous_keyring.gpg public: Alice + Bob + the look-alike Bob, so + that a "bob@home.io" User ID search is + ambiguous +mixed_secret_keyring.gpg secret: Alice + Dave, i.e. two keys whose + passphrases differ, which requires more than + one Key Password to unlock both + +The binary keyrings are concatenations of the individual dearmored keys: + gpg --dearmor < keyring.asc > keyring.bin + gpg --dearmor < spoofed_bob.asc > spoofed.bin + cat keyring.bin spoofed.bin > ambiguous_keyring.gpg + +v3 note: v2 shipped two distinct Alice keys sharing the User ID +"Alice ", which made a "Alice" key search ambiguous. The +stray key (keyid 1BB0EC4BF35325F6) was dropped; the remaining Alice key is the +one the messages in test_messages/ were encrypted to. diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc index 8ae976d7b2..ebe21a684e 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc +++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice.asc @@ -1,50 +1,18 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -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 -q+zIM+a7/I/TcOmDRO5s -=EEA6 +mQENBGmJ4mMBCADEYKe0I+Mr/IPKlYUVEgb0yC+iRRBfkhPfMQjbjfM3sD2jyf0g +k983pVWF5I4Tl3AFW+D2wAJCayFnIYQWaMnK133EdxZE61ewhvS9g3Zk2Q1JzO6F +B/w4hfCSlPkK82j3Xrd1U0ZJ+YP60DucYlanFPu8+Iw7reusOT9JOJWazzCo4XQX +ImXomT+KVFsN9UDaKv0mSoH5VgG6thQb6veajcYhrN7rU7isTA2hICxo1nLlnCpy +7LbAcN9sP1r5Ukpw0WsXKItol0f29p72biFrLbL7670lRxl/CuHTbHYpbCo7Cdng +R1ieQ3yEqX4qqrKbNocpylCrSSuc78F7pxYNABEBAAG0GUFsaWNlIDxhbGljZUBl +eGFtcGxlLmNvbT6JAVIEEwEIADwWIQSZnIioWmY7HCCheVW8zj/fugGdfgUCaYni +YwIbLwULCQgHAgMiAgEGFQoJCAsCBBYCAwECHgcCF4AACgkQvM4/37oBnX7RUQf7 +BZV8ZPlZxbQaZ8kMabC9meNVaYTOWIjJpDqJ7XqG6mMwvzeA3hvtgQOEEQTYbUcw ++ItlN9XQVrlNAyJZVGJQ+2UXp4XRmdogyShE/zFv/NkxvccfzC/BD5jMy88BS8lu +Gv8bANioE8bF/IwC538A5/6oPN1weQ4OpjcBfeYoyDD32sMc2P5uAWe9pLTAR+vz +F5mWG4K6TWrfH8/YuB6CCQSCr17N9ZPP2rpcPa2miyzQGJy5wdMOiahBFfdvHACO +bJXeKYcxZdLs7ImMw78IbbVkzmEKzCStLTqntL8Bsh+eu4nvescoYkMPUzW1E3P3 +BavsyDPmu/yP03Dpg0TubA== +=+8XC -----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 index 71af439899c8034d0cc23dbeda6ea6c2dd8fdb8d..8ef375e68d6a2fdff52eea414b3969f5b33756de 100644 GIT binary patch delta 9 Qcmew_(7?L+KWhUM02QhO&Hw-a delta 1485 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 literal 0 HcmV?d00001 diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc b/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc new file mode 100644 index 0000000000..e413d1fba2 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/dave.asc @@ -0,0 +1,14 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEarPhDRYJKwYBBAHaRw8BAQdAIutX9mnyeaFBjHNkOMPq9UsWaoZB3An5wfHn +pns7UJ+0F0RhdmUgPGRhdmVAZXhhbXBsZS5jb20+iK8EExYKAFcWIQRTkbiPC69l +G2IDzoApe2qIiH+2TwUCarPhDRsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMC +GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQKXtqiIh/tk8qLgEA7oKJ +95fgapuSqFLqDa2gAemjXHgeox4Jn2LayST3zrYA/ji0PvUzFxuFrgrl5eCGZfmr +FM2rPzkppJ5PAxCA/xQBuDgEarPhDhIKKwYBBAGXVQEFAQEHQBKa7zXTR3mYQ7et +L2oIYWHyRm4kbUmW3E4KGHfyOt4YAwEIB4iUBBgWCgA8FiEEU5G4jwuvZRtiA86A +KXtqiIh/tk8FAmqz4Q4bFIAAAAAABAAObWFudTIsMi41KzEuMTIsMCwzAhsMAAoJ +ECl7aoiIf7ZPnK0A/jt6JWNcf8f8GEQn57O8okYgmCgWFOBQCQ24JTiYfgCNAP9p +Mg/o4PePUr9kkvSNEdK0JKqYAohHzsKj0JszrmWhBg== +=D4Oc +-----END PGP PUBLIC KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc b/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc new file mode 100644 index 0000000000..f0aef7b0e7 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/dave_private.asc @@ -0,0 +1,18 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lIYEarPhDRYJKwYBBAHaRw8BAQdAIutX9mnyeaFBjHNkOMPq9UsWaoZB3An5wfHn +pns7UJ/+BwMCEfVsHfEs1cP8LAg9S+l4EcQP9um39mYC3bQ+JWOWFHHoGsA20tu7 +2ZX0pdvjh46UMVr39w7DAXsgPULKp7Bq+4qudaT0mKLHTd57ENaEV7QXRGF2ZSA8 +ZGF2ZUBleGFtcGxlLmNvbT6IrwQTFgoAVxYhBFORuI8Lr2UbYgPOgCl7aoiIf7ZP +BQJqs+ENGxSAAAAAAAQADm1hbnUyLDIuNSsxLjEyLDAsMwIbAwULCQgHAgIiAgYV +CgkICwIEFgIDAQIeBwIXgAAKCRApe2qIiH+2TyouAQDugon3l+Bqm5KoUuoNraAB +6aNceB6jHgmfYtrJJPfOtgD+OLQ+9TMXG4WuCuXl4IZl+asUzas/OSmknk8DEID/ +FAGciwRqs+EOEgorBgEEAZdVAQUBAQdAEprvNdNHeZhDt60vaghhYfJGbiRtSZbc +TgoYd/I63hgDAQgH/gcDAvn3D6dzSBZu/L7P7nkJxxHoW5QfnEpAqoua9mIBSlC8 ++UCTBeMNa13xUVNSlxaz8R+ukFhr4oX0e3nRs5YZm4rwa38rK8k1fOLJYmnD2tiI +lAQYFgoAPBYhBFORuI8Lr2UbYgPOgCl7aoiIf7ZPBQJqs+EOGxSAAAAAAAQADm1h +bnUyLDIuNSsxLjEyLDAsMwIbDAAKCRApe2qIiH+2T5ytAP47eiVjXH/H/BhEJ+ez +vKJGIJgoFhTgUAkNuCU4mH4AjQD/aTIP6OD3j1K/ZJL0jRHStCSqmAKIR87Co9Cb +M65loQY= +=+PcV +-----END PGP PRIVATE KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc b/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc new file mode 100644 index 0000000000..21efeead3d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/erin.asc @@ -0,0 +1,10 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEarPhDxYJKwYBBAHaRw8BAQdA4/aTB+10d9/3Ey4WHi5iXSlP9i9GWqE9xY8k +c/zzqva0F0VyaW4gPGVyaW5AZXhhbXBsZS5jb20+iK8EExYKAFcWIQTRATOyPqzK +Yn5F1i6MlkQUQKIvdAUCarPhDxsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMC +GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQjJZEFECiL3SlhAD/cCjS +ArPCcbLS8v5YUAJvw2M2/9fKSUsUA8zX/by17PUA/R50qJzXFWEbcG73cPda+PdZ +qiCywhY+HrzMJJ5DE7wC +=xbRI +-----END PGP PUBLIC KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc index 0fa816a55d..fb5c7cc62e 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc +++ b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc @@ -1,89 +1,58 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -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 -=fkCr +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 +wA== +=/AVq -----END PGP PUBLIC KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg index 6a84506f9da0f42bfb21b6bb58760d48b7e77aca..9bb901d97a9ad290cd501bb54f2a26048191a81e 100644 GIT binary patch delta 14 Vcmew$|512z0dE7-=KrjP>;N(|1@!;` delta 1490 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 delta 2899 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 x7WT}&rBM@L@Ya2wKz_|iE9maJ Date: Thu, 24 Sep 2026 10:59:53 +0200 Subject: [PATCH 22/25] get_raw_property does EL, so its not a good idea to check that in schedule --- .../extensions/minifi_pgp/src/processors/encrypt_content.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs index 185ad79538..14b6ce3cab 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -113,11 +113,9 @@ impl Schedule for EncryptContentPGP { let symmetric_password = context.get_property(&SYMMETRIC_PASSPHRASE)?; let public_key_service = context.get_controller_service(&PUBLIC_KEY_SERVICE)?; - let public_key_search = context.get_raw_property(&PUBLIC_KEY_SEARCH)?; - if symmetric_password.is_none() - && (public_key_search.is_none() || public_key_service.is_none()) - { + // Given API support we should check if PUBLIC_KEY_SEARCH is set (without EL) + if symmetric_password.is_none() && public_key_service.is_none() { return Err(MinifiError::custom( "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", )); From 77e0a824939d6186ddeaa3034d2c42b41ece351c Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 25 Sep 2026 17:14:37 +0200 Subject: [PATCH 23/25] Key -> Keyring, Key File -> Keyring File v4 tests key (regenerated for faster unit tests) --- minifi_rust/Cargo.toml | 6 + .../minifi_pgp/features/steps/steps.py | 4 +- .../private_key_service.rs | 24 ++- .../src/processors/decrypt_content.rs | 7 +- .../minifi_pgp/test_keys/README.txt | 14 +- .../minifi_pgp/test_keys/alice_private.asc | 49 ++--- .../minifi_pgp/test_keys/alice_private.gpg | Bin 1291 -> 1337 bytes .../minifi_pgp/test_keys/dave_private.asc | 10 +- .../test_keys/mixed_secret_keyring.gpg | Bin 1920 -> 1966 bytes .../minifi_pgp/test_keys/secret_keyring.asc | 183 +++++++++--------- .../minifi_pgp/test_keys/secret_keyring.gpg | Bin 4498 -> 4544 bytes 11 files changed, 158 insertions(+), 139 deletions(-) diff --git a/minifi_rust/Cargo.toml b/minifi_rust/Cargo.toml index 6dc306f11f..8bde74c44c 100644 --- a/minifi_rust/Cargo.toml +++ b/minifi_rust/Cargo.toml @@ -7,3 +7,9 @@ panic = "abort" [profile.dev] panic = "abort" + +# The PGP tests unlock passphrase-protected secret keys, which costs a full S2K hash +# chain per key. Unoptimized that dominates the test suite (6.4s -> 0.4s with this). +# Workspace members are not matched by "*", so our own code stays at opt-level 0. +[profile.dev.package."*"] +opt-level = 2 diff --git a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py index 8e3e46863e..fd56995537 100644 --- a/minifi_rust/extensions/minifi_pgp/features/steps/steps.py +++ b/minifi_rust/extensions/minifi_pgp/features/steps/steps.py @@ -46,7 +46,7 @@ def step_encrypt_content_with_service(context: MinifiTestContext): def step_decrypt_content_for_alice(context: MinifiTestContext): private_key_service = ControllerService(class_name="PGPPrivateKeyService", service_name="alice_private_key") alice_private_key = (context.resource_dir / "test_keys" / "alice_private.asc").read_text() - private_key_service.add_property("Key", alice_private_key) + private_key_service.add_property("Keyring", alice_private_key) private_key_service.add_property("Key Password", "whiterabbit") context.get_or_create_default_minifi_container().flow_definition.controller_services.append(private_key_service) @@ -59,7 +59,7 @@ def step_decrypt_content_for_alice(context: MinifiTestContext): def step_decrypt_content_for_bob(context: MinifiTestContext): private_key_service = ControllerService(class_name="PGPPrivateKeyService", service_name="bob_private_key") bob_private_key = (context.resource_dir / "test_keys" / "bob_private.asc").read_text() - private_key_service.add_property("Key", bob_private_key) + private_key_service.add_property("Keyring", bob_private_key) context.get_or_create_default_minifi_container().flow_definition.controller_services.append(private_key_service) processor = Processor("DecryptContentPGP", "DecryptBob") 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 index cf363da2b4..56171434e3 100644 --- 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 @@ -79,13 +79,13 @@ mod service_def { }; pub(super) const KEY_FILE: Property> = Property::new( - "Key File", + "Keyring File", "File path to PGP Secret Key encoded in binary or ASCII Armor", ) .supports_expression_language(); pub(super) const KEY: Property> = - Property::new("Key", "Secret Key encoded in ASCII Armor").sensitive(); + Property::new("Keyring", "Secret Key encoded in ASCII Armor").sensitive(); pub(super) const KEY_PASSWORD: Property> = Property::new( "Key Password", @@ -128,7 +128,7 @@ mod tests { fn single_armored_key_file() { let mut context = MockControllerServiceContext::new(); context.properties.insert( - "Key File".to_string(), + "Keyring File".to_string(), get_test_key_path("alice_private.asc"), ); @@ -145,7 +145,7 @@ mod tests { fn single_binary_key_file() { let mut context = MockControllerServiceContext::new(); context.properties.insert( - "Key File".to_string(), + "Keyring File".to_string(), get_test_key_path("alice_private.gpg"), ); @@ -165,7 +165,7 @@ mod tests { fn armored_keyring_key_file() { let mut context = MockControllerServiceContext::new(); context.properties.insert( - "Key File".to_string(), + "Keyring File".to_string(), get_test_key_path("secret_keyring.asc"), ); @@ -182,7 +182,7 @@ mod tests { fn binary_keyring_key_file() { let mut context = MockControllerServiceContext::new(); context.properties.insert( - "Key File".to_string(), + "Keyring File".to_string(), get_test_key_path("secret_keyring.gpg"), ); @@ -202,7 +202,9 @@ mod tests { 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); + context + .properties + .insert("Keyring".to_string(), file_content); let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); @@ -220,7 +222,9 @@ mod tests { 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); + context + .properties + .insert("Keyring".to_string(), file_content); let service = PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); @@ -236,7 +240,9 @@ mod tests { 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); + context + .properties + .insert("Keyring".to_string(), file_content); assert!(PGPPrivateKeyService::enable(&context, &MockLogger::new()).is_err()); } diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs index e74d9896f1..f801014101 100644 --- a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -205,9 +205,10 @@ mod tests { 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)); + context.properties.insert( + "Keyring File", + test_utils::get_test_key_path(self.key_filename), + ); if let Some(passphrase) = self.passphrase { context.properties.insert("Key Password", passphrase); diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt index 2b403e3275..86984f128e 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/README.txt +++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt @@ -1,4 +1,4 @@ -Testing keys v3 +Testing keys v4 ------------------------ uid [ultimate] Alice keyid BCCE3FDFBA019D7E @@ -49,7 +49,11 @@ The binary keyrings are concatenations of the individual dearmored keys: gpg --dearmor < spoofed_bob.asc > spoofed.bin cat keyring.bin spoofed.bin > ambiguous_keyring.gpg -v3 note: v2 shipped two distinct Alice keys sharing the User ID -"Alice ", which made a "Alice" key search ambiguous. The -stray key (keyid 1BB0EC4BF35325F6) was dropped; the remaining Alice key is the -one the messages in test_messages/ were encrypted to. +Passphrase protection +------------------------ +Every passphrase-protected secret key here is deliberately protected with the +lowest S2K iteration count gpg will emit (65536, its floor). Unlocking a key +runs the full S2K hash chain, and gpg's own calibrated default of 58720256 means +56 MiB of SHA-1 per protected packet. Dave has two protected packets and each +candidate passphrase is tried against each, which made the decrypt_content tests +take seconds apiece. Keep the low count when regenerating. diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc index b47c968200..6b7ca4d44b 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc +++ b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.asc @@ -1,31 +1,32 @@ -----BEGIN PGP PRIVATE KEY BLOCK----- -lQOYBGmJ4mMBCADEYKe0I+Mr/IPKlYUVEgb0yC+iRRBfkhPfMQjbjfM3sD2jyf0g +lQPGBGmJ4mMBCADEYKe0I+Mr/IPKlYUVEgb0yC+iRRBfkhPfMQjbjfM3sD2jyf0g k983pVWF5I4Tl3AFW+D2wAJCayFnIYQWaMnK133EdxZE61ewhvS9g3Zk2Q1JzO6F B/w4hfCSlPkK82j3Xrd1U0ZJ+YP60DucYlanFPu8+Iw7reusOT9JOJWazzCo4XQX ImXomT+KVFsN9UDaKv0mSoH5VgG6thQb6veajcYhrN7rU7isTA2hICxo1nLlnCpy 7LbAcN9sP1r5Ukpw0WsXKItol0f29p72biFrLbL7670lRxl/CuHTbHYpbCo7Cdng -R1ieQ3yEqX4qqrKbNocpylCrSSuc78F7pxYNABEBAAEAB/9WdfHQa86M4shJzQwD -i2TZqDvkt4Cue1vZdDbgp75ygduZvgh/K+vnDZm6cjclpBLToTDKox47jPxvcj+8 -OBXEg50hf4cj//QjSj/+Ip/hZfkmSZ6onqvrXPlfKE0AB8xqwV6Hvwre7gcwSjc8 -ssVRGfl+KXZgnkH5mVjmTY380r95ZXlH9vjdNhhONc9MeUwOb8OEfT5z1wB4sdJR -p/ThjAdYRDLj0FV1TRtF5X3H5nrEPbxQCg3namJujiGqp4xRgAqMEjEGjr7sX6gg -kS/3AH60BLiECwfO69ZQnbFwJAFa3+NBlLE/aGSdn+UgkJ2Umc/WeNnYxTp8ith0 -8Cs3BADVDRYWWQdwuDzLIxsWWhY3RtD4xO0VWKz8ETo+XcvXUBYHMTKLHSLiQXUx -LJvLBp51wVPKQasyd1OgP08tYC8wY4mSrYqxvKHjjXbaIdYvGPDjk4PB6EZoaZx6 -Jxyjn9o3Klb6BGXFR17wdIqwhseUKPJmyxDkDEDWARN52XFWEwQA6/cY/te9LmK9 -4i3vfy0V4CZRzm+Lb4BbeioOO7tpxEvKXkVSQ5ZNrahgJ/8h6Es05BgFfjIGSF0c -AYvI6aTtHYL2H8CLFuDMYrQCvkR9d643Bze4oOQwKcjdH0t/15iQgNw1kFIhAzhb -sdzF4EETkpbOi4iZVMI1sxnBzjP/518D/jcYq6mkIY5rZIPnZ2DYL8Mzaa1aT9vt -T6Q8ldS/Mcp7kaGfX1yd9aIj7W7o5ZvcZfoWiNbH41QXHOyFUbPpYkR5RwPBb6hC -G5GMVAeLmlfXxDsSjA9/QaudORQxFdoAygVCa4M64eO6/t4PZMiMD9xspRvyJ7sw -RmZMHam3iGymOcW0GUFsaWNlIDxhbGljZUBleGFtcGxlLmNvbT6JAVIEEwEIADwW -IQSZnIioWmY7HCCheVW8zj/fugGdfgUCaYniYwIbLwULCQgHAgMiAgEGFQoJCAsC -BBYCAwECHgcCF4AACgkQvM4/37oBnX7RUQf7BZV8ZPlZxbQaZ8kMabC9meNVaYTO -WIjJpDqJ7XqG6mMwvzeA3hvtgQOEEQTYbUcw+ItlN9XQVrlNAyJZVGJQ+2UXp4XR -mdogyShE/zFv/NkxvccfzC/BD5jMy88BS8luGv8bANioE8bF/IwC538A5/6oPN1w -eQ4OpjcBfeYoyDD32sMc2P5uAWe9pLTAR+vzF5mWG4K6TWrfH8/YuB6CCQSCr17N -9ZPP2rpcPa2miyzQGJy5wdMOiahBFfdvHACObJXeKYcxZdLs7ImMw78IbbVkzmEK -zCStLTqntL8Bsh+eu4nvescoYkMPUzW1E3P3BavsyDPmu/yP03Dpg0TubA== -=DPaN +R1ieQ3yEqX4qqrKbNocpylCrSSuc78F7pxYNABEBAAH+BwMC47UFwy38Rctg1BsL +M8qlY9DTZ4OSEupNBRmlzoBs8VDNNnMpm97qvxJfG4LVb8/rkqTqsPs4BBBB2rkU +afdstA9z6ctk4fAbbP4+EO5ZNWx4vX0+LbwyfACVZe+lV0XUC7UddKmG4lNzi+3N +aOOQSAUsPVYcKBGWh7QvqjQ6FHsLcCTCJVCwJCJ3HfyLx+RVq64k2kakCRpVXpU7 +q2y3VHzICXney7GtlkuGLOZBjxi/82BQCMn4KqH1BjlA/bE6kCnThisV8C02gg75 +7BKKbzSxmoyMDCtLzPoECEoZOihpNtND/px6yh6QOTYIy/bAv8siPTVEe/ht/4Gz +FEOvYMWkYxaZEiTdaBTXyG5BaIs4ZwOnJAjHQX7De3dbbQGCOIdCvHCJwrtnNMCk +878k9dFm68a1NGK3gEWY7b66sYOXbZDA+ADv3wH2uhE/vMG8yuTfVA7Vb7saUqbf +SVAN3kGgd04T1z1bVGRLMG0q6/2Im+pveTTyPBhMW0hAmZMHXioUi49E78FVBYhy +3fa1NbXIVP2VUXISaPyzU5NLoKJ25PQT0paKB83ICaxWBNC7Htljack30pUgsaht +aPVITGBefrU9dUX0dn+ZbvlhKivQvDi/pZ6ybpMUZ7nvBpCchMwGfpTo+9E6gfMI +PDj0WPOw/8wy5GL9zuYCYzVbjCINJRUV2CYDkcIkXiyGAkbdaVyL/e4b1J9Zd32L ++5QL59ZnQZ14NEDTavzTL6QkKG+Pu6Js2zVm23ycCprUC7l7vIYSS4li3hKRMcb7 +izm7rOephgfeCsCJNoGWIBORhSqmBtT3A9Ui6TZcFnyMq2oY6+eW2lppMq8uYH/n +1+LFc3jIEb4CPMCqwItrGfpAmpfqEdzg4rh6g755KADdMmzygk18CiThb42mVZTj +sFRsEKnlv/46tBlBbGljZSA8YWxpY2VAZXhhbXBsZS5jb20+iQFSBBMBCAA8FiEE +mZyIqFpmOxwgoXlVvM4/37oBnX4FAmmJ4mMCGy8FCwkIBwIDIgIBBhUKCQgLAgQW +AgMBAh4HAheAAAoJELzOP9+6AZ1+0VEH+wWVfGT5WcW0GmfJDGmwvZnjVWmEzliI +yaQ6ie16hupjML83gN4b7YEDhBEE2G1HMPiLZTfV0Fa5TQMiWVRiUPtlF6eF0Zna +IMkoRP8xb/zZMb3HH8wvwQ+YzMvPAUvJbhr/GwDYqBPGxfyMAud/AOf+qDzdcHkO +DqY3AX3mKMgw99rDHNj+bgFnvaS0wEfr8xeZlhuCuk1q3x/P2LgeggkEgq9ezfWT +z9q6XD2tposs0BicucHTDomoQRX3bxwAjmyV3imHMWXS7OyJjMO/CG21ZM5hCswk +rS06p7S/AbIfnruJ73rHKGJDD1M1tRNz9wWr7Mgz5rv8j9Nw6YNE7mw= +=+91n -----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 index 7cb214908244368adf633006c70bf045db4ab401..c0cfc2c529448db9fc3c5700660ae1ce92016f53 100644 GIT binary patch delta 720 zcmV;>0x$iG3b_gcl>^4H0}TOx{s#jBc97g*yeNz;U_?T4t?bGErSNsIXKP_R)6hpH z(QL2f7x~sHWKu!aACX*@2wVBj7pY8P>D#uo!)oqZBa+Wd{~!D{ixSRLS8#@&hQbf@ z1jvj^r2hkjn$yg`GiQ`ou}REy|8Gt=+bzKXSn0q4^evOWcKW98X=JB=Rk~GE^=_Z2 zEp(XX9nQ}yYP-iLTZ*t_+`VO<{L@Xx1bj zCV1yl4L$zM$6-2KwbS6rSCwVatz@;rktU^)sdP}Qm>h|;jjuU>hN#ii3*TS2{X&f6 z$Cj!hD>j|qeBK{vh{@%DT55>i;ow%-RcBiHDfD3CZNk=8yjvBXUnHb~_F8UR5Klb= z6$oYWxD}C#gE00y^P+6-GH*JK1oFsa9?%ca!G32!wVT}P;QlLYcJ3W~i0fWk;%Bnx znjY9%^h>k!;n}<==n9jefcH6#E?J_b=y^ylz%=Z5>Iex!9t!-t=sE@c=b zYN3>CNzpAJ>RB$NZJNhHwD1?B50V~~tS5)k z`Sr0^+`?PInS7OLxNQ7Dr%kfrQgean1y1gSbJq$c=c5E;DWURIUXwhNT$P*)G%8*v zT)9F|q{S<|9CaxNImXTTVW@D~;v(FeA%J9t+HSEL9nLX6&Sd=DsMstUGHW{=0zyw^ zBJmd44Op0sC4kIO6~q~$*v3LdlAM)(nCRao<=rnKIFaRFWdPr{Hpj4O(w?)M18@N` CcWt%+ delta 674 zcmV;T0$u&N3X2K@l>?Zu0}TOx00;k8b@9+^&Wz&7NzDuci)7iTJLI>3u6tYAbT;6p zzH))vnZ5{rE9>VCnYwZ}C8QG5p)ks$9y^TuZ*o7pI2FW$ogsgRBmeXxN9U|gEbulcP%Lblx!BfgXt1@>}pg&J7U@tIZiIS~~vAm(hR2jB@@C5rP4A1OV&z z82;D2E@HjnE$@FV72qaO&TorvfLnSh4m-PP#7oLvMN&hSO|7V4C;uVnOElyd1%5IH zNL?HOi^%Du?Hz*lAHa(i;LKvQ0=`6jcdj=FH@KkWFe%90A4`ANn2>^4H0}TOx{s#jBc97g*yeNz;U_?T4t?bGErSNsIXKP_R)6hpH z(QL2f7x~sHWKu!aACX*@2wVBj7pY8P>D#uo!)oqZBa+Wd{~!D{ixSRLS8#@&hQbf@ z1jvj^r2hkjn$yg`GiQ`ou}REy|8Gt=+bzKXSn0q4^evOWcKW98X=JB=Rk~GE^=_Z2 zEp(XX9nQ}yYP-iLTZ*t_+`VO<{L@Xx1bj zCV1yl4L$zM$6-2KwbS6rSCwVatz@;rktU^)sdP}Qm>h|;jjuU>hN#ii3*TS2{X&f6 z$Cj!hD>j|qeBK{vh{@%DT55>i;ow%-RcBiHDfD3CZNk=8yjvBXUnHb~_F8UR5Klb= z6$oYWxD}C#gE00y^P+6-GH*JK1oFsa9?%ca!G32!wVT}P;QlLYcJ3W~i0fWk;%Bnx znjY9%^h>k!;n}<==n9jefcH6#E?J_b=y^ylz%=Z5>Iex!9t!-t=sE@c=b zYN3>CNzpAJ>RB$NZJNhHwD1?B50V~~tS5)k z`Sr0^+`?PInS7OLxNQ7Dr%kfrQgean1y1gSbJq$c=c5E;DWURIUXwhNT$P*)G%8*v zT)9F|q{S<|9CaxNImXTTVW@D~;v(FeA%J9t+HSEL9nLX6&Sd=DsMstUGHW{=0zyw^ zBJmd44Op0sC4kIO6~q~$*v3LdlAM)(nCRao<=rnKIFaRFWdPr{Hpj4O(w?)M1E~Q| zipGkYzDqauV5t9zL21A%c$%E6eQNE(GX8IsZs5`a`B#Pk^o3Top(l@4@s!t$C)zVW z3$qy?o>nnM@BUKJr`E&4a3XIfLj3xyQw58yONQY1ULtEZ1L|Q>A*30BO(Wg}V{j#iTTK9I;8K~g PY$Dy2ZN%twlj;VR0Ib7V delta 843 zcmV-R1GN0E4}cE?l>?Zu0}TOx00;k8b@9+^&Wz&7NzDuci)7iTJLI>3u6tYAbT;6p zzH))vnZ5{rE9>VCnYwZ}C8QG5p)ks$9y^TuZ*o7pI2FW$ogsgRBmeXxN9U|gEbulcP%Lblx!BfgXt1@>}pg&J7U@tIZiIS~~vAm(hR2jB@@C5rP4A1OV&z z82;D2E@HjnE$@FV72qaO&TorvfLnSh4m-PP#7oLvMN&hSO|7V4C;uVnOElyd1%5IH zNL?HOi^%Du?Hz*lAHa(i;LKvQ0=`6jcdj=FH@KkWFe%90A4`ANn2>IG{K$8XI4QpNTQBzWv7PIjmu8>%3;)V2kdC{|$8Jmjm VYkw;%$u)f9$zo~4+SrpA2bQRsnyLT* diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc index 3af68369d8..12a5227a09 100644 --- a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc +++ b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc @@ -1,98 +1,99 @@ -----BEGIN PGP PRIVATE KEY BLOCK----- -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 -=pfOS +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 +=RDvt -----END PGP PRIVATE KEY BLOCK----- diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg index bc5eac46924e0b78aae0543b94f3a728817326c9..6a5c0f02ab3a596d88d5522c5569a61988f29d42 100644 GIT binary patch delta 720 zcmV;>0x$iNBfujAl>^4H0}TOx{s#jB^P=llg|dK%U>#TU`n3We9?gRBq6>ov31_gM zaa3knZCvU;HSg#uo^)Lku`A}L!0%0F$`%HkMHci)NuXSRpY@&Nwlt+a zn+-L@c}ZSVuJk0a^Q4-prV#=`myTg6-mU38aHyf8Cu6*>s+dBRBqc%JtA&S=);hLf z{$JqDYg7sT=qgAEJ+EJ9*pi$LfXC)TdriW1Y7(?Ktja0~P9|DiQonC6pfJL+l89y> zeEBqkh6y^8i3=i?;CwOwT9n#hQ1SV8A8T7buhkqCboP&a2s) zTA2ClW0y13^2(DyX~h>QIbxJ*O}E{aO4aOH3!+;>q_xBU$}k`jqoF>d7=z!uz)kW| zP$N8aZFjSrA3yj+MpQ~zmPrt(Ymo|;qToHsGf7)|-Xn=tpAc?;2mcTa#q&yt^h@P- z<6b1zPX9{;>tXpqR-#nxJEdYD^`2)E3#YIG46Mo$Dh7Q3cNoaq>9Nh~ndzgGK+aD{ z4OPig&Ri|?IFvMuwMC2(HkD@#-qW*yBU^;fZwiUFmbg?mV*nNwyBeJDRuF+`XH!Fj zF=~Xa@Xv=pZ?ousF4F|fa11zs?O5~VbBZLD?fZMqn$ci*kzzw^ZoonXZ3Cx-r$R)i*-+()%ApRKTTL+o$9t0~YAFMA`Y~`b zSIG!2Q-_j6?A}b(gH+ia*{@)&Ul#g6avhyIz8DNT=GNst&DtF@*upsLfwotJsK1|T zplzNd9?yXF*GM-Y+z1I>#gP!q>`D9tQKKc#yvF`qKBW)PD(H5S&9wS>f|9eF1NRN| CeqAa6 delta 674 zcmV;T0$u&UBa$Nnl>?Zu0}TOx00;k8b@9+^&Wz&7NzDuci)7iTJLI>3u6tYAbT;6p zzH))vnZ5{rE9>VCnYwZ}C8QG5p)ks$9y^TuZ*o7pI2FW$ogsgRBmeXxN9U|gEbulcP%Lblx!BfgXt1@>}pg&J7U@tIZiIS~~vAm(hR2jB@@C5rP4A1OV&z z82;D2E@HjnE$@FV72qaO&TorvfLnSh4m-PP#7oLvMN&hSO|7V4C;uVnOElyd1%5IH zNL?HOi^%Du?Hz*lAHa(i;LKvQ0=`6jcdj=FH@KkWFe%90A4`ANn2>%&ZU6uP From 24162d3265d5de0d4027de4b72a99ae3f4a04926 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 25 Sep 2026 17:15:41 +0200 Subject: [PATCH 24/25] docs update --- CONTROLLERS.md | 10 +++++----- minifi_rust/extensions/minifi_pgp/minifi_pgp.md | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTROLLERS.md b/CONTROLLERS.md index af57c16d13..a888c22a5a 100644 --- a/CONTROLLERS.md +++ b/CONTROLLERS.md @@ -257,11 +257,11 @@ PGP Private Key Service provides Private Keys loaded from files or 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 | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | -| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | -| Key Password | | | Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn
**Sensitive Property: true** | +| Name | Default Value | Allowable Values | Description | +|--------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Key Password | | | Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn
**Sensitive Property: true** | +| Keyring | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Keyring File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | ## PGPPublicKeyService diff --git a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md index 5898d25194..e72bc0b3f3 100644 --- a/minifi_rust/extensions/minifi_pgp/minifi_pgp.md +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -91,9 +91,9 @@ In the list below, the names of required properties appear in bold. Any other pr | Name | Default Value | Allowable Values | Description | |--------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Key | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | -| Key File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | | Key Password | | | Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn
**Sensitive Property: true** | +| Keyring | | | Secret Key encoded in ASCII Armor
**Sensitive Property: true** | +| Keyring File | | | File path to PGP Secret Key encoded in binary or ASCII Armor
**Supports Expression Language: true** | ## PGPPublicKeyService From a4cd71878b0ba43e53af2f0fd237f0840f0fbe14 Mon Sep 17 00:00:00 2001 From: Martin Zink Date: Fri, 25 Sep 2026 18:08:29 +0200 Subject: [PATCH 25/25] manifest refresh --- .../ubuntu_22_04_clang_arm_manifest.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index ed32c5ff74..efc67a971b 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12221,29 +12221,29 @@ "controllerServices": [ { "propertyDescriptors": { - "Key": { - "name": "Key", + "Key Password": { + "name": "Key Password", + "description": "Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn", + "validator": "NON_BLANK_VALIDATOR", + "required": "false", + "sensitive": "true", + "expressionLanguageScope": "NONE" + }, + "Keyring": { + "name": "Keyring", "description": "Secret Key encoded in ASCII Armor", "validator": "VALID", "required": "false", "sensitive": "true", "expressionLanguageScope": "NONE" }, - "Key File": { - "name": "Key File", + "Keyring File": { + "name": "Keyring File", "description": "File path to PGP Secret Key encoded in binary or ASCII Armor", "validator": "VALID", "required": "false", "sensitive": "false", "expressionLanguageScope": "FLOWFILE_ATTRIBUTES" - }, - "Key Password": { - "name": "Key Password", - "description": "Password used for decrypting Private Keys. Multiple passwords may be supplied one per line, each of them is tried in turn", - "validator": "NON_BLANK_VALIDATOR", - "required": "false", - "sensitive": "true", - "expressionLanguageScope": "NONE" } }, "typeDescription": "PGP Private Key Service provides Private Keys loaded from files or properties",