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;
// }
}
}
, multiple selections available,