Hi,
I am trying to implement a new payment system for my largest client who are hoping to move to CyberSource as a payment gateway.
I have previously implemented payments thru WorldPay & Protx/SagePay by ‘simulating’ a web page doing an HTTPS POST, but CyberSource create a unique include file that holds the clients ‘keys’ for security and the include file can be generated in PHP, JSP, ASP, VB or CS (C#).
I thought that trying to ‘translate’ the JSP into JavaScript might be the easiest as I know there is some access to some Java stuff in Servoy (I do not know PHP, JSP, ASP, VB or C#), but I am getting stuck and was hoping someone here might be able to help.
Part of the include file calls some SHA1 functions to encrypt data etc. and I found a JavaScript module that seemed to do all of that
/**
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*
*/
var hexcase = 0;
/**
* hex output format. 0 - lowercase; 1 - uppercase
*
*/
var b64pad = "";
/**
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*
*/
function hex_sha1 ( s )
{
return rstr2hex ( rstr_sha1 ( str2rstr_utf8 ( s ) ) );
}
function b64_sha1 ( s )
{
return rstr2b64 ( rstr_sha1 ( str2rstr_utf8 ( s ) ) );
}
function any_sha1 ( s, e )
{
return rstr2any ( rstr_sha1 ( str2rstr_utf8 ( s ) ), e );
}
function hex_hmac_sha1 ( k, d )
{
return rstr2hex ( rstr_hmac_sha1 ( str2rstr_utf8 ( k ), str2rstr_utf8 ( d ) ) );
}
function b64_hmac_sha1 ( k, d )
{
return rstr2b64 ( rstr_hmac_sha1 ( str2rstr_utf8 ( k ), str2rstr_utf8 ( d ) ) );
}
function any_hmac_sha1 ( k, d, e )
{
return rstr2any ( rstr_hmac_sha1 ( str2rstr_utf8 ( k ), str2rstr_utf8 ( d ) ), e );
}
/**
* Perform a simple self-test to see if the VM is working
*
*/
function sha1_vm_test ()
{
return hex_sha1 ( "abc" ).toLowerCase ( ) == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/**
* Calculate the SHA1 of a raw string
*
*/
function rstr_sha1 ( s )
{
return binb2rstr ( binb_sha1 ( rstr2binb ( s ), s.length * 8 ) );
}
/**
* Calculate the HMAC-SHA1 of a key and some data (raw strings)
*
*/
function rstr_hmac_sha1 ( key, data )
{
var bkey = rstr2binb ( key );
if ( bkey.length > 16 ) bkey = binb_sha1 ( bkey, key.length * 8 );
var ipad = Array ( 16 ), opad = Array ( 16 );
for ( var i = 0; i < 16; i++ )
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binb_sha1 ( ipad.concat ( rstr2binb ( data ) ), 512 + data.length * 8 );
return binb2rstr ( binb_sha1 ( opad.concat ( hash ), 512 + 160 ) );
}
/**
* Convert a raw string to a hex string
*
*/
function rstr2hex ( input )
{
try
{
hexcase
}
catch ( e )
{
hexcase = 0;
}
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for ( var i = 0; i < input.length; i++ )
{
x = input.charCodeAt ( i );
output += hex_tab.charAt ( ( x >>> 4 ) & 0x0F )
+ hex_tab.charAt ( x & 0x0F );
}
return output;
}
/**
* Convert a raw string to a base-64 string
*
*/
function rstr2b64 ( input )
{
try
{
b64pad
}
catch ( e )
{
b64pad = '';
}
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for ( var i = 0; i < len; i += 3 )
{
var triplet = ( input.charCodeAt ( i ) << 16 )
| ( i + 1 < len ? input.charCodeAt ( i + 1 ) << 8 : 0 )
| ( i + 2 < len ? input.charCodeAt ( i + 2 ) : 0 );
for ( var j = 0; j < 4; j++ )
{
if ( i * 8 + j * 6 > input.length * 8 ) output += b64pad;
else output += tab.charAt ( ( triplet >>> 6 * ( 3 - j ) ) & 0x3F );
}
}
return output;
}
/**
* Convert a raw string to an arbitrary string encoding
*
*/
function rstr2any ( input, encoding )
{
var divisor = encoding.length;
var remainders = Array ( );
var i, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array ( Math.ceil ( input.length / 2 ) );
for ( i = 0; i < dividend.length; i++ )
{
dividend[i] = ( input.charCodeAt ( i * 2 ) << 8 ) | input.charCodeAt ( i * 2 + 1 );
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. We stop when the dividend is zero.
* All remainders are stored for later use.
*/
while ( dividend.length > 0 )
{
quotient = Array ( );
x = 0;
for ( i = 0; i < dividend.length; i++ )
{
x = ( x << 16 ) + dividend[i];
q = Math.floor ( x / divisor );
x -= q * divisor;
if ( quotient.length > 0 || q > 0 )
quotient[quotient.length] = q;
}
remainders[remainders.length] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for ( i = remainders.length - 1; i >= 0; i-- )
output += encoding.charAt ( remainders[i] );
/* Append leading zero equivalents */
var full_length = Math.ceil ( input.length * 8 /
( Math.log ( encoding.length ) / Math.log ( 2 ) ) )
for ( i = output.length; i < full_length; i++ )
output = encoding[0] + output;
return output;
}
/**
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*
*/
function str2rstr_utf8 ( input )
{
var output = "";
var i = -1;
var x, y;
while ( ++i < input.length )
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt ( i );
y = i + 1 < input.length ? input.charCodeAt ( i + 1 ) : 0;
if ( 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF )
{
x = 0x10000 + ( ( x & 0x03FF ) << 10 ) + ( y & 0x03FF );
i++;
}
/* Encode output as utf-8 */
if ( x <= 0x7F )
output += String.fromCharCode ( x );
else if ( x <= 0x7FF )
output += String.fromCharCode ( 0xC0 | ( ( x >>> 6 ) & 0x1F ),
0x80 | ( x & 0x3F ) );
else if ( x <= 0xFFFF )
output += String.fromCharCode ( 0xE0 | ( ( x >>> 12 ) & 0x0F ),
0x80 | ( ( x >>> 6 ) & 0x3F ),
0x80 | ( x & 0x3F ) );
else if ( x <= 0x1FFFFF )
output += String.fromCharCode ( 0xF0 | ( ( x >>> 18 ) & 0x07 ),
0x80 | ( ( x >>> 12 ) & 0x3F ),
0x80 | ( ( x >>> 6 ) & 0x3F ),
0x80 | ( x & 0x3F ) );
}
return output;
}
/**
* Encode a string as utf-16
*
*/
function str2rstr_utf16le ( input )
{
var output = "";
for ( var i = 0; i < input.length; i++ )
output += String.fromCharCode ( input.charCodeAt ( i ) & 0xFF,
( input.charCodeAt ( i ) >>> 8 ) & 0xFF );
return output;
}
function str2rstr_utf16be ( input )
{
var output = "";
for ( var i = 0; i < input.length; i++ )
output += String.fromCharCode ( ( input.charCodeAt ( i ) >>> 8 ) & 0xFF,
input.charCodeAt ( i ) & 0xFF );
return output;
}
/**
* Convert a raw string to an array of big-endian words
* Characters >255 have their high-byte silently ignored.
*
*/
function rstr2binb ( input )
{
var output = Array ( input.length >> 2 );
for ( var i = 0; i < output.length; i++ )
output[i] = 0;
for ( var i = 0; i < input.length * 8; i += 8 )
output[i >> 5] |= ( input.charCodeAt ( i / 8 ) & 0xFF ) << ( 24 - i % 32 );
return output;
}
/**
* Convert an array of big-endian words to a string
*
*/
function binb2rstr ( input )
{
var output = "";
for ( var i = 0; i < input.length * 32; i += 8 )
output += String.fromCharCode ( ( input[i >> 5] >>> ( 24 - i % 32 ) ) & 0xFF );
return output;
}
/**
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*
*/
function binb_sha1 ( x, len )
{
/* append padding */
x[len >> 5] |= 0x80 << ( 24 - len % 32 );
x[ ( ( len + 64 >> 9 ) << 4 ) + 15] = len;
var w = Array ( 80 );
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for ( var i = 0; i < x.length; i += 16 )
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for ( var j = 0; j < 80; j++ )
{
if ( j < 16 ) w[j] = x[i + j];
else w[j] = bit_rol ( w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1 );
var t = safe_add ( safe_add ( bit_rol ( a, 5 ), sha1_ft ( j, b, c, d ) ),
safe_add ( safe_add ( e, w[j] ), sha1_kt ( j ) ) );
e = d;
d = c;
c = bit_rol ( b, 30 );
b = a;
a = t;
}
a = safe_add ( a, olda );
b = safe_add ( b, oldb );
c = safe_add ( c, oldc );
d = safe_add ( d, oldd );
e = safe_add ( e, olde );
}
return Array ( a, b, c, d, e );
}
/**
* Perform the appropriate triplet combination function for the current
* iteration
*
*/
function sha1_ft ( t, b, c, d )
{
if ( t < 20 ) return ( b & c ) | ( ( ~b ) & d );
if ( t < 40 ) return b ^ c ^ d;
if ( t < 60 ) return ( b & c ) | ( b & d ) | ( c & d );
return b ^ c ^ d;
}
/**
* Determine the appropriate additive constant for the current iteration
*
*/
function sha1_kt ( t )
{
return ( t < 20 ) ? 1518500249 : ( t < 40 ) ? 1859775393 :
( t < 60 ) ? -1894007588 : -899497514;
}
/**
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*
*/
function safe_add ( x, y )
{
var lsw = ( x & 0xFFFF ) + ( y & 0xFFFF );
var msw = ( x >> 16 ) + ( y >> 16 ) + ( lsw >> 16 );
return ( msw << 16 ) | ( lsw & 0xFFFF );
}
/**
* Bitwise rotate a 32-bit number to the left.
*
*/
function bit_rol ( num, cnt )
{
return ( num << cnt ) | ( num >>> ( 32 - cnt ) );
}
but now I need help converting this (HOP.JSP)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page import="sun.misc.BASE64Encoder, javax.crypto.*, javax.crypto.spec.*"%>
<%@ page import="java.util.*" %>
<%!
private String getMerchantID() {
return "merchantid";
}
private String getSharedSecret() {
return "longsharedsectretstingwithlotsofhex;
}
private String getSerialNumber() {
return "1234567890";
}
public String getPublicDigest(String customValues) throws Exception{
String pub = getSharedSecret();
BASE64Encoder encoder = new BASE64Encoder();
Mac sha1Mac = Mac.getInstance("HmacSHA1");
SecretKeySpec publicKeySpec = new SecretKeySpec(pub.getBytes(), "HmacSHA1");
sha1Mac.init(publicKeySpec);
byte[] publicBytes = sha1Mac.doFinal(customValues.getBytes());
String publicDigest = encoder.encodeBuffer(publicBytes);
return publicDigest.replaceAll("\n", "");
}
/**
* @param map - Map containing fields that are to be signed.
* Can only contain fields and values that should not be changed.
* At the very minimum, map should contain 'amount', 'currency', and 'orderPage_transactionType'
* if 'orderPage_transactionType' is 'subscription' or 'subscription_modify', the following are also required:
* 'recurringSubscriptionInfo_amount', 'recurringSubscriptionInfo_numberOfPayments', 'recurringSubscriptionInfo_frequency',
* 'recurringSubscriptionInfo_startDate', 'recurringSubscriptionInfo_automaticRenew'
* if 'orderPage_transactionType' is 'subscription_modify' then 'paySubscriptionCreateReply_subscriptionID' is also required
* @return html of hidden fields
*/
public String insertSignature(Map map) {
if (map == null) {
return "";
}
try {
map.put("merchantID", getMerchantID());
map.put("orderPage_timestamp", String.valueOf(System.currentTimeMillis()));
map.put("orderPage_version", "7");
map.put("orderPage_serialNumber", getSerialNumber());
Set keys = map.keySet();
StringBuffer customFields = new StringBuffer();
StringBuffer dataToSign = new StringBuffer();
StringBuffer output = new StringBuffer();
for (Iterator i = keys.iterator(); i.hasNext();) {
String key = (String) i.next();
customFields.append(key);
dataToSign.append(key + "=" + String.valueOf(map.get(key)));
if (i.hasNext()) {
customFields.append(',');
dataToSign.append(',');
}
output.append("<input type=\"hidden\" name=\"");
output.append(key);
output.append("\" value=\"");
output.append(String.valueOf(map.get(key)));
output.append("\">\n");
}
if(customFields.length() > 0) {
dataToSign.append(',');
}
dataToSign.append("signedFieldsPublicSignature=");
dataToSign.append(getPublicDigest(customFields.toString()).trim());
output.append("<input type=\"hidden\" name=\"orderPage_signaturePublic\" value=\"" + getPublicDigest(dataToSign.toString()) + "\">\n");
output.append("<input type=\"hidden\" name=\"orderPage_signedFields\" value=\"" + customFields.toString() + "\">\n");
return output.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public String insertSignature(String amount, String currency) {
try {
if (amount == null) {
amount = "0.00";
}
if (currency == null) {
currency = "usd";
}
String time = String.valueOf(System.currentTimeMillis());
String merchantID = getMerchantID();
String data = merchantID + amount + currency + time;
String serialNumber = getSerialNumber();
StringBuffer sb = new StringBuffer();
sb.append("<input type=\"hidden\" name=\"amount\" value=\"");
sb.append(amount);
sb.append("\">\n<input type=\"hidden\" name=\"currency\" value=\"");
sb.append(currency);
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_timestamp\" value=\"");
sb.append(time);
sb.append("\">\n<input type=\"hidden\" name=\"merchantID\" value=\"");
sb.append(merchantID);
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_signaturePublic\" value=\"");
sb.append(getPublicDigest(data));
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_version\" value=\"7\">\n");
sb.append("<input type=\"hidden\" name=\"orderPage_serialNumber\" value=\"");
sb.append(serialNumber);
sb.append("\">\n");
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public String insertSignature(String amount, String currency, String orderPage_transactionType) {
try {
if (amount == null) {
amount = "0.00";
}
if (currency == null) {
currency = "usd";
}
String time = String.valueOf(System.currentTimeMillis());
String merchantID = getMerchantID();
String data = merchantID + amount + currency + time + orderPage_transactionType;
String serialNumber = getSerialNumber();
StringBuffer sb = new StringBuffer();
sb.append("<input type=\"hidden\" name=\"amount\" value=\"");
sb.append(amount);
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_transactionType\" value=\"");
sb.append(orderPage_transactionType);
sb.append("\">\n<input type=\"hidden\" name=\"currency\" value=\"");
sb.append(currency);
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_timestamp\" value=\"");
sb.append(time);
sb.append("\">\n<input type=\"hidden\" name=\"merchantID\" value=\"");
sb.append(merchantID);
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_signaturePublic\" value=\"");
sb.append(getPublicDigest(data));
sb.append("\">\n<input type=\"hidden\" name=\"orderPage_version\" value=\"7\">\n");
sb.append("<input type=\"hidden\" name=\"orderPage_serialNumber\" value=\"");
sb.append(serialNumber);
sb.append("\">\n");
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public String insertSubscriptionSignature(String subscriptionAmount, String subscriptionStartDate, String subscriptionFrequency,
String subscriptionNumberOfPayments, String subscriptionAutomaticRenew) {
if (subscriptionFrequency == null) {
return "";
}
if (subscriptionAmount == null) {
subscriptionAmount = "0.00";
}
if (subscriptionStartDate == null) {
subscriptionStartDate = "00000000";
}
if (subscriptionNumberOfPayments == null) {
subscriptionNumberOfPayments = "0";
}
if (subscriptionAutomaticRenew == null) {
subscriptionAutomaticRenew = "true";
}
try {
String data = subscriptionAmount + subscriptionStartDate + subscriptionFrequency + subscriptionNumberOfPayments + subscriptionAutomaticRenew;
StringBuffer sb = new StringBuffer();
sb.append("<input type=\"hidden\" name=\"recurringSubscriptionInfo_amount\" value=\"");
sb.append(subscriptionAmount);
sb.append("\">\n<input type=\"hidden\" name=\"recurringSubscriptionInfo_numberOfPayments\" value=\"");
sb.append(subscriptionNumberOfPayments);
sb.append("\">\n<input type=\"hidden\" name=\"recurringSubscriptionInfo_frequency\" value=\"");
sb.append(subscriptionFrequency);
sb.append("\">\n<input type=\"hidden\" name=\"recurringSubscriptionInfo_automaticRenew\" value=\"");
sb.append(subscriptionAutomaticRenew);
sb.append("\">\n<input type=\"hidden\" name=\"recurringSubscriptionInfo_startDate\" value=\"");
sb.append(subscriptionStartDate);
sb.append("\">\n<input type=\"hidden\" name=\"recurringSubscriptionInfo_signaturePublic\" value=\"");
sb.append(getPublicDigest(data));
sb.append("\">\n");
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public String insertSubscriptionIDSignature(String subscriptionID) {
if (subscriptionID == null) {
return "";
}
try {
StringBuffer sb = new StringBuffer();
sb.append("<input type=\"hidden\" name=\"paySubscriptionCreateReply_subscriptionID\" value=\"");
sb.append(subscriptionID);
sb.append("\">\n<input type=\"hidden\" name=\"paySubscriptionCreateReply_subscriptionIDPublicSignature\" value=\"");
sb.append(getPublicDigest(subscriptionID));
sb.append("\">\n");
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public boolean verifySignature(String data, String signature) {
if (data == null || signature == null) {
return false;
}
try {
String pub = getSharedSecret();
BASE64Encoder encoder = new BASE64Encoder();
Mac sha1Mac = Mac.getInstance("HmacSHA1");
SecretKeySpec publicKeySpec = new SecretKeySpec(pub.getBytes(), "HmacSHA1");
sha1Mac.init(publicKeySpec);
byte[] publicBytes = sha1Mac.doFinal(data.getBytes());
String publicDigest = encoder.encodeBuffer(publicBytes);
publicDigest = publicDigest.replaceAll("[\r\n\t]", "");
return signature.equals(publicDigest);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean verifyTransactionSignature(Map map) {
if (map == null) {
return false;
}
String transactionSignature = (String) map.get("signedDataPublicSignature");
if (transactionSignature == null) {
return false;
}
String transactionSignatureFields = (String) map.get("signedFields");
if (transactionSignatureFields == null) {
return false;
}
StringTokenizer tokenizer = new StringTokenizer(transactionSignatureFields, ",", false);
StringBuffer data = new StringBuffer();
while (tokenizer.hasMoreTokens()) {
String key = tokenizer.nextToken();
data.append(key + "=" + map.get(key));
data.append(',');
}
data.append("signedFieldsPublicSignature=");
try{
data.append(getPublicDigest(transactionSignatureFields).trim());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return verifySignature(data.toString(), transactionSignature);
}
%>
the first bits (get…) are easy to make into Servoy functions and the import of the sun and java stuff should be the SHA1 module I made above, it’s the rest that’s the problem
Any pointers as to what I can do (or a full translation ) would be GREATLY appreciated.
(If the HOP include module in any of the other languages would be better for someone, let me know…)
Thanks
Rafi