Re: How to run C# Sample Code for MLE

shameem
Visa Employee

How to run C# Sample Code for MLE

In this “How-to” guide, we will show you how to test MLE (Message Level Encryption) enabled APIs using C#.

 

Important Links:

 

Enable MLE for the API(s) you are interested in.

 

Login to your Visa Developer Dashboard and go to your project, you should see something like this:

 

2020-11-11_09-45-12.png

 

Enable the APIs for which MLE needs to be active in VDP by toggling the API for which MLE needs to be enforced.

 

In this example, we will enable MLE for Funds Transfer API And Query API as below:

 

2020-11-11_09-46-41.png

 

 

How to get credentials

 

You can obtain your project credentials by browsing the left side navigation menu of your project and click on “Credentials”.

 

2020-11-11_11-25-10.png

 

Next step we will create a Key-ID by clicking on the Generate Key-ID button.

 

2020-11-11_11-29-02.png

 

After you have clicked the button, you will get Key-ID. Copy the Key-ID for your reference.

The Key-ID will look like this: 41d9f2a1-xxxx-4xxx-b40c-a0480c2xxxxx

 

2020-11-11_11-30-49.png

 

The next step is to add a CSR (Certificate Signing Request). Click on the link "Add CSR" .   

 

You will be prompt to submit a Certificate Signing Request. 

 

2020-11-11_11-32-46.png

 

We have option to Generate a CSR for me (default) or submit your own. In this example we will use the Generate a CSR for me (default) and Click Confirm button.

 

After submitting the request, you will be prompt to download the Certificate/Copy Private Key. 

 

2020-11-11_11-34-26.png

 

After you have downloaded the private key, check the box "I confirm that I've downloaded my certificate key" and click continue. You will see the Status change to "Active".

 

 

Expand the Key-ID and you will see the Server Encryption Certificate and Client Encryption Certificate.

Download both certificates and save it.

 

2020-11-11_11-36-33.png

To be able to make an API call with MLE, you need to have the following

  • Server Encryption Certificate
  • Key-ID
  • Certificate Private Key

 

How to run C# Sample Code for MLE

 

 

Step 1 - Create a new project on Visual Studio

 

  • On the Visual Studio Welcome Screen, click Create a new Project.

  • In the Create a new project wizard, we will select Console App (.Net Framework)
  • Provide the project Project Name, check on box Place solution and project in the same directory and click Create.

 

Step 2 - Add the following dependencies.

 

You will need to add the following dependencies using NuGet - the package manager for .NET. 

 

 

Step 3 - Copy the below code to the “Program.cs” file

 

To convert PEM certificate to a PKCS12 certificate, we will use Open SSL

 

Execute the following OpenSSL command to create a PKCS12 (.p12) file:

openssl pkcs12 -export -inkey cert.pem -in cert.pem -out cert.p12

 

Once you have copied the below code, please set the below parameters:


public static string userId = "<YOUR USER ID HERE>";
public static string password = "<YOUR PASSWORD HERE>";
public static string cert = "<ABSOLUTE PATH TO .P12 CERTIFICATE HERE>";
public static string certPassword = "<PASSPHRASE FOR P12 CERTIFICATE HERE>";

//For MLE
public static string keyId = "<YOUR MLE KID HERE>";
public static string mleClientPrivateKey = "<YOUR MLE PRIVATE KEY PATH>";
public static string mleServerPublicCertificate = "<YOUR MLE SERVER CERTIFICATE HERE>";

 

 

 

/*
 * (c) Copyright 2018 - 2020 Visa.All Rights Reserved.**
*
* NOTICE: The software and accompanying information and documentation(together, the "Software") remain the property of and are proprietary to Visa and its suppliers and affiliates.The Software remains protected by intellectual property rights and may be covered by U.S.and foreign patents or patent applications.The Software is licensed and not sold.*
 *
 *  By accessing the Software you are agreeing to Visa's terms of use (developer.visa.com/terms) and privacy policy (developer.visa.com/privacy).In addition, all permissible uses of the Software must be in support of Visa products, programs and services provided through the Visa Developer Program (VDP) platform only (developer.visa.com). **THE SOFTWARE AND ANY ASSOCIATED INFORMATION OR DOCUMENTATION IS PROVIDED ON AN "AS IS," "AS AVAILABLE," "WITH ALL FAULTS" BASIS WITHOUT WARRANTY OR  CONDITION OF ANY KIND. YOUR USE IS AT YOUR OWN RISK.** All brand names are the property of their respective owners, used for identification purposes only, and do not imply product endorsement or affiliation with Visa. Any links to third party sites are for your information only and equally  do not constitute a Visa endorsement. Visa has no insight into and control over third party content and code and disclaims all liability for any such components, including continued availability and functionality. Benefits depend on implementation details and business factors and coding steps shown are exemplary only and do not reflect all necessary elements for the described capabilities. Capabilities and features are subject to Visa's terms and conditions and may require development,implementation and resources by you based on your business and operational details. Please refer to the specific API documentation for details on the requirements, eligibility and geographic availability.*
 *
 * This Software includes programs, concepts and details under continuing development by Visa. Any Visa features, functionality, implementation, branding, and schedules may be amended, updated or canceled at Visa"s discretion.The timing of widespread availability of programs and functionality is also subject to a number of factors outside Visa's control, including but not limited to deployment of necessary infrastructure by issuers, acquirers, merchants and mobile device manufacturers.*
*
*/
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Jose;
using Org.BouncyCastle.OpenSsl;

using Newtonsoft.Json;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;

using Org.BouncyCastle.Crypto.Parameters;

using Newtonsoft.Json.Linq;

namespace Vdp
{
    class Program
    {
        public static string visaUrl = "https://sandbox.api.visa.com/";
        public static string userId = "<YOUR USER ID HERE>";
        public static string password = "<YOUR PASSWORD HERE>";
        public static string cert = "<ABSOLUTE PATH TO .P12 CERTIFICATE HERE>";
        public static string certPassword = "<PASSPHRASE FOR P12 CERTIFICATE HERE>";
        
        //For MLE
        public static string keyId = "<YOUR MLE KID HERE>";
        public static string mleClientPrivateKey = "<YOUR MLE PRIVATE KEY PATH>";
        public static string mleServerPublicCertificate = "<YOUR MLE SERVER CERTIFICATE HERE>"; 

        
        static void Main(string[] args)
        {
            Program p = new Program();
            Console.WriteLine("MLE OCT Test");
            string decryptedPayload = p.PushFundsTransactions();
            Console.WriteLine("Decrypted OCT Response\n" + decryptedPayload);

            var responseObj = JObject.Parse(decryptedPayload) as JToken;
            var aquiringBin = "408999";
            var transactionIdentifier = responseObj["transactionIdentifier"].ToString();

            var queryResponse= p.Query(aquiringBin, transactionIdentifier);
            Console.WriteLine("Transaction Query Response:\n" + queryResponse);
        }

        private void LogRequest(string url, string requestBody)
        {
            Console.WriteLine(url);
            Console.WriteLine(requestBody);
        }

        private void LogResponse(string info, HttpWebResponse response)
        {

            Debug.WriteLine(info);
            Console.WriteLine("Response Status: \n" + response.StatusCode);
            Console.WriteLine("Response Headers: \n" + response.Headers.ToString());

            Console.WriteLine("Response Body: \n" + GetResponseBody(response));
        }

        private string GetResponseBody(HttpWebResponse response)
        {
            string responseBody = "";
            using (var reader = new StreamReader(response.GetResponseStream(), ASCIIEncoding.ASCII))
            {
                responseBody = reader.ReadToEnd();
            }
            return responseBody;
        }

        //Correlation Id ( ex-correlation-id ) is an optional header while making an API call. You can skip passing the header while calling the API's.
        private string GetCorrelationId()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var random = new Random();
            return new string(Enumerable.Repeat(chars, 12).Select(s => s[random.Next(s.Length)]).ToArray()) + "_SC";

        }

        private string GetBasicAuthHeader(string userId, string password)
        {
            string authString = userId + ":" + password;
            var authStringBytes = Encoding.UTF8.GetBytes(authString);
            string authHeaderString = Convert.ToBase64String(authStringBytes);
            return "Basic " + authHeaderString;
        }

        public string DoMutualAuthCall(string path, string method, string testInfo, string requestBodyString, Dictionary<string, string> headers = null)
        {
            string requestURL = visaUrl + path;
            string certificatePath = cert;
            string certificatePassword = certPassword;
            string statusCode = "";
            string responseBody = "";
            LogRequest(requestURL, requestBodyString);
            // Create the POST request object 
            HttpWebRequest request = WebRequest.Create(requestURL) as HttpWebRequest;

            request.Method = method;
            if (method.Equals("POST") || method.Equals("PUT"))
            {
                request.ContentType = "application/json";
                request.Accept = "application/json";
                // Load the body for the post request
                var requestStringBytes = Encoding.UTF8.GetBytes(requestBodyString);
                request.GetRequestStream().Write(requestStringBytes, 0, requestStringBytes.Length);
            }

            if (headers != null)
            {
                foreach (KeyValuePair<string, string> header in headers)
                {
                    request.Headers[header.Key] = header.Value;
                }
            }

            // Add headers
            request.Headers["Authorization"] = GetBasicAuthHeader(userId, password);
            request.Headers["ex-correlation-id"] = GetCorrelationId();
            request.Headers["keyId"] = keyId;

            // Add certificate
            var certificate = new X509Certificate2(certificatePath, certificatePassword);
            request.ClientCertificates.Add(certificate);
            try
            {
                // Make the call
                using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
                {
                    responseBody = GetResponseBody(response);
                    LogResponse(testInfo, response);
                    statusCode = response.StatusCode.ToString();

                }
            }
            catch (WebException e)
            {
                Console.WriteLine(e.ToString());
                if (e.Response is HttpWebResponse)
                {
                    HttpWebResponse response = (HttpWebResponse)e.Response;
                    responseBody = GetResponseBody(response);
                    LogResponse(testInfo, response);
                    statusCode = response.StatusCode.ToString();
                }
            }
            return responseBody;
        }
        public string PushFundsTransactions()
        {
            string localTransactionDateTime = DateTime.Now.ToString("yyyy-MM-dd'T'HH:mm:ss");
            string requestBody = "{ \"acquirerCountryCode\": \"840\", \"acquiringBin\": \"408999\", \"amount\": \"124.05\", \"businessApplicationId\": \"AA\", \"cardAcceptor\": {   \"address\": {   \"country\": \"USA\",   \"county\": \"San Mateo\",   \"state\": \"CA\",   \"zipCode\": \"94404\"   },   \"idCode\": \"CA-IDCode-77765\",   \"name\": \"Visa Inc. USA-Foster City\",   \"terminalId\": \"TID-9999\" }, \"localTransactionDateTime\": \"" + localTransactionDateTime + "\", \"merchantCategoryCode\": \"6012\", \"pointOfServiceData\": {   \"motoECIIndicator\": \"0\",   \"panEntryMode\": \"90\",   \"posConditionCode\": \"00\" }, \"recipientName\": \"rohan\", \"recipientPrimaryAccountNumber\": \"4957030420210462\", \"retrievalReferenceNumber\": \"412770451018\", \"senderAccountNumber\": \"4957030420210454\", \"senderAddress\": \"901 Metro Center Blvd\", \"senderCity\": \"Foster City\", \"senderCountryCode\": \"124\", \"senderName\": \"Mohammed Qasim\", \"senderReference\": \"\", \"senderStateCode\": \"CA\", \"sourceOfFundsCode\": \"05\", \"systemsTraceAuditNumber\": \"451018\", \"transactionCurrencyCode\": \"USD\", \"transactionIdentifier\": \"381228649430015\" }";

            string requestURL = "visadirect/fundstransfer/v1/pushfundstransactions";

            return GetDecryptedPayload(DoMutualAuthCall(requestURL, "POST", "OCT With MLE", getEncryptedPayload(requestBody), null));
        }

        public string Query(string acquiringBin, string transactionIdentifier)
        {
            var queryString = "?acquiringBIN=" + acquiringBin + "&transactionIdentifier=" + transactionIdentifier;

            var requestUrl = "visadirect/v1/transactionquery" + queryString;

            return GetDecryptedPayload(DoMutualAuthCall(requestUrl, "GET", "Transaction Query With MLE", null, null));
        }

        private static string GetTimestamp()
        {
            long timeStamp = ((long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds) / 1000;
            return timeStamp.ToString();
        }

        private String getEncryptedPayload(String requestBody)
        {
            RSA clientCertificate = new X509Certificate2(mleServerPublicCertificate).GetRSAPublicKey();
            DateTime now = DateTime.UtcNow;
            long unixTimeMilliseconds = new DateTimeOffset(now).ToUnixTimeMilliseconds();
            IDictionary<string, object> extraHeaders = new Dictionary<string, object>{
                {"kid", keyId},{"iat",unixTimeMilliseconds}
            };
            string token = JWT.Encode(requestBody, clientCertificate, JweAlgorithm.RSA_OAEP_256, JweEncryption.A128GCM, null, extraHeaders);
            return "{\"encData\":\"" + token + "\"}";
        }

        private static String GetDecryptedPayload(String encryptedPayload)
        {
            var jsonPayload = JsonConvert.DeserializeObject<EncryptedPayload>(encryptedPayload);
            return JWT.Decode(jsonPayload.encData, ImportPrivateKey(mleClientPrivateKey));
        }

        private static RSA ImportPrivateKey(string privateKeyFile)
        {
            var pemValue = System.Text.Encoding.Default.GetString(File.ReadAllBytes(privateKeyFile));
            var pr = new PemReader(new StringReader(pemValue));
            var keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
            var rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair.Private);

            var rsa = RSA.Create();
            rsa.ImportParameters(rsaParams);

            return rsa;
        }

    }

    public class EncryptedPayload
    {
        public string encData { get; set; }
    }

} 

 

Step 4 - Compile Your Code 

  • Simply click on the “Start” to run Program

 

Want more? Join the Visa Developer Community to get alerts on the latest tutorials, guides and new developer resources. Stay tuned for more in the series. 

20 REPLIES 20
locdh31439
Regular Visitor

Re: How to run C# Sample Code for MLE

Could you explain more about DotNetUtilities - use to create RSA parameter from privateKey
shameem
Visa Employee

Re: How to run C# Sample Code for MLE

Hi @locdh31439 ,

 

.NET does not have an easy way to directly deal with .pem format files generated using OpenSSL. I had to look into Bouncy Castle library to do it.

 

The DotNetUtilities class from BouncyCastle provides helper methods. In this sample code, I have used theDotNetUtilities.ToRSAParameters(...) to get the RSAParameters to be able to construct the RSA object, from the private key which is in PEM format.

 

Please note, the sample code is exemplary only to demonstrate how MLE works.

 

Thank you.

Shameem

 

Was your question answered? Don't forget to click on "Accept as Solution".

locdh31439
Regular Visitor

Re: How to run C# Sample Code for MLE

Many thanks for your detail explained.
I have already run your sample code but still got error "System.Net.WebException: An error occurred while sending the request. The client certificate credentials were not recognized". I wonder it occurs because X509Certificate2 function has problem. Do you have problem with it?

I already checked my certificatePath and certificatePassword in SoapUI successfully.
asaldanha
New Contributor

Re: How to run C# Sample Code for MLE

In Step 3 - Copy the below code to the “Program.cs” file

>To convert PEM certificate to a PKCS12 certificate, we will use Open SSL.

 

>Execute the following OpenSSL command to create a PKCS12 (.p12) file:

>openssl pkcs12 -export -inkey cert.pem -in cert.pem -out cert.p12

 

Running that on the client and server certificates for the key ID of the project I get the following for both:

 

C:\Projects\Visa\Transactions>openssl pkcs12 -export -inkey client_cert_0ece2225-470c-4445-8614-ea9fb9e38b3b.pem -in client_cert_0ece2225-470c-4445-8614-ea9fb9e38b3b.pem -out client_cert.p12
unable to load private key
26384:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:707:Expecting: ANY PRIVATE KEY

asaldanha
New Contributor

Re: How to run C# Sample Code for MLE

when I try and run this it seems to make the call to the server, but when opening the reply it runs into an error when it tries to decrypt the payload.

 

It works OK when I use the same cert with Soap UI - though the API called in that example is visaaliasdirectory/v1/manage/createalias

 

I tried

 

AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();

instead of 

var keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();

 

but it did not help

 

image.png

 

System.InvalidCastException
HResult=0x80004002
Message=Unable to cast object of type 'Org.BouncyCastle.X509.X509Certificate' to type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair'.
Source=VisaMLENet
StackTrace:
at VisaMLENet.Program.ImportPrivateKey(String privateKeyFile) in 

shameem
Visa Employee

Re: How to run C# Sample Code for MLE

Hello @asaldanha ,

 

Thank you for trying out our sample codes. Kindly find comments below:

 

1. unable to load private key

The -inkey option should be the private key not the certificate or public key.

Both inkey and in options you are providing the same certificate. Hope you have managed to resolve this.

 

C:\Projects\Visa\Transactions>openssl pkcs12 -export -inkey client_cert_0ece2225-470c-4445-8614-ea9fb9e38b3b.pem -in client_cert_0ece2225-470c-4445-8614-ea9fb9e38b3b.pem -out client_cert.p12
unable to load private key


2. Unable to cast object of type 'Org.BouncyCastle.X509.X509Certificate' to type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair'.

 

Looks like you are using the wrong import or the wrong library. Below is the project dependencies I am using:

 

<Project Sdk="Microsoft.NET.Sdk">
 
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>
 
    <ItemGroup>
      <PackageReference Include="jose-jwt" Version="3.1.1" />
      <PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
      <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.11.0" />
    </ItemGroup>
 
</Project>

 

 

Here are the required imports:

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Jose;
using Newtonsoft.Json;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

 

 

Let us know if you have any further questions.

 

Thank you.

asaldanha
New Contributor

Re: How to run C# Sample Code for MLE

This is WRT 2.

 

Does console application have to be in .Net 5 ? 

 

Mine is 4.8 and  your code needs more imports that you specified to compile for 4.8

 

My libraries are the same as yours (below)

 

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Jose;
using Org.BouncyCastle.OpenSsl;

using Newtonsoft.Json;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;

using Org.BouncyCastle.Crypto.Parameters;

using Newtonsoft.Json.Linq;

 

 

 

    <Reference Include="BouncyCastle.Crypto, Version=1.8.10.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
      <HintPath>..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll</HintPath>
    </Reference>
    <Reference Include="jose-jwt, Version=3.1.1.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\packages\jose-jwt.3.1.1\lib\net461\jose-jwt.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.IdentityModel.JsonWebTokens, Version=6.11.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.IdentityModel.JsonWebTokens.6.11.1\lib\net472\Microsoft.IdentityModel.JsonWebTokens.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.IdentityModel.Logging, Version=6.11.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.IdentityModel.Logging.6.11.1\lib\net472\Microsoft.IdentityModel.Logging.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.IdentityModel.Tokens, Version=6.11.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.IdentityModel.Tokens.6.11.1\lib\net472\Microsoft.IdentityModel.Tokens.dll</HintPath>
    </Reference>
    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.IdentityModel.Tokens.Jwt, Version=6.11.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <HintPath>..\packages\System.IdentityModel.Tokens.Jwt.6.11.0\lib\net472\System.IdentityModel.Tokens.Jwt.dll</HintPath>
 

 

asaldanha
New Contributor

Re: How to run C# Sample Code for MLE

Have not heard back on this.

 

I have re-built another project in .Net 5 - but am getting the same run time error as earlier.

 

Would appreciate feedback

shameem
Visa Employee

Re: How to run C# Sample Code for MLE

Hi @asaldanha ,

 

Looks like you are not using the correct file to be loaded as the mle private key. Consequently causing a casting exception.

 

By looking at the error message, looks like you are provide a public certificate not a private key.

Message=Unable to cast object of type 'Org.BouncyCastle.X509.X509Certificate' to type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair'.

 

Kindly ensure that the mle private key is set to avoid this error, The certificates required for MLE configuration contains your MLE Key ID as below:

 

 

public static string keyId = "<MLE_KEY_ID>";
public static string mleClientPrivateKey = "<PATH>/server_cert_<MLE_KEY_ID>.pem";
public static string mleServerPublicCertificate = "<PATH>/key_<MLE_KEY_ID>.pem"; 

 

 

That should solve this issue.

 

Let us know if you have any further questions.

 

Thank you.