Summary of Steps


This How To includes the following steps:

Step 1 . Create the EncryptionPermission class.

Step 2. Create the EncryptionPermissionAttribute class.

Step 3. Install the Permission assembly in the global assembly cache (GAC).

Step 4. Update the DPAPI managed wrapper code.

Step 5. Call DPAPI from a medium trust Web application.

Step 1. Create the EncryptionPermission Class

The EncryptionPermission class is the custom permission implementation used to authorize access to the unmanaged DPAPI functionality.

 Task   To create the CustomPermission class

  1. Create a new Visual C# TM development tool Class Library project CustomPermission , and rename class1.cs to EncryptionPermission.cs .

  2. Add a strong name to the assembly so that you can install it in the GAC. Use the following attribute in assemblyinfo.cs :

     [assembly: AssemblyKeyFile(@"..\..\CustomPermissions.snk")] 
  3. Use a fixed assembly version.

     [assembly: AssemblyVersion("1.0.0.1")] 
  4. Add the following using statements to the top of EncryptionPermission.cs.

     using System.Security; using System.Security.Permissions; 
  5. Add the following enumerated types to the CustomPermissions namespace.

     [Flags, Serializable] public enum EncryptionPermissionFlag {Encrypt = 0x01, Decrypt = 0x02}     [Flags, Serializable] public enum StorePermissionFlag {User = 0x01, Machine = 0x02} 
  6. Add serialization support to the EncryptionPermission class with the [ Serializable ] attribute, and derive it from CodeAccessSecurity and IUnrestrictedPermission . Also, seal the class, as the following shows.

     [Serializable] public sealed class EncryptionPermission : CodeAccessPermission,                                            IUnrestrictedPermission 
  7. Add two private member variables to maintain the permission state.

     private EncryptionPermissionFlag _permFlag; private StorePermissionFlag _storePermFlag; 
  8. Replace the default constructor with the following constructors.

     // It is convention for permission types to provide a constructor // that accepts the PermissionState enumeration. public EncryptionPermission(PermissionState state) {   if (state.Equals(PermissionState.Unrestricted))   {     _permFlag = EncryptionPermissionFlag.Encrypt                  EncryptionPermissionFlag.Decrypt;     _storePermFlag = StorePermissionFlag.User  StorePermissionFlag.Machine;   }   else   {     _permFlag &= ~(EncryptionPermissionFlag.Encrypt                     EncryptionPermissionFlag.Decrypt);     _storePermFlag &= ~(StorePermissionFlag.User                          StorePermissionFlag.Machine);   } } // This constructor allows you to specify the encryption type (encrypt // or decrypt) by using the EncryptionPermissionFlag enumeration and the DPAPI // key store to use (user or machine) as defined by the StorePermissionFlag // enumeration. public EncryptionPermission(EncryptionPermissionFlag cipher,                             StorePermissionFlag store) {   _permFlag = cipher;   _storePermFlag = store; } public EncryptionPermission() {   _permFlag &= ~EncryptionPermissionFlag.Encrypt                  EncryptionPermissionFlag.Decrypt;   _storePermFlag &= ~(StorePermissionFlag.User  StorePermissionFlag.Machine); } 
  9. Add the following public properties to allow a consumer application to set the permission class state.

     // Set this property to true to allow encryption. public bool Encrypt {   set {     if(true == value)     {       _permFlag = EncryptionPermissionFlag.Encrypt;     }     else     {       _permFlag &= ~EncryptionPermissionFlag.Encrypt;     }   }                get {     return (_permFlag & EncryptionPermissionFlag.Encrypt).Equals(                         EncryptionPermissionFlag.Encrypt);   } }     // Set this property to true to allow decryption. public bool Decrypt {   set {     if(true == value)     {       _permFlag = EncryptionPermissionFlag.Decrypt;     }     else     {       _permFlag &= ~EncryptionPermissionFlag.Decrypt;     }   }   get {     return (_permFlag & EncryptionPermissionFlag.Decrypt).Equals(                         EncryptionPermissionFlag.Decrypt);   } } // Set this property to true to use the DPAPI machine key. public bool MachineStore {   set {     if(true == value)     {       _storePermFlag = StorePermissionFlag.Machine;     }     else     {       _storePermFlag &= ~StorePermissionFlag.Machine;     }   }            get {     return (_storePermFlag & StorePermissionFlag.Machine).Equals(                              StorePermissionFlag.Machine);   } } // Set this property to true to use the DPAPI user key. public bool UserStore {   set {     if(true == value)     {       _storePermFlag = StorePermissionFlag.User;     }         else     {       _storePermFlag &= ~StorePermissionFlag.User;     }   }   get {     return (_storePermFlag & StorePermissionFlag.User).Equals(                              StorePermissionFlag.User);   } } 
  10. Implement IPermission.Copy . This creates an identical copy of the current permission instance and returns it to the caller.

     public override IPermission Copy() {   return  new EncryptionPermission(_permFlag, _storePermFlag); } 
  11. Implement IPermission.Intersect . This returns a permission object that is the result of the set intersection between the current permission and the supplied permission.

     public override IPermission Intersect(IPermission target) {   // An input of null indicates a permission with no state.   // There can be no common state, so the method returns null.   if (target == null)     return null;       if (!(target.GetType().Equals(this.GetType())))     throw new ArgumentException(                   "Argument must be of type EncryptionPermission.");       // Cast target to an EncryptionPermission.   EncryptionPermission targetPerm = (EncryptionPermission)target;       EncryptionPermissionFlag intersectEncryption = this._permFlag &                                                  targetPerm._permFlag;   StorePermissionFlag intersectStore = this._storePermFlag &                                        targetPerm._storePermFlag;       return new EncryptionPermission(intersectEncryption, intersectStore); } 
  12. Implement IPermission.Union . This returns a permission object that is the result of the set union between the current permission and the supplied permission.

     public override IPermission Union(IPermission target) {   if (target == null)     return Copy();       if (!(target.GetType().Equals(this.GetType())))     throw new ArgumentException(                        "Argument must be of type EncryptionPermission.");       // Cast the target to an EncryptionPermission.   EncryptionPermission targetPerm = (EncryptionPermission)target;       EncryptionPermissionFlag unionEncryption = this._permFlag                                               targetPerm._permFlag;   StorePermissionFlag unionStore = this._storePermFlag                                      targetPerm._storePermFlag;       return new EncryptionPermission(unionEncryption, unionStore); } 
  13. Implement the IPermission.IsSubsetOf . This method returns a bool to indicate whether or not the current permission is a subset of the supplied permission. To be a subset, every item of state in the current permission must also be in the target permission.

     public override bool IsSubsetOf(IPermission target) {   // An input of null indicates a permission with no state.   // The permission can only be a subset if it's in a similar empty state.   bool canEncrypt, canDecrypt;   bool canUseMachineStore, canUseUserStore;       bool canTargetEncrypt, canTargetDecrypt;   bool canTargetUseMachineStore, canTargetUseUserStore;       canEncrypt = (this._permFlag &                 EncryptionPermissionFlag.Encrypt).                 Equals(EncryptionPermissionFlag.Encrypt);   canDecrypt = (this._permFlag &                 EncryptionPermissionFlag.Decrypt).                 Equals(EncryptionPermissionFlag.Decrypt);   canUseMachineStore = (this._storePermFlag &                         StorePermissionFlag.Machine).                         Equals(StorePermissionFlag.Machine);   canUseUserStore = (this._storePermFlag &                      StorePermissionFlag.User).                      Equals(StorePermissionFlag.User);       if (target == null)   {     if ((canEncrypt == false && canDecrypt == false) && (canUseMachineStore ==          false  && canUseUserStore == false))       return true;     else       return false;   }       if (!(target.GetType().Equals(this.GetType())))     throw new ArgumentException(                            "Argument must be of type EncryptionPermission.");       // Cast the target to an EncryptionPermission.   EncryptionPermission targetPerm = (EncryptionPermission)target;       canTargetEncrypt = (targetPerm._permFlag &                       EncryptionPermissionFlag.Encrypt).                       Equals(EncryptionPermissionFlag.Encrypt);   canTargetDecrypt = (targetPerm._permFlag &                       EncryptionPermissionFlag.Decrypt).                       Equals(EncryptionPermissionFlag.Decrypt);       canTargetUseMachineStore = (targetPerm._storePermFlag &                               StorePermissionFlag.Machine).                               Equals(StorePermissionFlag.Machine);   canTargetUseUserStore = (targetPerm._storePermFlag &                            StorePermissionFlag.User).                            Equals(StorePermissionFlag.User);       // Every value set (true) in this permission must be in the target.   // The following code checks to see if the current permission is a subset   // of the target. If the current permission has something that the target   // does not have, it cannot be a subset.   if(canEncrypt == true && canTargetEncrypt == false)     return false;   if(canDecrypt == true && canTargetDecrypt == false)     return false;   if(canUseMachineStore == true && canTargetUseMachineStore == false)     return false;   if(canUseUserStore == true && canTargetUseUserStore == false)     return false;       return true; } 
  14. Implement ISecurityEncodable.ToXml and FromXml . These methods convert instances of a permission object into an XML format and vice-versa. These methods are used to support serialization. This is used, for example, when the security attribute is stored in assembly metadata.

     public override SecurityElement ToXml() {   // Create a new element. The tag name must always be IPermission.   SecurityElement elem = new SecurityElement("IPermission");       // Determine the fully qualified type name (including the assembly name) of   // the EncryptionPermission class. (The security system uses this name to   // locate and load the class.)   string name = typeof(EncryptionPermission).AssemblyQualifiedName;       // Add attributes for the class name and protocol version.   // The version must currently be 1.   elem.AddAttribute("class", name);   elem.AddAttribute("version", "1" );       if (IsUnrestricted())   {     // Using the Unrestricted attribute is consistent with the     // built-in .NET Framework permission types and helps keep     // the encoding compact.     elem.AddAttribute("Unrestricted", Boolean.TrueString);   }   else   {     // Encode each state field as an attribute of the Permission element.     // To compact, encode only nondefault state parameters.     elem.AddAttribute("Flags", this._permFlag.ToString());     elem.AddAttribute("Stores", this._storePermFlag.ToString());   }   // Return the completed element.   return elem; }     // Converts a SecurityElement (or tree of elements) to a permission // instance. public override void FromXml(SecurityElement elem) {   string attrVal = "";   // Check for an unrestricted instance.   attrVal = elem.Attribute("Unrestricted");   if (attrVal != null)   {     if(attrVal.ToLower().Equals("true"))     {       this._permFlag = EncryptionPermissionFlag.Encrypt                         EncryptionPermissionFlag.Decrypt;       this._storePermFlag = StorePermissionFlag.Machine                              StorePermissionFlag.User;     }     return;   }       //Turn off the permission and store flags.   this._permFlag &= ~(EncryptionPermissionFlag.Encrypt                        EncryptionPermissionFlag.Decrypt);   this._storePermFlag &= ~(StorePermissionFlag.Machine                             StorePermissionFlag.User);       attrVal = elem.Attribute("Flags");   if (attrVal != null)   {     if(!attrVal.Trim().Equals(""))     {       this._permFlag =        (EncryptionPermissionFlag)Enum.Parse(typeof(EncryptionPermissionFlag),                                                    attrVal);     }   }       attrVal = elem.Attribute("Stores");   if (attrVal != null)   {     if(!attrVal.Trim().Equals(""))     {       this._storePermFlag =                   (StorePermissionFlag)Enum.Parse(typeof(StorePermissionFlag),                                                          attrVal);     }   } } 
  15. Implement IUnrestrictedPermission.IsUnrestricted . This method returns true if the permission instance is in the unrestricted state. In this case, an unrestricted EncryptionPermission instance allows code to encrypt and decrypt data using both the DPAPI machine and user stores.

     public bool IsUnrestricted() {   bool canEncrypt, canDecrypt, canUseUserStore, canUseMachineStore;   canEncrypt = (this._permFlag &                 EncryptionPermissionFlag.Encrypt).                 Equals(EncryptionPermissionFlag.Encrypt);   canDecrypt = (this._permFlag &                 EncryptionPermissionFlag.Decrypt).                 Equals(EncryptionPermissionFlag.Decrypt);   canUseUserStore = (this._storePermFlag &                      StorePermissionFlag.User).                      Equals(StorePermissionFlag.User);   canUseMachineStore = (this._storePermFlag &                         StorePermissionFlag.Machine).                         Equals(StorePermissionFlag.Machine);   return ((canEncrypt && canDecrypt) &&           (canUseUserStore && canUseMachineStore)); } 

Step 2. Create the EncryptionPermissionAttribute Class

The .NET Framework uses attribute classes that are associated with their partner permission classes to encode permission instances. You need permission attributes to support declarative security syntax.

 Task   To create the EncryptionPermissionAttribute class

  1. Add a new class file to the current project, EncryptionPermissionAttribute.cs .

  2. Add the following using statements to the top of the new file.

     using System.Security; using System.Diagnostics; using System.Security.Permissions; 
  3. Derive the attribute class from CodeAccessSecurityAttribute , and seal it.

     public sealed class EncryptionPermissionAttribute :                                      CodeAccessSecurityAttribute 
  4. Add serialization support to the class, and use the AttributeUsage attribute to indicate where the custom permission attribute can be used.

     [Serializable, AttributeUsage(AttributeTargets.Method       // Can use on methods                AttributeTargets.Constructor  // Can use on constructors                AttributeTargets.Class        // Can use on classes                AttributeTargets.Struct       // Can use on structures                AttributeTargets.Assembly,     // Can use at the assembly level                AllowMultiple = true,          // Can use multiple attribute                                               // instances per program element                                               // (class, method and so on)                Inherited = false)]            // Can not be inherited 
  5. Add private member variables to the class to mirror the state maintained by the associated permission class.

     // The following state fields mirror those used in the associated // permission type. private bool _encrypt = false; private bool _decrypt = false; private bool _machineStore = false; private bool _userStore = false; 
  6. Replace the default constructor with the following constructor.

     // Pass the action code back to the base class. public EncryptionPermissionAttribute(SecurityAction action) : base(action) { } 
  7. Add the following public properties to mirror those provided by the associated permission class.

     public bool Encrypt {   get {     return _encrypt;   }   set {     _encrypt = value;   } } public bool Decrypt {   get {     return _decrypt;   }   set {     _decrypt = value;   } } public bool UserStore {   get {     return _userStore;   }   set {     _userStore = value;   } } public bool MachineStore {   get {     return _machineStore;   }   set {     _machineStore = value;   } } 
  8. Implement SecurityPermissionAttribute.CreatePermission . This method creates a permission object that can then be serialized and persisted with the specified SecurityAction enumeration in an assembly's metadata.

     public override IPermission CreatePermission() {   // The runtime automatically provides a property to indicate   // whether or not an unrestricted instance is required.   if((Unrestricted)  ((_encrypt && _decrypt) &&                         (_userStore && _machineStore)))   {     return new EncryptionPermission(PermissionState.Unrestricted);   }       // Copy the state from the attribute to the permission object   EncryptionPermissionFlag cipher = 0x0;   StorePermissionFlag store = 0x0;       if(_encrypt)     cipher = EncryptionPermissionFlag.Encrypt;       if(_decrypt)     cipher = EncryptionPermissionFlag.Decrypt;       if(_userStore)     store = StorePermissionFlag.User;       if(_machineStore)     store = StorePermissionFlag.Machine;       // Return the final permission.   return new EncryptionPermission(cipher, store); } 
  9. Build the solution.

Step 3. Install the Permission Assembly in the GAC

You must grant full trust to any assembly that implements a custom security permission. In practice, this means that you need to install the assembly on the computer where it is used, to ensure that it is granted full trust by default security policy. Code within the My_Computer_Zone is granted full trust by default policy.

Installing an assembly in the GAC is one way to be sure it is granted full trust by code access security policy. The GAC is an appropriate location for the permission assembly because the assembly is used by code access security policy on the local computer and is available for any .NET Framework application that is installed on the local computer.

To install the custom permission assembly in the local computer's GAC, run the following command.

 gacutil.exe /i custompermission.dll 

Step 4. Update the DPAPI Managed Wrapper Code

DPAPI functionality is not currently exposed by the .NET Framework class library. To call DPAPI from a .NET Framework application, you must use P/Invoke . For code that demonstrates how to create a managed DPAPI wrapper assembly, see "How To: Create a DPAPI Library," in "Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication," in the MSDN Library.

Without further modification, you can only call the managed DPAPI wrapper in the referenced How To article from full trust code. To be able to call the DPAPI wrapper from partial trust code, such as a medium trust ASP.NET Web application, you must sandbox the calls to the unmanaged DPAPI functions. To do this, make the following modifications:

  • Assert the unmanaged code permission in the DPAPI wrapper code. This means that any calling code does not require the unmanaged code permission.

  • Authorize the calling code inside the wrapper by demanding the custom EncryptionPermission . The Demand call occurs before the Assert call to, in accordance with the Demand/Assert usage pattern. For more information about using Assert safely, see "Assert and RevertAssert," in Chapter 8, "Code Access Security in Practice."

 Task   To modify the DPAPI managed wrapper

  1. Build the DPAPI managed wrapper by following the instructions in "How To: Create a DPAPI Library."

  2. Add a reference to the CustomPermission assembly.

  3. Open dataprotection.cs from the managed wrapper library, and add the following using statements beneath the existing using statements at the top of the file.

     using System.Security; using System.Security.Permissions; using CustomPermissions; 
  4. Locate the Encrypt method in dataprotection.cs , and add the following code at the top of the outer try block in the Encrypt method.

     // Set the storeFlag depending on how the caller uses // the managed DPAPI wrapper. StorePermissionFlag storeFlag; if(Store.USE_MACHINE_STORE == store) {   storeFlag = StorePermissionFlag.Machine; } else {   storeFlag = StorePermissionFlag.User; } // Demand custom EncryptionPermission. (new EncryptionPermission(EncryptionPermissionFlag.Encrypt, storeFlag)).                                                                Demand();     // Assert the unmanaged code permission. (new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert(); // Now use P/Invoke to call the unmanaged DPAPI functions. 
  5. Add the following finally block to the outer try block in the Encrypt method.

     finally {   CodeAccessPermission.RevertAssert(); } 
  6. Locate the Decrypt method in dataprotection.cs , and add the following code at the top of the outer try block.

     StorePermissionFlag storeFlag; if(Store.USE_MACHINE_STORE == store) {   storeFlag = StorePermissionFlag.Machine; } else {   storeFlag = StorePermissionFlag.User; }   // Demand custom EncryptionPermission.   (new EncryptionPermission(EncryptionPermissionFlag.Decrypt, storeFlag)).                                                               Demand();   // Assert the unmanaged code permission.   (new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert(); 
  7. Add the following finally block to the outer try block in the Decrypt method.

     finally {   CodeAccessPermission.RevertAssert(); } 

Step 5. Call DPAPI from a Medium Trust Web Application

To use the DPAPI managed wrapper from a medium trust Web application or any partial trust code, you must configure code access security policy to grant the code the custom EncryptionPermission .

In this step, you create a test Web application and then modify ASP.NET code access security policy for a medium trust Web application to grant it the EncryptionPermission .

 Task   To create a test Web application

  1. Add a new C# ASP.NET Web application project to your current solution.

  2. Add a reference to the Dataprotection.dll assembly.

  3. Add the following fields to Webform1.aspx.

    • An input field for the data to encrypt. Use the ID txtDataToEncrypt .

    • A field for the encrypted data. Use the ID txtEncryptedData .

    • A field for the decrypted data. Use the ID txtDecryptedData .

    • An Encrypt button. Use the ID btnEncrypt .

    • A Decrypt button. Use the ID btnDecrypt .

    • A label for an error message. Use the ID lblError .

  4. Add the following using statement to the top of WebForm1.aspx.cs beneath the existing using statements.

     using DataProtection; 
  5. Add the following code for the Encrypt button-click event handler.

     private void btnEncrypt_Click(object sender, System.EventArgs e) {   DataProtector dp = new DataProtector(                               DataProtector.Store.USE_MACHINE_STORE );   try   {     byte[] dataToEncrypt = Encoding.ASCII.GetBytes(txtDataToEncrypt.Text);     // Not passing optional entropy in this example     // Could pass random value (stored by the application) for added security     // when using DPAPI with the machine store.     txtEncryptedData.Text =              Convert.ToBase64String(dp.Encrypt(dataToEncrypt,null));   }   catch(Exception ex)   {     lblError.ForeColor = Color.Red;     lblError.Text = "Exception.<br>" + ex.Message;     return;   }   lblError.Text = ""; } 
  6. Add the following code for the Decrypt button-click event handler.

     private void btnDecrypt_Click(object sender, System.EventArgs e) {   DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);   try   {     byte[] dataToDecrypt = Convert.FromBase64String(txtEncryptedData.Text);     // Optional entropy parameter is null.     // If entropy was used within the Encrypt method, the same entropy     // parameter must be supplied here.     txtDecryptedData.Text =          Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));   }   catch(Exception ex)   {     lblError.ForeColor = Color.Red;     lblError.Text = "Exception.<br>" + ex.Message;     return;   }   lblError.Text = ""; } 
  7. Configure the Web application for medium trust by adding the following element to the application's Web.config file inside the <system.web> section.

     <trust level="Medium" /> 
  8. Build the solution.

 Task   To modify medium trust policy

  1. Open the medium trust policy file using Visual Studio .NET or Notepad. The policy file is in the following location.

     %windir%\Microsoft.NET\Framework\{version}\CONFIG\web_mediumtrust.config 
  2. Declare the EncryptionPermission by adding the following <SecurityClass> element to the <SecurityClasses> element.

     <SecurityClass Name="EncryptionPermission"                Description="CustomPermission.EncryptionPermission,                             CustomPermission, Version=1.0.0.1,                             Culture=neutral,                             PublicKeyToken=951cd7d57a536a94"/> 

    Set the PublicKeyToken attribute value to the specific public key token for your assembly. To extract the public key token for your custom permission assembly, use the following command.

     sn -T custompermission.dll 
    Note  

    Use a capital -T switch.

  3. Locate the ASP.NET named permission set in the medium trust policy file, and add the following permission element.

     <IPermission class="EncryptionPermission"              version="1" Flags="Encrypt,Decrypt"              Stores="Machine,User"> </IPermission> 

    This permission grants medium trust Web applications the unrestricted EncryptionPermission because it allows code to encrypt and decrypt data and to use the DPAPI machine and user store. The above element demonstrates the supported syntax. It is equivalent to the following:

     <IPermission class="EncryptionPermission"              version="1" Unrestricted="true" > </IPermission> 

    You can grant code a restricted permission by using only the relevant attributes. For example, to limit code to decrypt data using only the machine key in the machine store, use the following element.

     <IPermission class="EncryptionPermission"              version="1" Flags="Decrypt"              Stores="Machine"> </IPermission> 
  4. Save the policy file.

    You can now run the test Web application and verify that you can encrypt and decrypt data by using DPAPI from a partial trust Web application.

For more information about sandboxing highly privileged code and about working with ASP.NET code access security policy, see Chapter 9, "Using Code Access Security with ASP.NET."




Improving Web Application Security. Threats and Countermeasures
Improving Web Application Security: Threats and Countermeasures
ISBN: 0735618429
EAN: 2147483647
Year: 2003
Pages: 613

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net