From 084454ff94660db03d44e36451cf6094be261037 Mon Sep 17 00:00:00 2001 From: viogroza Date: Fri, 1 May 2026 13:01:29 +0300 Subject: [PATCH] Cryptography: Coded support [STUD-79807] Refactor Cryptography API tests --- .../CryptographyServiceTests.cs | 376 +++--------- .../CryptoOptions.cs | 67 +++ .../PGPOptions.cs | 74 +++ .../Services/CryptographyService.cs | 565 ++++++++---------- .../Services/ICryptographyService.cs | 269 ++++----- 5 files changed, 608 insertions(+), 743 deletions(-) create mode 100644 Activities/Cryptography/UiPath.Cryptography.Activities.API/CryptoOptions.cs create mode 100644 Activities/Cryptography/UiPath.Cryptography.Activities.API/PGPOptions.cs diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/CryptographyServiceTests.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/CryptographyServiceTests.cs index bb51efbf..a124f55a 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/CryptographyServiceTests.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/CryptographyServiceTests.cs @@ -16,90 +16,106 @@ public class CryptographyServiceTests #region EncryptText / DecryptText [Fact] - public void EncryptText_NullInput_Throws() + public void EncryptText_NullOptions_Throws() { Should.Throw(() => - _service.EncryptText(null, EncryptionAlgorithm.AES, "key", Encoding.UTF8)); + _service.EncryptText(null)); } [Fact] - public void EncryptText_NullEncoding_Throws() + public void EncryptText_NoInputData_Throws() { - Should.Throw(() => - _service.EncryptText("input", EncryptionAlgorithm.AES, "key", null)); + Should.Throw(() => + _service.EncryptText(new CryptoOptions { Key = "key", Encoding = Encoding.UTF8 })); } [Fact] - public void EncryptText_EmptyKey_Throws() + public void EncryptText_NoKeyMaterial_Throws() { Should.Throw(() => - _service.EncryptText("input", EncryptionAlgorithm.AES, string.Empty, Encoding.UTF8)); + _service.EncryptText(new CryptoOptions { Input = "hello", Encoding = Encoding.UTF8 })); } [Fact] - public void DecryptText_NullInput_Throws() + public void DecryptText_NullOptions_Throws() { Should.Throw(() => - _service.DecryptText(null, EncryptionAlgorithm.AES, "key", Encoding.UTF8)); + _service.DecryptText(null)); } - [Fact] - public void DecryptText_NullEncoding_Throws() + [Theory] + [InlineData(EncryptionAlgorithm.AES)] + [InlineData(EncryptionAlgorithm.TripleDES)] + public void EncryptText_ThenDecryptText_StringKey_ReturnsOriginal(EncryptionAlgorithm algorithm) { - Should.Throw(() => - _service.DecryptText("aGVsbG8=", EncryptionAlgorithm.AES, "key", null)); - } + const string original = "Hello, coded workflows!"; + var encryptOptions = new CryptoOptions { Input = original, Key = "mySecretKey", Algorithm = algorithm, Encoding = Encoding.UTF8 }; - [Fact] - public void DecryptText_EmptyKey_Throws() - { - Should.Throw(() => - _service.DecryptText("aGVsbG8=", EncryptionAlgorithm.AES, string.Empty, Encoding.UTF8)); + string ciphertext = _service.EncryptText(encryptOptions); + string plaintext = _service.DecryptText(new CryptoOptions { Input = ciphertext, Key = "mySecretKey", Algorithm = algorithm, Encoding = Encoding.UTF8 }); + + plaintext.ShouldBe(original); } [Theory] [InlineData(EncryptionAlgorithm.AES)] [InlineData(EncryptionAlgorithm.TripleDES)] - public void EncryptText_ThenDecryptText_ReturnsOriginal(EncryptionAlgorithm algorithm) + public void EncryptText_ThenDecryptText_SecureStringKey_ReturnsOriginal(EncryptionAlgorithm algorithm) { - string original = "Hello, coded workflows!"; - string key = "mySecretKey"; + const string original = "Hello, SecureString!"; + var encryptOptions = new CryptoOptions { Input = original, KeySecure = ToSecureString("mySecretKey"), Algorithm = algorithm, Encoding = Encoding.UTF8 }; - string encrypted = _service.EncryptText(original, algorithm, key, Encoding.UTF8); - string decrypted = _service.DecryptText(encrypted, algorithm, key, Encoding.UTF8); + string ciphertext = _service.EncryptText(encryptOptions); + string plaintext = _service.DecryptText(new CryptoOptions { Input = ciphertext, KeySecure = ToSecureString("mySecretKey"), Algorithm = algorithm, Encoding = Encoding.UTF8 }); - decrypted.ShouldBe(original); + plaintext.ShouldBe(original); } - #endregion + [Theory] + [InlineData(EncryptionAlgorithm.AES)] + [InlineData(EncryptionAlgorithm.TripleDES)] + public void EncryptText_ThenDecryptText_RawKeyBytes_ReturnsOriginal(EncryptionAlgorithm algorithm) + { + const string original = "Hello, byte[] key!"; + byte[] keyBytes = Encoding.UTF8.GetBytes("myRawKeyBytes!!"); + var encryptOptions = new CryptoOptions { Input = original, KeyRaw = keyBytes, Algorithm = algorithm, Encoding = Encoding.UTF8 }; - #region KeyedHashText + string ciphertext = _service.EncryptText(encryptOptions); + string plaintext = _service.DecryptText(new CryptoOptions { Input = ciphertext, KeyRaw = keyBytes, Algorithm = algorithm, Encoding = Encoding.UTF8 }); - [Fact] - public void KeyedHashText_NullInput_Throws() - { - Should.Throw(() => - _service.KeyedHashText(null, KeyedHashAlgorithms.HMACSHA256, "key", Encoding.UTF8)); + plaintext.ShouldBe(original); } - [Fact] - public void KeyedHashText_NullEncoding_Throws() + [Theory] + [InlineData(EncryptionAlgorithm.AES)] + [InlineData(EncryptionAlgorithm.TripleDES)] + public void EncryptText_ThenDecryptText_InputRaw_ReturnsOriginal(EncryptionAlgorithm algorithm) { - Should.Throw(() => - _service.KeyedHashText("input", KeyedHashAlgorithms.HMACSHA256, "key", null)); + const string original = "Hello, raw input!"; + byte[] rawInput = Encoding.UTF8.GetBytes(original); + byte[] keyBytes = Encoding.UTF8.GetBytes("myRawKeyBytes!!"); + + string ciphertext = _service.EncryptText(new CryptoOptions { InputRaw = rawInput, KeyRaw = keyBytes, Algorithm = algorithm, Encoding = Encoding.UTF8 }); + string plaintext = _service.DecryptText(new CryptoOptions { Input = ciphertext, KeyRaw = keyBytes, Algorithm = algorithm, Encoding = Encoding.UTF8 }); + + plaintext.ShouldBe(original); } + #endregion + + #region KeyedHashText + [Fact] - public void KeyedHashText_EmptyKey_Throws() + public void KeyedHashText_NullOptions_Throws() { - Should.Throw(() => - _service.KeyedHashText("input", KeyedHashAlgorithms.HMACSHA256, string.Empty, Encoding.UTF8)); + Should.Throw(() => + _service.KeyedHashText(null)); } [Fact] public void KeyedHashText_ReturnsHexString() { - string result = _service.KeyedHashText("hello", KeyedHashAlgorithms.HMACSHA256, "key", Encoding.UTF8); + string result = _service.KeyedHashText(new CryptoOptions { Input = "hello", Key = "key", KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }); result.ShouldNotBeNull(); result.ShouldMatch("^[0-9A-F]+$"); @@ -108,8 +124,10 @@ public void KeyedHashText_ReturnsHexString() [Fact] public void KeyedHashText_SameInputAndKey_ReturnsSameHash() { - string hash1 = _service.KeyedHashText("hello", KeyedHashAlgorithms.HMACSHA256, "key", Encoding.UTF8); - string hash2 = _service.KeyedHashText("hello", KeyedHashAlgorithms.HMACSHA256, "key", Encoding.UTF8); + var options = new CryptoOptions { Input = "hello", Key = "key", KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }; + + string hash1 = _service.KeyedHashText(options); + string hash2 = _service.KeyedHashText(options); hash1.ShouldBe(hash2); } @@ -117,35 +135,47 @@ public void KeyedHashText_SameInputAndKey_ReturnsSameHash() [Fact] public void KeyedHashText_DifferentKeys_ReturnsDifferentHash() { - string hash1 = _service.KeyedHashText("hello", KeyedHashAlgorithms.HMACSHA256, "key1", Encoding.UTF8); - string hash2 = _service.KeyedHashText("hello", KeyedHashAlgorithms.HMACSHA256, "key2", Encoding.UTF8); + string hash1 = _service.KeyedHashText(new CryptoOptions { Input = "hello", Key = "key1", KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }); + string hash2 = _service.KeyedHashText(new CryptoOptions { Input = "hello", Key = "key2", KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }); hash1.ShouldNotBe(hash2); } + [Fact] + public void KeyedHashText_SecureStringKey_MatchesStringKeyHash() + { + const string input = "hello"; + const string keyStr = "myHmacKey"; + + string hashFromString = _service.KeyedHashText(new CryptoOptions { Input = input, Key = keyStr, KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }); + string hashFromSecure = _service.KeyedHashText(new CryptoOptions { Input = input, KeySecure = ToSecureString(keyStr), KeyedHashAlgorithm = KeyedHashAlgorithms.HMACSHA256, Encoding = Encoding.UTF8 }); + + hashFromSecure.ShouldBe(hashFromString); + } + #endregion #region EncryptFile / DecryptFile [Fact] - public void EncryptFile_NullInputPath_Throws() + public void EncryptFile_NullInputFilePath_Throws() { Should.Throw(() => - _service.EncryptFile(null, "out.bin", EncryptionAlgorithm.AES, "key", Encoding.UTF8, true)); + _service.EncryptFile(new CryptoOptions { OutputFile = "out.bin", Key = "key", Overwrite = true })); } [Fact] - public void EncryptFile_NullOutputPath_Throws() + public void EncryptFile_NullOutputFile_Throws() { Should.Throw(() => - _service.EncryptFile("in.txt", null, EncryptionAlgorithm.AES, "key", Encoding.UTF8, true)); + _service.EncryptFile(new CryptoOptions { Input = "in.txt", Key = "key", Overwrite = true })); } [Fact] - public void DecryptFile_NullInputPath_Throws() + public void DecryptFile_NullInputFilePath_Throws() { Should.Throw(() => - _service.DecryptFile(null, "out.txt", EncryptionAlgorithm.AES, "key", Encoding.UTF8, true)); + _service.DecryptFile(new CryptoOptions { OutputFile = "out.txt", Key = "key", Overwrite = true })); } [Fact] @@ -157,10 +187,10 @@ public void EncryptFile_ExistingOutputWithoutOverwrite_Throws() try { File.WriteAllText(inputPath, "test content"); - File.WriteAllText(outputPath, "existing output"); // output must exist to trigger the guard + File.WriteAllText(outputPath, "existing output"); Should.Throw(() => - _service.EncryptFile(inputPath, outputPath, EncryptionAlgorithm.AES, "key", Encoding.UTF8, overwrite: false)); + _service.EncryptFile(new CryptoOptions { Input = inputPath, OutputFile = outputPath, Key = "key", Overwrite = false })); } finally { @@ -170,9 +200,9 @@ public void EncryptFile_ExistingOutputWithoutOverwrite_Throws() } [Fact] - public void EncryptFile_ThenDecryptFile_ReturnsOriginalContent() + public void EncryptFile_ThenDecryptFile_StringKey_ReturnsOriginalContent() { - string original = "Coded workflow file encryption test"; + const string original = "Coded workflow file encryption test"; string inputPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string encryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string decryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); @@ -181,11 +211,10 @@ public void EncryptFile_ThenDecryptFile_ReturnsOriginalContent() { File.WriteAllText(inputPath, original, Encoding.UTF8); - _service.EncryptFile(inputPath, encryptedPath, EncryptionAlgorithm.AES, "testKey", Encoding.UTF8, overwrite: true); - _service.DecryptFile(encryptedPath, decryptedPath, EncryptionAlgorithm.AES, "testKey", Encoding.UTF8, overwrite: true); + _service.EncryptFile(new CryptoOptions { Input = inputPath, OutputFile = encryptedPath, Key = "testKey", Algorithm = EncryptionAlgorithm.AES, Encoding = Encoding.UTF8, Overwrite = true }); + _service.DecryptFile(new CryptoOptions { Input = encryptedPath, OutputFile = decryptedPath, Key = "testKey", Algorithm = EncryptionAlgorithm.AES, Encoding = Encoding.UTF8, Overwrite = true }); - string result = File.ReadAllText(decryptedPath, Encoding.UTF8); - result.ShouldBe(original); + File.ReadAllText(decryptedPath, Encoding.UTF8).ShouldBe(original); } finally { @@ -195,134 +224,20 @@ public void EncryptFile_ThenDecryptFile_ReturnsOriginalContent() } } - #endregion - - #region PGP guard clauses - - [Fact] - public void PgpEncrypt_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpEncrypt(null, Stream.Null)); - } - - [Fact] - public void PgpEncrypt_NullPublicKeyStream_Throws() - { - Should.Throw(() => - _service.PgpEncrypt(new byte[1], null)); - } - - [Fact] - public void PgpDecrypt_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpDecrypt(null, Stream.Null, "pass")); - } - - [Fact] - public void PgpDecrypt_NullPrivateKeyStream_Throws() - { - Should.Throw(() => - _service.PgpDecrypt(new byte[1], null, "pass")); - } - - [Fact] - public void PgpEncryptText_NullInput_Throws() - { - Should.Throw(() => - _service.PgpEncryptText(null, Stream.Null)); - } - - [Fact] - public void PgpDecryptText_NullInput_Throws() - { - Should.Throw(() => - _service.PgpDecryptText(null, Stream.Null, "pass")); - } - - [Fact] - public void PgpSignFile_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpSignFile(null, Stream.Null, "pass")); - } - - [Fact] - public void PgpClearSignFile_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpClearSignFile(null, Stream.Null, "pass")); - } - - [Fact] - public void PgpVerify_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpVerify(null, Stream.Null)); - } - - [Fact] - public void PgpVerifyClear_NullInputBytes_Throws() - { - Should.Throw(() => - _service.PgpVerifyClear(null, Stream.Null)); - } - [Fact] - public void PgpGenerateKeyPair_NullPublicKeyPath_Throws() + public void EncryptFile_ThenDecryptFile_SecureStringKey_ReturnsOriginalContent() { - Should.Throw(() => - _service.PgpGenerateKeyPair(null, "private.asc", "user", "pass")); - } - - [Fact] - public void PgpGenerateKeyPair_NullPrivateKeyPath_Throws() - { - Should.Throw(() => - _service.PgpGenerateKeyPair("public.asc", null, "user", "pass")); - } - - #endregion - - #region SecureString key overloads - - [Theory] - [InlineData(EncryptionAlgorithm.AES)] - [InlineData(EncryptionAlgorithm.TripleDES)] - public void EncryptText_SecureStringKey_ThenDecryptText_SecureStringKey_ReturnsOriginal(EncryptionAlgorithm algorithm) - { - string original = "Hello, SecureString!"; - SecureString key = ToSecureString("mySecretKey"); - - string encrypted = _service.EncryptText(original, algorithm, key, Encoding.UTF8); - string decrypted = _service.DecryptText(encrypted, algorithm, key, Encoding.UTF8); - - decrypted.ShouldBe(original); - } - - [Fact] - public void EncryptText_SecureStringKey_NullKey_Throws() - { - Should.Throw(() => - _service.EncryptText("input", EncryptionAlgorithm.AES, (SecureString)null, Encoding.UTF8)); - } - - [Fact] - public void EncryptFile_SecureStringKey_ThenDecryptFile_ReturnsOriginalContent() - { - string original = "SecureString file encryption test"; + const string original = "SecureString file encryption test"; string inputPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string encryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string decryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - SecureString key = ToSecureString("secureFileKey"); try { File.WriteAllText(inputPath, original, Encoding.UTF8); - _service.EncryptFile(inputPath, encryptedPath, EncryptionAlgorithm.AES, key, Encoding.UTF8, overwrite: true); - _service.DecryptFile(encryptedPath, decryptedPath, EncryptionAlgorithm.AES, key, Encoding.UTF8, overwrite: true); + _service.EncryptFile(new CryptoOptions { Input = inputPath, OutputFile = encryptedPath, KeySecure = ToSecureString("secureFileKey"), Algorithm = EncryptionAlgorithm.AES, Encoding = Encoding.UTF8, Overwrite = true }); + _service.DecryptFile(new CryptoOptions { Input = encryptedPath, OutputFile = decryptedPath, KeySecure = ToSecureString("secureFileKey"), Algorithm = EncryptionAlgorithm.AES, Encoding = Encoding.UTF8, Overwrite = true }); File.ReadAllText(decryptedPath, Encoding.UTF8).ShouldBe(original); } @@ -335,75 +250,9 @@ public void EncryptFile_SecureStringKey_ThenDecryptFile_ReturnsOriginalContent() } [Fact] - public void KeyedHashText_SecureStringKey_MatchesStringKeyHash() - { - const string input = "hello"; - const string keyStr = "myHmacKey"; - SecureString keySecure = ToSecureString(keyStr); - - string hashFromString = _service.KeyedHashText(input, KeyedHashAlgorithms.HMACSHA256, keyStr, Encoding.UTF8); - string hashFromSecure = _service.KeyedHashText(input, KeyedHashAlgorithms.HMACSHA256, keySecure, Encoding.UTF8); - - hashFromSecure.ShouldBe(hashFromString); - } - - [Fact] - public void KeyedHashText_SecureStringKey_NullKey_Throws() - { - Should.Throw(() => - _service.KeyedHashText("input", KeyedHashAlgorithms.HMACSHA256, (SecureString)null, Encoding.UTF8)); - } - - [Fact] - public void PgpEncrypt_SecureStringPassphrase_NullPassphrase_Throws() - { - Should.Throw(() => - _service.PgpEncrypt(new byte[1], Stream.Null, Stream.Null, (SecureString)null)); - } - - [Fact] - public void PgpDecrypt_SecureStringPassphrase_NullPassphrase_Throws() + public void EncryptFile_ThenDecryptFile_RawKeyBytes_ReturnsOriginalContent() { - Should.Throw(() => - _service.PgpDecrypt(new byte[1], Stream.Null, (SecureString)null)); - } - - #endregion - - #region byte[] key overloads - - [Theory] - [InlineData(EncryptionAlgorithm.AES)] - [InlineData(EncryptionAlgorithm.TripleDES)] - public void EncryptText_ByteArrayKey_ThenDecryptText_ByteArrayKey_ReturnsOriginal(EncryptionAlgorithm algorithm) - { - string original = "Hello, byte[] key!"; - byte[] keyBytes = Encoding.UTF8.GetBytes("myRawKeyBytes!!"); - - string encrypted = _service.EncryptText(original, algorithm, keyBytes, Encoding.UTF8); - string decrypted = _service.DecryptText(encrypted, algorithm, keyBytes, Encoding.UTF8); - - decrypted.ShouldBe(original); - } - - [Fact] - public void EncryptText_ByteArrayKey_NullKeyBytes_Throws() - { - Should.Throw(() => - _service.EncryptText("input", EncryptionAlgorithm.AES, (byte[])null, Encoding.UTF8)); - } - - [Fact] - public void EncryptText_ByteArrayKey_EmptyKeyBytes_Throws() - { - Should.Throw(() => - _service.EncryptText("input", EncryptionAlgorithm.AES, Array.Empty(), Encoding.UTF8)); - } - - [Fact] - public void EncryptFile_ByteArrayKey_ThenDecryptFile_ReturnsOriginalContent() - { - string original = "byte[] key file encryption test"; + const string original = "byte[] key file encryption test"; string inputPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string encryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string decryptedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); @@ -413,8 +262,8 @@ public void EncryptFile_ByteArrayKey_ThenDecryptFile_ReturnsOriginalContent() { File.WriteAllText(inputPath, original, Encoding.UTF8); - _service.EncryptFile(inputPath, encryptedPath, EncryptionAlgorithm.AES, keyBytes, overwrite: true); - _service.DecryptFile(encryptedPath, decryptedPath, EncryptionAlgorithm.AES, keyBytes, overwrite: true); + _service.EncryptFile(new CryptoOptions { Input = inputPath, OutputFile = encryptedPath, KeyRaw = keyBytes, Algorithm = EncryptionAlgorithm.AES, Overwrite = true }); + _service.DecryptFile(new CryptoOptions { Input = encryptedPath, OutputFile = decryptedPath, KeyRaw = keyBytes, Algorithm = EncryptionAlgorithm.AES, Overwrite = true }); File.ReadAllText(decryptedPath, Encoding.UTF8).ShouldBe(original); } @@ -426,41 +275,6 @@ public void EncryptFile_ByteArrayKey_ThenDecryptFile_ReturnsOriginalContent() } } - [Fact] - public void EncryptFile_ByteArrayKey_NullKeyBytes_Throws() - { - Should.Throw(() => - _service.EncryptFile("in.txt", "out.bin", EncryptionAlgorithm.AES, (byte[])null, overwrite: true)); - } - - [Fact] - public void KeyedHashText_ByteArrayKey_MatchesEquivalentStringKeyHash() - { - const string input = "hello"; - byte[] keyBytes = Encoding.UTF8.GetBytes("hmacKey"); - - string hashFromBytes = _service.KeyedHashText(input, KeyedHashAlgorithms.HMACSHA256, keyBytes, Encoding.UTF8); - string hashFromString = _service.KeyedHashText(input, KeyedHashAlgorithms.HMACSHA256, "hmacKey", Encoding.UTF8); - - // Both must be valid hex and the same — the byte[] overload skips the PBKDF2 path, - // but KeyEncoding(encoding, key, null) == encoding.GetBytes(key) for ASCII keys, so they match. - hashFromBytes.ShouldBe(hashFromString); - } - - [Fact] - public void KeyedHashText_ByteArrayKey_NullKeyBytes_Throws() - { - Should.Throw(() => - _service.KeyedHashText("input", KeyedHashAlgorithms.HMACSHA256, (byte[])null, Encoding.UTF8)); - } - - [Fact] - public void KeyedHashFile_ByteArrayKey_NullKeyBytes_Throws() - { - Should.Throw(() => - _service.KeyedHashFile("file.txt", KeyedHashAlgorithms.HMACSHA256, (byte[])null)); - } - #endregion // ── Helpers ─────────────────────────────────────────────────────────── diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.API/CryptoOptions.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.API/CryptoOptions.cs new file mode 100644 index 00000000..3fae2c63 --- /dev/null +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.API/CryptoOptions.cs @@ -0,0 +1,67 @@ +using System.Security; +using System.Text; +using UiPath.Cryptography.Enums; + +namespace UiPath.Cryptography.Activities.API +{ + /// + /// Encapsulates all inputs needed by a cryptographic operation. + /// + /// + /// Input data — supply exactly one of , , + /// or depending on how the data is available. + /// For file operations holds the file path. + /// Key material — supply exactly one of , , + /// or . Resolution priority: . + /// Key fields are ignored when is . + /// PGP — set to and + /// populate . PGP is supported for file operations only. + /// + public sealed class CryptoOptions + { + // ── Input data ──────────────────────────────────────────────────────── + + /// Plain-text input string, ciphertext, or file path. + public string Input { get; set; } + + /// Input data as a (text operations only). + public SecureString InputSecure { get; set; } + + /// Input data as raw bytes (text operations only). + public byte[] InputRaw { get; set; } + + // ── Key material ────────────────────────────────────────────────────── + + /// Plain-text cryptographic key. + public string Key { get; set; } + + /// Cryptographic key as a ; zeroed after use where possible. + public SecureString KeySecure { get; set; } + + /// Raw key bytes; used directly without PBKDF2 conversion. + public byte[] KeyRaw { get; set; } + + // ── Algorithm / behaviour ───────────────────────────────────────────── + + /// Symmetric encryption / decryption algorithm. + public EncryptionAlgorithm Algorithm { get; set; } + + /// Keyed-hash algorithm (used only by KeyedHashText / KeyedHashFile). + public KeyedHashAlgorithms KeyedHashAlgorithm { get; set; } + + /// Whether to overwrite the output file if it already exists (file operations only). + public bool Overwrite { get; set; } + + /// Output file path (file operations only). + public string OutputFile { get; set; } + + /// Text encoding used to convert strings to bytes. Defaults to when . + public Encoding Encoding { get; set; } + + /// + /// PGP-specific options. Must be set when is + /// ; ignored otherwise. + /// + public PGPOptions PgpConfig { get; set; } + } +} diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.API/PGPOptions.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.API/PGPOptions.cs new file mode 100644 index 00000000..c677046b --- /dev/null +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.API/PGPOptions.cs @@ -0,0 +1,74 @@ +using System.Security; +using UiPath.Cryptography.Enums; + +namespace UiPath.Cryptography.Activities.API +{ + /// + /// PGP-specific options used by PGP operations on . + /// + /// + /// For and + /// set to + /// and assign an instance to . + /// For dedicated PGP operations (, + /// , , + /// ) pass the instance directly. + /// Not all fields are required for every operation — see each property's remarks. + /// + public sealed class PGPOptions + { + // ── Key files ───────────────────────────────────────────────────────── + + /// + /// Path to the ASCII-armored or binary PGP public key file. + /// Required for: EncryptFile, PgpVerify (Signature/ClearSignature/PublicKey modes), + /// DecryptFile when verifying signature, PgpGenerateKeyPair (output path). + /// + public string PublicKeyFilePath { get; set; } + + /// + /// Path to the ASCII-armored or binary PGP private key file. + /// Required for: DecryptFile, PgpSignFile, PgpClearSignFile, + /// EncryptFile when is , + /// PgpGenerateKeyPair (output path). + /// + public string PrivateKeyFilePath { get; set; } + + // ── Authentication ──────────────────────────────────────────────────── + + /// + /// Passphrase protecting the private key. + /// Required for: DecryptFile, PgpSignFile, PgpClearSignFile, + /// EncryptFile when is , + /// PgpGenerateKeyPair. + /// + public SecureString Passphrase { get; set; } + + // ── Behaviour flags ─────────────────────────────────────────────────── + + /// + /// When the encrypted output is also signed using + /// and . + /// Used by: EncryptFile, DecryptFile (verify signature on decrypt). + /// + public bool SignData { get; set; } + + /// + /// Verification mode used by . + /// + /// — verify a detached/inline PGP signature. + /// — verify a clear-signed file. + /// — validate the public key itself (no input file needed). + /// + /// + public PgpVerifyMode VerifyMode { get; set; } + + // ── Key generation ──────────────────────────────────────────────────── + + /// + /// PGP user identity string (e.g. "Alice <alice@example.com>"). + /// Used only by . + /// + public string Username { get; set; } + } +} diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/CryptographyService.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/CryptographyService.cs index 0092b52e..962f3fdb 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/CryptographyService.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/CryptographyService.cs @@ -1,410 +1,370 @@ using System; using System.IO; +using System.Net; using System.Runtime.InteropServices; using System.Security; using System.Text; +using UiPath.Cryptography; using UiPath.Cryptography.Enums; namespace UiPath.Cryptography.Activities.API { internal class CryptographyService : ICryptographyService { - // ── Symmetric: string key ──────────────────────────────────────────── + // ── Public API ──────────────────────────────────────────────────────── - public string EncryptText(string input, EncryptionAlgorithm algorithm, string key, Encoding encoding) + public string EncryptText(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = encoding.GetBytes(input); - byte[] encryptedBytes = CryptographyHelper.EncryptData(algorithm, inputBytes, keyBytes); - return Convert.ToBase64String(encryptedBytes); - } - - public string DecryptText(string input, EncryptionAlgorithm algorithm, string key, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = Convert.FromBase64String(input); - byte[] decryptedBytes = CryptographyHelper.DecryptData(algorithm, inputBytes, keyBytes); - return encoding.GetString(decryptedBytes); - } - - public void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, string key, Encoding encoding, bool overwrite) - { - if (string.IsNullOrWhiteSpace(inputFilePath)) - throw new ArgumentException("Input file path must not be null or empty.", nameof(inputFilePath)); - if (string.IsNullOrWhiteSpace(outputFilePath)) - throw new ArgumentException("Output file path must not be null or empty.", nameof(outputFilePath)); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - if (!overwrite && File.Exists(outputFilePath)) - throw new InvalidOperationException($"Output file already exists: {outputFilePath}"); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = File.ReadAllBytes(inputFilePath); - byte[] encryptedBytes = CryptographyHelper.EncryptData(algorithm, inputBytes, keyBytes); - File.WriteAllBytes(outputFilePath, encryptedBytes); - } - - public void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, string key, Encoding encoding, bool overwrite) - { - if (string.IsNullOrWhiteSpace(inputFilePath)) - throw new ArgumentException("Input file path must not be null or empty.", nameof(inputFilePath)); - if (string.IsNullOrWhiteSpace(outputFilePath)) - throw new ArgumentException("Output file path must not be null or empty.", nameof(outputFilePath)); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - if (!overwrite && File.Exists(outputFilePath)) - throw new InvalidOperationException($"Output file already exists: {outputFilePath}"); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = File.ReadAllBytes(inputFilePath); - byte[] decryptedBytes = CryptographyHelper.DecryptData(algorithm, inputBytes, keyBytes); - File.WriteAllBytes(outputFilePath, decryptedBytes); - } - - // ── Symmetric: SecureString key ────────────────────────────────────── - - public string EncryptText(string input, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + if (options.Algorithm == EncryptionAlgorithm.PGP) + throw new InvalidOperationException("PGP is not supported for text operations. Use EncryptFile with Algorithm = PGP."); + var encoding = ResolveEncoding(options); + byte[] inputBytes = ResolveInputBytes(options, encoding); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - return EncryptText(input, algorithm, keyBytes, encoding); + byte[] encryptedBytes = CryptographyHelper.EncryptData(options.Algorithm, inputBytes, keyBytes); + return Convert.ToBase64String(encryptedBytes); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - public string DecryptText(string input, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding) + public string DecryptText(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + if (options.Algorithm == EncryptionAlgorithm.PGP) + throw new InvalidOperationException("PGP is not supported for text operations. Use DecryptFile with Algorithm = PGP."); + var encoding = ResolveEncoding(options); + byte[] inputBytes = ResolveCiphertextBytes(options); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - return DecryptText(input, algorithm, keyBytes, encoding); + byte[] decryptedBytes = CryptographyHelper.DecryptData(options.Algorithm, inputBytes, keyBytes); + return encoding.GetString(decryptedBytes); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - public void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding, bool overwrite) + public void EncryptFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the input file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.OutputFile)) + throw new ArgumentException("CryptoOptions.OutputFile must contain the output file path.", nameof(options)); + if (!options.Overwrite && File.Exists(options.OutputFile)) + throw new InvalidOperationException($"Output file already exists: {options.OutputFile}"); + + if (options.Algorithm == EncryptionAlgorithm.PGP) + { + ExecutePgpEncryptFile(options); + return; + } + + var encoding = ResolveEncoding(options); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - EncryptFile(inputFilePath, outputFilePath, algorithm, keyBytes, overwrite); + byte[] inputBytes = File.ReadAllBytes(options.Input); + byte[] encryptedBytes = CryptographyHelper.EncryptData(options.Algorithm, inputBytes, keyBytes); + File.WriteAllBytes(options.OutputFile, encryptedBytes); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - public void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding, bool overwrite) + public void DecryptFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the input file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.OutputFile)) + throw new ArgumentException("CryptoOptions.OutputFile must contain the output file path.", nameof(options)); + if (!options.Overwrite && File.Exists(options.OutputFile)) + throw new InvalidOperationException($"Output file already exists: {options.OutputFile}"); + + if (options.Algorithm == EncryptionAlgorithm.PGP) + { + ExecutePgpDecryptFile(options); + return; + } + + var encoding = ResolveEncoding(options); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - DecryptFile(inputFilePath, outputFilePath, algorithm, keyBytes, overwrite); + byte[] inputBytes = File.ReadAllBytes(options.Input); + byte[] decryptedBytes = CryptographyHelper.DecryptData(options.Algorithm, inputBytes, keyBytes); + File.WriteAllBytes(options.OutputFile, decryptedBytes); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - // ── Symmetric: byte[] key ───────────────────────────────────────────── - - public string EncryptText(string input, EncryptionAlgorithm algorithm, byte[] keyBytes, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - byte[] inputBytes = encoding.GetBytes(input); - byte[] encryptedBytes = CryptographyHelper.EncryptData(algorithm, inputBytes, keyBytes); - return Convert.ToBase64String(encryptedBytes); - } - - public string DecryptText(string input, EncryptionAlgorithm algorithm, byte[] keyBytes, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - byte[] inputBytes = Convert.FromBase64String(input); - byte[] decryptedBytes = CryptographyHelper.DecryptData(algorithm, inputBytes, keyBytes); - return encoding.GetString(decryptedBytes); - } - - public void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, byte[] keyBytes, bool overwrite) - { - if (string.IsNullOrWhiteSpace(inputFilePath)) - throw new ArgumentException("Input file path must not be null or empty.", nameof(inputFilePath)); - if (string.IsNullOrWhiteSpace(outputFilePath)) - throw new ArgumentException("Output file path must not be null or empty.", nameof(outputFilePath)); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - if (!overwrite && File.Exists(outputFilePath)) - throw new InvalidOperationException($"Output file already exists: {outputFilePath}"); - - byte[] inputBytes = File.ReadAllBytes(inputFilePath); - byte[] encryptedBytes = CryptographyHelper.EncryptData(algorithm, inputBytes, keyBytes); - File.WriteAllBytes(outputFilePath, encryptedBytes); - } - - public void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, byte[] keyBytes, bool overwrite) - { - if (string.IsNullOrWhiteSpace(inputFilePath)) - throw new ArgumentException("Input file path must not be null or empty.", nameof(inputFilePath)); - if (string.IsNullOrWhiteSpace(outputFilePath)) - throw new ArgumentException("Output file path must not be null or empty.", nameof(outputFilePath)); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - if (!overwrite && File.Exists(outputFilePath)) - throw new InvalidOperationException($"Output file already exists: {outputFilePath}"); - - byte[] inputBytes = File.ReadAllBytes(inputFilePath); - byte[] decryptedBytes = CryptographyHelper.DecryptData(algorithm, inputBytes, keyBytes); - File.WriteAllBytes(outputFilePath, decryptedBytes); - } - - // ── Keyed hash: string key ──────────────────────────────────────────── - - public string KeyedHashText(string input, KeyedHashAlgorithms algorithm, string key, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = encoding.GetBytes(input); - byte[] hashBytes = CryptographyHelper.HashDataWithKey(algorithm, inputBytes, keyBytes); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty); - } - - public string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, string key, Encoding encoding) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException("File path must not be null or empty.", nameof(filePath)); - ArgumentNullException.ThrowIfNull(encoding); - if (string.IsNullOrEmpty(key)) - throw new ArgumentException("Key must not be null or empty.", nameof(key)); - - byte[] keyBytes = CryptographyHelper.KeyEncoding(encoding, key, null); - byte[] inputBytes = File.ReadAllBytes(filePath); - byte[] hashBytes = CryptographyHelper.HashDataWithKey(algorithm, inputBytes, keyBytes); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty); - } - - // ── Keyed hash: SecureString key ────────────────────────────────────── - - public string KeyedHashText(string input, KeyedHashAlgorithms algorithm, SecureString key, Encoding encoding) + public string KeyedHashText(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + var encoding = ResolveEncoding(options); + byte[] inputBytes = ResolveInputBytes(options, encoding); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - return KeyedHashText(input, algorithm, keyBytes, encoding); + byte[] hashBytes = CryptographyHelper.HashDataWithKey(options.KeyedHashAlgorithm, inputBytes, keyBytes); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - public string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, SecureString key, Encoding encoding) + public string KeyedHashFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(key); - ArgumentNullException.ThrowIfNull(encoding); - byte[] keyBytes = SecureStringToBytes(key, encoding); + ArgumentNullException.ThrowIfNull(options); + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the file path.", nameof(options)); + + var encoding = ResolveEncoding(options); + byte[] keyBytes = ResolveKeyBytes(options, encoding); try { - return KeyedHashFile(filePath, algorithm, keyBytes); + byte[] inputBytes = File.ReadAllBytes(options.Input); + byte[] hashBytes = CryptographyHelper.HashDataWithKey(options.KeyedHashAlgorithm, inputBytes, keyBytes); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); } finally { - Array.Clear(keyBytes, 0, keyBytes.Length); + ClearIfDerived(options, keyBytes); } } - // ── Keyed hash: byte[] key ──────────────────────────────────────────── - - public string KeyedHashText(string input, KeyedHashAlgorithms algorithm, byte[] keyBytes, Encoding encoding) + public void PgpSignFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(encoding); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - byte[] inputBytes = encoding.GetBytes(input); - byte[] hashBytes = CryptographyHelper.HashDataWithKey(algorithm, inputBytes, keyBytes); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty); - } + ArgumentNullException.ThrowIfNull(options); + var pgp = ResolvePgpConfig(options); + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the input file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.OutputFile)) + throw new ArgumentException("CryptoOptions.OutputFile must contain the output file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.PrivateKeyFilePath)) + throw new ArgumentException("PGPOptions.PrivateKeyFilePath is required for signing.", nameof(options)); + if (!options.Overwrite && File.Exists(options.OutputFile)) + throw new InvalidOperationException($"Output file already exists: {options.OutputFile}"); - public string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, byte[] keyBytes) - { - if (string.IsNullOrWhiteSpace(filePath)) - throw new ArgumentException("File path must not be null or empty.", nameof(filePath)); - if (keyBytes is null || keyBytes.Length == 0) - throw new ArgumentException("Key bytes must not be null or empty.", nameof(keyBytes)); - - byte[] inputBytes = File.ReadAllBytes(filePath); - byte[] hashBytes = CryptographyHelper.HashDataWithKey(algorithm, inputBytes, keyBytes); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + string passphrase = ResolvePassphrase(pgp); + byte[] inputBytes = File.ReadAllBytes(options.Input); + using var privateKeyStream = File.OpenRead(pgp.PrivateKeyFilePath); + byte[] signed = CryptographyHelper.PgpSign(inputBytes, privateKeyStream, passphrase); + File.WriteAllBytes(options.OutputFile, signed); } - // ── PGP: string passphrase ───────────────────────────────────────────── - - public byte[] PgpEncrypt(byte[] inputBytes, Stream publicKeyStream, Stream privateKeyStream = null, string passphrase = null, bool sign = false) + public void PgpClearSignFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(publicKeyStream); + ArgumentNullException.ThrowIfNull(options); + var pgp = ResolvePgpConfig(options); + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the input file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.OutputFile)) + throw new ArgumentException("CryptoOptions.OutputFile must contain the output file path.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.PrivateKeyFilePath)) + throw new ArgumentException("PGPOptions.PrivateKeyFilePath is required for clear-signing.", nameof(options)); + if (!options.Overwrite && File.Exists(options.OutputFile)) + throw new InvalidOperationException($"Output file already exists: {options.OutputFile}"); - return CryptographyHelper.PgpEncrypt(inputBytes, publicKeyStream, privateKeyStream, passphrase, sign); + string passphrase = ResolvePassphrase(pgp); + byte[] inputBytes = File.ReadAllBytes(options.Input); + using var privateKeyStream = File.OpenRead(pgp.PrivateKeyFilePath); + byte[] signed = CryptographyHelper.PgpClearSign(inputBytes, privateKeyStream, passphrase); + File.WriteAllBytes(options.OutputFile, signed); } - public byte[] PgpDecrypt(byte[] inputBytes, Stream privateKeyStream, string passphrase, Stream publicKeyStream = null, bool verifySignature = false) + public bool PgpVerify(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(privateKeyStream); + ArgumentNullException.ThrowIfNull(options); + var pgp = ResolvePgpConfig(options); + if (string.IsNullOrWhiteSpace(pgp.PublicKeyFilePath)) + throw new ArgumentException("PGPOptions.PublicKeyFilePath is required for verification.", nameof(options)); - return CryptographyHelper.PgpDecrypt(inputBytes, privateKeyStream, passphrase, publicKeyStream, verifySignature); - } + using var publicKeyStream = File.OpenRead(pgp.PublicKeyFilePath); - public string PgpEncryptText(string input, Stream publicKeyStream, Stream privateKeyStream = null, string passphrase = null, bool sign = false) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(publicKeyStream); + if (pgp.VerifyMode == PgpVerifyMode.PublicKey) + return CryptographyHelper.PgpVerifyPublicKey(publicKeyStream); - return CryptographyHelper.PgpEncryptText(input, publicKeyStream, privateKeyStream, passphrase, sign); - } + if (string.IsNullOrWhiteSpace(options.Input)) + throw new ArgumentException("CryptoOptions.Input must contain the input file path.", nameof(options)); - public string PgpDecryptText(string input, Stream privateKeyStream, string passphrase, Stream publicKeyStream = null, bool verifySignature = false) - { - ArgumentNullException.ThrowIfNull(input); - ArgumentNullException.ThrowIfNull(privateKeyStream); - - return CryptographyHelper.PgpDecryptText(input, privateKeyStream, passphrase, publicKeyStream, verifySignature); + byte[] inputBytes = File.ReadAllBytes(options.Input); + return pgp.VerifyMode == PgpVerifyMode.ClearSignature + ? CryptographyHelper.PgpVerifyClear(inputBytes, publicKeyStream) + : CryptographyHelper.PgpVerify(inputBytes, publicKeyStream); } - public byte[] PgpSignFile(byte[] inputBytes, Stream privateKeyStream, string passphrase) + public void PgpGenerateKeyPair(PGPOptions options) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(privateKeyStream); + ArgumentNullException.ThrowIfNull(options); + if (string.IsNullOrWhiteSpace(options.PublicKeyFilePath)) + throw new ArgumentException("PGPOptions.PublicKeyFilePath is required.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.PrivateKeyFilePath)) + throw new ArgumentException("PGPOptions.PrivateKeyFilePath is required.", nameof(options)); + if (string.IsNullOrWhiteSpace(options.Username)) + throw new ArgumentException("PGPOptions.Username is required.", nameof(options)); - return CryptographyHelper.PgpSign(inputBytes, privateKeyStream, passphrase); + string passphrase = ResolvePassphrase(options); + CryptographyHelper.PgpGenerateKeyPair(options.PublicKeyFilePath, options.PrivateKeyFilePath, options.Username, passphrase); } - public byte[] PgpClearSignFile(byte[] inputBytes, Stream privateKeyStream, string passphrase) + public void PgpGenerateKeyPair(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(privateKeyStream); + ArgumentNullException.ThrowIfNull(options); + var pgp = ResolvePgpConfig(options); + if (string.IsNullOrWhiteSpace(pgp.PublicKeyFilePath)) + throw new ArgumentException("PGPOptions.PublicKeyFilePath is required.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.PrivateKeyFilePath)) + throw new ArgumentException("PGPOptions.PrivateKeyFilePath is required.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.Username)) + throw new ArgumentException("PGPOptions.Username is required.", nameof(options)); + if (!options.Overwrite && File.Exists(pgp.PublicKeyFilePath)) + throw new InvalidOperationException($"Public key file already exists: {pgp.PublicKeyFilePath}"); + if (!options.Overwrite && File.Exists(pgp.PrivateKeyFilePath)) + throw new InvalidOperationException($"Private key file already exists: {pgp.PrivateKeyFilePath}"); - return CryptographyHelper.PgpClearSign(inputBytes, privateKeyStream, passphrase); + string passphrase = ResolvePassphrase(pgp); + CryptographyHelper.PgpGenerateKeyPair(pgp.PublicKeyFilePath, pgp.PrivateKeyFilePath, pgp.Username, passphrase); } - // ── PGP: SecureString passphrase ────────────────────────────────────── - // BouncyCastle's PGP API requires a plain string passphrase. The SecureString - // is materialised to a managed string for the duration of the call and cannot - // be zeroed afterward because strings are immutable in .NET. - // This is a known limitation documented on ICryptographyService. + // ── Private helpers ─────────────────────────────────────────────────── - public byte[] PgpEncrypt(byte[] inputBytes, Stream publicKeyStream, Stream privateKeyStream, SecureString passphrase, bool sign = false) - { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpEncrypt(inputBytes, publicKeyStream, privateKeyStream, SecureStringToManagedString(passphrase), sign); - } + // Validates that PgpConfig is set; throws a clear ArgumentException otherwise. + private static PGPOptions ResolvePgpConfig(CryptoOptions options) => + options.PgpConfig + ?? throw new ArgumentException("CryptoOptions.PgpConfig must be set for PGP operations.", nameof(options)); - public byte[] PgpDecrypt(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase, Stream publicKeyStream = null, bool verifySignature = false) + private static void ExecutePgpEncryptFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpDecrypt(inputBytes, privateKeyStream, SecureStringToManagedString(passphrase), publicKeyStream, verifySignature); - } + var pgp = options.PgpConfig + ?? throw new ArgumentException("CryptoOptions.PgpConfig must be set when Algorithm is PGP.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.PublicKeyFilePath)) + throw new ArgumentException("PGPOptions.PublicKeyFilePath is required.", nameof(options)); - public string PgpEncryptText(string input, Stream publicKeyStream, Stream privateKeyStream, SecureString passphrase, bool sign = false) - { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpEncryptText(input, publicKeyStream, privateKeyStream, SecureStringToManagedString(passphrase), sign); - } + string passphrase = pgp.SignData ? ResolvePassphrase(pgp) : null; - public string PgpDecryptText(string input, Stream privateKeyStream, SecureString passphrase, Stream publicKeyStream = null, bool verifySignature = false) - { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpDecryptText(input, privateKeyStream, SecureStringToManagedString(passphrase), publicKeyStream, verifySignature); + using var publicKeyStream = File.OpenRead(pgp.PublicKeyFilePath); + Stream privateKeyStream = pgp.SignData ? File.OpenRead(pgp.PrivateKeyFilePath) : null; + try + { + byte[] inputBytes = File.ReadAllBytes(options.Input); + byte[] encryptedBytes = CryptographyHelper.PgpEncrypt(inputBytes, publicKeyStream, privateKeyStream, passphrase, pgp.SignData); + File.WriteAllBytes(options.OutputFile, encryptedBytes); + } + finally + { + privateKeyStream?.Dispose(); + } } - public byte[] PgpSignFile(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase) + private static void ExecutePgpDecryptFile(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpSignFile(inputBytes, privateKeyStream, SecureStringToManagedString(passphrase)); - } + var pgp = options.PgpConfig + ?? throw new ArgumentException("CryptoOptions.PgpConfig must be set when Algorithm is PGP.", nameof(options)); + if (string.IsNullOrWhiteSpace(pgp.PrivateKeyFilePath)) + throw new ArgumentException("PGPOptions.PrivateKeyFilePath is required for decryption.", nameof(options)); - public byte[] PgpClearSignFile(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase) - { - ArgumentNullException.ThrowIfNull(passphrase); - return PgpClearSignFile(inputBytes, privateKeyStream, SecureStringToManagedString(passphrase)); + string passphrase = ResolvePassphrase(pgp); + + using var privateKeyStream = File.OpenRead(pgp.PrivateKeyFilePath); + Stream publicKeyStream = pgp.SignData ? File.OpenRead(pgp.PublicKeyFilePath) : null; + try + { + byte[] inputBytes = File.ReadAllBytes(options.Input); + byte[] decryptedBytes = CryptographyHelper.PgpDecrypt(inputBytes, privateKeyStream, passphrase, publicKeyStream, pgp.SignData); + File.WriteAllBytes(options.OutputFile, decryptedBytes); + } + finally + { + publicKeyStream?.Dispose(); + } } - // ── PGP: verify / key-gen ───────────────────────────────────────────── + // Extracts the passphrase from PGPOptions.Passphrase (SecureString) to a plain string. + // NetworkCredential is used to avoid creating a managed string via Marshal — it is the + // same pattern used in the existing activity layer (PgpStreamHelper). + private static string ResolvePassphrase(PGPOptions pgp) => + pgp.Passphrase is { Length: > 0 } + ? new System.Net.NetworkCredential(string.Empty, pgp.Passphrase).Password + : null; - public bool PgpVerify(byte[] inputBytes, Stream publicKeyStream) + private static Encoding ResolveEncoding(CryptoOptions options) => + options.Encoding ?? Encoding.UTF8; + + /// + /// Resolves plain-text input bytes from . + /// Priority: . + /// + private static byte[] ResolveInputBytes(CryptoOptions options, Encoding encoding) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(publicKeyStream); + if (options.InputRaw is { Length: > 0 }) + return options.InputRaw; + + if (options.InputSecure is not null) + return SecureStringToBytes(options.InputSecure, encoding); - return CryptographyHelper.PgpVerify(inputBytes, publicKeyStream); + if (!string.IsNullOrEmpty(options.Input)) + return encoding.GetBytes(options.Input); + + throw new ArgumentException("CryptoOptions must supply input data via Input, InputSecure, or InputRaw.", nameof(options)); } - public bool PgpVerifyClear(byte[] inputBytes, Stream publicKeyStream) + /// + /// Resolves ciphertext bytes for decryption. + /// is used directly (raw cipher bytes); + /// otherwise is treated as a Base-64 string and decoded. + /// + private static byte[] ResolveCiphertextBytes(CryptoOptions options) { - ArgumentNullException.ThrowIfNull(inputBytes); - ArgumentNullException.ThrowIfNull(publicKeyStream); + if (options.InputRaw is { Length: > 0 }) + return options.InputRaw; + + if (!string.IsNullOrEmpty(options.Input)) + return Convert.FromBase64String(options.Input); - return CryptographyHelper.PgpVerifyClear(inputBytes, publicKeyStream); + throw new ArgumentException("CryptoOptions must supply ciphertext via Input (Base-64) or InputRaw.", nameof(options)); } - public void PgpGenerateKeyPair(string publicKeyPath, string privateKeyPath, string username, string password) + /// + /// Resolves key bytes from . + /// Priority: . + /// + private static byte[] ResolveKeyBytes(CryptoOptions options, Encoding encoding) { - if (string.IsNullOrWhiteSpace(publicKeyPath)) - throw new ArgumentException("Public key path must not be null or empty.", nameof(publicKeyPath)); - if (string.IsNullOrWhiteSpace(privateKeyPath)) - throw new ArgumentException("Private key path must not be null or empty.", nameof(privateKeyPath)); + if (options.KeyRaw is { Length: > 0 }) + return options.KeyRaw; + + if (options.KeySecure is not null) + return SecureStringToBytes(options.KeySecure, encoding); - CryptographyHelper.PgpGenerateKeyPair(publicKeyPath, privateKeyPath, username, password); + if (!string.IsNullOrEmpty(options.Key)) + return CryptographyHelper.KeyEncoding(encoding, options.Key, null); + + throw new ArgumentException("CryptoOptions must supply key material via Key, KeySecure, or KeyRaw.", nameof(options)); } - // ── Private helpers ─────────────────────────────────────────────────── + // Zero derived key bytes only when they were not passed in directly as KeyRaw + // (caller owns KeyRaw and is responsible for clearing it). + private static void ClearIfDerived(CryptoOptions options, byte[] keyBytes) + { + if (!ReferenceEquals(keyBytes, options.KeyRaw)) + Array.Clear(keyBytes, 0, keyBytes.Length); + } // Extracts the SecureString content via unmanaged memory, encodes it with the // caller-supplied Encoding, then zeros both the unmanaged buffer and the char[] @@ -429,22 +389,5 @@ private static byte[] SecureStringToBytes(SecureString value, Encoding encoding) Array.Clear(chars, 0, chars.Length); } } - - // Used only by PGP overloads: BouncyCastle requires a plain string passphrase - // and offers no byte[]-based API. The managed string cannot be zeroed afterward. - private static string SecureStringToManagedString(SecureString value) - { - IntPtr ptr = IntPtr.Zero; - try - { - ptr = Marshal.SecureStringToGlobalAllocUnicode(value); - return Marshal.PtrToStringUni(ptr); - } - finally - { - if (ptr != IntPtr.Zero) - Marshal.ZeroFreeGlobalAllocUnicode(ptr); - } - } } } diff --git a/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/ICryptographyService.cs b/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/ICryptographyService.cs index 0e704913..2f6e3ced 100644 --- a/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/ICryptographyService.cs +++ b/Activities/Cryptography/UiPath.Cryptography.Activities.API/Services/ICryptographyService.cs @@ -1,8 +1,4 @@ -using System.IO; -using System.Net; -using System.Security; using System.Text; -using UiPath.Cryptography.Enums; namespace UiPath.Cryptography.Activities.API { @@ -10,163 +6,134 @@ namespace UiPath.Cryptography.Activities.API /// Provides cryptography capabilities for coded workflows. /// /// - /// IV / salt / nonce strategy (symmetric methods) + /// Key material /// - /// All symmetric encrypt methods (, ) are - /// non-deterministic: a fresh random salt (8 bytes, PBKDF2) and IV/nonce are generated - /// on every call and prepended to the ciphertext. Encrypting the same plaintext twice always - /// produces different ciphertext. The paired decrypt methods reconstruct the salt and IV from - /// the same prefix, so the output of Encrypt can always be fed directly to Decrypt without - /// supplying the IV separately. + /// Each method resolves the cryptographic key from in the following + /// priority order: (raw bytes, used directly) → + /// (SecureString, extracted via unmanaged memory, zeroed after use) → + /// (plain string, encoded to bytes via ). /// + /// Input data /// - /// CBC-family algorithms (AES/Rijndael/DES/3DES/RC2) use PKCS7 padding, CBC mode, and a - /// randomly-generated IV. AES-GCM () uses a - /// randomly-generated 96-bit nonce and a 128-bit authentication tag, providing authenticated - /// encryption with associated data (AEAD) — it is the recommended choice for new workflows. + /// For text operations the input is resolved from , + /// , or (first non-null wins). + /// For file operations holds the input file path and + /// holds the output file path. /// - /// Key material + /// PGP /// - /// Every method that accepts a string key has a paired overload that accepts - /// byte[] keyBytes (raw key material) or SecureString key. - /// Prefer the byte[] overload when key material is already loaded into memory as bytes. - /// The SecureString overload is supported for symmetric operations (encrypt, decrypt, - /// keyed hash): the key is extracted via unmanaged memory, encoded to bytes, used for the - /// cryptographic operation, and then zeroed — no managed is created. - /// The plain-string overload remains for compatibility; note that - /// values are immutable and may be interned, meaning the secret can linger on the heap until GC. + /// Set to + /// and populate with a instance. + /// PGP is supported for file operations only (, ); + /// calling or with PGP throws . + /// Key fields on are ignored when PGP is selected. /// - /// PGP passphrase limitation + /// IV / salt / nonce strategy (symmetric methods) /// - /// PGP overloads that accept SecureString passphrase must materialise the passphrase - /// to a managed because the underlying BouncyCastle library requires - /// a plain string and offers no byte[]-based passphrase API. The managed string cannot be - /// zeroed afterward. For maximum security with PGP, prefer passing a key ring that does not - /// require a passphrase, or accept that the passphrase will briefly exist as a managed string. + /// All symmetric encrypt methods (, ) are + /// non-deterministic: a fresh random salt (8 bytes, PBKDF2) and IV/nonce are generated + /// on every call and prepended to the ciphertext. The paired decrypt methods reconstruct the + /// salt and IV from the same prefix. /// /// public interface ICryptographyService { - // ── Symmetric: string key ──────────────────────────────────────────── - - /// Encrypts a string using the specified algorithm and key. - /// - string EncryptText(string input, EncryptionAlgorithm algorithm, string key, Encoding encoding); - - /// Encrypts a string using the specified algorithm and a key. - /// - string EncryptText(string input, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding); - - /// Encrypts a string using the specified algorithm and raw key bytes. - /// - string EncryptText(string input, EncryptionAlgorithm algorithm, byte[] keyBytes, Encoding encoding); - - /// Decrypts a string using the specified algorithm and key. - string DecryptText(string input, EncryptionAlgorithm algorithm, string key, Encoding encoding); - - /// Decrypts a string using the specified algorithm and a key. - string DecryptText(string input, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding); - - /// Decrypts a string using the specified algorithm and raw key bytes. - string DecryptText(string input, EncryptionAlgorithm algorithm, byte[] keyBytes, Encoding encoding); - - // ── Symmetric file: string key ─────────────────────────────────────── - - /// Encrypts a file and writes the result to the output path. - void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, string key, Encoding encoding, bool overwrite); - - /// Decrypts a file and writes the result to the output path. - void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, string key, Encoding encoding, bool overwrite); - - // ── Symmetric file: SecureString key ───────────────────────────────── - - /// Encrypts a file and writes the result to the output path using a key. - void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding, bool overwrite); - - /// Decrypts a file and writes the result to the output path using a key. - void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, SecureString key, Encoding encoding, bool overwrite); - - // ── Symmetric file: byte[] key (raw material — no PBKDF2 string conversion) ── - - /// Encrypts a file and writes the result to the output path using raw key bytes. - void EncryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, byte[] keyBytes, bool overwrite); - - /// Decrypts a file and writes the result to the output path using raw key bytes. - void DecryptFile(string inputFilePath, string outputFilePath, EncryptionAlgorithm algorithm, byte[] keyBytes, bool overwrite); - - // ── Keyed hash: string key ─────────────────────────────────────────── - - /// Computes a keyed hash of a string and returns the hex-encoded result. - string KeyedHashText(string input, KeyedHashAlgorithms algorithm, string key, Encoding encoding); - - /// Computes a keyed hash of a file and returns the hex-encoded result. - string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, string key, Encoding encoding); - - // ── Keyed hash: SecureString key ───────────────────────────────────── - - /// Computes a keyed hash of a string using a key and returns the hex-encoded result. - string KeyedHashText(string input, KeyedHashAlgorithms algorithm, SecureString key, Encoding encoding); - - /// Computes a keyed hash of a file using a key and returns the hex-encoded result. - string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, SecureString key, Encoding encoding); - - // ── Keyed hash: byte[] key ──────────────────────────────────────────── - - /// Computes a keyed hash of a string using raw key bytes and returns the hex-encoded result. - string KeyedHashText(string input, KeyedHashAlgorithms algorithm, byte[] keyBytes, Encoding encoding); - - /// Computes a keyed hash of a file using raw key bytes and returns the hex-encoded result. - string KeyedHashFile(string filePath, KeyedHashAlgorithms algorithm, byte[] keyBytes); - - // ── PGP: string passphrase ──────────────────────────────────────────── - - /// Encrypts bytes using PGP with the provided public key stream. - byte[] PgpEncrypt(byte[] inputBytes, Stream publicKeyStream, Stream privateKeyStream = null, string passphrase = null, bool sign = false); - - /// Decrypts PGP-encrypted bytes using the provided private key stream. - byte[] PgpDecrypt(byte[] inputBytes, Stream privateKeyStream, string passphrase, Stream publicKeyStream = null, bool verifySignature = false); - - /// Encrypts a string using PGP with the provided public key stream. - string PgpEncryptText(string input, Stream publicKeyStream, Stream privateKeyStream = null, string passphrase = null, bool sign = false); - - /// Decrypts a PGP-encrypted string using the provided private key stream. - string PgpDecryptText(string input, Stream privateKeyStream, string passphrase, Stream publicKeyStream = null, bool verifySignature = false); - - /// Signs bytes using PGP with the provided private key stream. - byte[] PgpSignFile(byte[] inputBytes, Stream privateKeyStream, string passphrase); - - /// Creates a PGP clear-text signature for the given bytes. - byte[] PgpClearSignFile(byte[] inputBytes, Stream privateKeyStream, string passphrase); - - // ── PGP: SecureString passphrase ────────────────────────────────────── - - /// Encrypts bytes using PGP with the provided public key stream and a passphrase. - byte[] PgpEncrypt(byte[] inputBytes, Stream publicKeyStream, Stream privateKeyStream, SecureString passphrase, bool sign = false); - - /// Decrypts PGP-encrypted bytes using the provided private key stream and a passphrase. - byte[] PgpDecrypt(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase, Stream publicKeyStream = null, bool verifySignature = false); - - /// Encrypts a string using PGP with the provided public key stream and a passphrase. - string PgpEncryptText(string input, Stream publicKeyStream, Stream privateKeyStream, SecureString passphrase, bool sign = false); - - /// Decrypts a PGP-encrypted string using the provided private key stream and a passphrase. - string PgpDecryptText(string input, Stream privateKeyStream, SecureString passphrase, Stream publicKeyStream = null, bool verifySignature = false); - - /// Signs bytes using PGP with the provided private key stream and a passphrase. - byte[] PgpSignFile(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase); - - /// Creates a PGP clear-text signature using a passphrase. - byte[] PgpClearSignFile(byte[] inputBytes, Stream privateKeyStream, SecureString passphrase); - - // ── PGP: verify / key-gen ───────────────────────────────────────────── - - /// Verifies a PGP signature against the provided public key stream. - bool PgpVerify(byte[] inputBytes, Stream publicKeyStream); - - /// Verifies a PGP clear-text signature against the provided public key stream. - bool PgpVerifyClear(byte[] inputBytes, Stream publicKeyStream); - - /// Generates a PGP key pair and writes the keys to the specified paths. - void PgpGenerateKeyPair(string publicKeyPath, string privateKeyPath, string username, string password); + /// + /// Encrypts a string and returns the Base-64 encoded ciphertext. + /// The plaintext is read from , , + /// or (first non-null wins). + /// + /// Cryptographic options including input data, key material, algorithm, and encoding. + /// Base-64 encoded ciphertext. + string EncryptText(CryptoOptions options); + + /// + /// Decrypts a ciphertext and returns the original plaintext. + /// Supply raw cipher bytes via , or a Base-64 encoded + /// ciphertext string via (first non-null wins). + /// + /// Cryptographic options including input data, key material, algorithm, and encoding. + /// The original plaintext. + string DecryptText(CryptoOptions options); + + /// + /// Encrypts a file and writes the result to the output path. + /// The input file path is read from and the output path from . + /// + /// Cryptographic options including input/output file paths, key material, algorithm, encoding, and overwrite flag. + void EncryptFile(CryptoOptions options); + + /// + /// Decrypts a file and writes the result to the output path. + /// The input file path is read from and the output path from . + /// + /// Cryptographic options including input/output file paths, key material, algorithm, encoding, and overwrite flag. + void DecryptFile(CryptoOptions options); + + /// + /// Computes a keyed hash of a string and returns the hex-encoded result. + /// The input string is read from , , + /// or (first non-null wins). + /// + /// Cryptographic options including input data, key material, keyed-hash algorithm, and encoding. + /// Hex-encoded hash string. + string KeyedHashText(CryptoOptions options); + + /// + /// Computes a keyed hash of a file and returns the hex-encoded result. + /// The file path is read from . + /// + /// Cryptographic options including input file path, key material, and keyed-hash algorithm. + /// Hex-encoded hash string. + string KeyedHashFile(CryptoOptions options); + + // ── PGP operations ──────────────────────────────────────────────────── + + /// + /// Signs a file using a PGP private key and writes the signed output. + /// holds the input file path, + /// the output path. + /// + /// Options with supplying + /// and . + void PgpSignFile(CryptoOptions options); + + /// + /// Clear-signs a file using a PGP private key and writes the signed output. + /// holds the input file path, + /// the output path. + /// + /// Options with supplying + /// and . + void PgpClearSignFile(CryptoOptions options); + + /// + /// Verifies a PGP signature or validates a public key. + /// holds the input file path (not required when + /// is ). + /// + /// Options with supplying + /// and . + /// if verification succeeds. + bool PgpVerify(CryptoOptions options); + + /// + /// Generates a PGP key pair and writes the public and private key files. + /// Output paths are taken from and + /// . + /// + /// PGP options supplying , + /// , , + /// and . + void PgpGenerateKeyPair(PGPOptions options); + + /// + /// Generates a PGP key pair using PGP config from . + /// Output paths, username, and passphrase are read from . + /// controls whether existing key files are overwritten. + /// + /// Crypto options whose carries + /// the key generation parameters. + void PgpGenerateKeyPair(CryptoOptions options); } }