| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- //
- // ManagedProtection.cs -
- // Protect (encrypt) data without (user involved) key management
- //
- // Author:
- // Sebastien Pouliot <[email protected]>
- //
- // 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."));
- }
- }
- }
- }
|