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>
Related
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
I’m just trying to get something up and running in Amazon MWS with Coldfusion. Here is the code I’m using… super simple. When I replace the querystring part with an exact query string from the Amazon MWS scratchpad webpage, I get the EXACT same signature… so I know my signature code is working. But despite that, the only response I ever get from amazon is “The request signature we calculated does not match the signature you provided.” So I’m just thinking it's somehow in the way I’m sending it rather than the actual signature. I’ve tried a ton of different things and spent so much time, but I never get any other response even though I can get the signature to exactly match that of an example run on scratchpad.
Here is my code with obviously the relevant private data as variables that I have filled in in my actual code: (note I have to add 5 hours to my server’s time to make amazon happy with the request time so you might have to change that)
<CFSET nowtime = DateAdd(‘h’, 5, Now())>
<CFSET awsaccesskey = “”>
<CFSET sellerid = “”>
<CFSET secretkey = “”>
<CFSET mwsaccess = “”>
<CFSET queryString = “AWSAccessKeyId=#awsaccesskey#&Action=ListAllFulfillmentOrders&MWSAuthToken=#mwsaccess#&QueryStartDateTime=2020-04-12T04%3A00%3A00Z&SellerId=#sellerid#&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=#dateFormat(nowtime, ‘yyyy-mm-dd’)#T#encodeForURL(timeFormat(nowtime, ‘HH:mm:ss’))#Z&Version=2010-10-01”>
<CFSET signaturestring = “POST#chr(10)#mws.amazonservices.com#chr(10)#/FulfillmentOutboundShipment/2010-10-01#chr(10)##listSort(queryString, ‘text’, ‘asc’, ‘&’)#”>
<CFSET signature = encodeForURL(toBase64(binaryDecode(hmac(signaturestring, “#secretkey#”, “HmacSHA256”, “UTF-8”), “hex”)))>
<CFHTTP method=“POST” url=“https://mws.amazonservices.com/FulfillmentOutboundShipment/2010-10-01/?#queryString#&Signature=#signature#” >
<cfhttpparam type=“Header” name=“Content-Type” value=“application/x-www-form-urlencoded; charset=utf-8”/>
<cfhttpparam type=“Header” name=“x-amazon-user-agent” value=“Whatever/test (Language=Coldfusion)”/>
</CFHTTP>
<CFDUMP var="#CFHTTP.FileContent#" />
It’s a super simple request… but I just can’t get anywhere with it. Any help would be appreciated.
sigh. A guy on the Amazon forums figured it out for me. His handle is Autonomoose. Anyway, I have a / in the CFHTTP post line. this one:
<CFHTTP method=“POST” url=“https://mws.amazonservices.com/FulfillmentOutboundShipment/2010-10-01/?#queryString#&Signature=#signature#” >
Where it says 2010-10-01/?... That / there. Delete it and the whole thing works.
I have a scenario where I need to store simple counter in config registry and increment it at end of sequence flow. Reason we need to store in config registry is in case server get restarted we have last counter value persisted. Can someone suggest how to increment the counter in config registry ?
Sample javascript you can use in your mediation to save current message inside registry :
<script language="js"><![CDATA[
importPackage(Packages.org.apache.synapse.config);
mc.getConfiguration().getRegistry().newResource("gov:/trunk/mypath/MyResource.xml",false);
mc.getConfiguration().getRegistry().updateResource("gov:/trunk/mypath/MyResource.xml",mc.getPayloadXML().toString());
]]></script>
newResource is used the first time to create the resource
I have this solution for you!!
<script language="nashornJs"><![CDATA[
var body = mc.getPayloadXML();
print(body);
var registryPath = "gov:/portales/date.xml";
if(body != null && body != ''){
var existingProperty = mc.getConfiguration().getRegistry().getResource(registryPath);
print(body);
if(existingProperty == null){
print(body);
// Create the registry entry if no such entry exists.
mc.getConfiguration().getRegistry().newResource(registryPath, false);
mc.getConfiguration().getRegistry().updateResource(registryPath, body);
} else {
print(body);
// Update the registry entry if it already exists.
mc.getConfiguration().getRegistry().updateResource(registryPath, body);
}
}]]></script>
the idea was taken from http://wso2-oxygen-tank.10903.n7.nabble.com/How-to-Store-Log-Message-in-a-Registry-File-in-EI-td159169.html
I used this way to get json payload from POST request and stored in the registry on xml format
<datamapper config="gov:datamapper/conversionToSaveInRegistry.dmc" description="conversionToSaveInRegistry" inputSchema="gov:datamapper/conversionToSaveInRegistry_inputSchema.json" inputType="JSON" outputSchema="gov:datamapper/conversionToSaveInRegistry_outputSchema.json" outputType="XML" xsltStyleSheet="gov:datamapper/conversionToSaveInRegistry_xsltStyleSheet.xml"/>
<property name="messageType" scope="axis2" type="STRING" value="application/xml"/>
<script language="nashornJs"><![CDATA[
var body = mc.getPayloadXML();
var registryPath = "gov:/generated/date.xml";
if(body != null && body != ''){
var existingProperty = mc.getConfiguration().getRegistry().getResource(registryPath);
if(existingProperty == null){
// Create the registry entry if no such entry exists.
mc.getConfiguration().getRegistry().newResource(registryPath, false);
mc.getConfiguration().getRegistry().updateResource(registryPath, body);
} else {
// Update the registry entry if it already exists.
mc.getConfiguration().getRegistry().updateResource(registryPath, body);
}
}]]></script>
<property name="NO_ENTITY_BODY" scope="axis2" type="BOOLEAN" value="true"/>
<property name="HTTP_SC" scope="axis2" type="STRING" value="201"/>
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!
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.