Merge pull request #1349 from martinjt/MachineKeyProtect
authorJoão Matos <joao@tritao.eu>
Tue, 16 Dec 2014 19:26:08 +0000 (14:26 -0500)
committerJoão Matos <joao@tritao.eu>
Tue, 16 Dec 2014 19:26:08 +0000 (14:26 -0500)
Implement MachineKey.Protect and MachineKey.Unprotect

mcs/class/System.Web/System.Web.Security/MachineKey.cs
mcs/class/System.Web/Test/System.Web.Security/MachineKeyTest.cs

index 636997e5d9bc00af5de927cce806400f4720ce26..193ec75195243a58e80e86d7b88dda0e270e7fb6 100644 (file)
@@ -30,6 +30,9 @@ using System;
 using System.Web;
 using System.Web.Configuration;
 using System.Web.Util;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
 
 namespace System.Web.Security 
 {
@@ -104,5 +107,61 @@ namespace System.Web.Security
                        
                        return MachineKeySectionUtils.GetHexString (result);
                }
+
+#if NET_4_5
+               public static byte[] Protect (byte[] userData, params string[] purposes)
+               {
+                       if (userData == null)
+                               throw new ArgumentNullException ("userData");
+
+                       foreach (var purpose in purposes)
+                       {
+                               if (string.IsNullOrWhiteSpace (purpose))
+                                       throw new ArgumentException ("all purpose parameters must contain text");
+                       }
+
+                       var config = WebConfigurationManager.GetWebApplicationSection ("system.web/machineKey") as MachineKeySection;
+                       var purposeJoined = string.Join (";", purposes);
+                       var purposeBytes = GetHashed (purposeJoined);
+                       var bytes = new byte [userData.Length + purposeBytes.Length];
+                       purposeBytes.CopyTo (bytes, 0);
+                       userData.CopyTo (bytes, purposeBytes.Length);
+                       return MachineKeySectionUtils.Encrypt (config, bytes);
+               }
+
+               public static byte[] Unprotect (byte[] protectedData, params string[] purposes)
+               {
+                       if (protectedData == null)
+                               throw new ArgumentNullException ("protectedData");
+
+                       foreach (var purpose in purposes) {
+                               if (string.IsNullOrWhiteSpace (purpose))
+                                       throw new ArgumentException ("all purpose parameters must contain text");
+                       }
+
+                       var config = WebConfigurationManager.GetWebApplicationSection ("system.web/machineKey") as MachineKeySection;
+                       var purposeJoined = string.Join (";", purposes);
+                       var purposeBytes = GetHashed (purposeJoined);
+                       var unprotected = MachineKeySectionUtils.Decrypt (config, protectedData);
+
+                       for (int i = 0; i < purposeBytes.Length; i++) {
+                               if (purposeBytes [i] != unprotected [i])
+                                       throw new CryptographicException ();
+                       }
+
+                       var dataLength = unprotected.Length - purposeBytes.Length;
+                       var result = new byte [dataLength];
+                       Array.Copy (unprotected, purposeBytes.Length, result, 0, dataLength);
+                       return result;
+               }
+
+               static byte[] GetHashed (string purposes)
+               {
+                       using (var hash = SHA512.Create ()) {
+                               var bytes = Encoding.UTF8.GetBytes (purposes);
+                               return hash.ComputeHash (bytes, 0, bytes.Length);
+                       }
+               }
+#endif
        }
 }
index f00e5c500e064520d1a4693b09e2aab408e08aa2..d00e0eb60d793a84fcfb84cfc4039a622c338c06 100644 (file)
@@ -25,6 +25,9 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
+using System.Security.Cryptography;
+
+
 #if NET_4_0
 using System;
 using System.Text;
@@ -166,6 +169,39 @@ namespace MonoTests.System.Web.Security
                                // Success
                        }
                }
+
+#if NET_4_5
+               [Test]
+               public void Protect ()
+               {
+                       AssertExtensions.Throws<ArgumentNullException> (() =>
+                               MachineKey.Protect (null, null), 
+                               "MachineKey.Protect not throwing an ArgumentNullException");
+
+                       AssertExtensions.Throws<ArgumentNullException> (() => 
+                               MachineKey.Protect (null, new [] { "test" }), 
+                               "MachineKey.Protect not throwing an ArgumentNullException");
+
+                       var testString = "asfgasd43tqrt4";
+                       var validUsages = new [] { "usage1", "usage2" };
+                       var oneUsage = new [] { "usage1" };
+                       var invalidUsages = new [] { "usage1", "invalidUsage" };
+
+                       var plainBytes = Encoding.ASCII.GetBytes (testString);
+                       var encryptedBytes = MachineKey.Protect (plainBytes, validUsages);
+                       var validDecryptedBytes = MachineKey.Unprotect (encryptedBytes, validUsages);
+
+                       Assert.AreEqual (plainBytes, validDecryptedBytes, "Decryption didn't work");
+
+                       AssertExtensions.Throws<CryptographicException> (() => 
+                               MachineKey.Unprotect (encryptedBytes, invalidUsages), 
+                               "Purposes not encrypting properly");
+
+                       AssertExtensions.Throws<CryptographicException> (() => 
+                               MachineKey.Unprotect (encryptedBytes, oneUsage), 
+                               "Single purpose working when multiple supplied");
+               }
+#endif
        }
 }
-#endif
\ No newline at end of file
+#endif