Coldfusion CFHTTP with SHA512-hmac signed REST request body - api

I am trying to make a signed request to the trading API at bitfloor.com (it's a REST API)
Bitfloor gives me:
1) API Key (i.e. 6bd2b780-00be-11e2-bde3-2837371c3c3a)
2) Secret Key (i.e. oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==)
The following is Bitfloor's exact instructions for making the request:
Requests must be HTTPS POST requests on port 443 (https). Each request must contain the required headers (listed below). The headers identify, verify, and validate your request to prevent tampering.
headers
bitfloor-key This is the provided by bitfloor to uniquely identify your account. (i.e. 6bd2b780-00be-11e2-bde3-2837371c3c3a)
bitfloor-sign The sign field is a sha512-hmac of the request body using the secret key which corresponds to your api key.
To sign your request: base64 decode the secret key into the raw bytes (64 bytes). Use those bytes for your sha512-hmac signing of the http request body. Base64 encode the signing result and send in this header field.
bitfloor-passphrase The passphrase you specified when creating this api key. We cannot recover your passphrase if forgotten. You will need to create a new API key.
bitfloor-version The api version of the resource you are interested in. The only valid value currently is 1
After a full eight hours of trial and error and searching the internet repeatedly for any sort of insight or information, the following code is as close as I can come to what I think might be somewhere in the direction of how to construct the request properly, alas, no matter what I attmept I get "Invalid Signature" returned by their API.
Here is what I have so far...
FIRST, I found this function on the web that someone wrote to do the SHA512 signing:
<cffunction name="HMAC_SHA512" returntype="binary" access="public" output="false">
<cfargument name="signKey" type="string" required="true">
<cfargument name="signMessage" type="string" required="true">
<cfset var jMsg = JavaCast("string",arguments.signMessage).getBytes("iso-8859-1")>
<cfset var jKey = JavaCast("string",arguments.signKey).getBytes("iso-8859-1")>
<cfset var key = createObject("java","javax.crypto.spec.SecretKeySpec")>
<cfset var mac = createObject("java","javax.crypto.Mac")>
<cfset key = key.init(jKey,"HmacSHA512")>
<cfset mac = mac.getInstance(key.getAlgorithm())>
<cfset mac.init(key)>
<cfset mac.update(jMsg)>
<cfreturn mac.doFinal()>
</cffunction>
I have no idea what it does, but it seems to work and does so without error.
Here is my implementation of this function and my attempt at making the request:
NOTE: The "nonce" value is a required param that must be sent with the request.
<cffunction name="myorders">
<cfset nonce = dateDiff("s",createDateTime(2012,01,01,0,0,0),now())>
<cfset requestbody = "?nonce=#nonce#">
<cfset key = "oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==">
<cfset sign = HMAC_SHA512(key,requestbody)>
<cfset signed = binaryEncode(sign,"Base64")>
<!--- HTTP REQUEST --->
<cfhttp url = "https://api.bitfloor.com/orders#requestbody#"
method = "post"
result = "bitfloor">
<!--- HEADERS --->
<cfhttpparam
type = "body"
value = requestbody>
<cfhttpparam
type = "header"
name = "bitfloor-key"
value = "6bd2b780-00be-11e2-bde3-2837371c3c3a">
<cfhttpparam
type = "header"
name = "bitfloor-sign"
value = signed>
<cfhttpparam
type = "header"
name = "bitfloor-passphrase"
value = "mysecretpassphrase">
<cfhttpparam
type = "header"
name = "bitfloor-version"
value = "1">
</cfhttp>
</cffunction>
I think most of my confusion comes from not knowing exactly what the "request body" is. I feel like I'm not signing the right thing perhaps.
I hope there is a Coldfusion programmer out there who is familiar with signed requests. I'm at my wit's end.
Please help! Namaste

I have not used that api, but I ran some tests and it seems to work with the following tweaks:
Since the secretKey value is base64 encoded, your signing function needs to use binaryDecode to properly extract the bytes. Using String.getBytes(...) produces a completely different (and wrong) result.
The expected request body value is just: nonce=#nonceValue# (without the leading "?")
It seems to require the Content-Type=application/x-www-form-urlencoded header, otherwise it fails to parse the content and the response is: {"error":"no nonce specified"}
Code
<cfset apiKey = "6bd2b780-00be-11e2-bde3-2837371c3c3a">
<cfset secretKey = "oaFz62YpmbWiXwseMUSod53D8pOjdyVcweNYdiab/TSQqxk6IuemDvimNaQoA==">
<cfset passphrase = "your secret phrase">
<cfset requestBody = "nonce="& now().getTime()>
<cfset signBytes = HMAC_SHA512(secretKey, requestbody)>
<cfset signBase64 = binaryEncode(signBytes, "base64")>
<cfhttp url="https://api.bitfloor.com/orders" method="post" port="443" result="bitfloor">
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
<cfhttpparam type="header" name="bitfloor-key" value="#apiKey#">
<cfhttpparam type="header" name="bitfloor-sign" value="#signBase64#">
<cfhttpparam type="header" name="bitfloor-passphrase" value="#passphrase#">
<cfhttpparam type="header" name="bitfloor-version" value="1">
<cfhttpparam type="body" value="#requestBody#">
</cfhttp>
<cfdump var="#bitfloor#" label="Response">
<cffunction name="HMAC_SHA512" returntype="binary" access="public" output="false">
<cfargument name="base64Key" type="string" required="true">
<cfargument name="signMessage" type="string" required="true">
<cfargument name="encoding" type="string" default="UTF-8">
<cfset var messageBytes = JavaCast("string",arguments.signMessage).getBytes(arguments.encoding)>
<cfset var keyBytes = binaryDecode(arguments.base64Key, "base64")>
<cfset var key = createObject("java","javax.crypto.spec.SecretKeySpec")>
<cfset var mac = createObject("java","javax.crypto.Mac")>
<cfset key = key.init(keyBytes,"HmacSHA512")>
<cfset mac = mac.getInstance(key.getAlgorithm())>
<cfset mac.init(key)>
<cfset mac.update(messageBytes)>
<cfreturn mac.doFinal()>
</cffunction>

Related

How to consume API in ColdFusion 10

Can you please help me? I am trying to consume response in my ColdFusion application. Just wanted to try with this fake API before proceeding to the actual one.
I have created a component with two functions inside it. My cfc looks like this:
photoUploadNew.cfc
<cfcomponent displayname="test" hint="testing.." output="yes">
<cfsetting enablecfoutputonly="true" showdebugoutput="true">
<cffunction name="start" access="public" output="no" returntype="any" description="initialize the component">
<cfset variables.testUrl = "https://jsonplaceholder.typicode.com/posts">
<cfreturn this>
</cffunction>
<cffunction access="public" output="false" name="testGetReq" displayname="TestGetReq" description="testing" returntype="any">
<cfset variables.testUrl = "https://jsonplaceholder.typicode.com/posts">
<cfhttp
result="httpResponsetest"
url="#variables.testUrl#"
timeout="30"
method="get"
>
<cfhttpparam
type="header"
name="Content-Type"
value="application/json"
/>
</cfhttp>
</cfhttp>
<cfreturn httpResponsetest>
</cffunction>
</cfcomponent>
In my cfm page. I am trying to instantiate this component and print whatever I am getting as a response but I am not able to print anything out there.
<cfset testObj = CreateObject("component","usedGear_admin.cfc.photoUploadNew").testGetReq()>
<cfoutput >
#testObj#
</cfoutput>
Any help would be greatly appreciated.
I think you are using cfhttp result wrong here. When we do a cfhttp call,
<cfhttp
method="get"
result="httpResponsetest"
url="https://jsonplaceholder.typicode.com/posts"
timeout="30"
>
</cfhttp>
They try the following, you will see httpResponsetest has multiple keys. The data provided by API will be present in httpResponsetest.fileContent. Also most of the time there is Mimetype,Responseheader,Statuscode etc.
<cfdump var="#httpResponsetest.fileContent#">
Here you can see the data is in JSON format. That means you'll need to deserialize them to be able to use it.
<cfdump var="#deserializeJSON(httpResponsetest.fileContent)#">
You can deserialize it and return from the function. Along with that you'll need to handle the case where API responds with am error.
Demo

I am trying to fetch a pdf document from Companies House API

I am trying to fetch a pdf document from API using ColdFusion and I receive this error:
<?xml version="1.0" encoding="UTF-8"?> <Error><Code>InvalidArgument</Code> <Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message><ArgumentName>Authorization</ArgumentName> <ArgumentValue>#key#</ArgumentValue> <RequestId>Some requestid</RequestId> <HostId>some host id</HostId>
400 Bad Request
Here is my code:
<cfset urlD="https://document-api.company-information.service.gov.uk/document/#documentID#/content">
<cfhttp urlD="#Url#" method="GET" result="takeDoc" resolveurl="Yes" timeout="999">
<cfhttpparam type="HEADER" name="Accept" value="application/pdf">
<cfhttpparam type="HEADER" name="content-length" value=93295 />
<cfhttpparam type="HEADER" name="Authorization" value="#key#"/>
</cfhttp>
<cfdump var="#takeDoc#">
Any ideas how to solve this final step?
I appears you are transmitting the value #key# as your Authorization instead of the value for the variable key. try putting cfoutput tags around your cfhttp tags.

Why the unknown error?

This is my error message:
ColdFusion could not delete the file
C:\path\guid.png
for an unknown reason.
I've already checked to make sure my coldFusion user has permission to do so, as suggested here. That is not the problem, the user has all the permissions. Unfortunately, the code is breaking on production servers, and not my own local server (could be relevant). Production is on CF11 but I am on CF9.
Here is the code:
<cffunction name="svgToPDF" access="public" output="false" returntype="string">
<cfargument name="svg" required="true" type="string" />
<cfset var local = {} />
<cfset local.guid = CreateUUID() />
<cfset local.outPDF = ExpandPath(local.guid & '.pdf') />
<cfset local.svg = DeserializeJSON(exportToImage(svg=arguments.svg)) />
<cfif local.svg.error>
<!-- Conversion error -->
<cfset FileDelete(#local.svg.file#) />
<cfreturn '' />
</cfif>
<!--- rotate the png --->
<cfimage
action="read"
source="#local.svg.file#"
name="local.objImage" />
<cfimage
action="rotate"
source="#local.objImage#"
angle="90"
name="local.objImage" />
<cfimage
action="write"
source="#local.objImage#"
destination="#local.svg.file#"
overwrite="yes" />
<!--- Add the png to the pdf, write the pdf file, and delete the png --->
<cfscript>
img = CreateObject("java", "com.lowagie.text.Image");
png = img.getInstance(local.svg.file);
document = CreateObject("java", "com.lowagie.text.Document");
document.init(png);
fileIO = CreateObject("java", "java.io.FileOutputStream");
fileIO.init(local.outPDF);
writer = CreateObject("java", "com.lowagie.text.pdf.PdfWriter");
writer.getInstance(document, fileIO);
document.setMargins(0, 0, 0, 0);
document.open();
document.add(png);
document.close();
FileDelete(#local.svg.file#); <!--- This is the line where it breaks--->
</cfscript>
<cfreturn local.outPDF />
</cffunction>
How can I fix this function?
Thanks
If you production server is CF11 then this is a bug that has been mentioned here, in Bug# 4031026 - http://bugbase.adobe.com/index.cfm?event=bug&id=4031026
The bug has been verified by Adobe and fix will be due, most probably in their next hotfix.
JPEG seems to be the only format that works at the moment without locking issues.

Cannot authenticate against Windows domain using ColdFusion 10, IIS 7.5, LDAP

We have been struggling on an upgrade from Windows 2003 Server (ColdFusion 8) to Windows 2008 with ColdFusion 10. We finally have the settings correct to handle and process all of our ColdFusion code, with custom error handlers, and SSL functioning as expected.
However, when we started to have some users test different applications, we found out that no Domain Users can log in to the website, unless they are part of the Local Machine's Administrators group. We have another Windows 2008 Server running .NET and authenticating users correctly. I thoroughly compared the settings and they are the same.
This is how it is setup:
ColdFusion Services: all services (5) are running under the Local System, with the exception of the ColdFusion Application Server, which is running under a domain account.
IIS: We have 1 active website (Main Website) running on it's own Application Pool Integrated .NET 4.0, running as LocalSystem.
Authentication: Anonymous works, Anonymous account MUST be Application Pool Identity or else it won’t display anything. Basic Authentication is configured, and the default domain is configured.
Any and all help is appreciated as we have been working on this for months and thought the migration was ready to go. No one on my team is an expert at the installation of ColdFusion or IIS 7.5.
After much ado, I found the answer here: What are the proper permissions for ColdFusion 9 on IIS 7.5 with Windows Authentication
I needed to give the domain users (which is just a very large A/D group) read-only access to the config folder under the CF10 install location. From then on, it worked great!
If you're interested in a fairly robust solution as well, here's an example component (CFC) you could call, which also scrubs out possible injection characters. We've evolved this over the years for authentication so it is fairly battle tested.
<cfcomponent output="false">
<cffunction access="public" name="init" output="FALSE" returntype="any" hint="This is the pseudo constructor that allows us to play little object games." >
<cfset variables.ldapserver = application.yoursiteLDAP.server />
<cfset variables.ldapuser = application.yoursiteLDAP.user />
<cfset variables.ldappassword = application.yoursiteLDAP.password />
<cfset variables.ldaptimeout = application.yoursiteLDAP.timeout />
<cfset variables.ldapsecuremode = application.yoursiteLDAP.securemode />
<cfset variables.port = application.yoursiteLDAP.port />
<cfreturn This />
</cffunction>
<cffunction name="authenticate" access="public" output="false" returntype="struct" hint="">
<cfargument name="username" type="string">
<cfargument name="password" type="string">
<cfset var returnData = StructNew() />
<cfset var queryResult = QueryNew('') />
<cfset var userInfo = "" />
<cfset arguments.username = scrubStringforLDAPQuery(arguments.username) />
<cfset userInfo = retrieveUserInfo(arguments.username) />
<cfset returnData["authenticated"] = false />
<cfset returnData["detail"] = "" />
<cfset returnData["user_info"] = QueryNew("") />
<cftry>
<cfldap
action="query"
server="#variables.ldapserver#"
username="#userInfo.DN#"
password="#arguments.password#"
filter="(&(uid=#arguments.username#)(objectClass=account))"
name="queryResult"
attributes="cn,dn,uid,displayName,objectClass,uidNumber"
start="dc=yoursite,dc=subdomain,dc=domain,dc=com"
maxrows="1"
port="#variables.port#"
timeout="#variables.ldaptimeout#"
secure="#variables.ldapsecuremode#" />
<cfset returnData["authenticated"] = queryResult.RecordCount EQ 1 />
<cfset returnData["user_info"] = queryResult />
<cfcatch>
<cfif FindNoCase("Invalid Credentials",cfcatch.Message) LTE 0>
<cfrethrow />
</cfif>
<cfset returnData["detail"] = cfcatch.Message />
</cfcatch>
</cftry>
<cfreturn returnData />
</cffunction>
<cffunction name="retrieveUserInfo" access="public" output="false" returntype="query" hint="">
<cfargument name="username" type="string">
<cfset var queryResult = QueryNew('') />
<cfset arguments.username = scrubStringforLDAPQuery(arguments.username) />
<cfldap
action="query"
server="#variables.ldapserver#"
username="#variables.ldapuser#"
password="#variables.ldappassword#"
filter="(&(uid=#arguments.username#)(objectClass=account))"
name="queryResult"
attributes="cn,dn,uid,displayName,objectClass,uidNumber,shadowExpire,gecos,homeDirectory,loginShell"
start="dc=yoursite,dc=subdomain,dc=domain,dc=com"
maxrows="10"
port="#variables.port#"
timeout="#variables.ldaptimeout#"
secure="#variables.ldapsecuremode#" />
<cfif queryResult.RecordCount GT 1>
<cfthrow message="More than 1 user found in LDAP" detail="More than 1 user matched uid=#arguments.username#" />
</cfif>
<cfreturn queryResult />
</cffunction>
<cffunction name="retrieveGroupInfo" access="public" output="false" returntype="query" hint="">
<cfargument name="groupname" type="string">
<cfset var queryResult = QueryNew('') />
<cfset arguments.groupname = scrubStringforLDAPQuery(arguments.groupname) />
<cfldap
action="query"
server="#variables.ldapserver#"
username="#variables.ldapuser#"
password="#variables.ldappassword#"
filter="(&(cn=#arguments.groupname#)(objectClass=posixGroup))"
name="queryResult"
attributes="cn,dn,objectClass"
start="dc=yoursite,dc=subdomain,dc=domain,dc=com"
maxrows="10"
port="#variables.port#"
timeout="#variables.ldaptimeout#"
secure="#variables.ldapsecuremode#" />
<cfif queryResult.RecordCount GT 1>
<cfthrow message="More than 1 group found in LDAP" detail="More than 1 group matched uid=#arguments.groupname#" />
</cfif>
<cfreturn queryResult />
</cffunction>
<cffunction name="scrubStringforLDAPQuery" access="public" output="false" returntype="string" hint="Removes offensive characters from string for use in an LDAP query">
<cfargument name="stringToScrub" type="string">
<cfargument name="blockWildcard" type="boolean" default="false">
<cfset replaceCharacterList = ";=" />
<cfif arguments.blockWildcard>
<cfset replaceCharacterList &= "*" />
</cfif>
<cfset arguments.stringToScrub = REReplace(arguments.stringToScrub,"[#replaceCharacterList#]","","all") />
<cfreturn arguments.stringToScrub />
</cffunction>
</cfcomponent>

ColdFusion - Enabling ORM stops onApplicationStart from running

Can anyone explain this behaviour to me?
I have set up a bunch of application scoped settings in onApplicationStart, and some of them are referred to in onSessionStart. However, when I enable ORM, it seems that onApplicationStart isn't running at all and thus my onSessionStart method fails.
It took me a while to figure out this was the issue, generally I'll test by hitting onApplicationStart programatically during development. So it was only after a restart of the service that I found a symptom. Eventually I traced it back to ORM and it's as simple as:
THIS.ormenabled = true; // Error
THIS.ormenabled = false; // Everything peachy
I stripped down the Application.cfc and put some timestamps in the various methods so that I could see what was executing:
<cfscript>
THIS.Name = "TestyMcTestable"
THIS.datasource = 'Test';
THIS.ormenabled = true;
</cfscript>
<cfsetting
requesttimeout="20"
showdebugoutput="false"
enablecfoutputonly="false"
/>
<cfset request.pseudo = Now() />
<cfset sleep(1500)>
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false">
<cfset request.application = Now() />
<cfset sleep(1500)>
<!--- Return out. --->
<cfreturn true />
</cffunction>
<cffunction name="OnSessionStart" access="public" returntype="void" output="false">
<cfset request.session = Now() />
<cfset sleep(1500)>
<!--- Return out. --->
<cfreturn />
</cffunction>
<cffunction name="OnRequestStart" access="public" returntype="boolean" output="false">
<cfargument name="TargetPage" type="string" required="true" />
<cfset request.requeststart = Now() />
<cfset sleep(1500)>
<!--- Return out. --->
<cfreturn true />
</cffunction>
<cffunction name="OnRequest" access="public" returntype="void" output="true">
<cfargument name="TargetPage" type="string" required="true" />
<cfset request.request = Now() />
<cfset sleep(1500)>
<!--- Include the requested page. --->
<cfinclude template="#ARGUMENTS.TargetPage#" />
<!--- Return out. --->
<cfreturn />
</cffunction>
My index.cfm just contains a dump of the request scope.
If I remove the orm setting, it all comes back as expected. However with the setting in there, the variable set on application start is missing entirely and it seems onApplicationStart hasn't been run at all.
I'm testing by changing the name of the application inbetween requests, but just to be certain I've restarted the service too.
Am I missing something? Is this documented behavior? I'm running this on Railo - I haven't tested extensively on ACF, but the initial problem occurred there too so I assume it's the same cause.
Can anyone shed any light on this?
OnApplicationStart is only run when the Application is first created in memory and is only run once for the life of the Application unless you explicitly call things like ApplicationStop(); or call the method again, etc. In addition, the variable in the Application scope you set is a request scope variable which only exists for the lifetime of one request. Same with onSessionStart, that request variable will only exist when the user session is created for the first time.
First Request: (ApplicationStart & SessionStart Fire)
struct: request
application {ts '2013-03-20 18:50:17'}
cfdumpinited false
pseudo {ts '2013-03-20 18:50:20'}
session {ts '2013-03-20 18:50:19'}
struct: session
cfid 5600
cftoken a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
session {ts '2013-03-20 18:50:19'}
sessionid TESTYMCTESTABLE_5600_a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
urltoken CFID=5600&CFTOKEN=a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
Second Request: (Application/SessionStart Do Not Fire)
struct: request
cfdumpinited false
pseudo {ts '2013-03-20 18:50:59'}
struct: session
cfid 5600
cftoken a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
session {ts '2013-03-20 18:50:19'}
sessionid TESTYMCTESTABLE_5600_a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
urltoken CFID=5600&CFTOKEN=a8bf96c34c2cac7a-68400880-E1E6-ACDF-3B204DBBCD9A04A9
This was tested on CF9.
application.cfm
<cfcomponent>
<cfscript>
this.Name = "TestyMcTestable";
this.ormenabled = true;
this.datasource = 'cfartgallery';
this.sessionManagement = true;
</cfscript>
<cfset request.pseudo = Now() />
<cfset sleep(1500)>
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false">
<cfset request.application = Now() />
<cfset session.application = Now() />
<cfset sleep(1500)>
<!--- Return out. --->
<cfreturn true />
</cffunction>
<cffunction name="OnSessionStart" access="public" returntype="void" output="false">
<cfset request.session = Now() />
<cfset session.session = Now() />
<cfset sleep(1500)>
<!--- Return out. --->
<cfreturn />
</cffunction>
<cffunction name="OnRequestStart">
</cffunction>
</cfcomponent>
Alternatively if you put your request variables in the OnRequestStart, they will be there every time. Hope this helps.
Turns out this was a problem with the cfclocation setting. In the original app, it was pointing to an incorrect location. When I stripped the Application.cfc down for testing, I removed this setting - not realising that default behaviour is to traverse the folders from root looking for persistent CFCs.
It seems then that the app ran into a problem during that process which bombed out of onApplicationStart, but continued running the other functions in Application.cfc.
One of the guys on the Railo group identified an issue with using pseudo constructors in non-persistent CFCs that are read by the Hibernate engine. I'm not certain if that contributed to this issue, but it seems likely.