ColdFusion oAuth RSA-SHA1 authorisation for Xero wrapper - coldfusion

I'm writing a wrapper for the Xero API, using oAuth and two-legged authentication. It is a "private" application, as Xero calls it, which requires a RSA-SHA1 signature. The Coldfusion oAuth wrapper doesn't have a function for encrypting in RSA-SHA1, only HMAC-SHA1. We are on CF9.
Consequently, in the course of running a GET request, I get the following error:
signature_method_rejected
Private applications must use the RSA-SHA1 signature method
So, it looks like the GET call is working, but the issue is with the signature method. I found what I thought looked like the solution someone created, as follows:
<cffunction name="rsa_sha1" returntype="string" access="public" descrition="RSA-SHA1 computation based on supplied private key and supplied base signature string.">
<cfargument name="signKey" type="string" required="true" hint="base64 formatted PKCS8 private key">
<cfargument name="signMessage" type="string" required="true" hint="msg to sign">
<cfargument name="sFormat" type="string" required="false" default="UTF-8">
<cfset var jKey = JavaCast("string", arguments.signKey)>
<cfset var jMsg = JavaCast("string",arguments.signMessage).getBytes(arguments.sFormat)>
<cfset var key = createObject("java", "java.security.PrivateKey")>
<cfset var keySpec = createObject("java","java.security.spec.PKCS8EncodedKeySpec")>
<cfset var keyFactory = createObject("java","java.security.KeyFactory")>
<cfset var b64dec = createObject("java", "sun.misc.BASE64Decoder")>
<cfset var sig = createObject("java", "java.security.Signature")>
<cfset var byteClass = createObject("java", "java.lang.Class")>
<cfset var byteArray = createObject("java","java.lang.reflect.Array")>
<cfset byteClass = byteClass.forName(JavaCast("string","java.lang.Byte"))>
<cfset keyBytes = byteArray.newInstance(byteClass, JavaCast("int","1024"))>
<cfset keyBytes = b64dec.decodeBuffer(jKey)>
<!--- keyBytes = 48-111-10345-125-5349-114-581835-28-330-3984120-2848-4384-1-43 --->
<cfset sig = sig.getInstance("SHA1withRSA", "SunJSSE")>
<!--- error occurs on the line below --->
<cfset sig.initSign(keyFactory.getInstance("RSA").generatePrivate(keySpec.init(keyBytes)))>
<cfset sig.update(jMsg)>
<cfset signBytes = sig.sign()>
<cfreturn ToBase64(signBytes)>
</cffunction>
It receives the following arguments:
SFORMAT UTF-8
SIGNKEY 0JxxxxxxxxxxxxxxxxxxxP&
SIGNMESSAGE GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FContacts&contactid%3D%26contactnumber%3D%26name%3D7-Eleven%26oauth_consumer_key%3Dxxxxxxxxxxxxxxxx%26oauth_nonce%3Dxxxxxxxxxxxxxxxx%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1339055484%26oauth_version%3D1.0
However, this produces the following error:
Could not read BER data.(ASN1Lengths.determineLengthLen: length greater than 0x7FFF,FFFF.)
ColdFusion cannot determine the line of the template that caused this error. This is often caused by an error in the exception handling subsystem.
Can anyone could shed any light on this?
EDIT
----
There is a link for uploading the public cert, which I did. THere is also a note saying: "Note, For Private applications, the consumer token and secret are also used as the access token and secret.". So I am assuming I need the "Consumer Secret" value shown there in order to sign the request. That being the case, how do I convert that secret key value to RSA-SH1 format for the signature?

(Summary from the comments)
According to their API private applications must generate an RSA public/private pair (one time event). You then upload your public certificate to their server, and use the private key to sign all requests using RSA-SHA1. If you followed the instructions in their link your private key will be in PEM format, which is just the key value encoded in base64, enclosed within a BEGIN/END wrapper:
-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
....
-----END RSA PRIVATE KEY-----
You need to extract the portion between the wrapper before using the key with PKCS8EncodedKeySpec. There are at least three ways to do that. The simplest option is to use string functions:
// read in the key file and remove the wrapper
pathToKey = "c:/path/to/file/privateKey.pem";
rawKey = FileRead( pathToKey );
rawKey = replace( rawKey, "-----BEGIN RSA PRIVATE KEY-----"& chr(10), "" );
rawKey = replace( rawKey, "-----END RSA PRIVATE KEY-----", "" );
yourMessage = "GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2F...";
signature = rsa_sha1( rawKey, yourMessage, "utf-8" );
Another option is to use the BouncyCastle PEMReader class. It does it all for you (and also supports password protected keys).
pathToKey = "c:/path/to/file/privateKey.pem";
provider = createObject("java", "org.bouncycastle.jce.provider.BouncyCastleProvider").init();
security = createObject("java", "java.security.Security").addProvider( provider );
fileReader = createObject("java", "java.io.FileReader").init( pathToKey );
keyReader = createObject("java", "org.bouncycastle.openssl.PEMReader").init( fileReader);
privateKey = keyReader.readObject().getPrivate();
rawKey = binaryEncode( privateKey.getEncoded(), "base64" );
yourMessage = "GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FContacts&....";
signature = rsa_sha1( rawKey, yourMessage, "utf-8" );
Yet another option is to convert the key to DER format with openssl
$ openssl pkcs8 -topk8 -in privateKey.pem -outform DER -nocrypt -out privateKey.pk8
pathToKey = "c:/path/to/file/privateKey.pk8";
bytes = binaryEncode( fileReadBinary(pathToKey), "base64");
yourMessage = "GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2F...";
signature = rsa_sha1(rawKey, testMessage, "utf-8");
Note If you posted your actual private key here (or on another forum), it is compromised. So I would strongly recommend generating new keys.

Related

Coinbase Pro API Invalid Signature ColdFusion

I'm trying to get simple account info from Coinbase Pro and can't seem to figure out the CB-ACCESS-SIGN header. I keep getting an "invalid signature" message.
From Coinbase Pro API Docs:
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using
the base64-decoded secret key on the prehash string timestamp + method
requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the
CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request
body (typically for GET requests).
The method should be UPPER CASE.
My Code:
<cfscript>
accessKey = 'xxx';
passPhrase = 'yyy';
theSecret = 'zzz';
// create an epoch time stamp
theTime = now();
utcTime = dateConvert('local2UTC',theTime);
ISOtimeStamp = dateFormat(utcTime,"yyyy-mm-dd")&"T"&timeFormat(utcTime,"HH:mm:ss")&".761Z";
timeStamp = numberFormat(dateDiff("s","January 1 1970 00:00",ISOtimeStamp) + 28800.761,"_.000");
requestPath = '/accounts';
theBody = "";
method = "GET";
// create the prehash string by concatenating required parts
what = '#timeStamp#' & '#method#' & '#requestPath#' & '#theBody#';
// decode the base64 secret
theKey = binaryDecode(theSecret,"base64");
// create s sha256 hmac with the key
theHmac = hmac("#what#","#theKey#", "HMACSHA256");
// encode the result
cas = toBase64(theHmac);
</cfscript>
<cfhttp url="https://api.pro.coinbase.com/v2/accounts" method="get" result="y">
<cfhttpparam type="header" name="CB-ACCESS-KEY" value="#accessKey#" >
<cfhttpparam type="header" name="CB-ACCESS-SIGN" value="#ucase(cas)#" >
<cfhttpparam type="header" name="CB-ACCESS-TIMESTAMP" value="#timeStamp#" >
<cfhttpparam type="header" name="CB-ACCESS-PASSPHRASE" value="#passPhrase#" >
</cfhttp>

api authentication failing as trying with hash

I am trying to match the api signatures with the following as:
API-Sign = Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key
my code as of now
<cfset x = createobject("java","java.lang.System").currentTimeMillis()>
<cfset sts = "https://api.exmaple.com/records" & hash(x, "SHA-512", "UTF-8")>
<cfset sig = tobase64(hmac(sts, "#secret_key#", "HMACSHA256"))>
but something is wrong, unable to validate, getting internal error

What is the specific structure of variables for submitting a document to EchoSign using ColdFusion?

I have been working on this for several days now and cannot figure it out. I am trying to create the data structure necessary to send a WSDL webservice request to EchoSign using ColdFusion. I have no problem with sending simple structures (testPing and testEchoFile work), but when I try nested structures like those required for sending a document (sendDocument) it fails saying "Web service operation sendDocument with parameters ... cannot be found. Here is one of the many attempts:
<cfset strFilePath = "#ExpandPath("tempupload/file.pdf")#" />
<cffile action="readbinary" file="#strFilePath#" variable="FileData" >
<cfscript>
apiKey = "MY_API_KEY";
documentCreationInfo = structNew();
documentCreationInfo.recipients = structNew();
documentCreationInfo.recipients.recipientInfo = arrayNew(1);
documentCreationInfo.recipients.recipientInfo[1] = structNew();
documentCreationInfo.recipients.recipientInfo[1].email = "myemail#gmail.com";
documentCreationInfo.recipients.recipientInfo[1].role = "SIGNER";
documentCreationInfo.name = "test";
documentCreationInfo.fileInfos = structNew();
documentCreationInfo.fileInfos.fileInfo = arrayNew(1);
documentCreationInfo.fileInfos.fileInfo[1] = structNew();
documentCreationInfo.fileInfos.fileInfo[1].fileName = "file.pdf";
documentCreationInfo.fileInfos.fileInfo[1].file = #FileData#;
documentCreationInfo.signatureType = "ESIGN";
documentCreationInfo.signatureFlow = "SENDER_SIGNATURE_NOT_REQUIRED";
ws = createObject("webservice", "https://secure.echosign.com/services/EchoSignDocumentService16?wsdl");
response = ws.sendDocument(apiKey = '#apiKey#', documentCreationInfo = #documentCreationInfo#);
</cfscript>
I have looked at examples using other programming languages, but that was not much help as it seems that the arrays and structures created by ColdFusion are handled differently. I want to avoid having to construct the SOAP XML. Any help would be greatly appreciated.
For clarity, here is the structure of the data that needs to be sent when using ColdFusion and communicating with EchoSign and using the sendDocument command:
<cfset strFilePath = "#ExpandPath("tempupload/file.pdf")#" />
<cffile action="readbinary" file="#strFilePath#" variable="FileData" >
<cfscript>
apiKey = "MY_API_KEY";
documentCreationInfo = structNew();
documentCreationInfo.recipients = structNew();
documentCreationInfo.recipients.recipientInfo = arrayNew(1);
documentCreationInfo.recipients.recipientInfo[1] = structNew();
documentCreationInfo.recipients.recipientInfo[1].email = "recipient#gmail.com";
documentCreationInfo.recipients.recipientInfo[1].role = "SIGNER";
documentCreationInfo.name = "test";
documentCreationInfo.fileInfos = structNew();
documentCreationInfo.fileInfos.fileInfo = arrayNew(1);
documentCreationInfo.fileInfos.fileInfo[1] = structNew();
documentCreationInfo.fileInfos.fileInfo[1].fileName = "file.pdf";
documentCreationInfo.fileInfos.fileInfo[1].file = #FileData#;
documentCreationInfo.signatureType = "ESIGN";
documentCreationInfo.signatureFlow = "SENDER_SIGNATURE_NOT_REQUIRED";
senderInfo = structNew();
senderInfo.email = "sender#gmail.com";
senderInfo.password = "password";
senderInfo.userKey = "";
ws = createObject("webservice", "https://secure.echosign.com/services/EchoSignDocumentService16?wsdl");
response = ws.sendDocument(apiKey = '#apiKey#', senderInfo = #senderInfo#, documentCreationInfo = #documentCreationInfo#);
</cfscript>
For data that can contain multiple entries, like recipients and files, the data is stored in arrays of structures. In this example, there is only one recipient, so the array recipientInfo only has one array element, recipientInfo[1], but we could easily add additional recipients just by repeating this section and using recipientInfo[2], etc. Since senderInfo cannot have multiple entries it is just a struct with elements email, password and userKey. According to the documentation, userKey will override email and password entries. To use Single Sign-On, I tried sending blank data for email, password and userKey but that did not work, I also tried just sending an empty senderInfo structure and that did not work. So my question becomes, how do I send a NULL value for senderInfo so that it defaults to the API owner?
After having a look at the sendDocument method of the web service in question, it appears as though you also need to include the senderInfo parameter, which is defined in the WSDL as:
<xsd:complexType name="SenderInfo">
<xsd:sequence>
<xsd:element minOccurs="0" name="email" nillable="true" type="xsd:string"/>
<xsd:element minOccurs="0" name="password" nillable="true" type="xsd:string"/>
<xsd:element minOccurs="0" name="userKey" nillable="true" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
So, after you create an instance of senderInfo (which could just be an empty struct), your web service call will look like this:
response = ws.sendDocument(apiKey = '#apiKey#', documentCreationInfo = documentCreationInfo, senderInfo = senderInfo);
Hope that helps!

Login with gmail account in Coldfusion

I want to login with gmail/google account and I found this tutorial Gmail Login in Coldfusion. I done All the steps and After login my page redirect then I want to display user Profile information so I dump this
<cfdump var="#session.profilesArray#">
but it gives me an empty array.why I am not getting my profile data after successfully lo-gin.
If I am getting wrong way for fetching my profile then what is correct way. Thanks.
You just add this line into your scope
Open your Application.cfc and then add this code
change scope = "https://www.googleapis.com/auth/analytics.readonly" with scope = "https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile
you can just add scope = "https://www.googleapis.com/auth/userinfo.profile but if u want to access email then add second one as I Post in my answer.
<cfset request.oauthSettings =
{scope = "https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile",
client_id = "Your-id",
client_secret = "your-secret",
redirect_uri = "redirect-page",
state = "optional"} />
Now you can get User Information from function that you can call like this
<cfscript>
public function getProfile(accesstoken) {
var h = new com.adobe.coldfusion.http();
h.setURL("https://www.googleapis.com/oauth2/v1/userinfo");
h.setMethod("get");
h.addParam(type="header",name="Authorization",value="OAuth #accesstoken#");
h.addParam(type="header",name="GData-Version",value="3");
h.setResolveURL(true);
var result = h.send().getPrefix();
return deserializeJSON(result.filecontent.toString());
}
</cfscript>
<cfoutput>
<cfset show = getProfile(session.ga_accessToken)>
<cfdump var="#show#">
</cfoutput>
Hope this will help you.

Issues with OAuth Authentication in LinkedIn API

I'm working on a LinkedIn library for a client of mine, and am having some trouble getting through the authorization piece of the puzzle. I used the OAuth library on RIAForge and rewrote everything in order to utilize script based CFCs in CF9 and MXUnit testing. When I try to create the OAuth signature using the HMAC-SHA1 algorithm, I can never seem to match what LinkedIn is looking for. He is my method to sign the request I have:
public void function signRequest(any req){
var params = Arguments.req.getAllParameters();
var secret = "#Variables.encoder.parameterEncodedFormat(getConsumer().getConsumerSecret())#&#Variables.encoder.parameterEncodedFormat(Arguments.req.getOAuthSecret())#";
var base = '';
params = Variables.encoder.encodedParameter(params, true, true);
secret = JavaCast('string', secret).getBytes();
local.mac = createObject('java', 'javax.crypto.Mac').getInstance('HmacSHA1');
local.key = createObject('java', 'javax.crypto.spec.SecretKeySpec').init(secret, local.mac.getAlgorithm());
base = reReplaceNoCase(Arguments.req.getRequestUrl(), 'http[s]?://', '/');
params = listSort(params, 'text', 'asc', '&');
base = JavaCast('string', "#base#&#params#").getBytes();
local.mac.init(local.key);
local.mac.update(base);
Arguments.req.addParameter('oauth_signature', toBase64(mac.doFinal()), true);
}
The issue, I believe is in the secret key for the encryption. I have compared the base string to the OAuth testing tool from LinkedIn, http://developer.linkedinlabs.com/oauth-test/, and it matches perfectly, so the key used to encrypt it must me the problem. I don't have the OAuth token secret yet, so my secret is something similar to fdsa43fdsa3j&. Is that what it should be, or should the ampersand at the end be in encoded format, or something else?
Correct Method
public void function signRequest(any req){
var params = Arguments.req.getAllParameters();
var secret = "#Variables.encoder.parameterEncodedFormat(getConsumer().getConsumerSecret())#&#Variables.encoder.parameterEncodedFormat(Arguments.req.getOAuthSecret())#";
var base = '';
params = Variables.encoder.encodedParameter(params, true, true);
secret = toBinary(toBase64(secret));
local.mac = createObject('java', 'javax.crypto.Mac').getInstance('HmacSHA1');
local.key = createObject('java', 'javax.crypto.spec.SecretKeySpec').init(secret, local.mac.getAlgorithm());
base = "#Arguments.req.getMethod()#&";
base = base & Variables.encoder.parameterEncodedFormat(Arguments.req.getRequestUrl());
params = listSort(params, 'text', 'asc', '&');
base = "#base#&#Variables.encoder.parameterEncodedFormat(params)#";
local.mac.init(local.key);
local.mac.update(JavaCast('string', base).getBytes());
//writeDump(toString(toBase64(mac.doFinal()))); abort;
Arguments.req.addParameter('oauth_signature', toString(toBase64(mac.doFinal())), true);
}
Ben Nadel has an example for using OAuth for connecting to Twilio. The major difference between your signing code and his is that he uses some encodings when setting up his SecretKeySpec.
Here is the relevant snip from his post:
<cfset secretKeySpec = createObject(
"java",
"javax.crypto.spec.SecretKeySpec"
).init(
toBinary( toBase64( twilioAuthKey ) ),
"HmacSHA1"
)
/>