diff options
author | Sebastien Pouliot <sebastien@ximian.com> | 2005-10-20 23:13:03 +0400 |
---|---|---|
committer | Sebastien Pouliot <sebastien@ximian.com> | 2005-10-20 23:13:03 +0400 |
commit | 70ced1644f5e5161d19657bd6795c8aac7dfcb45 (patch) | |
tree | 5c2b5a6b33d6a2d8124b488f96787a6d0ca10dba /mcs/class/System.Security | |
parent | 0ffc7318e92e72df0897ddd2055c14361324cce8 (diff) |
2005-10-20 Sebastien Pouliot <sebastien@ximian.com>
* ManagedProtection.cs: New. A managed class similar to ProtectedData.
Source code will be reused in other assemblies to protect data.
svn path=/trunk/mcs/; revision=51999
Diffstat (limited to 'mcs/class/System.Security')
-rw-r--r-- | mcs/class/System.Security/Mono.Security.Cryptography/ChangeLog | 4 | ||||
-rw-r--r-- | mcs/class/System.Security/Mono.Security.Cryptography/ManagedProtection.cs | 270 |
2 files changed, 274 insertions, 0 deletions
diff --git a/mcs/class/System.Security/Mono.Security.Cryptography/ChangeLog b/mcs/class/System.Security/Mono.Security.Cryptography/ChangeLog new file mode 100644 index 00000000000..38832930ba0 --- /dev/null +++ b/mcs/class/System.Security/Mono.Security.Cryptography/ChangeLog @@ -0,0 +1,4 @@ +2005-10-20 Sebastien Pouliot <sebastien@ximian.com> + + * ManagedProtection.cs: New. A managed class similar to ProtectedData. + Source code will be reused in other assemblies to protect data. diff --git a/mcs/class/System.Security/Mono.Security.Cryptography/ManagedProtection.cs b/mcs/class/System.Security/Mono.Security.Cryptography/ManagedProtection.cs new file mode 100644 index 00000000000..f4ee52ecc56 --- /dev/null +++ b/mcs/class/System.Security/Mono.Security.Cryptography/ManagedProtection.cs @@ -0,0 +1,270 @@ +// +// ManagedProtection.cs - +// Protect (encrypt) data without (user involved) key management +// +// Author: +// Sebastien Pouliot <sebastien@ximian.com> +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// 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; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Security.Permissions; + +namespace Mono.Security.Cryptography { + + // Managed Protection Implementation + // + // Features + // * Separate RSA 1536 bits keypairs for each user and the computer + // * AES 128 bits encryption (separate key for each data protected) + // * SHA256 digest to ensure integrity + +#if !NET_2_0 + internal enum DataProtectionScope { + CurrentUser, + LocalMachine + } +#endif + + internal static class ManagedProtection { + +// FIXME [KeyContainerPermission (SecurityAction.Assert, KeyContainerName = "DAPI", +// Flags = KeyContainerPermissionFlags.Open | KeyContainerPermissionFlags.Create)] + public static byte[] Protect (byte[] userData, byte[] optionalEntropy, DataProtectionScope scope) + { + if (userData == null) + throw new ArgumentNullException ("userData"); + + Rijndael aes = Rijndael.Create (); + aes.KeySize = 128; + + byte[] encdata = null; + using (MemoryStream ms = new MemoryStream ()) { + ICryptoTransform t = aes.CreateEncryptor (); + using (CryptoStream cs = new CryptoStream (ms, t, CryptoStreamMode.Write)) { + cs.Write (userData, 0, userData.Length); + cs.Close (); + encdata = ms.ToArray (); + } + } + + byte[] key = null; + byte[] iv = null; + byte[] secret = null; + byte[] header = null; + SHA256 hash = SHA256.Create (); + + try { + key = aes.Key; + iv = aes.IV; + secret = new byte[1 + 1 + 16 + 1 + 16 + 1 + 32]; + + byte[] digest = hash.ComputeHash (userData); + if ((optionalEntropy != null) && (optionalEntropy.Length > 0)) { + // the same optionalEntropy will be required to get the data back + byte[] mask = hash.ComputeHash (optionalEntropy); + for (int i = 0; i < 16; i++) { + key[i] ^= mask[i]; + iv[i] ^= mask[i + 16]; + } + secret[0] = 2; // entropy + } else { + secret[0] = 1; // without entropy + } + + secret[1] = 16; // key size + Buffer.BlockCopy (key, 0, secret, 2, 16); + secret[18] = 16; // iv size + Buffer.BlockCopy (iv, 0, secret, 19, 16); + secret[35] = 32; // digest size + Buffer.BlockCopy (digest, 0, secret, 36, 32); + + RSAOAEPKeyExchangeFormatter formatter = new RSAOAEPKeyExchangeFormatter (GetKey (scope)); + header = formatter.CreateKeyExchange (secret); + } + finally { + if (key != null) { + Array.Clear (key, 0, key.Length); + key = null; + } + if (secret != null) { + Array.Clear (secret, 0, secret.Length); + secret = null; + } + if (iv != null) { + Array.Clear (iv, 0, iv.Length); + iv = null; + } + aes.Clear (); + hash.Clear (); + } + + byte[] result = new byte[header.Length + encdata.Length]; + Buffer.BlockCopy (header, 0, result, 0, header.Length); + Buffer.BlockCopy (encdata, 0, result, header.Length, encdata.Length); + return result; + } + +// FIXME [KeyContainerPermission (SecurityAction.Assert, KeyContainerName = "DAPI", +// Flags = KeyContainerPermissionFlags.Open | KeyContainerPermissionFlags.Decrypt)] + public static byte[] Unprotect (byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope) + { + if (encryptedData == null) + throw new ArgumentNullException ("encryptedData"); + + byte[] decdata = null; + + Rijndael aes = Rijndael.Create (); + RSA rsa = GetKey (scope); + int headerSize = (rsa.KeySize >> 3); + bool valid1 = (encryptedData.Length >= headerSize); + if (!valid1) + headerSize = encryptedData.Length; + + byte[] header = new byte[headerSize]; + Buffer.BlockCopy (encryptedData, 0, header, 0, headerSize); + + byte[] secret = null; + byte[] key = null; + byte[] iv = null; + bool valid2 = false; + bool valid3 = false; + bool valid4 = false; + SHA256 hash = SHA256.Create (); + + try { + try { + RSAOAEPKeyExchangeDeformatter deformatter = new RSAOAEPKeyExchangeDeformatter (rsa); + secret = deformatter.DecryptKeyExchange (header); + valid2 = (secret.Length == 68); + } + catch { + valid2 = false; + } + + if (!valid2) + secret = new byte[68]; + + // known values for structure (version 1 or 2) + valid3 = ((secret[1] == 16) && (secret[18] == 16) && (secret[35] == 32)); + + key = new byte [16]; + Buffer.BlockCopy (secret, 2, key, 0, 16); + iv = new byte [16]; + Buffer.BlockCopy (secret, 19, iv, 0, 16); + + if ((optionalEntropy != null) && (optionalEntropy.Length > 0)) { + // the decrypted data won't be valid if the entropy isn't + // the same as the one used to protect (encrypt) it + byte[] mask = hash.ComputeHash (optionalEntropy); + for (int i = 0; i < 16; i++) { + key[i] ^= mask[i]; + iv[i] ^= mask[i + 16]; + } + valid3 &= (secret[0] == 2); // with entropy + } else { + valid3 &= (secret[0] == 1); // without entropy + } + + using (MemoryStream ms = new MemoryStream ()) { + ICryptoTransform t = aes.CreateDecryptor (key, iv); + using (CryptoStream cs = new CryptoStream (ms, t, CryptoStreamMode.Write)) { + try { + cs.Write (encryptedData, headerSize, encryptedData.Length - headerSize); + cs.Close (); + } + catch { + // whatever, we keep going + } + } + decdata = ms.ToArray (); + } + + byte[] digest = hash.ComputeHash (decdata); + valid4 = true; + for (int i=0; i < 32; i++) { + if (digest [i] != secret [36 + i]) + valid4 = false; + } + } + finally { + if (key != null) { + Array.Clear (key, 0, key.Length); + key = null; + } + if (secret != null) { + Array.Clear (secret, 0, secret.Length); + secret = null; + } + if (iv != null) { + Array.Clear (iv, 0, iv.Length); + iv = null; + } + aes.Clear (); + hash.Clear (); + } + + // single point of error (also limits timing informations) + if (!valid1 || !valid2 || !valid3 || !valid4) { + if (decdata != null) { + Array.Clear (decdata, 0, decdata.Length); + decdata = null; + } + throw new CryptographicException (Locale.GetText ("Invalid data.")); + } + return decdata; + } + + // private stuff + + private static RSA user; + private static RSA machine; + + private static RSA GetKey (DataProtectionScope scope) + { + switch (scope) { + case DataProtectionScope.CurrentUser: + if (user == null) { + CspParameters csp = new CspParameters (); + csp.KeyContainerName = "DAPI"; + user = new RSACryptoServiceProvider (1536, csp); + } + return user; + case DataProtectionScope.LocalMachine: + if (machine == null) { + CspParameters csp = new CspParameters (); + csp.KeyContainerName = "DAPI"; + csp.Flags = CspProviderFlags.UseMachineKeyStore; + machine = new RSACryptoServiceProvider (1536, csp); + } + return machine; + default: + throw new CryptographicException (Locale.GetText ("Invalid scope.")); + } + } + } +} |