This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Additional Topics

Learn about the AP .Net documentation with advanced operational insights and platform-specific guidance.

This section expands the core Application Protector (AP) .Net documentation.

  • Uninstalling AP .Net on Windows involves removing binaries, configuration files, and dependencies.
  • Understanding AP .Net’s memory footprint is critical for performance tuning and resource allocation.
  • Understanding the installation of AP .Net on Windows in a development environment.

1 - Memory Usage of the AP .Net

The memory usage in the AP .Net for different policy sizes with a sample.

Using the Sample .Net Application to Test Memory Usage for Policies

This article provides information about memory requirements of the AP .Net. A sample .Net application is provided below. It can be used to test the memory requirements of policies of various sizes. Results of that testing are based on the memory usage of a single instance of a .Net process, excluding LogForwarder and RP Agent.

Sample application

The following is a sample .Net application.

using System;
using System.Collections.Generic;
using System.Text;
using Protegrity.Net;
using Protegrity.PException;

namespace APDotNetTest
{
    /**********************************************************************************************/
    /**
     * @class   Program
     *
     * @brief   A sample program for Application .NET Protector.
     *
     **************************************************************************************************/
    class Program
    {
        private const string dataElementName = "alphanum";
        private const string newDataElementName = "alphanumreprot";
        private const string userName = "policyuser";


        /**********************************************************************************************/
        /**
         * @fn  static void Main(string[] args)
         *
         * @brief   Main entry-point for this application
         *
         * @param   args    An array of command-line argument strings.
         **************************************************************************************************/

        static void Main(string[] args)
        { 
            try
            {
                /* Dispose() is a method defined by the IDisposable interface in .NET. 
                 * Its used to release unmanaged resources.
                 * .NET garbage collector only cleans up managed memory. If your class uses unmanaged resources, 
                 * you must clean them up manually  and thats what Dispose() is for.
                 * 
                 * ====================
                 * With using 
                 * ====================
                 * You're telling the compiler:
                 * "Create this object, and automatically call Dispose() on it 
                 * when it goes out of scope."
                 * This is called a using declaration, and it ensures that cleanup happens even if
                 * an exception occurs.
                 * 
                 * ======================
                 * Without using
                 * ======================
                 * You have to call dispose manually.
                 */

                using Protector protector = Protector.GetProtector();

                /**
                 * Sample input string data for single operations.
                 */
                string singleInput = "Hello Protegrity";
                byte[] singleByteInput = Encoding.UTF8.GetBytes(singleInput);

                Console.WriteLine("#########################################");
                Console.WriteLine("# Protegrity Application .NET Protector #");
                Console.WriteLine("#########################################\n");

                /**
                 * Calling GetVersion to print APDotNet sdk and Core version.
                 */
                Console.WriteLine(protector.GetVersion() + "\n");
                Console.WriteLine("--------------------------------------");
                Console.WriteLine("-     Single Protect API             -");
                Console.WriteLine("--------------------------------------");
                Console.WriteLine($"Input Data is:        {singleInput}\n");


                /**
                 * Use protector object to call single string Protect API.
                 */
                string protectedData = protector.Protect(singleInput, userName, dataElementName);
                Console.WriteLine("With String Data Type");
                Console.WriteLine("-----------------------");
                Console.WriteLine($"Protected Data is:    {protectedData}");

                /**
                 * Use protector object to call single string Unprotect API
                 */
                string unprotectedData = protector.Unprotect(protectedData, userName, dataElementName);
                Console.WriteLine($"Unprotected Data is:  {unprotectedData}\n");

                /**
                * Use protector object to call single string Reprotect API.
                */
                string reprotectedData = protector.Reprotect(protectedData, userName, dataElementName, newDataElementName);
                Console.WriteLine($"Reprotected Data is:  {reprotectedData}");

                /**
                 * Use protector object to call single string Unprotect API
                 */
                string unprotectReprotectedData = protector.Unprotect(reprotectedData, userName, newDataElementName);
                Console.WriteLine($"Unprotected Data is:  {unprotectReprotectedData}\n");

                /** 
                 * Use protector object to call single byte Protect API.
                 */
                byte[] byteProtectedData = protector.Protect(singleByteInput, userName, dataElementName);
                Console.WriteLine("With Byte Data Type");
                Console.WriteLine("----------------------");
                Console.WriteLine($"Protected Byte Data is:    {Encoding.UTF8.GetString(byteProtectedData)}");

                /**
                 * Use protector object to call single byte Unprotect API
                 */
                byte[] byteUnprotectedData = protector.Unprotect(byteProtectedData, userName, dataElementName);
                Console.WriteLine($"Unprotected Byte Data is:  {Encoding.UTF8.GetString(byteUnprotectedData)}\n");

                /**
                 * Use protector object to call single byte Reprotect API.
                 */
                byte[] byteReprotectedData = protector.Reprotect(byteProtectedData, userName, dataElementName, newDataElementName);
                Console.WriteLine($"Reprotected Byte Data is:  {Encoding.UTF8.GetString(byteReprotectedData)}");

                /**
                 * Use protector object to call single byte Unprotect API
                 */
                byte[] byteUnprotectReprotectedData = protector.Unprotect(byteReprotectedData, userName, newDataElementName);
                Console.WriteLine($"Unprotected Byte Data is:  {Encoding.UTF8.GetString(byteUnprotectReprotectedData)}");
                Console.WriteLine("\n");

                /**
                 * Sample bulk string input data
                 */
                string[] bulkInput = { "The Alpha-numeric token type tokenizes all alphabetic symbols (both lowercase and uppercase letters), as well as digits.", "Digits 0 through 9, Lowercase letters a through z, Uppercase letters A through Z", "alphanumeric data 1234567890 !@#$%^&* with special characters", "ALL THE CHARACTERS IN THIS STRING ARE UPPERCASE", "UPPERCASE WITH 1234567890 NUMBERS AND !@#$%^&*() SPECIAL CHARACTERS" };
                List<byte[]> byteBulkInput = new List<byte[]>(bulkInput.Length);

                Console.WriteLine("--------------------------------------");
                Console.WriteLine("-         Bulk Protect API           -");
                Console.WriteLine("--------------------------------------");

                Console.WriteLine("Input Data is:");

                /**
                 * Converting string data to byte data.
                 */
                for (int i = 0; i < bulkInput.Length; i++)
                {
                    Console.WriteLine($"{bulkInput[i]}");
                    byteBulkInput.Add(Encoding.UTF8.GetBytes(bulkInput[i]));
                }

                Console.WriteLine("\n");
                Console.WriteLine("With String Data Type");
                Console.WriteLine("----------------------");

                /**
                 * Use protector object to call bulk string Protect API
                 */
                Tuple<string[], int[]> bulkProtectedData = protector.Protect(bulkInput, userName, dataElementName);
                Console.WriteLine("Protected Data is: ");
                for (int i = 0; i < bulkProtectedData.Item1.Length; i++)
                {
                    Console.WriteLine(bulkProtectedData.Item1[i] + " " + bulkProtectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                 * Use protector object to call bulk string Unprotect API
                 */
                Tuple<string[], int[]> bulkUnprotectedData = protector.Unprotect(bulkProtectedData.Item1, userName, dataElementName);
                Console.WriteLine("Unprotected Data is: ");
                for (int i = 0; i < bulkUnprotectedData.Item1.Length; i++)
                {
                    Console.WriteLine(bulkUnprotectedData.Item1[i] + " " + bulkUnprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                 * Use protector object to call bulk string Reprotect API
                 */
                Tuple<string[], int[]> bulkReprotectedData = protector.Reprotect(bulkProtectedData.Item1, userName, dataElementName, newDataElementName);
                Console.WriteLine("Reprotected Data is: ");
                for (int i = 0; i < bulkReprotectedData.Item1.Length; i++)
                {
                    Console.WriteLine(bulkReprotectedData.Item1[i] + " " + bulkReprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                * Use protector object to call bulk string Unprotect API
                */
                Tuple<string[], int[]> bulkUnprotectReprotectedData = protector.Unprotect(bulkReprotectedData.Item1, userName, newDataElementName);
                Console.WriteLine("Unprotected Data is: ");
                for (int i = 0; i < bulkUnprotectReprotectedData.Item1.Length; i++)
                {
                    Console.WriteLine(bulkUnprotectReprotectedData.Item1[i] + " " + bulkUnprotectReprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                Console.WriteLine("With Byte Data Type");
                Console.WriteLine("----------------------");

                /**
                 * Use protector object to call bulk byte Protect API
                 */
                Tuple<List<byte[]>, int[]> byteBulkProtectedData = protector.Protect(byteBulkInput, userName, dataElementName);
                Console.WriteLine("Protected Data is: ");
                for (int i = 0; i < byteBulkProtectedData.Item1.Count; i++)
                {
                    Console.WriteLine(Encoding.UTF8.GetString(byteBulkProtectedData.Item1[i]) + " " + byteBulkProtectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                * Use protector object to call bulk byte Unprotect API
                */
                Tuple<List<byte[]>, int[]> byteBulkUnprotectedData = protector.Unprotect(byteBulkProtectedData.Item1, userName, dataElementName);
                Console.WriteLine("Unprotected Data is: ");
                for (int i = 0; i < byteBulkUnprotectedData.Item1.Count; i++)
                {
                    Console.WriteLine(Encoding.UTF8.GetString(byteBulkUnprotectedData.Item1[i]) + " " + byteBulkUnprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                * Use protector object to call bulk byte Reprotect API
                */
                Tuple<List<byte[]>, int[]> byteBulkReprotectedData = protector.Reprotect(byteBulkProtectedData.Item1, userName, dataElementName, newDataElementName);
                Console.WriteLine("Reprotected Data is: ");
                for (int i = 0; i < byteBulkReprotectedData.Item1.Count; i++)
                {
                    Console.WriteLine(Encoding.UTF8.GetString(byteBulkReprotectedData.Item1[i]) + " " + byteBulkReprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");

                /**
                * Use protector object to call bulk byte Unprotect API
                */
                Tuple<List<byte[]>, int[]> byteBulkUnprotectReprotectedData = protector.Unprotect(byteBulkReprotectedData.Item1, userName, newDataElementName);
                Console.WriteLine("Unprotected Data is: ");
                for (int i = 0; i < byteBulkUnprotectReprotectedData.Item1.Count; i++)
                {
                    Console.WriteLine(Encoding.UTF8.GetString(byteBulkUnprotectReprotectedData.Item1[i]) + " " + byteBulkUnprotectReprotectedData.Item2[i]);
                }
                Console.WriteLine("\n");
            }
            catch (ProtectorException e)
            {
                Console.WriteLine(e);
            }
        } /* End scope of main function */

    } /* End scope of class */

} /* closure of namespace */

Expected Memory Usage

The process to find the policy size and expected memory usage for different policy sizes used by the .Net application is described in this section.

To find the policy size:

  1. On Insights dashboard, under the Discover section, navigate to the troubleshooting index.
  2. Search using the process.module.keyword: coreprovider filter.
  3. Navigate to the logs with description as Policy successfully loaded. The additional_info.memoryUsed field depicts the policy size.

Memory Usage

Dynamic Memory Usage

The following is the expected memory usage for different policy sizes used by the .Net application.

Policy sizeProcess memory consumption
171 MB223 MB
240 MB292 MB
931 MB982 MB

The process memory increases substantially for a few milliseconds when the application is running in the following cases:

  • The policy is replaced with another policy
  • Changes are made in the current policy

DevOps Memory Usage

If we increase the policy size, the time to load the policy file in the memory increases. For example; for 37 MB, it takes 1 min to load the policy file whereas for 370 MB, it takes approximately an hour to load the policy file.

2 - Setting Up AP .Net Mock on Windows in a Development Environment

Learn about installating AP .Net on Windows in a development environment.

This section describes how to install Application Protector (AP) .Net on a Windows platform for development purposes.

Note: The Log Forwarder and RP Agent should not be installed in a development environment.

Verifying prerequisites

Ensure that the following prerequisites are met:

  • A supported version of .NET Standard 2.0 is installed on the machine.

Extracting the AP .Net Setup Scripts and Package

To extract the setup scripts and package:

  1. Download the ApplicationProtector_WIN-ALL-64_x86-64_NET-STD-2.0-64_<version>.zip file to any location on the machine where you want to install the protector.
  2. Extract the contents of the ApplicationProtector_WIN-ALL-64_x86-64_NET-STD-2.0-64_<version>.zip file.
    The following setup files are extracted:
    • ApplicationProtector_WIN-ALL-64_x86-64_NET-STD-2.0-64_<version>.zip
    • signatures/ApplicationProtector_WIN-ALL-64_x86-64_NET-STD-2.0-64_<version>.zip_10.0.sig
  3. Verify the digital signature of the signed AP .Net build.
    For more information about verifying the signed AP .Net build, refer to Verification of Signed Protector Build.
  4. Extract the contents of the AP .Net installation package again.
    The following setup files are extracted:
    • LogforwarderSetup_Windows_x64_<version>.exe
    • RPAgentSetup_Windows_x64_<version>.exe
    • APDotNetSetup_Windows_x64_<version>.exe
    • APDotNetMockSetup_Windows_x64_<version>.exe

Installing AP .Net Mock on Windows in a Development Environment

This section describes how to install AP .Net Mock on a Windows platform for development purposes.

Important:

  • Ensure that the following folders do not exist before installation:
    - C:\Users\Administrator\.nuget\packages\dotnetprotector
    - C:\Users\Administrator\.nuget\packages\dotnetprotectormocksetup
  • You can install only one environment at a time either production or development.

Using Windows Wizard

  1. Run the APDotNetMockSetup_Windows_x64_<version>.exe installer from the created directory.
    The Select Destination Location screen appears.

  2. Set the installation directory to C:\Program Files\Protegrity\sdk\dotnet.

  3. Click Next.
    The Ready to Install screen appears.

  4. Click Install.
    The Completing the Defiance AP DOTNET API Setup Wizard screen appears.

  5. Click Finish to exit the installer.

    The AP .Net Mock is installed successfully.

    The default installation directories for different platforms are given in the following table.
    Table: Default Installation Directories

    PlatformDirectory
    Windows 64-bitC:\Program Files\Protegrity\sdk\dotnet\lib
    API DocsC:\Program Files\Protegrity\sdk\dotnet\doc
  6. Verify that DotNetProtectorMockSetup.<version>.nupkg NuGet package for the AP .NET is installed in the <installation_directory>\dotnet\lib installation directory.

  7. Install the NuGet Package.
    a. In Visual Studio, open Solution Explorer.

    b. Right-click on Dependencies and select Manage NuGet Packages.
    The NuGet Package Manager screen appears.

    c. Click the Settings icon next to the Package source list.

    d. In the Options dialog box, click the ellipsis next to the Source field.

    e. Browse to and select the C:\Program Files\Protegrity\sdk\dotnet\lib lib directory.

    f. Enter a name for the package source, for example APDotNetMockLib and click Update.
    The lib directory path appears in the Package sources list.

    g. Click OK to close the dialog box.

    h. In the Package source list, select the newly created source.

    i. Click Browse, locate the DotNetProtectorMockSetup package.

    The DotNetProtectorMockSetup package appears in the list of packages.

    j. Click Install.

    The DotNetProtectorMockSetup package is installed.

    1. Run the Program.
      After installation, run your application to verify integration.
      For more information about installing a NuGet package, refer to Install and manage packages in Visual Studio using the NuGet Package Manager.

Using Silent Mode

This section describes how to perform a silent installation of AP .Net Mock using command-line parameters.

Silent installation allows you to install AP .Net Mock without user interaction, making it suitable for automated deployments and scripting.

To view usage of the AP .Net Mock installation, use the -help parameter during silent installation.

APDotNetMockSetup_Windows_x64_10.0.0+0.g8606.exe -help

To specify the directory for the AP .Net Mock installation, use the -dir parameter during silent installation.

APDotNetMockSetup_Windows_x64_10.0.0+0.g8606.exe -dir C:\Users\Administrator

Note: -dir is a mandatory parameter for silent installation.

Uninstalling Application Protector .Net Mock on Windows in a Development Environment

This section describes how to completely remove AP .Net Mock from a Windows platform used for development.

To uninstall the .Net Mock from the development environment:

  1. Navigate to the Installation Directory.
    a. Go to C:\Program Files\Protegrity\sdk\dotnet.

  2. Run the Uninstall Utility located in the directory.

  3. Delete the Installation Directory.
    a. After uninstallation, delete the C:\Program Files\Protegrity\sdk folder.

  4. Uninstall the NuGet Package.
    a. Open Visual Studio and go to Manage NuGet Packages for Solution.
    b. Browse and select the DotNetProtectorMockSetup package.
    c. Click Uninstall.

    The AP .Net Mock Setup is uninstalled from the development environment.
    The dotnetprotector package will be deleted from the NuGet Package Folder during the unintallation process.

3 - DevOps Approach for Application Protector

The DevOps approach for package deployment.

Note: The DLL file creation is supported only by .NET Core 8.0 and .NET Core 9.0. The .NET application can be run by using any supported versions.

The DevOps approach enables immutable package deployment. It uses a REST API call to download packages from the ESA in an encrypted format.

Note: The RP Agent should not be installed for immutable package deployments using DevOps.

For more information about package deployment approaches, refer to Resilient Package Deployment.

A REST API call is used to download the package on your local machine. Configure the package path in the config.ini file within the DevOps section and the path to decryptor dll file.

If a downloaded path is overwritten, a new package will be reflected in the running application at the set time interval. This occurs when another package with the same name overwrites the existing one. This changes the protector’s behaviour. The protector no longer functions as an immutable protector.

DevOps approach architecture

  1. A REST API call is used to download the policy from the ESA in an envelop encrypted format. A public key is created using a Key Management System (KMS) or Hardware Security Module (HSM). This public key must be passed to the REST API.
  2. The ESA generates a JSON file for the package with policy.
  3. The encrypted DEK needs to be decrypted to perform the security operations. A Decryptor class is implemented using the Decryptor interface, to decrypt the Data Encryption Key (DEK) using a private key.

Before you begin

Ensure the following prerequisites are met:

  • The installation of the RP Agent is not required for immutable package deployment using the DevOps approach.
  • The decryptor parameter must have a complete path to a decryptor dll file.
    A Decryptor class needs to be implemented using the Decryptor interface, which decrypts the Data Encryption Key (DEK) using a private key. It returns the decrypted DEK in bytes.
    For more information on the decryptor interface of AP .Net, refer to Configuring the Decryptor interface.
  • A decryptor dll file needs to be created using the decryptor interface and decryptor class.
  • Create a solution project “DotNetDecryptor” where you want to generate the Decryptor DLL file.

    Note: To create Decryptor DLL file, we require the IDEKDecryptor.cs, DotNetDecryptor.csproj, Decryptor.cs, and cloud specific decryptor files for AWSKMSDecryptor.cs, AzureKeyVaultDecryptor.cs, and GCPKMSDecryptor.cs.

  • The data store is properly configured before exporting your Application Protector policy. Define allowed servers for seamless policy deployment and secure access control.
    For more information about configuring a data store, refer to -

AP .Net

Using the DevOps Approach

Perform the following steps to use the DevOps approach for immutable package deployment.

  1. Add the [devops] parameter in the config.ini file.
    Ensure the decryptor class has a fully qualified domain name.

    [devops]
    package.path = /path/to/policyFile
    decryptor = /path/to/DotNetDecryptor.dll
    

    The following is an example for adding the [devops] parameter in the config.ini file.

    [devops]
    package.path = C:\Users\User1\policies\test.json
    decryptor = C:\Users\User1\DotNetDecryptor\DotNetDecryptor.dll
    

Note: For ESA 10.2.0 and later, Application Protector DevOps must use the Encrypted Resilient Package REST APIs using GET method. The legacy Export API using POST method is deprecated and not supported for Teams (PPC). The deprecated API remains supported only for the Enterprise edition for backward compatibility.

For more information about exporting Resilient Package using POST method for 10.0.1 and 10.1.0 ESA, refer to Using the Encrypted Resilient Package REST APIs.

For more information about exporting Resilient Package using GET method for 10.2 ESA, refer to Using the Encrypted Resilient Package REST APIs.

For more information about exporting Resilient Package using GET method for PPC, refer to Using the Encrypted Resilient Package REST APIs.

Sample Code for DevOps Approach

The sample code for DevOps approach for the AP .Net using different cloud platforms is provided in this section.

Configuring the Decryptor Interface

A Decryptor class must implement the IDEKDecryptor interface to decrypt the DEK. This interface includes the decrypt method. The decrypt method provides keyLabel, algorithmId, and encDek parameters. The decrypted DEK must be returned in byte[] format.

The following is the sample code for the decryptor file for using the DevOps Approach with specific cloud platforms.

IDEKDecryptor.cs

The following is a sample code for IDEKDecryptor.cs.

namespace Decryptor
{
    public interface IDEKDecryptor
    {
        byte[] Decrypt(string keyLabel, string algorithmId, byte[] encDEK, out int decryptedDekLength);

    }
}
DotNetDecryptor.csproj

The following is a sample code for DotNetDecryptor.csproj.

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>
		<PlatformTarget>AnyCPU</PlatformTarget>
		<PublishAot>true</PublishAot>
		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
		<EnableDynamicLoading>true</EnableDynamicLoading>
		<RuntimeIdentifier>win-x64</RuntimeIdentifier>
		<UseNativeAot>true</UseNativeAot>
	</PropertyGroup>

</Project>
Decryptor.cs

The following is a sample code for Decryptor.cs.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Decryptor
{

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate IntPtr DecryptDelegate(
       [MarshalAs(UnmanagedType.LPUTF8Str)] string keyLabel,
       [MarshalAs(UnmanagedType.LPUTF8Str)] string algorithmId,
       IntPtr encData,
       int encLen,
       out int outLen);

   
    public static class DecryptorBridge
    {
        public static IDEKDecryptor Implementation { get; set; }
        static DecryptorBridge()
        {
            Implementation = new RSADecryptor();
        }
        public static IntPtr DecryptEntry(string keyLabel, string algorithmId, IntPtr encData, int encLen, out int outLen)
        {
            if (Implementation == null)
                throw new InvalidOperationException("Decryptor implementation not set.");
            byte[] encryptedDek = new byte[encLen];
            Marshal.Copy(encData, encryptedDek, 0, encLen);
            byte[] result = Implementation.Decrypt(keyLabel, algorithmId, encryptedDek, out outLen);
            IntPtr resultPtr = Marshal.AllocHGlobal(outLen);
            Marshal.Copy(result, 0, resultPtr, outLen);
            return resultPtr;
        }

        [UnmanagedCallersOnly(EntryPoint = "GetDecryptDelegate")]
        public static IntPtr GetDecryptDelegate() 
        {
            DecryptDelegate del = DecryptEntry;
            return Marshal.GetFunctionPointerForDelegate(del);
        }

        [UnmanagedCallersOnly(EntryPoint = "FreeBuffer", CallConvs = new[] { typeof(CallConvCdecl) })]
        public static void FreeBuffer(IntPtr ptr) { Marshal.FreeHGlobal(ptr); }

        

    }

}

Using AWS

For AWS, we require the IDEKDecryptor.cs, DotNetDecryptor.csproj, Decryptor.cs along with AWSKMSDecryptor.cs. The following is a sample implementation using the private key obtained from AWS KMS for decryption.

using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Amazon;
using Amazon.KeyManagementService;
using Amazon.KeyManagementService.Model;
using Decryptor;

namespace DotNetDecryptor
{
    public class AWSKMSDecryptor : IDEKDecryptor
    {
        // Replace with your actual AWS region name (e.g., "us-east-1", "ap-south-1")
        private static readonly RegionEndpoint KMSRegion = RegionEndpoint.GetBySystemName("your-default-region");

        // Replace with your actual KMS Key ID 
        private static readonly string KMSKeyId = "your-key-id";
        public byte[] Decrypt(string keyLabel, string algorithmId, byte[] encDek, out int decryptedDekLength)
        {
            Console.WriteLine("Key Label: " + keyLabel);
            Console.WriteLine("AlgorithmID: " + algorithmId);
            Console.WriteLine("Base64 encoded input: " + Convert.ToBase64String(encDek));

            // Initialize the AWS KMS client using the specified region
            var kmsClient = new AmazonKeyManagementServiceClient(KMSRegion); 
            // Specify the encryption algorithm used during encryption
            EncryptionAlgorithmSpec algorithm = EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256;
            // Create a decryption request with the encrypted DEK and algorithm
            var decryptRequest = new DecryptRequest
            {
                KeyId = KMSKeyId,
                CiphertextBlob = new MemoryStream(encDek),
                EncryptionAlgorithm = algorithm
            };
            // Send the decryption request to AWS KMS and wait for the response
            var decryptResponse = kmsClient.DecryptAsync(decryptRequest).Result;
            byte[] plaintext = decryptResponse.Plaintext.ToArray();
            decryptedDekLength = plaintext.Length;

            return plaintext;
        }

    }
}

Using Azure

For Azure, we require the IDEKDecryptor.cs, DotNetDecryptor.csproj, Decryptor.cs along with AzureKeyVaultDecryptor.cs. The following is a sample implementation using the private key obtained from Azure Key Vault for decryption.

using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Decryptor;
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Decryptor
{
    public class AzureKeyVaultDecryptor : IDEKDecryptor
    {

        public byte[] Decrypt(string keyLabel, string algorithmId, byte[] encDek, out int decryptedDekLength)
        {
            Console.WriteLine("Key Label:" + keyLabel);
            Console.WriteLine("AlgorithmID:" + algorithmId);
            Console.WriteLine("Base64 encoded input: " + Convert.ToBase64String(encDek));
            var keyVaultUrl = "https://devops-key.vault.azure.net/";
            var keyName = "testkey";
            var credential = new DefaultAzureCredential();
            var keyClient = new KeyClient(new Uri(keyVaultUrl), credential);
            KeyVaultKey key = keyClient.GetKey(keyName);
            CryptographyClient _cryptoClient = new CryptographyClient(key.Id, credential);
            EncryptionAlgorithm algorithm = EncryptionAlgorithm.RsaOaep256;
            DecryptResult result = _cryptoClient.Decrypt(algorithm, encDek);
            decryptedDekLength = result.Plaintext.Length;
            Console.WriteLine("Base64 encoded output: " + Convert.ToBase64String(result.Plaintext));
            return result.Plaintext;
        }
    }
}

Using GCP

For GCP, we require the IDEKDecryptor.cs, DotNetDecryptor.csproj, Decryptor.cs along with GCPKMSDecryptor.cs. The following is a sample implementation using the private key obtained from Google Cloud KMS for decryption.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Decryptor;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace DotNetDecryptor
{
    public class GCPKMSDecryptor : IDEKDecryptor
    {
        public byte[] Decrypt(string keyLabel, string algorithmId, byte[] encDEK, out int decryptedDekLength)
        {
            Console.WriteLine("Key Name: " + keyLabel);
            Console.WriteLine("AlgorithmID: " + algorithmId);
            Console.WriteLine("Base64 encoded input: " + Convert.ToBase64String(encDEK));

            // TODO: Replace the below path with the full path to your service account key JSON file
            // Example: @"C:\Path\To\Your\File\your-file-name.json"

            string jsonPath = @"C:\Path\To\Your\File\your-file-name.json";
            var serviceAccount = JsonConvert.DeserializeObject<Dictionary<string, string>>(System.IO.File.ReadAllText(jsonPath));

            string privateKey = serviceAccount["private_key"];
            string clientEmail = serviceAccount["client_email"];
            string tokenUri = serviceAccount["token_uri"];

            var rsa = RSA.Create();
            rsa.ImportFromPem(privateKey.ToCharArray());

            var creds = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);

            var now = DateTimeOffset.UtcNow;
            var jwtHeader = new JwtHeader(creds);
            var jwtPayload = new JwtPayload
            {
                { "iss", clientEmail },
                { "scope", "https://www.googleapis.com/auth/cloud-platform" },
                { "aud", tokenUri },
                { "iat", now.ToUnixTimeSeconds() },
                { "exp", now.AddMinutes(60).ToUnixTimeSeconds() }
            };

            var jwt = new JwtSecurityToken(jwtHeader, jwtPayload);
            string signedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            var httpClient = new HttpClient();
            var tokenResponse = httpClient.PostAsync(tokenUri, new FormUrlEncodedContent(new Dictionary<string, string>
            {
                { "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" },
                { "assertion", signedJwt }
            })).Result;

            var tokenJson = JsonConvert.DeserializeObject<Dictionary<string, string>>(tokenResponse.Content.ReadAsStringAsync().Result);
            string accessToken = tokenJson["access_token"];

            string ciphertext = Convert.ToBase64String(encDEK);

            // TODO: Replace the below URL with your actual Google Cloud KMS key version URL
            // Format: https://cloudkms.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/keyRings/{KEY_RING}/cryptoKeys/{KEY_NAME}/cryptoKeyVersions/{VERSION}:asymmetricDecrypt
            string kmsUrl = "https://cloudkms.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/keyRings/{KEY_RING}/cryptoKeys/{KEY_NAME}/cryptoKeyVersions/{VERSION}:asymmetricDecrypt";

            var payload = new Dictionary<string, string>
            {
                { "ciphertext", ciphertext }
            };

            var kmsRequest = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");

            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

            var kmsResponse = httpClient.PostAsync(kmsUrl, kmsRequest).Result;
            
            var responseContent = kmsResponse.Content.ReadAsStringAsync().Result;

            if (!kmsResponse.IsSuccessStatusCode)
            {
                Console.WriteLine("KMS API Error:");
                Console.WriteLine(responseContent);
                throw new Exception("KMS decryption failed.");
            }

            var kmsJson = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent);
            string plaintextBase64 = kmsJson["plaintext"];

            byte[] plaintext = Convert.FromBase64String(plaintextBase64);

            decryptedDekLength = plaintext.Length;
          
            return plaintext;
        }
    }
}

Create a DotNetDecryptor.dll File

To create a DLL file:

  1. Open x64 Native Tools Command Prompt for VS.

  2. Navigate to the folder where you have saved the DevOps Approach files using the following command.

    cd path\to\your\directory
    
  3. To build the Decryptor DLL, run the following command:

    dotnet publish -r win-x64 -c Release
    
  4. Navigate to the bin\x64\Release\net8.0\win-x64\publish\ folder.

  5. The DotNetDecryptor.dll is created in the publish folder.

    Note: It is recommended to move the DotNetDecryptor file to a shorter path location.

  6. Update the decryptor parameter to the DotNetDecryptor.dll full path in the config.ini file.

4 - Application Protector API Return Codes

Learn about the Application Protector API Return Codes.

When an application is developed using the APIs of the Protegrity Application Protector Suite, you may encounter the Application Protector API Return Codes. For more information about log return codes, refer to Log return codes.

Sample Log for AP Return Codes

The following is a sample log generated in Discover on the Audit Store Dashboards in ESA.

Sample log for AP return codes

Protection audit logs are stored in the Audit Store. Select the pty_insight_*audit* index to view the protection logs.

For more information about viewing the logs, refer to Working with Discover.

5 - Config.ini file for Application Protector

Sample config.ini file for Application Protector.

The Application Protector can be configured using the config.ini file. By default, this file is located in the <installation directory>/sdk/<protector>/data/ directory.

The various configurations required for setting up the Application Protector are described in this section.

Sample config.ini file

The following represents a sample config.ini file.

# -----------------------------
# Protector configuration
# ----------------------------- 
[protector]

# Cadence determines how often the protector connects with shared memory to fetch the policy updates in background.
# Default is 60 seconds. So by default, every 60 seconds protector tries to fetch the policy updates.
#
# Default 60.
cadence = 60

# The time during which a session object is valid. Default = 15 minutes.
session.sessiontimeout = 15

###############################################################################
# Log Provider Config
###############################################################################
[log]

# In case that connection to fluent-bit is lost, set how audits/logs are handled
# 
# drop  : (default) Protector throws logs away if connection to the fluentbit is lost
# error : Protector returns error without protecting/unprotecting 
#         data if connection to the fluentbit is lost
mode = drop

# Host/IP to fluent-bit where audits/logs will be forwarded from the protector
#
# Default localhost
host = localhost

Different configurations for Application Protector

The following are the various configurations:

Protector configurations

  • cadence: The interval at which the protector synchronizes with the shared memory for fetching the package with policy. The default value for cadence is 60 seconds. The maximum and minimum value that can be set for cadence are 86400 seconds (24 hours) and 1 respectively.
    For more information about the policy deployment with different cadence configurations, refer to Policy Deployment.
    For more information about the Resilient Package sync configuration parameters, refer to Resilient Package Sync Configuration Parameters.
    For more information about changing protector status interval, refer to Resilient Package Status Configuration Parameter.
  • session.sessiontimeout: The time during which a session object is valid. The default value for session.sessiontimeout is 15 minutes.

    Note: The session.sessiontimeout parameter is a feature of AP Java and not valid for AP Python and AP .Net.

Log Provider configurations

  • mode: This describes how the protector logs are handled if you lose connection to the Log Forwarder host, can be set to one of the following values:
    • drop: The logs are dropped when the connection to the Log Forwarder is lost. The default mode is drop.
    • error: The data security operations are stopped and an error is generated when the connection to the Log Forwarder is lost.
  • host: The Log Forwarder hostname or IP address where the logs will be forwarded from the protector. The default host for Log Forwarder is localhost.

For more information about the configuration parameters for forwarding the audits and logs, refer to Configuration Parameters for Forwarding Audits and Logs.

6 - Multi-node Application Protector Architecture

Architecture for multi-node Application Protector.

The multi-node Application Protector (AP) architecture, its individual components, and how logs are collected using the Log Forwarder are described in this section.

The following figure describes the multi-node AP architecture.

Multi-node AP architecture

For example, some AP nodes are connected to an ESA, which includes the Audit Store component. Each AP node contains a Log Forwarder, RP Agent, and AP instance for sending logs to the ESA.

Protector: The AP can be configured using the config.ini file.
For more information about the configurations, refer to Config.ini file for Application Protector.

RP Agent: The RP Agent downloads the package with policy from the ESA, which is used by the protector to perform the protect, unprotect, or reprotect operations. It checks for the updates in the policy at set intervals and downloads the latest policy package when an update is detected.

Log Forwarder: The Log Forwarder component collects the logs from the AP and forwards them to the Audit Store. The Log Forwarder uses the 15780 port which is configurable to transport protection and audit logs to the ESA. The ESA receives the logs and stores it in the Audit Store.

7 - Uninstalling the Application Protector

Uninstalling Application Protector .Net in a Production Environment

This section provides step-by-step instructions to uninstall Application Protector .Net (AP .Net) and its associated components from a Windows platform.

Uninstalling the Log Forwarder

Follow these instructions to uninstall the Log Forwarder.

Before you begin

  • To preserve all the configurations during an upgrade, ensure that all the files present under the C:\Program Files\Protegrity\logforwarder\data\config.d directory are backed up.
  • Close all AP .Net files and folders.

Instructions

For more information about uninstalling the Log Forwarder, refer to Uninstalling Log Forwarder on Windows.

Uninstalling the RPAgent

Follow these instructions to uninstall the RPAgent

Before you begin

  • To preserve all the configurations during an upgrade, ensure that all the files present under the C:\Program Files\Protegrity\sdk\data directory are backed up.
  • Close all AP .Net files and folders.

Instructions

For more information about uninstalling the RP Agent, refer to Unstalling RP Agent on Windows.

Uninstalling Application Protector .Net

To uninstall the AP .Net:

  1. Run the Uninstall Utility.
    a. Navigate to C:\Program Files\Protegrity\sdk\dotnet directory.
    b. Run the unins000 file located in this directory.

  2. Delete the Installation Directory.
    a. After uninstallation, delete the C:\Program Files\Protegrity\sdk folder.

  3. Remove the Environment Variable.
    a. Delete the path to the dotnetprovider.plm file from the Environment Variables.

  4. Uninstall the NuGet Package.
    a. Open Visual Studio and go to Manage NuGet Packages for Solution.
    b. Search for and select DotNetProtector.
    c. Click Uninstall.

The Application Protector .Net and its components are successfully uninstalled from the Windows platform.
The dotnetprotector package will be deleted from the NuGet Package Folder during the unintallation process.