From 2bdbcbc16e64065d609f69395b01dfece79d51a2 Mon Sep 17 00:00:00 2001 From: Eric Roth Date: Wed, 18 Dec 2019 11:00:10 -0500 Subject: [PATCH 1/3] Added Ability to encrypt variables with RSA Certificate. --- source/Octostache.Tests/UsageFixture.cs | 34 ++++++++++++++ .../Octostache/Templates/BuiltInFunctions.cs | 3 +- .../Functions/TextEncryptFunction.cs | 45 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 source/Octostache/Templates/Functions/TextEncryptFunction.cs diff --git a/source/Octostache.Tests/UsageFixture.cs b/source/Octostache.Tests/UsageFixture.cs index c053d4b..72ffe97 100644 --- a/source/Octostache.Tests/UsageFixture.cs +++ b/source/Octostache.Tests/UsageFixture.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using Octostache.Templates; using Xunit; @@ -581,5 +583,37 @@ public void CanGetIndexes() absentIndexes.Should().HaveCount(0); } + + [Fact] + public void EncryptText() + { + var certificate = MakeCert(); + if (certificate != null) + { + var variableDictionary = new VariableDictionary + { + //TODO: Verify this is how Octopus certificate variables create certificate base64 strings. #{CertificateVariable.Certificate} + ["CertificateBase64String"] = Convert.ToBase64String(certificate.RawData), + ["Data"] = "Test", + ["EncryptedData"] = "#{Data | RSAEncrypt #{CertificateBase64String}}" + }; + + var encryptedString = variableDictionary.Evaluate("#{EncryptedData}"); + encryptedString.Should().NotBeNullOrWhiteSpace(); + } + } + + static X509Certificate2 MakeCert() + { +#if NETCOREAPP + var rsa = RSA.Create(); + var req = new CertificateRequest("cn=test", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1)); +#else + //TODO: could potentially manually create certificate and include it as an embedded resource to use here. + return null; +#endif + + } } } diff --git a/source/Octostache/Templates/BuiltInFunctions.cs b/source/Octostache/Templates/BuiltInFunctions.cs index 2bf284d..1e59178 100644 --- a/source/Octostache/Templates/BuiltInFunctions.cs +++ b/source/Octostache/Templates/BuiltInFunctions.cs @@ -26,7 +26,8 @@ static class BuiltInFunctions {"substring", TextSubstringFunction.Substring}, {"truncate", TextManipulationFunction.Truncate}, {"trim", TextManipulationFunction.Trim}, - {"uripart", TextManipulationFunction.UriPart} + {"uripart", TextManipulationFunction.UriPart}, + {"rsaencrypt", TextEncryptFunction.RSAEncrypt } }; // Configuration should be done at startup, this isn't thread-safe. diff --git a/source/Octostache/Templates/Functions/TextEncryptFunction.cs b/source/Octostache/Templates/Functions/TextEncryptFunction.cs new file mode 100644 index 0000000..526bd9a --- /dev/null +++ b/source/Octostache/Templates/Functions/TextEncryptFunction.cs @@ -0,0 +1,45 @@ +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Octostache.Templates.Functions +{ + internal class TextEncryptFunction + { + public static string RSAEncrypt(string data, string[] arguments) + { + if (arguments == null || arguments.Length == 0 || arguments.Length > 2) + { + return null; + } + + try + { + + var certificate = new X509Certificate2(Convert.FromBase64String(arguments[0])); +#if NET40 + var publicKey = (RSACryptoServiceProvider)certificate.PublicKey.Key; +#else + var publicKey = certificate.GetRSAPublicKey(); +#endif + using (publicKey) + { + var encoding = Encoding.GetEncoding(arguments.Length > 1 ? arguments[1] : "utf-8"); + var byteData = encoding.GetBytes(data); +#if NET40 + var encryptedBytes = publicKey.Encrypt(byteData, false); +#else + var encryptedBytes = publicKey.Encrypt(byteData, RSAEncryptionPadding.OaepSHA1); +#endif + return Convert.ToBase64String(encryptedBytes); + } + } + catch (Exception) + { + } + + return null; + } + } +} \ No newline at end of file From f05af4279f221facc946024e7cae9a0df209bfce Mon Sep 17 00:00:00 2001 From: Eric Roth Date: Wed, 18 Dec 2019 11:23:02 -0500 Subject: [PATCH 2/3] Changed to enable OAEP padding in .Net Framework 4.0 --- source/Octostache/Templates/Functions/TextEncryptFunction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Octostache/Templates/Functions/TextEncryptFunction.cs b/source/Octostache/Templates/Functions/TextEncryptFunction.cs index 526bd9a..cbd30f0 100644 --- a/source/Octostache/Templates/Functions/TextEncryptFunction.cs +++ b/source/Octostache/Templates/Functions/TextEncryptFunction.cs @@ -28,7 +28,7 @@ public static string RSAEncrypt(string data, string[] arguments) var encoding = Encoding.GetEncoding(arguments.Length > 1 ? arguments[1] : "utf-8"); var byteData = encoding.GetBytes(data); #if NET40 - var encryptedBytes = publicKey.Encrypt(byteData, false); + var encryptedBytes = publicKey.Encrypt(byteData, true); #else var encryptedBytes = publicKey.Encrypt(byteData, RSAEncryptionPadding.OaepSHA1); #endif From 2d45a97396a65c5cec241278ffc192fdd976a298 Mon Sep 17 00:00:00 2001 From: Eric Roth Date: Tue, 28 Jan 2020 09:35:56 -0500 Subject: [PATCH 3/3] Added code to dispose of certificate. --- .../Templates/Functions/TextEncryptFunction.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/source/Octostache/Templates/Functions/TextEncryptFunction.cs b/source/Octostache/Templates/Functions/TextEncryptFunction.cs index cbd30f0..c5d28c6 100644 --- a/source/Octostache/Templates/Functions/TextEncryptFunction.cs +++ b/source/Octostache/Templates/Functions/TextEncryptFunction.cs @@ -14,10 +14,12 @@ public static string RSAEncrypt(string data, string[] arguments) return null; } + X509Certificate2 certificate = null; + try { - var certificate = new X509Certificate2(Convert.FromBase64String(arguments[0])); + certificate = new X509Certificate2(Convert.FromBase64String(arguments[0])); #if NET40 var publicKey = (RSACryptoServiceProvider)certificate.PublicKey.Key; #else @@ -38,6 +40,14 @@ public static string RSAEncrypt(string data, string[] arguments) catch (Exception) { } + finally + { + // IDisposable was only added to certificate after.Net 4.6 + if (certificate != null && certificate is IDisposable disposableCertificate) + { + disposableCertificate.Dispose(); + } + } return null; }