diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptFile.md b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptFile.md index 1b1768a2..72ccc0c6 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptFile.md +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptFile.md @@ -24,10 +24,10 @@ Decrypts a file using a symmetric algorithm and key, or using PGP with a private | `AesKeySize` | AES key size | Property | `AesKeySize` | | `Aes256` | AES key size in bits used to encrypt the input. Applies only when `Algorithm = AES` and `Format = OpenSslEnc`; ignored otherwise. Must match the key size the producer used (e.g. `openssl enc -aes-128-cbc` / `-aes-192-cbc` / `-aes-256-cbc`). Not stored in the wire format — encrypt and decrypt sides must use matching values. | | `OutputFilePath` | Output file path | InArgument | `string` | | | The full path where the decrypted file will be saved. When empty, the file is written next to the input file using the name `_Decrypted`. | | `OutputFileName` | Decrypted file name | InArgument | `string` | | | The file name to use for the decrypted file. Honored when `OutputFilePath` is empty. | -| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required when `Algorithm = PGP`. | +| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required when `Algorithm = PGP`. Paired with a hidden `IResource` alternative (`PrivateKeyFile`) selectable via a designer menu action. | | `Passphrase` | Passphrase | InArgument | `string` | Conditional | | Passphrase that unlocks the private key. Provide either `Passphrase` or `PassphraseSecureString`. PGP only. | | `PassphraseSecureString` | Passphrase (secure) | InArgument | `SecureString` | Conditional | | Secure-string variant of the passphrase. Provide either `Passphrase` or `PassphraseSecureString`. PGP only. | -| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the signer's PGP public key file. Required only when `VerifySignature = True`. | +| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the signer's PGP public key file. Required only when `VerifySignature = True`. Paired with a hidden `IResource` alternative (`PublicKeyFile`) selectable via a designer menu action. | ### Configuration diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptText.md b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptText.md index 7aa8123e..8b76af8b 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptText.md +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptText.md @@ -23,10 +23,10 @@ Decrypts a text string using a symmetric algorithm, or using PGP with a private | `KeyFormat` | Key bytes format | Property | `KeyBytesFormat` | | `Encoded` | How the `Key` string is interpreted. `Hex` or `Base64` are required when `Format = Raw`. Symmetric algorithms only. | | `KdfIterations` | KDF iterations | InArgument | `int` | | `0` | PBKDF2 iteration count. Must match the value used at encrypt time. `0` uses the format's OWASP-recommended default. Rejected for `Classic` and `Raw`. | | `AesKeySize` | AES key size | Property | `AesKeySize` | | `Aes256` | AES key size in bits used to encrypt the input. Applies only when `Algorithm = AES` and `Format = OpenSslEnc`; ignored otherwise. Must match the key size the producer used (e.g. `openssl enc -aes-128-cbc` / `-aes-192-cbc` / `-aes-256-cbc`). Not stored in the wire format — encrypt and decrypt sides must use matching values. | -| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required when `Algorithm = PGP`. | +| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required when `Algorithm = PGP`. Paired with a hidden `IResource` alternative (`PrivateKeyFile`) selectable via a designer menu action. | | `Passphrase` | Passphrase | InArgument | `string` | Conditional | | Passphrase that unlocks the private key. Provide either `Passphrase` or `PassphraseSecureString`. PGP only. | | `PassphraseSecureString` | Passphrase (secure) | InArgument | `SecureString` | Conditional | | Secure-string variant of the passphrase. PGP only. | -| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the signer's PGP public key file. Required only when `VerifySignature = True`. | +| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the signer's PGP public key file. Required only when `VerifySignature = True`. Paired with a hidden `IResource` alternative (`PublicKeyFile`) selectable via a designer menu action. | ### Configuration diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptFile.md b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptFile.md index 8d7d11d5..84571312 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptFile.md +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptFile.md @@ -20,13 +20,13 @@ Encrypts a file using a symmetric algorithm and key, or using PGP with a recipie | `KeyEncoding` | Key encoding | InArgument | `Encoding` | | UTF-8 | The encoding used to interpret the key. Symmetric algorithms only. | | `Format` | Wire format | Property | `SymmetricWireFormat` | | `Classic` | The symmetric ciphertext layout. `Classic` (default) is UiPath's byte-stable layout; `Owasp2026` uses the same layout with stronger KDF iterations; `Raw` is caller-supplied key + IV for third-party interop; `OpenSslEnc` produces `openssl enc`-compatible output. Symmetric algorithms only. | | `KeyFormat` | Key bytes format | Property | `KeyBytesFormat` | | `Encoded` | How the `Key` string is interpreted. `Hex` or `Base64` are required when `Format = Raw`. Symmetric algorithms only. | -| `Iv` | IV | InArgument | `string` | Conditional | | Initialization vector when `Format = Raw`. Interpreted per `KeyFormat`. Optional — leave empty to let the cipher generate one. Rejected for all other formats. | +| `Iv` | IV | InArgument | `string` | Conditional | | Initialization vector when `Format = Raw`. Interpreted per `KeyFormat`. Optional — leave empty to let the cipher generate one. Rejected for all other formats. Never reuse the same (Key, IV) pair — it breaks confidentiality, and under AEAD modes (AES-GCM, ChaCha20-Poly1305) also lets an attacker forge messages; use each pair at most once, or leave empty for a fresh random IV. | | `KdfIterations` | KDF iterations | InArgument | `int` | | `0` | PBKDF2 iteration count. `0` uses the format's OWASP-recommended default (1 300 000 for `Owasp2026`, 600 000 for `OpenSslEnc`). Rejected for `Classic` and `Raw`. | | `AesKeySize` | AES key size | Property | `AesKeySize` | | `Aes256` | AES key size in bits. Applies only when `Algorithm = AES` and `Format = OpenSslEnc`; ignored otherwise. Must match the key size the peer uses (e.g. `openssl enc -aes-128-cbc` / `-aes-192-cbc` / `-aes-256-cbc`). Not stored in the wire format — encrypt and decrypt sides must use matching values. | | `OutputFilePath` | Output file path | InArgument | `string` | | | The full path where the encrypted file will be saved. When empty, the file is written next to the input file using the name `_Encrypted`. | | `OutputFileName` | Encrypted file name | InArgument | `string` | | | The file name to use for the encrypted file. Honored when `OutputFilePath` is empty. | -| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the recipient's PGP public key file. Required when `Algorithm = PGP`. | -| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required only when `SignData = True`. | +| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the recipient's PGP public key file. Required when `Algorithm = PGP`. Paired with a hidden `IResource` alternative (`PublicKeyFile`) selectable via a designer menu action. | +| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required only when `SignData = True`. Paired with a hidden `IResource` alternative (`PrivateKeyFile`) selectable via a designer menu action. | | `Passphrase` | Passphrase | InArgument | `string` | Conditional | | Passphrase that unlocks the private key (signing). Provide either `Passphrase` or `PassphraseSecureString`. PGP-sign only. | | `PassphraseSecureString` | Passphrase (secure) | InArgument | `SecureString` | Conditional | | Secure-string variant of the passphrase. PGP-sign only. | diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptText.md b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptText.md index 08ed5211..56f32f76 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptText.md +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptText.md @@ -21,11 +21,11 @@ Encrypts a text string using a symmetric algorithm, or using PGP with a recipien | `PlaintextEncoding` | Text encoding | InArgument | `Encoding` | | UTF-8 | The encoding used to convert the input text to bytes before encryption. Set this to match the encoding expected by the third-party tool that will consume the ciphertext. Symmetric algorithms only. | | `Format` | Wire format | Property | `SymmetricWireFormat` | | `Classic` | The symmetric ciphertext layout. `Classic` (default) is UiPath's byte-stable layout; `Owasp2026` uses the same layout with stronger KDF iterations; `Raw` is caller-supplied key + IV for third-party interop; `OpenSslEnc` produces `openssl enc`-compatible output. Symmetric algorithms only. | | `KeyFormat` | Key bytes format | Property | `KeyBytesFormat` | | `Encoded` | How the `Key` string is interpreted. `Hex` or `Base64` are required when `Format = Raw`; otherwise the key is treated as a password. Symmetric algorithms only. | -| `Iv` | IV | InArgument | `string` | Conditional | | Initialization vector when `Format = Raw`. Interpreted per `KeyFormat`. Optional — leave empty to let the cipher generate one. Rejected for all other formats. | +| `Iv` | IV | InArgument | `string` | Conditional | | Initialization vector when `Format = Raw`. Interpreted per `KeyFormat`. Optional — leave empty to let the cipher generate one. Rejected for all other formats. Never reuse the same (Key, IV) pair — it breaks confidentiality, and under AEAD modes (AES-GCM, ChaCha20-Poly1305) also lets an attacker forge messages; use each pair at most once, or leave empty for a fresh random IV. | | `KdfIterations` | KDF iterations | InArgument | `int` | | `0` | PBKDF2 iteration count. `0` uses the format's OWASP-recommended default (1 300 000 for `Owasp2026`, 600 000 for `OpenSslEnc`). Rejected for `Classic` and `Raw`. | | `AesKeySize` | AES key size | Property | `AesKeySize` | | `Aes256` | AES key size in bits. Applies only when `Algorithm = AES` and `Format = OpenSslEnc`; ignored otherwise. Must match the key size the peer uses (e.g. `openssl enc -aes-128-cbc` / `-aes-192-cbc` / `-aes-256-cbc`). Not stored in the wire format — encrypt and decrypt sides must use matching values. | -| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the recipient's PGP public key file. Required when `Algorithm = PGP`. | -| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required only when `SignData = True`. | +| `PublicKeyFilePath` | Public key file path | InArgument | `string` | Conditional | | Path to the recipient's PGP public key file. Required when `Algorithm = PGP`. Paired with a hidden `IResource` alternative (`PublicKeyFile`) selectable via a designer menu action. | +| `PrivateKeyFilePath` | Private key file path | InArgument | `string` | Conditional | | Path to your PGP private key file. Required only when `SignData = True`. Paired with a hidden `IResource` alternative (`PrivateKeyFile`) selectable via a designer menu action. | | `Passphrase` | Passphrase | InArgument | `string` | Conditional | | Passphrase that unlocks the private key (signing). Provide either `Passphrase` or `PassphraseSecureString`. PGP-sign only. | | `PassphraseSecureString` | Passphrase (secure) | InArgument | `SecureString` | Conditional | | Secure-string variant of the passphrase. PGP-sign only. | diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/Helpers/PgpFileResolverTests.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/Helpers/PgpFileResolverTests.cs new file mode 100644 index 00000000..9e6367ff --- /dev/null +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/Helpers/PgpFileResolverTests.cs @@ -0,0 +1,49 @@ +using Moq; +using UiPath.Cryptography.Activities.Helpers; +using UiPath.Platform.ResourceHandling; +using Xunit; + +namespace UiPath.Cryptography.Activities.Tests.Helpers +{ + /// + /// Covers the path↔resource precedence of — the + /// shared resolver used by the synchronous Encrypt/Decrypt Text & File activities for the PGP + /// public/private key inputs. The string path wins; the resource is only consulted when the path + /// is empty (the resource-resolution adapter itself is exercised end-to-end by the PGP activity + /// tests, not here, since ToLocalResource needs the platform resource stack). + /// + public class PgpFileResolverTests + { + [Fact] + public void ResolveLocalPath_PathProvided_ReturnsPath_AndDoesNotTouchResource() + { + // Strict mock: any access to the resource would throw, proving the path short-circuits it. + var resource = new Mock(MockBehavior.Strict); + + var result = PgpFileResolver.ResolveLocalPath(@"C:\keys\key.asc", resource.Object); + + Assert.Equal(@"C:\keys\key.asc", result); + resource.VerifyNoOtherCalls(); + } + + [Fact] + public void ResolveLocalPath_PathProvided_NullResource_ReturnsPath() + { + var result = PgpFileResolver.ResolveLocalPath(@"C:\keys\key.asc", null); + + Assert.Equal(@"C:\keys\key.asc", result); + } + + [Fact] + public void ResolveLocalPath_EmptyPath_NullResource_ReturnsEmpty() + { + Assert.Equal(string.Empty, PgpFileResolver.ResolveLocalPath(string.Empty, null)); + } + + [Fact] + public void ResolveLocalPath_NullPath_NullResource_ReturnsNull() + { + Assert.Null(PgpFileResolver.ResolveLocalPath(null, null)); + } + } +} diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpStandaloneTests.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpStandaloneTests.cs index 2b62ba57..89f0eb90 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpStandaloneTests.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpStandaloneTests.cs @@ -674,6 +674,30 @@ public void PgpVerify_Has_IResource_Properties() Assert.NotNull(typeof(PgpVerify).GetProperty(nameof(PgpVerify.PublicKeyFile))); } + [Fact] + public void EncryptText_Has_PrivateKeyFile_IResource_Property() + { + Assert.NotNull(typeof(EncryptText).GetProperty(nameof(EncryptText.PrivateKeyFile))); + } + + [Fact] + public void EncryptFile_Has_PrivateKeyFile_IResource_Property() + { + Assert.NotNull(typeof(EncryptFile).GetProperty(nameof(EncryptFile.PrivateKeyFile))); + } + + [Fact] + public void DecryptText_Has_PrivateKeyFile_IResource_Property() + { + Assert.NotNull(typeof(DecryptText).GetProperty(nameof(DecryptText.PrivateKeyFile))); + } + + [Fact] + public void DecryptFile_Has_PrivateKeyFile_IResource_Property() + { + Assert.NotNull(typeof(DecryptFile).GetProperty(nameof(DecryptFile.PrivateKeyFile))); + } + #endregion } } diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpTests.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpTests.cs index 63db3745..362a768b 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpTests.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpTests.cs @@ -3,7 +3,9 @@ using System.Activities.Statements; using System.IO; using System.Security; +using Moq; using UiPath.Cryptography.Enums; +using UiPath.Platform.ResourceHandling; using Xunit; #pragma warning disable CS0618 // tests intentionally set the obsolete PassphraseInputModeSwitch property to exercise the SecureString branch @@ -197,6 +199,66 @@ public void PgpDecryptFile_WithoutPrivateKey_Throws() } } + [Fact] + public void PgpEncryptText_SignDataFalse_DoesNotResolvePrivateKeyResource() + { + // Regression (STUD-80718): with signing disabled, a bound-but-unused PrivateKeyFile + // resource must never be resolved — the private key isn't needed for a non-signing + // encrypt, and resolving it (e.g. from a Storage Bucket) can fail. A strict-behavior + // failure would surface as any interaction with the mock. + var privateKeyResource = new Mock(); + + var encryptText = new EncryptText + { + Algorithm = EncryptionAlgorithm.PGP, + Input = new InArgument("Hello PGP without signing!"), + PublicKeyFilePath = new InArgument(_publicKeyPath), + SignData = false, + // Bind via a lambda (not a literal) — WF rejects Literal of arbitrary reference types. + PrivateKeyFile = new InArgument(c => privateKeyResource.Object), + }; + + var result = WorkflowInvoker.Invoke(encryptText); + + Assert.False(string.IsNullOrEmpty(result)); + privateKeyResource.VerifyNoOtherCalls(); + } + + [Fact] + public void PgpEncryptFile_SignDataFalse_DoesNotResolvePrivateKeyResource() + { + var tempInputFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var tempEncryptedFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + File.WriteAllText(tempInputFile, "Hello PGP file without signing!"); + var privateKeyResource = new Mock(); + + var encryptFile = new EncryptFile + { + InputFilePath = new InArgument(tempInputFile), + Algorithm = EncryptionAlgorithm.PGP, + PublicKeyFilePath = new InArgument(_publicKeyPath), + OutputFilePath = new InArgument(tempEncryptedFile), + SignData = false, + // Bind via a lambda (not a literal) — WF rejects Literal of arbitrary reference types. + PrivateKeyFile = new InArgument(c => privateKeyResource.Object), + Overwrite = true, + }; + + WorkflowInvoker.Invoke(encryptFile); + + Assert.True(File.Exists(tempEncryptedFile)); + Assert.True(new FileInfo(tempEncryptedFile).Length > 0); + privateKeyResource.VerifyNoOtherCalls(); + } + finally + { + File.Delete(tempInputFile); + File.Delete(tempEncryptedFile); + } + } + [Fact] public void PgpEncryptDecryptText_Activity_Works() { diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptFile.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptFile.cs index edb2d360..fbfa52af 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptFile.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptFile.cs @@ -59,7 +59,7 @@ public class DecryptFile : CodeActivity [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_KeySecureString_Description))] public InArgument KeySecureString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_KeyEncoding_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_KeyEncoding_Description))] public InArgument KeyEncoding { get; set; } @@ -67,25 +67,25 @@ public class DecryptFile : CodeActivity [Browsable(false)] public InArgument KeyEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_Format_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_Format_Description))] [DefaultValue(SymmetricWireFormat.Classic)] public SymmetricWireFormat Format { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_KeyFormat_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_KeyFormat_Description))] [DefaultValue(KeyBytesFormat.Encoded)] public KeyBytesFormat KeyFormat { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_KdfIterations_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_KdfIterations_Description))] public InArgument KdfIterations { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_AesKeySize_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_AesKeySize_Description))] [DefaultValue(AesKeySize.Aes256)] @@ -106,7 +106,7 @@ public class DecryptFile : CodeActivity public InArgument OutputFileName { get; set; } [RequiredArgument] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Options_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_Overwrite_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_Overwrite_Description))] public bool Overwrite { get; set; } @@ -130,6 +130,13 @@ public class DecryptFile : CodeActivity [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_PrivateKeyFilePath_Description))] public InArgument PrivateKeyFilePath { get; set; } + [Browsable(false)] + [DefaultValue(null)] + [LocalizedCategory(nameof(Resources.Input))] + [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_PrivateKeyFile_Name))] + [LocalizedDescription(nameof(Resources.Activity_DecryptFile_Property_PrivateKeyFile_Description))] + public InArgument PrivateKeyFile { get; set; } + [DefaultValue(null)] [LocalizedCategory(nameof(Resources.Input))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptFile_Property_Passphrase_Name))] @@ -257,7 +264,7 @@ private void ValidateSymmetricKeyParams(CodeActivityContext context, out string private byte[] ExecutePgpDecrypt(CodeActivityContext context, byte[] encrypted) { - var privateKeyFilePath = PrivateKeyFilePath.Get(context); + var privateKeyFilePath = PgpFileResolver.ResolveLocalPath(PrivateKeyFilePath.Get(context), PrivateKeyFile?.Get(context)); var passphraseString = Passphrase.Get(context); if (string.IsNullOrWhiteSpace(passphraseString)) @@ -268,14 +275,7 @@ private byte[] ExecutePgpDecrypt(CodeActivityContext context, byte[] encrypted) passphraseString = new NetworkCredential(string.Empty, secure).Password; } - var publicKeyFilePath = PublicKeyFilePath.Get(context); - var publicKeyResource = PublicKeyFile?.Get(context); - if (string.IsNullOrEmpty(publicKeyFilePath) && publicKeyResource != null) - { - var localResource = publicKeyResource.ToLocalResource(); - localResource.ResolveAsync().GetAwaiter().GetResult(); - publicKeyFilePath = localResource.LocalPath; - } + var publicKeyFilePath = PgpFileResolver.ResolveLocalPath(PublicKeyFilePath.Get(context), PublicKeyFile?.Get(context)); return PgpStreamHelper.WithPgpDecryptStreams( privateKeyFilePath, passphraseString, publicKeyFilePath, VerifySignature, diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptText.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptText.cs index 1c232cb0..b692d79f 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptText.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/DecryptText.cs @@ -50,7 +50,7 @@ public partial class DecryptText : CodeActivity [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_KeySecureString_Description))] public InArgument KeySecureString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_Encoding_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_Encoding_Description))] public InArgument Encoding { get; set; } @@ -58,7 +58,7 @@ public partial class DecryptText : CodeActivity [Browsable(false)] public InArgument KeyEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_PlaintextEncoding_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_PlaintextEncoding_Description))] public InArgument PlaintextEncoding { get; set; } @@ -66,25 +66,25 @@ public partial class DecryptText : CodeActivity [Browsable(false)] public InArgument PlaintextEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_Format_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_Format_Description))] [DefaultValue(SymmetricWireFormat.Classic)] public SymmetricWireFormat Format { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_KeyFormat_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_KeyFormat_Description))] [DefaultValue(KeyBytesFormat.Encoded)] public KeyBytesFormat KeyFormat { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_KdfIterations_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_KdfIterations_Description))] public InArgument KdfIterations { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_AesKeySize_Name))] [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_AesKeySize_Description))] [DefaultValue(AesKeySize.Aes256)] @@ -107,6 +107,13 @@ public partial class DecryptText : CodeActivity [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_PrivateKeyFilePath_Description))] public InArgument PrivateKeyFilePath { get; set; } + [Browsable(false)] + [DefaultValue(null)] + [LocalizedCategory(nameof(Resources.Input))] + [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_PrivateKeyFile_Name))] + [LocalizedDescription(nameof(Resources.Activity_DecryptText_Property_PrivateKeyFile_Description))] + public InArgument PrivateKeyFile { get; set; } + [DefaultValue(null)] [LocalizedCategory(nameof(Resources.Input))] [LocalizedDisplayName(nameof(Resources.Activity_DecryptText_Property_Passphrase_Name))] @@ -199,7 +206,7 @@ protected override string Execute(CodeActivityContext context) private string ExecutePgpDecrypt(CodeActivityContext context, string input) { - var privateKeyFilePath = PrivateKeyFilePath.Get(context); + var privateKeyFilePath = PgpFileResolver.ResolveLocalPath(PrivateKeyFilePath.Get(context), PrivateKeyFile?.Get(context)); var passphraseString = Passphrase.Get(context); if (string.IsNullOrWhiteSpace(passphraseString)) @@ -210,14 +217,7 @@ private string ExecutePgpDecrypt(CodeActivityContext context, string input) passphraseString = new NetworkCredential(string.Empty, secure).Password; } - var publicKeyFilePath = PublicKeyFilePath.Get(context); - var publicKeyResource = PublicKeyFile?.Get(context); - if (string.IsNullOrEmpty(publicKeyFilePath) && publicKeyResource != null) - { - var localResource = publicKeyResource.ToLocalResource(); - localResource.ResolveAsync().GetAwaiter().GetResult(); - publicKeyFilePath = localResource.LocalPath; - } + var publicKeyFilePath = PgpFileResolver.ResolveLocalPath(PublicKeyFilePath.Get(context), PublicKeyFile?.Get(context)); return PgpStreamHelper.WithPgpDecryptStreams( privateKeyFilePath, passphraseString, publicKeyFilePath, VerifySignature, diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptFile.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptFile.cs index eb94a587..1f529d8b 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptFile.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptFile.cs @@ -68,7 +68,7 @@ public EncryptFile() [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_KeySecureString_Description))] public InArgument KeySecureString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_KeyEncoding_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_KeyEncoding_Description))] public InArgument KeyEncoding { get; set; } @@ -76,31 +76,31 @@ public EncryptFile() [Browsable(false)] public InArgument KeyEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_Format_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_Format_Description))] [DefaultValue(SymmetricWireFormat.Classic)] public SymmetricWireFormat Format { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_KeyFormat_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_KeyFormat_Description))] [DefaultValue(KeyBytesFormat.Encoded)] public KeyBytesFormat KeyFormat { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_Iv_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_Iv_Description))] public InArgument Iv { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_KdfIterations_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_KdfIterations_Description))] public InArgument KdfIterations { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_AesKeySize_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_AesKeySize_Description))] [DefaultValue(AesKeySize.Aes256)] @@ -117,7 +117,7 @@ public EncryptFile() public InArgument OutputFileName { get; set; } [RequiredArgument] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Options_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_Overwrite_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_Overwrite_Description))] public bool Overwrite { get; set; } @@ -160,6 +160,13 @@ public EncryptFile() [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_PrivateKeyFilePath_Description))] public InArgument PrivateKeyFilePath { get; set; } + [Browsable(false)] + [DefaultValue(null)] + [LocalizedCategory(nameof(Resources.Input))] + [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_PrivateKeyFile_Name))] + [LocalizedDescription(nameof(Resources.Activity_EncryptFile_Property_PrivateKeyFile_Description))] + public InArgument PrivateKeyFile { get; set; } + [DefaultValue(null)] [LocalizedCategory(nameof(Resources.Input))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptFile_Property_Passphrase_Name))] @@ -275,19 +282,16 @@ private byte[] ExecuteSymmetricEncrypt(CodeActivityContext context, byte[] input private byte[] ExecutePgpEncrypt(CodeActivityContext context, string inputPath) { - var publicKeyFilePath = PublicKeyFilePath.Get(context); - var publicKeyResource = PublicKeyFile?.Get(context); - if (string.IsNullOrEmpty(publicKeyFilePath) && publicKeyResource != null) - { - var localResource = publicKeyResource.ToLocalResource(); - localResource.ResolveAsync().GetAwaiter().GetResult(); - publicKeyFilePath = localResource.LocalPath; - } + var publicKeyFilePath = PgpFileResolver.ResolveLocalPath(PublicKeyFilePath.Get(context), PublicKeyFile?.Get(context)); var privateKeyFilePath = PrivateKeyFilePath.Get(context); string passphraseString = null; if (SignData) { + // The private key is only consumed when signing; resolve the IResource fallback here + // (not unconditionally) so a bound-but-unused private key can't fail a non-signing encrypt. + privateKeyFilePath = PgpFileResolver.ResolveLocalPath(privateKeyFilePath, PrivateKeyFile?.Get(context)); + passphraseString = Passphrase.Get(context); if (string.IsNullOrWhiteSpace(passphraseString)) { diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs index cc958672..27b61196 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs @@ -49,7 +49,7 @@ public partial class EncryptText : CodeActivity [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_KeySecureString_Description))] public InArgument KeySecureString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_Encoding_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_Encoding_Description))] public InArgument Encoding { get; set; } @@ -57,7 +57,7 @@ public partial class EncryptText : CodeActivity [Browsable(false)] public InArgument KeyEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_PlaintextEncoding_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_PlaintextEncoding_Description))] public InArgument PlaintextEncoding { get; set; } @@ -65,31 +65,31 @@ public partial class EncryptText : CodeActivity [Browsable(false)] public InArgument PlaintextEncodingString { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_Format_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_Format_Description))] [DefaultValue(SymmetricWireFormat.Classic)] public SymmetricWireFormat Format { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_KeyFormat_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_KeyFormat_Description))] [DefaultValue(KeyBytesFormat.Encoded)] public KeyBytesFormat KeyFormat { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_Iv_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_Iv_Description))] public InArgument Iv { get; set; } [DefaultValue(null)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_KdfIterations_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_KdfIterations_Description))] public InArgument KdfIterations { get; set; } - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Advanced_Name))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_AesKeySize_Name))] [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_AesKeySize_Description))] [DefaultValue(AesKeySize.Aes256)] @@ -131,6 +131,13 @@ public partial class EncryptText : CodeActivity [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_PrivateKeyFilePath_Description))] public InArgument PrivateKeyFilePath { get; set; } + [Browsable(false)] + [DefaultValue(null)] + [LocalizedCategory(nameof(Resources.Input))] + [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_PrivateKeyFile_Name))] + [LocalizedDescription(nameof(Resources.Activity_EncryptText_Property_PrivateKeyFile_Description))] + public InArgument PrivateKeyFile { get; set; } + [DefaultValue(null)] [LocalizedCategory(nameof(Resources.Input))] [LocalizedDisplayName(nameof(Resources.Activity_EncryptText_Property_Passphrase_Name))] @@ -208,19 +215,16 @@ protected override string Execute(CodeActivityContext context) private string ExecutePgpEncrypt(CodeActivityContext context, string input) { - var publicKeyFilePath = PublicKeyFilePath.Get(context); - var publicKeyResource = PublicKeyFile?.Get(context); - if (string.IsNullOrEmpty(publicKeyFilePath) && publicKeyResource != null) - { - var localResource = publicKeyResource.ToLocalResource(); - localResource.ResolveAsync().GetAwaiter().GetResult(); - publicKeyFilePath = localResource.LocalPath; - } + var publicKeyFilePath = PgpFileResolver.ResolveLocalPath(PublicKeyFilePath.Get(context), PublicKeyFile?.Get(context)); var privateKeyFilePath = PrivateKeyFilePath.Get(context); string passphraseString = null; if (SignData) { + // The private key is only consumed when signing; resolve the IResource fallback here + // (not unconditionally) so a bound-but-unused private key can't fail a non-signing encrypt. + privateKeyFilePath = PgpFileResolver.ResolveLocalPath(privateKeyFilePath, PrivateKeyFile?.Get(context)); + passphraseString = Passphrase.Get(context); if (string.IsNullOrWhiteSpace(passphraseString)) { diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/Helpers/PgpFileResolver.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/Helpers/PgpFileResolver.cs index 394d00a7..3108b9f7 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/Helpers/PgpFileResolver.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/Helpers/PgpFileResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Security; using System.Threading; @@ -35,6 +36,33 @@ public static async Task ResolveAsync( return local.LocalPath; } + /// + /// Resolves a paired string-path ↔ IResource input for the synchronous activities + /// (Encrypt/Decrypt Text & File). The string path takes precedence; only when it is empty + /// and a resource is supplied is the resource resolved to a local path. Non-throwing — returns + /// the (possibly empty) path so callers keep their own required/optional validation. Mirrors the + /// path-first precedence of the design-time path/resource toggle. + /// + public static string ResolveLocalPath(string filePath, IResource resource) + { + if (!string.IsNullOrEmpty(filePath) || resource == null) + return filePath; + + return ResolveResourceLocalPath(resource); + } + + // Thin sync-over-async adapter over the platform's IResource→local-file conversion. Excluded from + // coverage because ToLocalResource() is an extension over the platform converter that can't be + // exercised without the full resource-handling stack; the precedence logic in ResolveLocalPath is + // unit-tested directly. + [ExcludeFromCodeCoverage] + private static string ResolveResourceLocalPath(IResource resource) + { + var local = resource.ToLocalResource(); + local.ResolveAsync().GetAwaiter().GetResult(); + return local.LocalPath; + } + public static string ResolvePassphrase( string passphrase, SecureString securePassphrase, diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashFile.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashFile.cs index 8d26ffc9..f9bb6a7e 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashFile.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashFile.cs @@ -61,7 +61,7 @@ public class KeyedHashFile : CodeActivity public InArgument KeySecureString { get; set; } [Browsable(false)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_KeyedHashFile_Property_Encoding_Name))] [LocalizedDescription(nameof(Resources.Activity_KeyedHashFile_Property_Encoding_Description))] public InArgument Encoding { get; set; } diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashText.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashText.cs index f2cc9481..74683454 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashText.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/KeyedHashText.cs @@ -49,7 +49,7 @@ public partial class KeyedHashText : CodeActivity public InArgument KeySecureString { get; set; } [Browsable(false)] - [LocalizedCategory(nameof(Resources.Input))] + [LocalizedCategory(nameof(Resources.Category_Encoding_Name))] [LocalizedDisplayName(nameof(Resources.Activity_KeyedHashText_Property_Encoding_Name))] [LocalizedDescription(nameof(Resources.Activity_KeyedHashText_Property_Encoding_Description))] public InArgument Encoding { get; set; } diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptCryptoViewModelBase.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptCryptoViewModelBase.cs index 3dee15b7..a855c438 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptCryptoViewModelBase.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptCryptoViewModelBase.cs @@ -25,6 +25,7 @@ public abstract class DecryptCryptoViewModelBase : DesignPropertiesViewModel private readonly PairedInputToggle _keyToggle; private readonly PairedInputToggle _passphraseToggle; private readonly PairedInputToggle _publicKeyFileToggle; + private readonly PairedInputToggle _privateKeyFileToggle; protected DecryptCryptoViewModelBase(IDesignServices services) : base(services) { @@ -54,6 +55,14 @@ protected DecryptCryptoViewModelBase(IDesignServices services) : base(services) { AfterSwitch = ApplyPublicKeyVisibility, }; + + _privateKeyFileToggle = new PairedInputToggle( + PrivateKeyFilePath, PrivateKeyFile, + Resources.MenuAction_UseFilePath, + Resources.MenuAction_UseFile) + { + AfterSwitch = ApplyPrivateKeyVisibility, + }; } public DesignProperty Algorithm { get; set; } = new DesignProperty(); @@ -63,6 +72,7 @@ protected DecryptCryptoViewModelBase(IDesignServices services) : base(services) public DesignInArgument ContinueOnError { get; set; } = new DesignInArgument(); public DesignInArgument PrivateKeyFilePath { get; set; } = new DesignInArgument(); + public DesignInArgument PrivateKeyFile { get; set; } = new DesignInArgument(); public DesignInArgument Passphrase { get; set; } = new DesignInArgument(); public DesignInArgument PassphraseSecureString { get; set; } = new DesignInArgument(); public DesignProperty VerifySignature { get; set; } = new DesignProperty(); @@ -107,7 +117,7 @@ protected void ConfigureAlgorithmAndKeyProperties(ref int orderIndex) KeyEncodingString.IsPrincipal = false; KeyEncodingString.OrderIndex = orderIndex++; - KeyEncodingString.Category = Resources.Category_Options_Name; + KeyEncodingString.Category = Resources.Category_Encoding_Name; KeyEncodingString.DataSource = _encodingDataSource; KeyEncodingString.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; @@ -125,7 +135,7 @@ protected void ConfigureEncodingDropdown(DesignInArgument encodingProper var dataSource = EncodingHelpers.ConfigureEncodingDataSource(); encodingProperty.IsPrincipal = false; encodingProperty.OrderIndex = orderIndex++; - encodingProperty.Category = Resources.Input; + encodingProperty.Category = Resources.Category_Encoding_Name; encodingProperty.DataSource = dataSource; encodingProperty.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; dataSource.Data = EncodingHelpers.GetAvailableEncodings(); @@ -141,7 +151,7 @@ protected void ConfigureInteropProperties(ref int orderIndex) { Format.IsPrincipal = false; Format.OrderIndex = orderIndex++; - Format.Category = Resources.Input; + Format.Category = Resources.Category_Advanced_Name; Format.DataSource = DataSourceHelper.ForEnum( SymmetricWireFormat.Classic, SymmetricWireFormat.Owasp2026, @@ -152,7 +162,7 @@ protected void ConfigureInteropProperties(ref int orderIndex) KeyFormat.IsPrincipal = false; KeyFormat.IsVisible = false; KeyFormat.OrderIndex = orderIndex++; - KeyFormat.Category = Resources.Input; + KeyFormat.Category = Resources.Category_Advanced_Name; // Encoded is intentionally omitted — the dropdown is visible only when Format = Raw, // and Raw rejects Encoded at runtime. FormatChanged_Action keeps the underlying value // in sync (Hex when Raw, Encoded otherwise) so non-Raw runtime validation stays clean. @@ -164,12 +174,12 @@ protected void ConfigureInteropProperties(ref int orderIndex) KdfIterations.IsPrincipal = false; KdfIterations.IsVisible = false; KdfIterations.OrderIndex = orderIndex++; - KdfIterations.Category = Resources.Input; + KdfIterations.Category = Resources.Category_Advanced_Name; AesKeySize.IsPrincipal = false; AesKeySize.IsVisible = false; AesKeySize.OrderIndex = orderIndex++; - AesKeySize.Category = Resources.Input; + AesKeySize.Category = Resources.Category_Advanced_Name; AesKeySize.DataSource = DataSourceHelper.ForEnum( AesKeySizeEnum.Aes128, AesKeySizeEnum.Aes192, @@ -192,9 +202,15 @@ protected void ConfigureTailProperties(ref int orderIndex) PrivateKeyFilePath.IsPrincipal = false; PrivateKeyFilePath.IsVisible = false; - PrivateKeyFilePath.OrderIndex = orderIndex++; + PrivateKeyFilePath.OrderIndex = orderIndex; PrivateKeyFilePath.Category = Resources.Input; + PrivateKeyFile.IsPrincipal = false; + PrivateKeyFile.IsVisible = false; + PrivateKeyFile.OrderIndex = orderIndex; + PrivateKeyFile.Category = Resources.Input; + orderIndex++; + Passphrase.IsPrincipal = false; Passphrase.IsVisible = false; Passphrase.OrderIndex = orderIndex; @@ -229,6 +245,11 @@ protected void ConfigureTailProperties(ref int orderIndex) /// protected void ConfigurePublicKeyFileMenuActions() => _publicKeyFileToggle.ConfigureMenuActions(); + /// + /// Registers Main-menu actions to toggle between PrivateKeyFilePath (string) and PrivateKeyFile (IResource). + /// + protected void ConfigurePrivateKeyFileMenuActions() => _privateKeyFileToggle.ConfigureMenuActions(); + /// /// Registers Main-menu actions to toggle between Key (string) and KeySecureString (SecureString), /// and sets initial visibility based on which side has a persisted value. @@ -268,6 +289,19 @@ private void ApplyPublicKeyVisibility() PublicKeyFile.IsPrincipal = show; } + private void ApplyPrivateKeyVisibility() + { + // The private key is required to decrypt whenever Algorithm == PGP. + bool isPgp = Algorithm.Value == EncryptionAlgorithm.PGP; + bool useResource = _privateKeyFileToggle.UseSecondary; + PrivateKeyFilePath.IsVisible = isPgp && !useResource; + PrivateKeyFilePath.IsRequired = isPgp && !useResource; + PrivateKeyFile.IsVisible = isPgp && useResource; + PrivateKeyFile.IsRequired = isPgp && useResource; + PrivateKeyFilePath.IsPrincipal = isPgp; + PrivateKeyFile.IsPrincipal = isPgp; + } + private void ApplyPassphraseVisibility() { bool active = Algorithm.Value == EncryptionAlgorithm.PGP; @@ -302,9 +336,7 @@ private void AlgorithmChanged_Action() KeyEncodingString.IsVisible = !isPgp; ApplyKeyInputVisibility(); - PrivateKeyFilePath.IsVisible = isPgp; - PrivateKeyFilePath.IsRequired = isPgp; - PrivateKeyFilePath.IsPrincipal = isPgp; + ApplyPrivateKeyVisibility(); ApplyPassphraseVisibility(); VerifySignature.IsVisible = isPgp; VerifySignature.IsPrincipal = isPgp; diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptFileViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptFileViewModel.cs index 609eb88c..c63274b5 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptFileViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptFileViewModel.cs @@ -53,7 +53,7 @@ protected override void InitializeModel() OutputFilePath.OrderIndex = orderIndex++; OutputFilePath.Category = Resources.Category_Options_Name; - Overwrite.IsPrincipal = true; + Overwrite.IsPrincipal = false; Overwrite.OrderIndex = orderIndex++; Overwrite.Category = Resources.Category_Options_Name; Overwrite.Widget = new DefaultWidget { Type = ViewModelWidgetType.Toggle }; @@ -61,6 +61,7 @@ protected override void InitializeModel() ConfigureTailProperties(ref orderIndex); ConfigureKeyInputModeMenuActions(); ConfigurePublicKeyFileMenuActions(); + ConfigurePrivateKeyFileMenuActions(); ConfigureInputFileMenuActions(); ConfigurePassphraseInputModeMenuActions(); @@ -107,6 +108,8 @@ private void ConfigurePropertyTexts() ContinueOnError.Tooltip = Resources.Activity_DecryptFile_Property_ContinueOnError_Description; PrivateKeyFilePath.DisplayName = Resources.Activity_DecryptFile_Property_PrivateKeyFilePath_Name; PrivateKeyFilePath.Tooltip = Resources.Activity_DecryptFile_Property_PrivateKeyFilePath_Description; + PrivateKeyFile.DisplayName = Resources.Activity_DecryptFile_Property_PrivateKeyFile_Name; + PrivateKeyFile.Tooltip = Resources.Activity_DecryptFile_Property_PrivateKeyFile_Description; Passphrase.DisplayName = Resources.Activity_DecryptFile_Property_Passphrase_Name; Passphrase.Tooltip = Resources.Activity_DecryptFile_Property_Passphrase_Description; PassphraseSecureString.DisplayName = Resources.Activity_DecryptFile_Property_PassphraseSecureString_Name; diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptTextViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptTextViewModel.cs index 5795bc1a..68bf8086 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptTextViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptTextViewModel.cs @@ -1,5 +1,6 @@ using System.Activities.DesignViewModels; using System.Activities.ViewModels; +using System.Diagnostics.CodeAnalysis; using UiPath.Cryptography.Activities.NetCore.ViewModels; using UiPath.Cryptography.Activities.Properties; @@ -16,6 +17,7 @@ public partial class DecryptText namespace UiPath.Cryptography.Activities.NetCore.ViewModels { + [ExcludeFromCodeCoverage] public partial class DecryptTextViewModel : DecryptCryptoViewModelBase { public DecryptTextViewModel(IDesignServices services) : base(services) @@ -39,15 +41,17 @@ protected override void InitializeModel() ConfigureAlgorithmAndKeyProperties(ref orderIndex); ConfigureEncodingDropdown(PlaintextEncodingString, ref orderIndex); ConfigureInteropProperties(ref orderIndex); + ConfigureTailProperties(ref orderIndex); + // Output is assigned last so it renders after the Options section (guideline "outputs last"). Result.IsPrincipal = false; Result.OrderIndex = orderIndex++; Result.Category = Resources.Output; - ConfigureTailProperties(ref orderIndex); ConfigurePropertyTexts(); ConfigureKeyInputModeMenuActions(); ConfigurePublicKeyFileMenuActions(); + ConfigurePrivateKeyFileMenuActions(); ConfigurePassphraseInputModeMenuActions(); } @@ -82,6 +86,8 @@ private void ConfigurePropertyTexts() ContinueOnError.Tooltip = Resources.Activity_DecryptText_Property_ContinueOnError_Description; PrivateKeyFilePath.DisplayName = Resources.Activity_DecryptText_Property_PrivateKeyFilePath_Name; PrivateKeyFilePath.Tooltip = Resources.Activity_DecryptText_Property_PrivateKeyFilePath_Description; + PrivateKeyFile.DisplayName = Resources.Activity_DecryptText_Property_PrivateKeyFile_Name; + PrivateKeyFile.Tooltip = Resources.Activity_DecryptText_Property_PrivateKeyFile_Description; Passphrase.DisplayName = Resources.Activity_DecryptText_Property_Passphrase_Name; Passphrase.Tooltip = Resources.Activity_DecryptText_Property_Passphrase_Description; PassphraseSecureString.DisplayName = Resources.Activity_DecryptText_Property_PassphraseSecureString_Name; diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptCryptoViewModelBase.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptCryptoViewModelBase.cs index bc146d07..cd301eb4 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptCryptoViewModelBase.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptCryptoViewModelBase.cs @@ -27,6 +27,7 @@ public abstract class EncryptCryptoViewModelBase : DesignPropertiesViewModel private readonly PairedInputToggle _keyToggle; private readonly PairedInputToggle _passphraseToggle; private readonly PairedInputToggle _publicKeyFileToggle; + private readonly PairedInputToggle _privateKeyFileToggle; protected EncryptCryptoViewModelBase(IDesignServices services) : base(services) { @@ -56,6 +57,14 @@ protected EncryptCryptoViewModelBase(IDesignServices services) : base(services) { AfterSwitch = ApplyPublicKeyVisibility, }; + + _privateKeyFileToggle = new PairedInputToggle( + PrivateKeyFilePath, PrivateKeyFile, + Resources.MenuAction_UseFilePath, + Resources.MenuAction_UseFile) + { + AfterSwitch = ApplyPrivateKeyVisibility, + }; } public DesignProperty Algorithm { get; set; } = new DesignProperty(); @@ -71,6 +80,7 @@ protected EncryptCryptoViewModelBase(IDesignServices services) : base(services) public DesignInArgument PublicKeyFile { get; set; } = new DesignInArgument(); public DesignProperty SignData { get; set; } = new DesignProperty(); public DesignInArgument PrivateKeyFilePath { get; set; } = new DesignInArgument(); + public DesignInArgument PrivateKeyFile { get; set; } = new DesignInArgument(); public DesignInArgument Passphrase { get; set; } = new DesignInArgument(); public DesignInArgument PassphraseSecureString { get; set; } = new DesignInArgument(); @@ -125,7 +135,7 @@ protected void ConfigureAlgorithmAndKeyProperties(ref int orderIndex) KeyEncodingString.IsPrincipal = false; KeyEncodingString.OrderIndex = orderIndex++; - KeyEncodingString.Category = Resources.Category_Options_Name; + KeyEncodingString.Category = Resources.Category_Encoding_Name; KeyEncodingString.DataSource = _encodingDataSource; KeyEncodingString.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; @@ -143,7 +153,7 @@ protected void ConfigureEncodingDropdown(DesignInArgument encodingProper var dataSource = EncodingHelpers.ConfigureEncodingDataSource(); encodingProperty.IsPrincipal = false; encodingProperty.OrderIndex = orderIndex++; - encodingProperty.Category = Resources.Input; + encodingProperty.Category = Resources.Category_Encoding_Name; encodingProperty.DataSource = dataSource; encodingProperty.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; dataSource.Data = EncodingHelpers.GetAvailableEncodings(); @@ -158,7 +168,7 @@ protected void ConfigureInteropProperties(ref int orderIndex) { Format.IsPrincipal = false; Format.OrderIndex = orderIndex++; - Format.Category = Resources.Input; + Format.Category = Resources.Category_Advanced_Name; Format.DataSource = DataSourceHelper.ForEnum( SymmetricWireFormat.Classic, SymmetricWireFormat.Owasp2026, @@ -169,7 +179,7 @@ protected void ConfigureInteropProperties(ref int orderIndex) KeyFormat.IsPrincipal = false; KeyFormat.IsVisible = false; KeyFormat.OrderIndex = orderIndex++; - KeyFormat.Category = Resources.Input; + KeyFormat.Category = Resources.Category_Advanced_Name; // Encoded is intentionally omitted — the dropdown is visible only when Format = Raw, // and Raw rejects Encoded at runtime. FormatChanged_Action keeps the underlying value // in sync (Hex when Raw, Encoded otherwise) so non-Raw runtime validation stays clean. @@ -181,17 +191,17 @@ protected void ConfigureInteropProperties(ref int orderIndex) Iv.IsPrincipal = false; Iv.IsVisible = false; Iv.OrderIndex = orderIndex++; - Iv.Category = Resources.Input; + Iv.Category = Resources.Category_Advanced_Name; KdfIterations.IsPrincipal = false; KdfIterations.IsVisible = false; KdfIterations.OrderIndex = orderIndex++; - KdfIterations.Category = Resources.Input; + KdfIterations.Category = Resources.Category_Advanced_Name; AesKeySize.IsPrincipal = false; AesKeySize.IsVisible = false; AesKeySize.OrderIndex = orderIndex++; - AesKeySize.Category = Resources.Input; + AesKeySize.Category = Resources.Category_Advanced_Name; AesKeySize.DataSource = DataSourceHelper.ForEnum( AesKeySizeEnum.Aes128, AesKeySizeEnum.Aes192, @@ -231,9 +241,15 @@ protected void ConfigureTailProperties(ref int orderIndex) PrivateKeyFilePath.IsPrincipal = false; PrivateKeyFilePath.IsVisible = false; - PrivateKeyFilePath.OrderIndex = orderIndex++; + PrivateKeyFilePath.OrderIndex = orderIndex; PrivateKeyFilePath.Category = Resources.Input; + PrivateKeyFile.IsPrincipal = false; + PrivateKeyFile.IsVisible = false; + PrivateKeyFile.OrderIndex = orderIndex; + PrivateKeyFile.Category = Resources.Input; + orderIndex++; + Passphrase.IsPrincipal = false; Passphrase.IsVisible = false; Passphrase.OrderIndex = orderIndex; @@ -251,6 +267,11 @@ protected void ConfigureTailProperties(ref int orderIndex) /// protected void ConfigurePublicKeyFileMenuActions() => _publicKeyFileToggle.ConfigureMenuActions(); + /// + /// Registers Main-menu actions to toggle between PrivateKeyFilePath (string) and PrivateKeyFile (IResource). + /// + protected void ConfigurePrivateKeyFileMenuActions() => _privateKeyFileToggle.ConfigureMenuActions(); + /// /// Registers Main-menu actions to toggle between Key (string) and KeySecureString (SecureString), /// and sets initial visibility based on which side has a persisted value. @@ -289,6 +310,19 @@ private void ApplyPublicKeyVisibility() PublicKeyFile.IsPrincipal = isPgp; } + private void ApplyPrivateKeyVisibility() + { + // The private key is only relevant when Algorithm == PGP AND SignData is enabled. + bool active = Algorithm.Value == EncryptionAlgorithm.PGP && SignData.Value; + bool useResource = _privateKeyFileToggle.UseSecondary; + PrivateKeyFilePath.IsVisible = active && !useResource; + PrivateKeyFilePath.IsRequired = active && !useResource; + PrivateKeyFile.IsVisible = active && useResource; + PrivateKeyFile.IsRequired = active && useResource; + PrivateKeyFilePath.IsPrincipal = active; + PrivateKeyFile.IsPrincipal = active; + } + private void ApplyPassphraseVisibility() { bool active = Algorithm.Value == EncryptionAlgorithm.PGP && SignData.Value; @@ -328,9 +362,7 @@ private void AlgorithmChanged_Action() ApplyPublicKeyVisibility(); SignData.IsVisible = isPgp; SignData.IsPrincipal = isPgp; - PrivateKeyFilePath.IsVisible = isPgp && SignData.Value; - PrivateKeyFilePath.IsRequired = isPgp && SignData.Value; - PrivateKeyFilePath.IsPrincipal = isPgp && SignData.Value; + ApplyPrivateKeyVisibility(); ApplyPassphraseVisibility(); ApplyInteropVisibility(); OnAlgorithmChanged(isPgp); @@ -424,9 +456,7 @@ private void SignDataChanged_Action() { if (Algorithm.Value != EncryptionAlgorithm.PGP) return; - PrivateKeyFilePath.IsVisible = SignData.Value; - PrivateKeyFilePath.IsRequired = SignData.Value; - PrivateKeyFilePath.IsPrincipal = SignData.Value; + ApplyPrivateKeyVisibility(); ApplyPassphraseVisibility(); } } diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptFileViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptFileViewModel.cs index 6d68ae9c..58ff2016 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptFileViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptFileViewModel.cs @@ -53,7 +53,7 @@ protected override void InitializeModel() OutputFilePath.OrderIndex = orderIndex++; OutputFilePath.Category = Resources.Category_Options_Name; - Overwrite.IsPrincipal = true; + Overwrite.IsPrincipal = false; Overwrite.OrderIndex = orderIndex++; Overwrite.Category = Resources.Category_Options_Name; Overwrite.Widget = new DefaultWidget { Type = ViewModelWidgetType.Toggle }; @@ -61,6 +61,7 @@ protected override void InitializeModel() ConfigureTailProperties(ref orderIndex); ConfigureKeyInputModeMenuActions(); ConfigurePublicKeyFileMenuActions(); + ConfigurePrivateKeyFileMenuActions(); ConfigureInputFileMenuActions(); ConfigurePassphraseInputModeMenuActions(); @@ -115,6 +116,8 @@ private void ConfigurePropertyTexts() SignData.Tooltip = Resources.Activity_EncryptFile_Property_SignData_Description; PrivateKeyFilePath.DisplayName = Resources.Activity_EncryptFile_Property_PrivateKeyFilePath_Name; PrivateKeyFilePath.Tooltip = Resources.Activity_EncryptFile_Property_PrivateKeyFilePath_Description; + PrivateKeyFile.DisplayName = Resources.Activity_EncryptFile_Property_PrivateKeyFile_Name; + PrivateKeyFile.Tooltip = Resources.Activity_EncryptFile_Property_PrivateKeyFile_Description; Passphrase.DisplayName = Resources.Activity_EncryptFile_Property_Passphrase_Name; Passphrase.Tooltip = Resources.Activity_EncryptFile_Property_Passphrase_Description; PassphraseSecureString.DisplayName = Resources.Activity_EncryptFile_Property_PassphraseSecureString_Name; diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptTextViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptTextViewModel.cs index 066d4618..6c21b5bb 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptTextViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptTextViewModel.cs @@ -1,5 +1,6 @@ using System.Activities.DesignViewModels; using System.Activities.ViewModels; +using System.Diagnostics.CodeAnalysis; using UiPath.Cryptography.Activities.NetCore.ViewModels; using UiPath.Cryptography.Activities.Properties; @@ -16,6 +17,7 @@ public partial class EncryptText namespace UiPath.Cryptography.Activities.NetCore.ViewModels { + [ExcludeFromCodeCoverage] public partial class EncryptTextViewModel : EncryptCryptoViewModelBase { public EncryptTextViewModel(IDesignServices services) : base(services) @@ -39,15 +41,17 @@ protected override void InitializeModel() ConfigureAlgorithmAndKeyProperties(ref orderIndex); ConfigureEncodingDropdown(PlaintextEncodingString, ref orderIndex); ConfigureInteropProperties(ref orderIndex); + ConfigureTailProperties(ref orderIndex); + // Output is assigned last so it renders after the Options section (guideline "outputs last"). Result.IsPrincipal = false; Result.OrderIndex = orderIndex++; Result.Category = Resources.Output; - ConfigureTailProperties(ref orderIndex); ConfigurePropertyTexts(); ConfigureKeyInputModeMenuActions(); ConfigurePublicKeyFileMenuActions(); + ConfigurePrivateKeyFileMenuActions(); ConfigurePassphraseInputModeMenuActions(); } @@ -90,6 +94,8 @@ private void ConfigurePropertyTexts() SignData.Tooltip = Resources.Activity_EncryptText_Property_SignData_Description; PrivateKeyFilePath.DisplayName = Resources.Activity_EncryptText_Property_PrivateKeyFilePath_Name; PrivateKeyFilePath.Tooltip = Resources.Activity_EncryptText_Property_PrivateKeyFilePath_Description; + PrivateKeyFile.DisplayName = Resources.Activity_EncryptText_Property_PrivateKeyFile_Name; + PrivateKeyFile.Tooltip = Resources.Activity_EncryptText_Property_PrivateKeyFile_Description; Passphrase.DisplayName = Resources.Activity_EncryptText_Property_Passphrase_Name; Passphrase.Tooltip = Resources.Activity_EncryptText_Property_Passphrase_Description; PassphraseSecureString.DisplayName = Resources.Activity_EncryptText_Property_PassphraseSecureString_Name; diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashFileViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashFileViewModel.cs index b9cefb95..e7ddd7a0 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashFileViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashFileViewModel.cs @@ -94,23 +94,26 @@ protected override void InitializeModel() KeyEncodingString.IsPrincipal = false; KeyEncodingString.OrderIndex = propertyOrderIndex++; - KeyEncodingString.Category = Resources.Category_Options_Name; + KeyEncodingString.Category = Resources.Category_Encoding_Name; KeyEncodingString.DataSource = _encodingDataSource; KeyEncodingString.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; _encodingDataSource.Data = EncodingHelpers.GetAvailableEncodings(); - Result.IsPrincipal = true; - Result.OrderIndex = propertyOrderIndex++; - Result.Category = Resources.Output; - ContinueOnError.IsPrincipal = false; - ContinueOnError.OrderIndex = propertyOrderIndex; + ContinueOnError.OrderIndex = propertyOrderIndex++; ContinueOnError.Category = Resources.Category_Options_Name; ContinueOnError.Widget = new DefaultWidget { Type = ViewModelWidgetType.Toggle, Metadata = new Dictionary() }; ContinueOnError.Value = false; + // Output is assigned last so it renders after the Options section (guideline "outputs last"). + // Non-principal, matching every other Cryptography output — it belongs in the Output section + // of the properties panel, not the collapsed canvas card among the inputs. + Result.IsPrincipal = false; + Result.OrderIndex = propertyOrderIndex++; + Result.Category = Resources.Output; + _keyToggle.ConfigureMenuActions(); ApplyKeyInputModeVisibility(); diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashTextViewModel.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashTextViewModel.cs index 4bde9fa0..a603c6b0 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashTextViewModel.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/KeyedHashTextViewModel.cs @@ -2,6 +2,7 @@ using System.Activities.DesignViewModels; using System.Activities.ViewModels; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security; using System.Security.Cryptography; using UiPath.Cryptography.Activities.Helpers; @@ -34,6 +35,7 @@ public partial class HashText namespace UiPath.Cryptography.Activities.NetCore.ViewModels { + [ExcludeFromCodeCoverage] public partial class KeyedHashTextViewModel : DesignPropertiesViewModel { private readonly DataSource _encodingDataSource; @@ -99,23 +101,26 @@ protected override void InitializeModel() KeyEncodingString.IsPrincipal = false; KeyEncodingString.OrderIndex = propertyOrderIndex++; - KeyEncodingString.Category = Resources.Category_Options_Name; + KeyEncodingString.Category = Resources.Category_Encoding_Name; KeyEncodingString.DataSource = _encodingDataSource; KeyEncodingString.Widget = new DefaultWidget { Type = ViewModelWidgetType.Dropdown, Metadata = new Dictionary() }; _encodingDataSource.Data = EncodingHelpers.GetAvailableEncodings(); - Result.IsPrincipal = true; - Result.OrderIndex = propertyOrderIndex++; - Result.Category = Resources.Output; - ContinueOnError.IsPrincipal = false; - ContinueOnError.OrderIndex = propertyOrderIndex; + ContinueOnError.OrderIndex = propertyOrderIndex++; ContinueOnError.Category = Resources.Category_Options_Name; ContinueOnError.Widget = new DefaultWidget { Type = ViewModelWidgetType.Toggle, Metadata = new Dictionary() }; ContinueOnError.Value = false; + // Output is assigned last so it renders after the Options section (guideline "outputs last"). + // Non-principal, matching every other Cryptography output — it belongs in the Output section + // of the properties panel, not the collapsed canvas card among the inputs. + Result.IsPrincipal = false; + Result.OrderIndex = propertyOrderIndex++; + Result.Category = Resources.Output; + _keyToggle.ConfigureMenuActions(); ApplyKeyInputModeVisibility(); ConfigurePropertyTexts(); diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs b/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs index f7315006..145feaa9 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs @@ -1724,6 +1724,78 @@ public static string Activity_EncryptText_Property_PrivateKeyFilePath_Name { return ResourceManager.GetString("Activity_EncryptText_Property_PrivateKeyFilePath_Name", resourceCulture); } } + + /// + /// Looks up a localized string similar to Private key file. + /// + public static string Activity_EncryptText_Property_PrivateKeyFile_Name { + get { + return ResourceManager.GetString("Activity_EncryptText_Property_PrivateKeyFile_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your own PGP private key, supplied as a file resource. Used to sign the encrypted output.. + /// + public static string Activity_EncryptText_Property_PrivateKeyFile_Description { + get { + return ResourceManager.GetString("Activity_EncryptText_Property_PrivateKeyFile_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Private key file. + /// + public static string Activity_EncryptFile_Property_PrivateKeyFile_Name { + get { + return ResourceManager.GetString("Activity_EncryptFile_Property_PrivateKeyFile_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your own PGP private key, supplied as a file resource. Used to sign the encrypted output.. + /// + public static string Activity_EncryptFile_Property_PrivateKeyFile_Description { + get { + return ResourceManager.GetString("Activity_EncryptFile_Property_PrivateKeyFile_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Private key file. + /// + public static string Activity_DecryptText_Property_PrivateKeyFile_Name { + get { + return ResourceManager.GetString("Activity_DecryptText_Property_PrivateKeyFile_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your own PGP private key, supplied as a file resource. Used to decrypt the message.. + /// + public static string Activity_DecryptText_Property_PrivateKeyFile_Description { + get { + return ResourceManager.GetString("Activity_DecryptText_Property_PrivateKeyFile_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Private key file. + /// + public static string Activity_DecryptFile_Property_PrivateKeyFile_Name { + get { + return ResourceManager.GetString("Activity_DecryptFile_Property_PrivateKeyFile_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your own PGP private key, supplied as a file resource. Used to decrypt the message.. + /// + public static string Activity_DecryptFile_Property_PrivateKeyFile_Description { + get { + return ResourceManager.GetString("Activity_DecryptFile_Property_PrivateKeyFile_Description", resourceCulture); + } + } /// /// Looks up a localized string similar to The recipient's PGP public key, supplied as a file resource (e.g. from the project resources or Storage Bucket). When set, takes precedence over the file-path variant.. @@ -3209,7 +3281,25 @@ public static string Category_Options_Name { return ResourceManager.GetString("Category_Options_Name", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Advanced. + /// + public static string Category_Advanced_Name { + get { + return ResourceManager.GetString("Category_Advanced_Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Encoding. + /// + public static string Category_Encoding_Name { + get { + return ResourceManager.GetString("Category_Encoding_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Others. /// diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.resx b/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.resx index 9f1ca214..afdae469 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.resx +++ b/Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.resx @@ -200,25 +200,49 @@ Public key file - The recipient's PGP public key, supplied as a file resource (e.g. from the project resources or Storage Bucket). When set, takes precedence over the file-path variant. + The recipient's PGP public key, supplied as a file resource (e.g. from the project resources or Storage Bucket). Public key file - The sender's PGP public key, supplied as a file resource. Used to verify the signature. When set, takes precedence over the file-path variant. + The sender's PGP public key, supplied as a file resource. Used to verify the signature. Public key file - The recipient's PGP public key, supplied as a file resource (e.g. from the project resources or Storage Bucket). When set, takes precedence over the file-path variant. + The recipient's PGP public key, supplied as a file resource (e.g. from the project resources or Storage Bucket). Public key file - The sender's PGP public key, supplied as a file resource. Used to verify the signature. When set, takes precedence over the file-path variant. + The sender's PGP public key, supplied as a file resource. Used to verify the signature. + + + Private key file + + + Your own PGP private key, supplied as a file resource (e.g. from the project resources or Storage Bucket). Used to sign the encrypted output. Required only when Sign data is enabled. + + + Private key file + + + Your own PGP private key, supplied as a file resource (e.g. from the project resources or Storage Bucket). Used to sign the encrypted output. Required only when Sign data is enabled. + + + Private key file + + + Your own PGP private key, supplied as a file resource. Used to decrypt the message. + + + Private key file + + + Your own PGP private key, supplied as a file resource. Used to decrypt the message. ContinueOnError @@ -913,6 +937,14 @@ Options Category name + + Advanced + Category name + + + Encoding + Category name + Use Key Secure String @@ -1499,7 +1531,9 @@ Property name - Initialization vector for Raw format. Provide it in the same encoding selected in the "Key bytes format" dropdown above (Hex or Base64). Leave empty to auto-generate a random IV. Length must match the algorithm's IV size (16 bytes for AES/Rijndael, 12 for AESGCM/ChaCha20Poly1305, 8 for DES/TripleDES/RC2). + Initialization vector for Raw format. Provide it in the same encoding selected in the "Key bytes format" dropdown above (Hex or Base64). Leave empty to auto-generate a random IV. Length must match the algorithm's IV size (16 bytes for AES/Rijndael, 12 for AESGCM/ChaCha20Poly1305, 8 for DES/TripleDES/RC2). + +WARNING: Never reuse the same (Key, IV) pair — it breaks confidentiality, and under AEAD modes (AES-GCM, ChaCha20-Poly1305) also lets an attacker forge messages; use each pair at most once, or leave empty for a fresh random IV per call. Property description @@ -1573,7 +1607,9 @@ Property name - Initialization vector for Raw format. Provide it in the same encoding selected in the "Key bytes format" dropdown above (Hex or Base64). Leave empty to auto-generate a random IV. Length must match the algorithm's IV size (16 bytes for AES/Rijndael, 12 for AESGCM/ChaCha20Poly1305, 8 for DES/TripleDES/RC2). + Initialization vector for Raw format. Provide it in the same encoding selected in the "Key bytes format" dropdown above (Hex or Base64). Leave empty to auto-generate a random IV. Length must match the algorithm's IV size (16 bytes for AES/Rijndael, 12 for AESGCM/ChaCha20Poly1305, 8 for DES/TripleDES/RC2). + +WARNING: Never reuse the same (Key, IV) pair — it breaks confidentiality, and under AEAD modes (AES-GCM, ChaCha20-Poly1305) also lets an attacker forge messages; use each pair at most once, or leave empty for a fresh random IV per call. Property description