This is an automated email from the ASF dual-hosted git repository. martinzink pushed a commit to branch minifi_rust_pgp in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 97b24c8cf4c6f90813a085856abe8dc36182d754 Author: Martin Zink <[email protected]> AuthorDate: Tue Jun 30 17:22:35 2026 +0200 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 ++ .../minifi_pgp/features/encrypt_decrypt.feature | 54 +++++ .../extensions/minifi_pgp/features/environment.py | 73 ++++++ .../extensions/minifi_pgp/features/steps/steps.py | 100 ++++++++ minifi_rust/extensions/minifi_pgp/minifi_pgp.md | 120 ++++++++++ .../src/controller_services/key_lookup.rs | 56 +++++ .../minifi_pgp/src/controller_services/mod.rs | 3 + .../src/controller_services/private_key_service.rs | 94 ++++++++ .../controller_service_definition.rs | 10 + .../private_key_service/properties.rs | 32 +++ .../private_key_service/tests.rs | 214 +++++++++++++++++ .../src/controller_services/public_key_service.rs | 72 ++++++ .../controller_service_definition.rs | 10 + .../public_key_service/properties.rs | 22 ++ .../public_key_service/tests.rs | 245 ++++++++++++++++++++ minifi_rust/extensions/minifi_pgp/src/lib.rs | 23 ++ .../minifi_pgp/src/processors/decrypt_content.rs | 150 ++++++++++++ .../decrypt_content/output_attributes.rs | 13 ++ .../decrypt_content/processor_definition.rs | 22 ++ .../src/processors/decrypt_content/properties.rs | 36 +++ .../processors/decrypt_content/relationships.rs | 11 + .../src/processors/decrypt_content/tests.rs | 253 +++++++++++++++++++++ .../minifi_pgp/src/processors/encrypt_content.rs | 152 +++++++++++++ .../encrypt_content/output_attributes.rs | 7 + .../encrypt_content/processor_definition.rs | 20 ++ .../src/processors/encrypt_content/properties.rs | 46 ++++ .../processors/encrypt_content/relationships.rs | 11 + .../src/processors/encrypt_content/tests.rs | 137 +++++++++++ .../extensions/minifi_pgp/src/processors/mod.rs | 2 + .../extensions/minifi_pgp/src/test_utils/mod.rs | 15 ++ minifi_rust/extensions/minifi_pgp/src/utils.rs | 11 + .../extensions/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 .../extensions/minifi_pgp/test_keys/garbage.gpg | Bin 0 -> 1024 bytes .../extensions/minifi_pgp/test_keys/keyring.asc | 89 ++++++++ .../extensions/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 .../extensions/minifi_pgp/test_keys/truncated.asc | 10 + .../minifi_pgp/test_keys/truncated_private.asc | 17 ++ .../minifi_pgp/test_messages/foo_for_alice.asc | 12 + .../minifi_pgp/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_rust/minifi_rs_behave/Dockerfile.alpine | 2 +- minifi_rust/minifi_rs_behave/Dockerfile.debian | 2 +- 53 files changed, 2558 insertions(+), 2 deletions(-) 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 000000000..cb8c02ddc --- /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 000000000..f9f6d205f --- /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 000000000..af2c6e174 --- /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 000000000..3914e7130 --- /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 000000000..cdec44906 --- /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 000000000..b2a4d6e63 --- /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 000000000..f2d9cbeab --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/minifi_pgp.md @@ -0,0 +1,120 @@ +<!-- +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. +--> + +## 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<br/>PACKAGED | Strategy for writing files to success after decryption | +| Symmetric Password | | | Password used for decrypting data encrypted with Password-Based Encryption<br/>**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<br/>BINARY | File Encoding for encryption | +| Symmetric Password | | | Password used for encrypting data with Password-Based Encryption<br/>**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<br/>**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<br/>**Supports Expression Language: true** | +| Key | | | Secret Key encoded in ASCII Armor<br/>**Sensitive Property: true** | +| Key Passphrase | | | Passphrase used for decrypting Private Keys<br/>**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<br/>**Supports Expression Language: true** | +| Keyring | | | PGP Keyring or Secret Key encoded in ASCII Armor<br/>**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 000000000..cff723a71 --- /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 000000000..d2c83be43 --- /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 000000000..bef534236 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service.rs @@ -0,0 +1,94 @@ +mod controller_service_definition; +mod properties; + +#[cfg(test)] +use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::private_key_service::properties::KEY_PASSPHRASE; +use crate::utils; +use minifi_native::macros::ComponentIdentifier; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; +use pgp::composed::{Deserializable, SignedSecretKey, TheRing}; +#[cfg(test)] +use pgp::types::KeyDetails; +use std::path::PathBuf; + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct PGPPrivateKeyService { + private_keys: Vec<SignedSecretKey>, + passphrase: pgp::types::Password, +} + +impl EnableControllerService for PGPPrivateKeyService { + fn enable<P: GetProperty, L: Logger>(context: &P, logger: &L) -> Result<Self, MinifiError> + where + Self: Sized, + { + let mut private_keys = vec![]; + if let Some(keyring_file_path) = context.get_property::<PathBuf>(&properties::KEY_FILE)? { + if let Ok((keys, _headers)) = SignedSecretKey::from_armor_file_many(&keyring_file_path) + { + collect_keys(keys, &mut private_keys, logger); + } else if let Ok(keys) = SignedSecretKey::from_file_many(keyring_file_path) { + collect_keys(keys, &mut private_keys, logger); + } + } + if let Some(keyring_ascii) = context.get_property::<String>(&properties::KEY)? + && let Ok((keys, _headers)) = SignedSecretKey::from_armor_many(keyring_ascii.as_bytes()) + { + collect_keys(keys, &mut private_keys, logger); + } + + let passphrase = context + .get_property::<utils::Password>(&KEY_PASSPHRASE)? + .unwrap_or_default(); + + if private_keys.is_empty() { + return Err(MinifiError::controller_service_err( + "Could not load any valid keys", + )); + } + Ok(Self { + private_keys, + passphrase, + }) + } +} + +impl PGPPrivateKeyService { + pub fn get_the_ring(&'_ self) -> TheRing<'_> { + TheRing { + secret_keys: self.private_keys.iter().collect(), + key_passwords: vec![&self.passphrase], + message_password: vec![], + session_keys: vec![], + decrypt_options: Default::default(), + } + } + + #[cfg(test)] + pub fn get_secret_key(&self, target_id: &str) -> Option<&SignedSecretKey> { + self.private_keys.iter().find(|private_key| { + key_matches( + &private_key.primary_key.legacy_key_id(), + &private_key.details, + target_id, + ) + }) + } +} + +fn collect_keys<I, L>(keys: I, out: &mut Vec<SignedSecretKey>, logger: &L) +where + I: Iterator<Item = pgp::errors::Result<SignedSecretKey>>, + L: Logger, +{ + for key in keys { + match key { + Ok(k) => out.push(k), + Err(e) => warn!(logger, "Skipping unparseable private key: {}", e), + } + } +} + +#[cfg(test)] +mod tests; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs new file mode 100644 index 000000000..11f80a911 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/controller_service_definition.rs @@ -0,0 +1,10 @@ +use super::PGPPrivateKeyService; +use super::properties::*; +use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface}; + +impl ControllerServiceDefinition for PGPPrivateKeyService { + const DESCRIPTION: &'static str = + "PGP Private Key Service provides Private Keys loaded from files or properties"; + const PROPERTIES: &'static [Property] = &[KEY_FILE, KEY, KEY_PASSPHRASE]; + const PROVIDED_APIS: &'static [ProvidedInterface<Self>] = &[]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs new file mode 100644 index 000000000..6cabc0086 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/properties.rs @@ -0,0 +1,32 @@ +use minifi_native::Property; +use minifi_native::PropertyConstraints::NoConstraints; + +pub(crate) const KEY_FILE: Property = Property { + name: "Key File", + description: "File path to PGP Secret Key encoded in binary or ASCII Armor", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEY: Property = Property { + name: "Key", + description: "Secret Key encoded in ASCII Armor", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEY_PASSPHRASE: Property = Property { + name: "Key Passphrase", + description: "Passphrase used for decrypting Private Keys", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs new file mode 100644 index 000000000..efdb6e39a --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/private_key_service/tests.rs @@ -0,0 +1,214 @@ +use super::PGPPrivateKeyService; +use crate::test_utils::get_test_key_path; +use minifi_native::MinifiError::ControllerServiceError; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, MockControllerServiceContext, MockLogger, +}; + +fn assert_private_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, +) { + if let Err(ControllerServiceError(error)) = + PGPPrivateKeyService::enable(context, &MockLogger::new()) + { + assert_eq!(error, "Could not load any valid keys"); + } else { + panic!("Didnt fail with no_valid_keys"); + } +} + +#[test] +fn test_component_id() { + assert_eq!( + PGPPrivateKeyService::CLASS_NAME, + "minifi_pgp::controller_services::private_key_service::PGPPrivateKeyService" + ); + assert_eq!(PGPPrivateKeyService::GROUP_NAME, "minifi_pgp"); + assert_eq!(PGPPrivateKeyService::VERSION, "0.1.0"); +} + +#[test] +fn default_fails() { + let context = MockControllerServiceContext::new(); + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_binary_keyring_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Key File".to_string(), get_test_key_path("garbage.gpg")); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn armored_public_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("private_mistake.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("truncated_private.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn non_existent_keyfile() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("non_existent.asc"), + ); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn single_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Key File".to_string(), + get_test_key_path("alice_private.asc"), + ); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!( + controller_service + .get_secret_key("[email protected]") + .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 <[email protected]>") + .is_some() + ); + + assert!(controller_service.get_secret_key("<Alice>").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("[email protected]").is_some()); + assert!(controller_service.get_secret_key("[email protected]").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("[email protected]").is_some()); + assert!(controller_service.get_secret_key("[email protected]").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("[email protected]").is_some()); + assert!(controller_service.get_secret_key("[email protected]").is_some()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn armored_single_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + let controller_service = + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable"); + assert!(controller_service.get_secret_key("Alice").is_some()); + assert!(controller_service.get_secret_key("Bob").is_none()); + assert!(controller_service.get_secret_key("Carol").is_none()); +} + +#[test] +fn corrupted_armored_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = std::fs::read_to_string(get_test_key_path("truncated_private.asc")) + .expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn public_ascii_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for test"); + + context.properties.insert("Key".to_string(), file_content); + + assert_private_key_service_enable_fails_with_no_valid_keys(&context); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs new file mode 100644 index 000000000..93f03d20e --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs @@ -0,0 +1,72 @@ +mod controller_service_definition; +mod properties; + +use crate::controller_services::key_lookup::key_matches; +use crate::controller_services::public_key_service::properties::{KEYRING, KEYRING_FILE}; +use minifi_native::macros::ComponentIdentifier; +use minifi_native::{EnableControllerService, GetProperty, Logger, MinifiError, warn}; +use pgp::composed::{Deserializable, SignedPublicKey}; +use pgp::types::KeyDetails; +use std::path::PathBuf; + +#[derive(Debug, ComponentIdentifier, PartialEq)] +pub(crate) struct PGPPublicKeyService { + public_keys: Vec<SignedPublicKey>, +} + +impl EnableControllerService for PGPPublicKeyService { + fn enable<P: GetProperty, L: Logger>(context: &P, logger: &L) -> Result<Self, MinifiError> + where + Self: Sized, + { + let mut public_keys = vec![]; + if let Some(keyring_file_path) = context.get_property::<PathBuf>(&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::<String>(&KEYRING)? + && let Ok((keys, _headers)) = SignedPublicKey::from_armor_many(keyring_ascii.as_bytes()) + { + collect_keys(keys, &mut public_keys, logger); + } + + if public_keys.is_empty() { + return Err(MinifiError::controller_service_err( + "Could not load any valid keys", + )); + } + Ok(Self { public_keys }) + } +} + +fn collect_keys<I, L>(keys: I, out: &mut Vec<SignedPublicKey>, logger: &L) +where + I: Iterator<Item = pgp::errors::Result<SignedPublicKey>>, + L: Logger, +{ + for key in keys { + match key { + Ok(k) => out.push(k), + Err(e) => warn!(logger, "Skipping unparseable public key: {}", e), + } + } +} + +impl PGPPublicKeyService { + pub fn get(&self, target_id: &str) -> Option<&SignedPublicKey> { + self.public_keys.iter().find(|public_key| { + key_matches( + &public_key.primary_key.legacy_key_id(), + &public_key.details, + target_id, + ) + }) + } +} + +#[cfg(test)] +mod tests; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs new file mode 100644 index 000000000..77fb1c34a --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs @@ -0,0 +1,10 @@ +use super::PGPPublicKeyService; +use super::properties::*; +use minifi_native::{ControllerServiceDefinition, Property, ProvidedInterface}; + +impl ControllerServiceDefinition for PGPPublicKeyService { + const DESCRIPTION: &'static str = + "PGP Public Key Service providing Public Keys loaded from files"; + const PROPERTIES: &'static [Property] = &[KEYRING_FILE, KEYRING]; + const PROVIDED_APIS: &'static [ProvidedInterface<Self>] = &[]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs new file mode 100644 index 000000000..2087163ea --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/properties.rs @@ -0,0 +1,22 @@ +use minifi_native::Property; +use minifi_native::PropertyConstraints::NoConstraints; + +pub(crate) const KEYRING_FILE: Property = Property { + name: "Keyring File", + description: "File path to PGP Keyring or Secret Key encoded in binary or ASCII Armor", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const KEYRING: Property = Property { + name: "Keyring", + description: "PGP Keyring or Secret Key encoded in ASCII Armor", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs new file mode 100644 index 000000000..4adbb14b4 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/tests.rs @@ -0,0 +1,245 @@ +use super::PGPPublicKeyService; +use crate::test_utils::get_test_key_path; +use minifi_native::MinifiError::ControllerServiceError; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, MockControllerServiceContext, MockLogger, +}; +use pgp::types::KeyDetails; + +fn assert_public_key_service_enable_fails_with_no_valid_keys( + context: &MockControllerServiceContext, +) { + if let Err(ControllerServiceError(error)) = + PGPPublicKeyService::enable(context, &MockLogger::new()) + { + assert_eq!(error, "Could not load any valid keys"); + } else { + panic!("Didnt fail with no_valid_keys"); + } +} + +#[test] +fn test_component_id() { + assert_eq!( + PGPPublicKeyService::CLASS_NAME, + "minifi_pgp::controller_services::public_key_service::PGPPublicKeyService" + ); + assert_eq!(PGPPublicKeyService::GROUP_NAME, "minifi_pgp"); + assert_eq!(PGPPublicKeyService::VERSION, "0.1.0"); +} + +#[test] +fn default_fails() { + let context = MockControllerServiceContext::new(); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_binary_keyring_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("garbage.gpg")); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn armored_private_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("alice_private.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn corrupted_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("truncated.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn non_existent_keyfile() { + let mut context = MockControllerServiceContext::new(); + context.properties.insert( + "Keyring File".to_string(), + get_test_key_path("non_existent.asc"), + ); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn single_armored_key_file() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("alice.asc")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("[email protected]").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 <[email protected]>") + .is_some() + ); + + assert!(controller_service.get("<Alice>").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("[email protected]").is_some()); + assert!(controller_service.get("[email protected]").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("[email protected]").is_some()); + assert!(controller_service.get("[email protected]").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("[email protected]").is_some()); + assert!(controller_service.get("[email protected]").is_some()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn armored_single_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + assert!(controller_service.get("Alice").is_some()); + assert!(controller_service.get("Bob").is_none()); + assert!(controller_service.get("Carol").is_none()); +} + +#[test] +fn corrupted_armored_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("truncated.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn private_ascii_key() { + let mut context = MockControllerServiceContext::new(); + + let file_content = + std::fs::read_to_string(get_test_key_path("alice_private.asc")).expect("required for test"); + + context + .properties + .insert("Keyring".to_string(), file_content); + + assert_public_key_service_enable_fails_with_no_valid_keys(&context); +} + +#[test] +fn looks_up_by_key_id_hex() { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Keyring File".to_string(), get_test_key_path("alice.asc")); + + let controller_service = + PGPPublicKeyService::enable(&context, &MockLogger::new()).expect("enable should succeed"); + + // Get Alice's Key ID from the loaded key so the test doesn't hard-code hex bytes. + let alice = controller_service.get("Alice").expect("Alice should exist"); + let key_id_hex = alice.primary_key.legacy_key_id().to_string(); + assert_eq!(key_id_hex.len(), 16); + + // Full 16-char hex, both cases, should match. + assert!(controller_service.get(&key_id_hex).is_some()); + assert!( + controller_service + .get(&key_id_hex.to_ascii_uppercase()) + .is_some() + ); + + // A partial or unrelated hex string should not. + assert!(controller_service.get(&key_id_hex[..8]).is_none()); + assert!(controller_service.get("0123456789abcdef").is_none()); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/lib.rs b/minifi_rust/extensions/minifi_pgp/src/lib.rs new file mode 100644 index 000000000..6c0125687 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/lib.rs @@ -0,0 +1,23 @@ +mod controller_services; +mod processors; + +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::decrypt_content::DecryptContentPGP; +use crate::processors::encrypt_content::EncryptContentPGP; +use minifi_native::{FlowFileStreamTransformProcessorType, MultiThreaded}; + +minifi_native::declare_minifi_extension!( + processors: [ + (FlowFileStreamTransformProcessorType, MultiThreaded, EncryptContentPGP), + (FlowFileStreamTransformProcessorType, MultiThreaded, DecryptContentPGP), + ], + controllers: [ + PGPPublicKeyService, + PGPPrivateKeyService, + ] +); + +#[cfg(test)] +mod test_utils; +mod utils; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs new file mode 100644 index 000000000..e97b15e54 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs @@ -0,0 +1,150 @@ +mod output_attributes; +pub(crate) mod properties; +mod relationships; + +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::properties::{ + DECRYPTION_STRATEGY, PRIVATE_KEY_SERVICE, SYMMETRIC_PASSWORD, +}; +use crate::processors::decrypt_content::relationships::{FAILURE, SUCCESS}; +use crate::utils; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use minifi_native::{ + FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, Logger, MinifiError, + OutputStream, Schedule, TransformStreamResult, warn, +}; +use pgp::composed::{Message, TheRing}; +use std::collections::HashMap; +use std::fmt::Debug; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; + +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] +#[strum(serialize_all = "UPPERCASE", const_into_str)] +enum DecryptionStrategy { + Decrypted, + Packaged, +} + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct DecryptContentPGP { + decompress_data: bool, + symmetric_password: Option<pgp::types::Password>, +} + +impl Schedule for DecryptContentPGP { + fn schedule<P: GetProperty, L>(context: &P, _logger: &L) -> Result<Self, MinifiError> + where + Self: Sized, + L: Logger, + { + let decryption_strategy = + context.get_req_property::<DecryptionStrategy>(&DECRYPTION_STRATEGY)?; + + let symmetric_password = context.get_property::<utils::Password>(&SYMMETRIC_PASSWORD)?; + let has_context_service = context + .get_property::<String>(&PRIVATE_KEY_SERVICE)? + .is_some(); + if !has_context_service && symmetric_password.is_none() { + Err(MinifiError::schedule_err( + "Either Symmetric Password or Private Key Service must be set", + )) + } else { + Ok(DecryptContentPGP { + decompress_data: decryption_strategy == DecryptionStrategy::Decrypted, + symmetric_password, + }) + } + } +} + +impl DecryptContentPGP { + fn decrypt_msg<'a>( + &'a self, + msg: Message<'a>, + private_key_service: Option<&'a PGPPrivateKeyService>, + ) -> pgp::errors::Result<Message<'a>> { + let mut ring = if let Some(pks) = private_key_service { + pks.get_the_ring() + } else { + TheRing::default() + }; + + ring.decrypt_options = ring.decrypt_options.enable_gnupg_aead(); + + if let Some(sym_passwd) = &self.symmetric_password { + ring.message_password.push(sym_passwd); + } + let (decrypted_msg, _ring_result) = msg.decrypt_the_ring(ring, false)?; + Ok(decrypted_msg) + } + + fn extract_attributes_from_decrypted_message( + decrypted_msg: &Message, + ) -> HashMap<String, String> { + let mut attributes_to_add = HashMap::new(); + if let Some(literal_data_header) = decrypted_msg.literal_data_header() { + if let Ok(file_name) = str::from_utf8(literal_data_header.file_name()) { + attributes_to_add.insert( + output_attributes::LITERAL_DATA_FILENAME.name.to_string(), + file_name.to_string(), + ); + } + attributes_to_add.insert( + output_attributes::LITERAL_DATA_MODIFIED.name.to_string(), + (1000u64 * literal_data_header.created().as_secs() as u64).to_string(), // Nifi uses ms timestamp + ); + } + attributes_to_add + } +} + +impl FlowFileStreamTransform for DecryptContentPGP { + fn transform<Ctx: GetProperty + GetControllerService, LoggerImpl: Logger>( + &self, + context: &Ctx, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + logger: &LoggerImpl, + ) -> Result<TransformStreamResult, MinifiError> { + let Ok(msg) = Message::from_reader(input_stream).map(|(msg, _header)| msg) else { + warn!(logger, "No valid PGP message found"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + let private_key_service = + context.get_controller_service::<PGPPrivateKeyService>(&PRIVATE_KEY_SERVICE)?; + + let Ok(mut decrypted_msg) = self.decrypt_msg(msg, private_key_service) else { + warn!(logger, "Failed to decrypt data"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + if self.decompress_data && decrypted_msg.is_compressed() { + match decrypted_msg.decompress() { + Ok(decompressed_data) => { + decrypted_msg = decompressed_data; + } + Err(e) => { + warn!(logger, "Failed to decompress data: {}", e); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + } + } + }; + + let attributes_to_add = Self::extract_attributes_from_decrypted_message(&decrypted_msg); + let Ok(_written_bytes) = std::io::copy(&mut decrypted_msg.into_inner(), output_stream) + else { + warn!(logger, "Failed to extract raw data from decrypted message"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + }; + + Ok(TransformStreamResult::new(&SUCCESS, attributes_to_add)) + } +} + +#[cfg(test)] +mod tests; + +pub(crate) mod processor_definition; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs new file mode 100644 index 000000000..e4a894a1c --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/output_attributes.rs @@ -0,0 +1,13 @@ +use minifi_native::OutputAttribute; + +pub(crate) const LITERAL_DATA_FILENAME: OutputAttribute = OutputAttribute { + name: "pgp.literal.data.filename", + relationships: &["success"], + description: "Filename from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementations omit these inherently malleable metadata)", +}; + +pub(crate) const LITERAL_DATA_MODIFIED: OutputAttribute = OutputAttribute { + name: "pgp.literal.data.modified", + relationships: &["success"], + description: "Modified Date from decrypted Literal Data (Note that OpenPGP signatures do not include the formatting octet, the file name, and the date field of the Literal Data packet in a signature hash; therefore, those fields are not protected against tampering in a signed document. Therefore a lot of implementations omit these inherently malleable metadata)", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs new file mode 100644 index 000000000..c9c6fddf6 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/processor_definition.rs @@ -0,0 +1,22 @@ +use super::{DecryptContentPGP, output_attributes, properties, relationships}; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, +}; + +impl ProcessorDefinition for DecryptContentPGP { + const DESCRIPTION: &'static str = "Decrypt contents of OpenPGP messages. Using the Packaged Decryption Strategy preserves OpenPGP encoding to support subsequent signature verification."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[ + output_attributes::LITERAL_DATA_FILENAME, + output_attributes::LITERAL_DATA_MODIFIED, + ]; + const RELATIONSHIPS: &'static [Relationship] = + &[relationships::SUCCESS, relationships::FAILURE]; + const PROPERTIES: &'static [Property] = &[ + properties::DECRYPTION_STRATEGY, + properties::SYMMETRIC_PASSWORD, + properties::PRIVATE_KEY_SERVICE, + ]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs new file mode 100644 index 000000000..d642e7f35 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/properties.rs @@ -0,0 +1,36 @@ +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::DecryptionStrategy; +use minifi_native::ComponentIdentifier; +use minifi_native::Property; +use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, NoConstraints}; +use strum::VariantNames; + +pub(crate) const DECRYPTION_STRATEGY: Property = Property { + name: "Decryption Strategy", + description: "Strategy for writing files to success after decryption", + is_required: true, + is_sensitive: false, + supports_expr_lang: false, + default_value: Some(DecryptionStrategy::Decrypted.into_str()), + constraints: AllowedValues(DecryptionStrategy::VARIANTS), +}; + +pub(crate) const SYMMETRIC_PASSWORD: Property = Property { + name: "Symmetric Password", + description: "Password used for decrypting data encrypted with Password-Based Encryption", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PRIVATE_KEY_SERVICE: Property = Property { + name: "Private Key Service", + description: "PGP Private Key Service for decrypting data encrypted with Public Key Encryption", + is_required: false, + is_sensitive: false, + supports_expr_lang: false, + default_value: None, + constraints: AllowedType(PGPPrivateKeyService::CLASS_NAME), +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs new file mode 100644 index 000000000..a4403da17 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/relationships.rs @@ -0,0 +1,11 @@ +use minifi_native::Relationship; + +pub(crate) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Decryption Succeeded", +}; + +pub(crate) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Decryption Failed", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs new file mode 100644 index 000000000..65d4167dc --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content/tests.rs @@ -0,0 +1,253 @@ +use crate::controller_services::private_key_service::PGPPrivateKeyService; +use crate::processors::decrypt_content::{DecryptContentPGP, output_attributes}; +use crate::test_utils; +use crate::test_utils::get_test_message; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, FlowFileStreamTransform, IoState, + MockControllerServiceContext, MockLogger, MockProcessContext, Schedule, +}; + +#[test] +fn test_ids() { + assert_eq!( + DecryptContentPGP::CLASS_NAME, + "minifi_pgp::processors::decrypt_content::DecryptContentPGP" + ); + assert_eq!(DecryptContentPGP::GROUP_NAME, "minifi_pgp"); + assert_eq!(DecryptContentPGP::VERSION, "0.1.0"); +} + +#[test] +fn fails_to_schedule_by_default() { + let decrypt_content = + DecryptContentPGP::schedule(&MockProcessContext::new(), &MockLogger::new()); + assert!(decrypt_content.is_err()); +} + +#[test] +fn schedules_with_password() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + "my_secret_password".to_string(), + ); + let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(decrypt_content.is_ok()); +} + +#[test] +fn schedules_with_controller() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + let decrypt_content = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(decrypt_content.is_ok()); +} + +#[test] +fn schedule_rejects_invalid_strategy_without_panicking() { + let mut context = MockProcessContext::new(); + context.properties.insert( + super::properties::DECRYPTION_STRATEGY.name.to_string(), + "NOT_A_STRATEGY".to_string(), + ); + context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + "my_secret_password".to_string(), + ); + // Must return Err, not panic. + let result = DecryptContentPGP::schedule(&context, &MockLogger::new()); + assert!(result.is_err(), "expected schedule to fail on bad strategy"); +} + +#[derive(Copy, Clone)] +struct PrivateKeyData { + key_filename: &'static str, + passphrase: Option<&'static str>, +} + +impl PrivateKeyData { + fn into_controller(self) -> PGPPrivateKeyService { + let mut context = MockControllerServiceContext::new(); + context + .properties + .insert("Key File", test_utils::get_test_key_path(self.key_filename)); + + if let Some(passphrase) = self.passphrase { + context.properties.insert("Key Passphrase", passphrase); + } + + PGPPrivateKeyService::enable(&context, &MockLogger::new()).expect("should enable") + } +} + +fn test_decryption( + message_file_name: &str, + private_key_data: Option<PrivateKeyData>, + symmetric_password: Option<&'static str>, + expected_result: Result<&[u8], ()>, +) { + let mut processor_context = MockProcessContext::new(); + if let Some(private_key) = private_key_data { + processor_context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(private_key.into_controller()), + ); + processor_context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + } + if let Some(symmetric_password) = symmetric_password { + processor_context.properties.insert( + super::properties::SYMMETRIC_PASSWORD.name.to_string(), + symmetric_password.to_string(), + ); + } + + let decrypt_content = DecryptContentPGP::schedule(&processor_context, &MockLogger::new()) + .expect("Should schedule with the configured properties"); + let mut output: Vec<u8> = Vec::new(); + let mut flow_file_stream = std::io::Cursor::new(get_test_message(message_file_name)); + let res = decrypt_content + .transform( + &processor_context, + &mut flow_file_stream, + &mut output, + &MockLogger::new(), + ) + .expect("Should be able to transform"); + + match expected_result { + Ok(_result_bytes) => { + assert_eq!( + res.target_relationship_name(), + super::relationships::SUCCESS.name + ); + assert_eq!(res.write_status(), IoState::Ok); + let data_modified = res + .get_attribute(output_attributes::LITERAL_DATA_MODIFIED.name) + .unwrap() + .parse::<u64>() + .expect("Should be u64"); + assert!(data_modified > 1770000000000); + assert!(data_modified < 1780000000000); + assert!( + res.get_attribute(output_attributes::LITERAL_DATA_FILENAME.name) + .is_some() + ); + } + Err(_) => { + assert_eq!( + res.target_relationship_name(), + super::relationships::FAILURE.name + ); + assert_eq!(res.write_status(), IoState::Cancel); + } + } +} + +#[test] +fn decrypts_with_password() { + test_decryption( + "password_encrypted_foo.gpg", + None, + Some("my_secret_password"), + Ok("foo\n".as_bytes()), + ); + test_decryption( + "password_encrypted_foo.asc", + None, + Some("my_secret_password"), + Ok("foo\n".as_bytes()), + ); + test_decryption( + "foo_for_alice.gpg", + None, + Some("my_secret_password"), + Err(()), + ); + test_decryption( + "foo_for_alice.asc", + None, + Some("my_secret_password"), + Err(()), + ); +} + +#[test] +fn decrypts_for_alice() { + let alice_private_key_data = PrivateKeyData { + key_filename: "alice_private.asc", + passphrase: Some("whiterabbit"), + }; + + test_decryption( + "foo_for_alice.asc", + Some(alice_private_key_data), + None, + Ok("foo\n".as_bytes()), + ); + + test_decryption( + "foo_for_alice.gpg", + Some(alice_private_key_data), + None, + Ok("foo\n".as_bytes()), + ); + + test_decryption( + "password_encrypted_foo.gpg", + Some(alice_private_key_data), + None, + Err(()), + ); + + test_decryption( + "password_encrypted_foo.asc", + Some(alice_private_key_data), + None, + Err(()), + ); +} + +#[test] +fn decryption_of_not_encrypted_data() { + let alice_private_key = PrivateKeyData { + key_filename: "alice_private.asc", + passphrase: Some("whiterabbit"), + }; + + let mut processor_context = MockProcessContext::new(); + processor_context.controller_services.insert( + "my_private_key_service".to_string(), + Box::new(alice_private_key.into_controller()), + ); + processor_context.properties.insert( + super::properties::PRIVATE_KEY_SERVICE.name.to_string(), + "my_private_key_service".to_string(), + ); + + let logger = MockLogger::new(); + + let decrypt_content = DecryptContentPGP::schedule(&processor_context, &logger) + .expect("Should schedule without any properties"); + let mut result: Vec<u8> = vec![]; + let mut flow_file_stream = std::io::Cursor::new("something not encrypted".as_bytes()); + let res = decrypt_content + .transform( + &processor_context, + &mut flow_file_stream, + &mut result, + &logger, + ) + .expect("Should be able to transform"); + + assert_eq!( + res.target_relationship_name(), + super::relationships::FAILURE.name + ); + assert_eq!(res.write_status(), IoState::Cancel); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs new file mode 100644 index 000000000..1c1d47d7d --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs @@ -0,0 +1,152 @@ +use minifi_native::{ + FlowFileStreamTransform, GetAttribute, GetControllerService, GetProperty, InputStream, Logger, + MinifiError, OutputStream, Schedule, TransformStreamResult, warn, +}; +use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey}; +use pgp::types::StringToKey; +use std::collections::HashMap; + +mod output_attributes; +mod properties; +mod relationships; + +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::encrypt_content::output_attributes::FILE_ENCODING; +use crate::processors::encrypt_content::properties::{ + PASSWORD, PUBLIC_KEY_SEARCH, PUBLIC_KEY_SERVICE, +}; +use crate::processors::encrypt_content::relationships::{FAILURE, SUCCESS}; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; + +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] +#[strum(serialize_all = "UPPERCASE", const_into_str)] +enum FileEncoding { + Ascii, + Binary, +} + +#[derive(Debug, ComponentIdentifier)] +pub(crate) struct EncryptContentPGP { + file_encoding: FileEncoding, +} + +#[cfg(not(test))] +fn string_to_key() -> StringToKey { + StringToKey::new_argon2(rand::thread_rng(), 3, 4, 16) // 64 MiB with rpgp's recommended parameter choice +} + +#[cfg(test)] +fn string_to_key() -> StringToKey { + StringToKey::new_argon2(rand::thread_rng(), 1, 1, 10) // fast for unit tests +} + +impl EncryptContentPGP { + fn encrypt_bytes( + &self, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + pub_key: Option<&SignedPublicKey>, + passphrase: Option<&str>, + file_name: String, + ) -> pgp::errors::Result<()> { + let mut builder = MessageBuilder::from_reader(file_name, input_stream).seipd_v1( + rand::thread_rng(), + pgp::crypto::sym::SymmetricKeyAlgorithm::AES256, + ); + + if let Some(pub_key) = pub_key { + builder.encrypt_to_key(rand::thread_rng(), pub_key)?; + } + + if let Some(passphrase) = passphrase { + builder.encrypt_with_password(string_to_key(), &passphrase.into())?; + } + + match self.file_encoding { + FileEncoding::Ascii => builder.to_armored_writer( + rand::thread_rng(), + ArmorOptions::default(), + output_stream, + ), + FileEncoding::Binary => builder.to_writer(rand::thread_rng(), output_stream), + } + } +} + +impl Schedule for EncryptContentPGP { + fn schedule<P: GetProperty, L: Logger>(context: &P, _logger: &L) -> Result<Self, MinifiError> + where + Self: Sized, + { + let file_encoding = context.get_req_property::<FileEncoding>(&properties::FILE_ENCODING)?; + + let has_password = context.get_property::<String>(&PASSWORD)?.is_some(); + let has_public_key = context + .get_property::<String>(&PUBLIC_KEY_SERVICE)? + .is_some() + && context + .get_property::<String>(&PUBLIC_KEY_SEARCH)? + .is_some(); + + if !has_password && !has_public_key { + Err(MinifiError::schedule_err( + "Either a password or Public Key Service with Public Key Search should be configured to encrypt files", + )) + } else { + Ok(EncryptContentPGP { file_encoding }) + } + } +} + +impl FlowFileStreamTransform for EncryptContentPGP { + fn transform<Ctx: GetProperty + GetControllerService + GetAttribute, LoggerImpl: Logger>( + &self, + context: &Ctx, + input_stream: &mut dyn InputStream, + output_stream: &mut dyn OutputStream, + logger: &LoggerImpl, + ) -> Result<TransformStreamResult, MinifiError> { + let file_name = context.get_attribute("filename")?.unwrap_or_default(); + let public_key = if let (Some(pub_key_search), Some(public_key_service)) = ( + context.get_property::<String>(&PUBLIC_KEY_SEARCH)?, + context.get_controller_service::<PGPPublicKeyService>(&PUBLIC_KEY_SERVICE)?, + ) { + public_key_service.get(&pub_key_search) + } else { + None + }; + let password = context.get_property::<String>(&PASSWORD)?; + if public_key.is_none() && password.is_none() { + warn!(logger, "No password or public key to encrypt with"); + return Ok(TransformStreamResult::route_without_changes(&FAILURE)); + } + + match self.encrypt_bytes( + input_stream, + output_stream, + public_key, + password.as_deref(), + file_name, + ) { + Ok(_) => Ok(TransformStreamResult::new( + &SUCCESS, + HashMap::from([( + FILE_ENCODING.name.to_string(), + self.file_encoding.to_string(), + )]), + )), + Err(e) => { + warn!(logger, "Failed to encrypt content {:?}", e); + Ok(TransformStreamResult::route_without_changes(&FAILURE)) + } + } + } +} + +#[cfg(test)] +mod tests; + +pub(crate) mod processor_definition; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs new file mode 100644 index 000000000..07f339678 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/output_attributes.rs @@ -0,0 +1,7 @@ +use minifi_native::OutputAttribute; + +pub(crate) const FILE_ENCODING: OutputAttribute = OutputAttribute { + name: "pgp.file.encoding", + relationships: &["success"], + description: "File Encoding", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs new file mode 100644 index 000000000..4ac06faa7 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/processor_definition.rs @@ -0,0 +1,20 @@ +use super::{EncryptContentPGP, output_attributes, properties, relationships}; +use minifi_native::{ + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, +}; + +impl ProcessorDefinition for EncryptContentPGP { + const DESCRIPTION: &'static str = "Encrypt contents using OpenPGP."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[output_attributes::FILE_ENCODING]; + const RELATIONSHIPS: &'static [Relationship] = + &[relationships::SUCCESS, relationships::FAILURE]; + const PROPERTIES: &'static [Property] = &[ + properties::FILE_ENCODING, + properties::PASSWORD, + properties::PUBLIC_KEY_SEARCH, + properties::PUBLIC_KEY_SERVICE, + ]; +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs new file mode 100644 index 000000000..70ebdb404 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/properties.rs @@ -0,0 +1,46 @@ +use crate::controller_services::public_key_service::PGPPublicKeyService; +use crate::processors::encrypt_content::FileEncoding; +use minifi_native::ComponentIdentifier; +use minifi_native::Property; +use minifi_native::PropertyConstraints::{AllowedType, AllowedValues, NoConstraints}; +use strum::VariantNames; + +pub(crate) const FILE_ENCODING: Property = Property { + name: "File Encoding", + description: "File Encoding for encryption", + is_required: true, + is_sensitive: false, + supports_expr_lang: false, + default_value: Some(FileEncoding::Binary.into_str()), + constraints: AllowedValues(FileEncoding::VARIANTS), +}; + +pub(crate) const PASSWORD: Property = Property { + name: "Symmetric Password", + description: "Password used for encrypting data with Password-Based Encryption", + is_required: false, + is_sensitive: true, + supports_expr_lang: false, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PUBLIC_KEY_SEARCH: Property = Property { + name: "Public Key Search", + description: "PGP Public Key Search will be used to match against the User ID or Key ID when formatted as uppercase hexadecimal string of 16 characters", + is_required: false, + is_sensitive: false, + supports_expr_lang: true, + default_value: None, + constraints: NoConstraints, +}; + +pub(crate) const PUBLIC_KEY_SERVICE: Property = Property { + name: "Public Key Service", + description: "PGP Public Key Service for encrypting data with Public Key Encryption", + is_required: false, + is_sensitive: false, + supports_expr_lang: false, + default_value: None, + constraints: AllowedType(PGPPublicKeyService::CLASS_NAME), +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs new file mode 100644 index 000000000..fcdda9921 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/relationships.rs @@ -0,0 +1,11 @@ +use minifi_native::Relationship; + +pub(crate) const SUCCESS: Relationship = Relationship { + name: "success", + description: "Encryption Succeeded", +}; + +pub(crate) const FAILURE: Relationship = Relationship { + name: "failure", + description: "Encryption Failed", +}; diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs new file mode 100644 index 000000000..2e961dd7e --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content/tests.rs @@ -0,0 +1,137 @@ +use super::*; +use crate::test_utils; +use minifi_native::{ + ComponentIdentifier, EnableControllerService, IoState, MockControllerServiceContext, + MockLogger, MockProcessContext, +}; + +#[test] +fn test_ids() { + assert_eq!( + EncryptContentPGP::CLASS_NAME, + "minifi_pgp::processors::encrypt_content::EncryptContentPGP" + ); + assert_eq!(EncryptContentPGP::GROUP_NAME, "minifi_pgp"); + assert_eq!(EncryptContentPGP::VERSION, "0.1.0"); +} + +#[test] +fn cannot_schedule_without_password_or_public_key() { + assert!(EncryptContentPGP::schedule(&MockProcessContext::new(), &MockLogger::new()).is_err()); +} + +fn assert_content(transform_result: &TransformStreamResult, is_ascii: bool) { + assert_eq!(transform_result.target_relationship_name(), SUCCESS.name); + assert_eq!(transform_result.write_status(), IoState::Ok); + assert_eq!( + transform_result.get_attribute("pgp.file.encoding").unwrap(), + if is_ascii { "ASCII" } else { "BINARY" } + ); +} + +#[test] +fn encrypts_via_passphrase() { + let mut context = MockProcessContext::new(); + context.properties.insert(PASSWORD.name, "password"); + context + .attributes + .insert("filename".to_owned(), "mammut".to_owned()); + + let mut result: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = Vec::new(); + let mut input_stream = std::io::Cursor::new("foo".as_bytes()); + let processor = + EncryptContentPGP::schedule(&context, &MockLogger::new()).expect("should schedule"); + let transformed_ff = processor + .transform(&context, &mut input_stream, &mut result, &MockLogger::new()) + .expect("should transform"); + + assert_eq!(transformed_ff.target_relationship_name(), FAILURE.name); + assert_eq!(transformed_ff.write_status(), IoState::Cancel); +} diff --git a/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs b/minifi_rust/extensions/minifi_pgp/src/processors/mod.rs new file mode 100644 index 000000000..ac45357fe --- /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 000000000..963af7161 --- /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<u8> { + 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 000000000..95b346238 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/src/utils.rs @@ -0,0 +1,11 @@ +use minifi_native::{MinifiError, PropertyType}; + +pub(crate) struct Password {} + +impl PropertyType for Password { + type Output = pgp::types::Password; + + fn parse(s: &str) -> Result<Self::Output, MinifiError> { + 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 000000000..7b8ad1a61 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/README.txt @@ -0,0 +1,8 @@ +Testing keys v2 +------------------------ +uid [ultimate] Alice <[email protected]> +passphrase whiterabbit + +uid [ultimate] Bob Personal <[email protected]> +uid [ultimate] Bob Primary <[email protected]> +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 000000000..8ae976d7b --- /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 000000000..71af43989 Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/alice.gpg differ 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 000000000..955e52584 --- /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 000000000..5f385d9ee Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/alice_private.gpg differ 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 000000000..09fc1bc99 --- /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 000000000..a39370243 Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/bob_private.gpg differ diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg b/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg new file mode 100644 index 000000000..9e603b5c3 Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/garbage.gpg differ diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc new file mode 100644 index 000000000..0fa816a55 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.asc @@ -0,0 +1,89 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=fkCr +-----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 new file mode 100644 index 000000000..6a84506f9 Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/keyring.gpg differ diff --git a/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc new file mode 100644 index 000000000..6430facf8 --- /dev/null +++ b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.asc @@ -0,0 +1,159 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=f4lJ +-----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 new file mode 100644 index 000000000..4a07229aa Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_keys/secret_keyring.gpg differ 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 000000000..d25ddfb62 --- /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 000000000..9cc58539a --- /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 000000000..94b8561c9 --- /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 000000000..bd3d9b476 Binary files /dev/null and b/minifi_rust/extensions/minifi_pgp/test_messages/foo_for_alice.gpg differ 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 000000000..22d639e71 --- /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 000000000..40ab6a354 --- /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/minifi_rs_behave/Dockerfile.alpine b/minifi_rust/minifi_rs_behave/Dockerfile.alpine index 032c26ec5..e3c155779 100644 --- a/minifi_rust/minifi_rs_behave/Dockerfile.alpine +++ b/minifi_rust/minifi_rs_behave/Dockerfile.alpine @@ -22,4 +22,4 @@ RUN cargo build --release # Export Stage FROM scratch AS bin-export -COPY --from=builder /app/target/release/libminifi_rs_playground.so / +COPY --from=builder /app/target/release/libminifi_*.so / diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.debian b/minifi_rust/minifi_rs_behave/Dockerfile.debian index 274ed3cb5..a3c4aa03d 100644 --- a/minifi_rust/minifi_rs_behave/Dockerfile.debian +++ b/minifi_rust/minifi_rs_behave/Dockerfile.debian @@ -22,4 +22,4 @@ RUN cargo build --release # Export Stage FROM scratch AS bin-export -COPY --from=builder /app/target/release/libminifi_rs_playground.so / +COPY --from=builder /app/target/release/libminifi_*.so /
