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; // } } }