/
C# SDK Sample: Hosted Fields (Credit Card Numbers)

C# SDK Sample: Hosted Fields (Credit Card Numbers)

PemKeyUtils is used by the Hosted Fields to handle RSA encryption

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace SdkSample {
  public class PemKeyUtils {
        
    public static RSACryptoServiceProvider GetRsaProviderFromPublicKey( string pemString )
    {
        var pemkey = DecodeOpenSslPublicKey( pemString );

        return pemkey == null ? null : DecodeX509PublicKey( pemkey );

    }



    //--------   Get the binary RSA PUBLIC key   --------
      private static byte[] DecodeOpenSslPublicKey( string instr )
    {
        const string pempubheader = "-----BEGIN PUBLIC KEY-----";
        const string pempubfooter = "-----END PUBLIC KEY-----";
        string pemString = instr.Trim();
        byte[] binkey;
        if (!pemString.StartsWith( pempubheader ) || !pemString.EndsWith( pempubfooter ))
            return null;
        StringBuilder sb = new StringBuilder( pemString );
        sb.Replace( pempubheader, "" );  //remove headers/footers, if present
        sb.Replace( pempubfooter, "" );

        string pubstr = sb.ToString().Trim();   //get string after removing leading/trailing whitespace

        try
        {
            binkey = Convert.FromBase64String( pubstr );
        }
        catch (FormatException)
        {       //if can't b64 decode, data is not valid
            return null;
        }
        return binkey;
    }

    static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509Key)
    {
        // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
        using (var mem = new MemoryStream(x509Key))
        {
            using (var binr = new BinaryReader(mem))    //wrap Memory Stream with BinaryReader for easy reading
            {
                try
                {
                    var twobytes = binr.ReadUInt16();
                    switch (twobytes)
                    {
                        case 0x8130:
                            binr.ReadByte();    //advance 1 byte
                            break;
                        case 0x8230:
                            binr.ReadInt16();   //advance 2 bytes
                            break;
                        default:
                            return null;
                    }

                    var seq = binr.ReadBytes(15);
                    if (!CompareByteArrays(seq, seqOid))  //make sure Sequence for OID is correct
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8203)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    var bt = binr.ReadByte();
                    if (bt != 0x00)     //expect null byte next
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8230)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    twobytes = binr.ReadUInt16();
                    byte lowbyte;
                    byte highbyte = 0x00;

                    if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                        lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
                    else if (twobytes == 0x8202)
                    {
                        highbyte = binr.ReadByte(); //advance 2 bytes
                        lowbyte = binr.ReadByte();
                    }
                    else
                        return null;
                    byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
                    int modsize = BitConverter.ToInt32(modint, 0);

                    byte firstbyte = binr.ReadByte();
                    binr.BaseStream.Seek(-1, SeekOrigin.Current);

                    if (firstbyte == 0x00)
                    {   //if first byte (highest order) of modulus is zero, don't include it
                        binr.ReadByte();    //skip this null byte
                        modsize -= 1;   //reduce modulus buffer size by 1
                    }

                    byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes

                    if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
                        return null;
                    int expbytes = binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
                    byte[] exponent = binr.ReadBytes(expbytes);

                    // We don't really need to print anything but if we insist to...
                    //showBytes("\nExponent", exponent);
                    //showBytes("\nModulus", modulus);

                    // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                    RSAParameters rsaKeyInfo = new RSAParameters
                    {
                        Modulus = modulus,
                        Exponent = exponent
                    };
                    rsa.ImportParameters(rsaKeyInfo);
                    return rsa;
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }
    }


      private static bool CompareByteArrays( byte[] a, byte[] b )
    {
        if (a.Length != b.Length)
            return false;
        var i = 0;
        foreach (var c in a)
        {
            if (c != b[i])
                return false;
            i++;
        }
        return true;
    }
   
    
  }
}


using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text;

namespace SdkSample {
  public class HostedFields {

    private const string BaseAddress = "https://token.ultracart.com";
    private const string HostedFieldsPath = "/cgi-bin/UCCheckoutAPIHostedFields";
    private const string PublicKeyPath = "/cgi-bin/UCCheckoutAPIHostedFieldsPublicKey";
    private const string Referrer = "https://token.ultracart.com/";
    private static readonly Uri ReferrerUri = new Uri(Referrer);
    private const string Version = "1.0";

    // ReSharper disable once InconsistentNaming
    private const string OperationCVV = "storeCreditCardCvv2";

    // ReSharper disable once InconsistentNaming
    private const string OperationCC = "storeCreditCardNumber";


    public string MerchantId { get; set; }

    public string CartId { get; set; }

    //public string PublicKey { get; set; }
    public RSACryptoServiceProvider Rsa { get; set; }


    public static HostedFields Create(string merchantId) {
      return Create(merchantId, null);
    }


    public static HostedFields Create(string merchantId, string cartId) {
      var hostedFields =
        new HostedFields(merchantId, cartId) {Rsa = PemKeyUtils.GetRsaProviderFromPublicKey(GetPublicKey())};

      return hostedFields;
    }


    private HostedFields(string merchantId, string cartId) {
      MerchantId = merchantId;
      CartId = cartId;
    }


    private static string GetPublicKey() {

      var hc = new HttpClient {BaseAddress = new Uri(BaseAddress)};
      var result = hc.GetAsync(PublicKeyPath).Result;
      var key = result.Content.ReadAsStringAsync().Result;
      Console.WriteLine(key);
      return key;
    }


    private static JsonResult PostToTokenVault(HttpContent payload) {

      var hc = new HttpClient {BaseAddress = new Uri(BaseAddress)};

      hc.DefaultRequestHeaders.Referrer = ReferrerUri;
      var result = hc.PostAsync(HostedFieldsPath, payload).Result;

      var serializer = new DataContractJsonSerializer(typeof(JsonResult));
      var jsonResult = (JsonResult) serializer.ReadObject(result.Content.ReadAsStreamAsync().Result);

      Console.WriteLine($"PostJsonPayload Result: {jsonResult}");

      return jsonResult;
    }


    public JsonResult StoreNumber(string creditCardNumber) {

      Console.WriteLine($"Storing CC: {creditCardNumber}");

      var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
      Console.WriteLine($"Epoch: {epoch}");

      var timestamp = (long) (DateTime.UtcNow - epoch).TotalMilliseconds;
      Console.WriteLine($"Timestamp: {timestamp}");

      var unencryptedPayload = creditCardNumber + "|" + timestamp;
      Console.WriteLine($"Unencrypted Payload: {unencryptedPayload}");

      var encryptedBytes = Rsa.Encrypt(Encoding.ASCII.GetBytes(unencryptedPayload), false);
      //Console.WriteLine("Encrypted: " + Encoding.Default.GetString(encryptedBytes));

      var base64Payload = Convert.ToBase64String(encryptedBytes);
      Console.WriteLine("Base 64:");
      Console.WriteLine(base64Payload);

      var content = new List<KeyValuePair<string, string>>() {
        new KeyValuePair<string, string>("merchantId", MerchantId),
        new KeyValuePair<string, string>("operation", OperationCC),
        new KeyValuePair<string, string>("version", Version),
        new KeyValuePair<string, string>("creditCardNumberEncrypted", base64Payload),
        new KeyValuePair<string, string>("referrer", Referrer),
      };
      // if the card number belongs to a shopping cart, cart id should be provided.  if the card number will be used
      // to update an existing order, and auto order, and a customer profile (card already on file), then do not
      // provide a cart id and a token will be returned.  That token is then attached to the appropriate record and
      // the server side logic will retrieve the credit card from the token vault using the token as the key.
      if (CartId != null) {
        content.Add(new KeyValuePair<string, string>("shoppingCartId", CartId));
      }

      var formContent = new FormUrlEncodedContent(content.ToArray());


      return PostToTokenVault(formContent);

    }


    // ReSharper disable once InconsistentNaming
    public JsonResult StoreCVV(string cvv) {
      var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
      var timestamp = (long) (DateTime.UtcNow - epoch).TotalMilliseconds;
      var unencryptedPayload = cvv + "|" + timestamp;


      var encryptedBytes = Rsa.Encrypt(Encoding.ASCII.GetBytes(unencryptedPayload), false);
      var base64Payload = Convert.ToBase64String(encryptedBytes);

      var content = new List<KeyValuePair<string, string>>() {
        new KeyValuePair<string, string>("merchantId", MerchantId),
        new KeyValuePair<string, string>("operation", OperationCVV),
        new KeyValuePair<string, string>("version", Version),
        new KeyValuePair<string, string>("creditCardCvv2Encrypted", base64Payload),
        new KeyValuePair<string, string>("referrer", Referrer),
      };
      // if the card number belongs to a shopping cart, cart id should be provided.  if the card number will be used
      // to update an existing order, and auto order, and a customer profile (card already on file), then do not
      // provide a cart id and a token will be returned.  That token is then attached to the appropriate record and
      // the server side logic will retrieve the credit card from the token vault using the token as the key.
      if (CartId != null) {
        content.Add(new KeyValuePair<string, string>("shoppingCartId", CartId));
      }

      var formContent = new FormUrlEncodedContent(content.ToArray());

      return PostToTokenVault(formContent);

    }


    [DataContract]
    [SuppressMessage("ReSharper", "InconsistentNaming")]
    public class JsonResult {
      [DataMember] internal bool success;

      [DataMember] internal string maskedValue;

      [DataMember] internal string cardType;

      [DataMember] internal string token;

      [DataMember] internal string errorMessage;


      public override string ToString() {
        return
          $"{nameof(success)}: {success}, {nameof(maskedValue)}: {maskedValue}, {nameof(cardType)}: {cardType}, {nameof(token)}: {token}, {nameof(errorMessage)}: {errorMessage}";
      }

    }


//    private static int Main() {
//
//      Console.WriteLine("HostedFields.Main executing.");
//
//      const string merchantId = "DEMO";
//      const string cartId = "1234567890";
//
//      var hostedFields = Create(merchantId, cartId);
//
//
//      var ccResult = hostedFields.StoreNumber("4444333322221111");
//      Console.WriteLine("Result of Storing CC Number:");
//      Console.WriteLine(ccResult);
//
//      var cvvResult = hostedFields.StoreCVV("123");
//      Console.WriteLine("Result of Storing CVV Number:");
//      Console.WriteLine(cvvResult);
//
//      return 0;
//    }


  }
}


Related content

C# SDK Sample: Customer Credit Cards
C# SDK Sample: Customer Credit Cards
More like this
C# SDK Sample: Initializing Routine
C# SDK Sample: Initializing Routine
More like this
Obtain CyberSource Security Key
Obtain CyberSource Security Key
More like this
Authorize.net integration
Authorize.net integration
More like this
UltraCart Hosted Credit Card Fields
UltraCart Hosted Credit Card Fields
More like this
Collecting Credit Card Numbers with Rest API
Collecting Credit Card Numbers with Rest API
More like this