szaszm commented on code in PR #2225:
URL: https://github.com/apache/nifi-minifi-cpp/pull/2225#discussion_r4019353690


##########
minifi_rust/extensions/minifi_pgp/features/steps/steps.py:
##########


Review Comment:
   I think we need a test covering the most secure happy path: one where 
private keys are protected with a long passphrase. Changing existing tests is 
fine for me too.



##########
minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service.rs:
##########
@@ -0,0 +1,259 @@
+// 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 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 {
+    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 = 
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"));
+        }
+        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,
+            )
+        })
+    }
+}
+
+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<Option<PublicKeyFile>> = 
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<Option<PublicKey>> = Property::new(
+        "Keyring",
+        "PGP Keyring or Public Key encoded in ASCII Armor",
+    )
+    .sensitive();

Review Comment:
   Public keys are not sensitive



##########
minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs:
##########
@@ -0,0 +1,399 @@
+// 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 proc_def::*;
+
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+
+use minifi_native::macros::ComponentIdentifier;
+use minifi_native::{
+    FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, 
Logger, MinifiError,
+    OutputStream, ProcessError, RouteErrorExt, Schedule, TransformStreamResult,
+};
+use pgp::composed::{Message, TheRing};
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct DecryptContentPGP {
+    symmetric_password: Option<pgp::types::Password>,
+}
+
+impl Schedule for DecryptContentPGP {
+    fn schedule<P: GetProperty + GetControllerService, L>(
+        context: &P,
+        _logger: &L,
+    ) -> Result<Self, MinifiError>
+    where
+        Self: Sized,
+        L: Logger,
+    {
+        let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?;
+        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",
+            ))
+        } else {
+            Ok(DecryptContentPGP { 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,
+    ) -> 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<Ctx: GetProperty + GetControllerService, LoggerImpl: Logger>(
+        &self,
+        context: &Ctx,
+        input_stream: &mut dyn InputStream,
+        output_stream: &mut dyn OutputStream,
+        _logger: &LoggerImpl,
+    ) -> Result<TransformStreamResult, ProcessError> {
+        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 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))
+    }
+}
+
+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)",

Review Comment:
   same here



##########
minifi_rust/extensions/minifi_pgp/src/controller_services/key_property.rs:
##########


Review Comment:
   If there is an easy way to reduce the code duplication without unifying 
public and private key types, and without exploding code complexity, it might 
be worth exploring.



##########
minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs:
##########
@@ -0,0 +1,399 @@
+// 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 proc_def::*;
+
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+
+use minifi_native::macros::ComponentIdentifier;
+use minifi_native::{
+    FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, 
Logger, MinifiError,
+    OutputStream, ProcessError, RouteErrorExt, Schedule, TransformStreamResult,
+};
+use pgp::composed::{Message, TheRing};
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct DecryptContentPGP {
+    symmetric_password: Option<pgp::types::Password>,
+}
+
+impl Schedule for DecryptContentPGP {
+    fn schedule<P: GetProperty + GetControllerService, L>(
+        context: &P,
+        _logger: &L,
+    ) -> Result<Self, MinifiError>
+    where
+        Self: Sized,
+        L: Logger,
+    {
+        let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?;
+        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",
+            ))
+        } else {
+            Ok(DecryptContentPGP { 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,
+    ) -> 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<Ctx: GetProperty + GetControllerService, LoggerImpl: Logger>(
+        &self,
+        context: &Ctx,
+        input_stream: &mut dyn InputStream,
+        output_stream: &mut dyn OutputStream,
+        _logger: &LoggerImpl,
+    ) -> Result<TransformStreamResult, ProcessError> {
+        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 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))
+    }
+}
+
+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)",

Review Comment:
   Should we ignore the unprotected metadata instead of using them when they're 
present? As a defense-in-depth measure, I think we should protect flowfile 
attributes from untrusted values, as some of our processors treat them as 
trusted data, opening attack vectors.



##########
CONTROLLERS.md:
##########
@@ -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<br/>**Supports Expression Language: true** 
|

Review Comment:
   If this property allows inline key encoded in binary or ascii, then the 
property needs to be sensitive. I think we should disallow inline keys instead, 
or use the sensitive Key property for that purpose.
   
   I would also rename the properties to match NiFi, even at a slight cost to 
clarity.



##########
minifi_rust/extensions/minifi_pgp/src/processors/decrypt_content.rs:
##########
@@ -0,0 +1,399 @@
+// 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 proc_def::*;
+
+use crate::controller_services::private_key_service::PGPPrivateKeyService;
+
+use minifi_native::macros::ComponentIdentifier;
+use minifi_native::{
+    FlowFileStreamTransform, GetControllerService, GetProperty, InputStream, 
Logger, MinifiError,
+    OutputStream, ProcessError, RouteErrorExt, Schedule, TransformStreamResult,
+};
+use pgp::composed::{Message, TheRing};
+
+#[derive(Debug, ComponentIdentifier)]
+pub(crate) struct DecryptContentPGP {
+    symmetric_password: Option<pgp::types::Password>,
+}
+
+impl Schedule for DecryptContentPGP {
+    fn schedule<P: GetProperty + GetControllerService, L>(
+        context: &P,
+        _logger: &L,
+    ) -> Result<Self, MinifiError>
+    where
+        Self: Sized,
+        L: Logger,
+    {
+        let symmetric_password = context.get_property(&SYMMETRIC_PASSWORD)?;
+        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",
+            ))
+        } else {
+            Ok(DecryptContentPGP { 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);
+        }

Review Comment:
   What if both a private key and a symmetric passphrase are used? How does 
that work? Does it work at all? Does it double encrypt and decrypt the message, 
once with asymmetric and once with symmetric encryption? Does roundtrip with 
EncryptContent work in that case? If not, should we disable that and exforce an 
exclusive-OR relationship between the different key types, in schedule?



##########
minifi_rust/extensions/minifi_pgp/src/processors/encrypt_content.rs:
##########
@@ -0,0 +1,391 @@
+// 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,
+    TransformStreamResult,
+};
+use pgp::composed::{ArmorOptions, MessageBuilder, SignedPublicKey};
+use pgp::types::{Password, StringToKey};
+
+use proc_def::*;
+
+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<Password>,
+}
+
+#[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),
+        }
+    }
+}
+
+impl Schedule for EncryptContentPGP {
+    fn schedule<P: GetProperty + GetControllerService, L: Logger>(
+        context: &P,
+        _logger: &L,
+    ) -> Result<Self, MinifiError>
+    where
+        Self: Sized,
+    {
+        let file_encoding = context.get_property(&FILE_ENCODING)?;
+        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)?;
+
+        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,
+        })
+    }
+}
+
+impl EncryptContentPGP {
+    fn get_public_key<Ctx: GetProperty + GetControllerService>(
+        context: &Ctx,
+    ) -> Result<Option<&SignedPublicKey>, MinifiError> {
+        if let (Some(pub_key_search), Some(public_key_service)) = (
+            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"
+                ))),
+            }
+        } 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<TransformStreamResult, ProcessError> {
+        let file_name = context
+            .get_attribute("filename")?
+            .unwrap_or(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)
+            .route_err_to_failure()?;
+
+        Ok(TransformStreamResult::new(&SUCCESS)
+            .with_attribute(FILE_ENCODING_ATTR.name, 
self.file_encoding.into_str()))
+    }
+}
+
+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<FileEncoding> =
+        Property::new("File Encoding", "File Encoding for encryption")
+            .with_default(FileEncoding::Binary.into_str());
+    pub(crate) const SYMMETRIC_PASSWORD: Property<Option<utils::Password>> = 
Property::new(

Review Comment:
   ```suggestion
   
       pub(crate) const SYMMETRIC_PASSWORD: Property<Option<utils::Password>> = 
Property::new(
   ```



##########
minifi_rust/extensions/minifi_pgp/src/controller_services/public_key_service/controller_service_definition.rs:
##########
@@ -0,0 +1,43 @@
+// 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<Option<PublicKeyFile>> = Property::new(
+    "Keyring File",
+    "File path to PGP Keyring or Public Key encoded in binary or ASCII Armor",

Review Comment:
   Does raw binary work at all in a minifi flow definition? I suspect not, so 
I'd remove the claim from the description, even if we leave the handling code 
unchanged, and there may be a few lucky binary keys that happen to be JSON/YAML 
escape-able



##########
minifi_rust/extensions/minifi_pgp/src/utils.rs:
##########
@@ -0,0 +1,37 @@
+// 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 {

Review Comment:
   Can we make this sensitive by default?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to