0

I am trying to communicate with my Xero account through Xero API, a simple bash script and cURL. Also, I am working with a Xero Private App, this means I have already generated a public/private keypair, the public key uploaded to Xero and the private being used in my procedure.

Keypair generated as suggested here https://developer.xero.com/documentation/api-guides/create-publicprivate-key:

openssl genrsa -out privatekey.pem 1024
openssl req -new -x509 -key privatekey.pem -out publickey.cer -days 1825
openssl pkcs12 -export -out public_privatekey.pfx -inkey privatekey.pem -in publickey.cer

The following is my code and thought process.

First, I create the parameters for the OAuth request. I know Xero Private Apps work with OAuth1.0a and need to be signed with RSA-SHA1.

oauth_consumer_key="my_key"
oauth_nonce="$(($(date +%s) - 10000))"
oauth_signature_method="RSA-SHA1"
oauth_timestamp="$(date +%s)"
oauth_token="my_key"
oauth_version="1.0"

Now, I focus on generating the OAuth Signature as explained clearly in https://oauth1.wp-api.org/docs/basics/Signing.html. I make sure to create a base string using Method (I call it verb), URL and Params. I make sure that Params are sorted by name. Also, I URL_encode these values before concatenating with &.

verb=GET
url=https://api.xero.com/api.xro/2.0/Invoices/243216c5-369e-4056-ac67-05388f86dc81
params=oauth_consumer_key=$oauth_consumer_key\&oauth_nonce=$oauth_nonce\&oauth_signature_method=$oauth_signature_method\&oauth_timestamp=$oauth_timestamp\&oauth_token=$oauth_token\&oauth_version=$oauth_version
baseString=$(urlencode $verb)\&$(urlencode $url)\&$(urlencode $params)

echo $baseString returns GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FInvoices%2Fe4d08842-29fc-4228-8227-8661e0f93ea3&oauth_consumer_key%*%26oauth_nonce%3D1523125307%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1523135308%26oauth_token%*%26oauth_version%3D1.0

urlencode function:

function urlencode() {
    echo -n "$1" | perl -MURI::Escape -ne 'print uri_escape($_)'
}

I sign the baseString using OpenSSL as follows.

oauth_signature=$(echo -n "$baseString" | openssl dgst -sha1 -sign "C:\path\to\keys\privatekey.pem" | openssl enc -A -base64)
echo $oauth_signature

Now, I create the Authorization header, with the same parameters but including the signature which has just been generated.

auth_header="Authorization: OAuth oauth_consumer_key=\"$oauth_consumer_key\", oauth_nonce=\"$oauth_nonce\", oauth_signature=\"$oauth_signature\", oauth_signature_method=\"$oauth_signature_method\", oauth_timestamp=\"$oauth_timestamp\", oauth_token=\"$oauth_token\", oauth_version=\"$oauth_version\"" 

echo $auth_header returns Authorization: OAuth oauth_consumer_key="*", oauth_nonce="1523124975", oauth_signature="*", oauth_signature_method="RSA-SHA1", oauth_timestamp="1523134975", oauth_token="*", oauth_version="1.0"

Finally, I send a GET request via cURL to get a specific invoice.

curl -G "https://api.xero.com/api.xro/2.0/Invoices/243216c5-369e-4056-ac67-05388f86dc81" -H "$auth_header"

And I receive...

oauth_problem=signature_invalid&oauth_problem_advice=Failed%20to%20validate%20signature

Where I have expected a JSON response with the invoice or something telling me that the invoice does not exist.

I feel that I have followed the steps correctly. Well, I guess there is a problem with the signature. Either the thing it was signing wasn't formed correctly or a problem with RSA-SHA1? I'm at a loss. I would appreciate any feedback.

EDIT: Thanks to @rustyskates, I fixed some mistakes, however, now I get: <ApiException xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ErrorNumber>500</ErrorNumber> <Type>UnknownErrorException</Type> <Message>An error occurred in Xero. Check the API Status page http://status.developer.xero.com for current service status. Contact the API support team at api@xero.com for more assistance.</Message> </ApiException> which still looks like a problem because Xero doesn't report operational problems and many others seem to have experienced this too.

Zeruno
  • 1,391
  • 2
  • 20
  • 39
  • Zeruno, could you paste the output of your echo $baseString and echo $auth_header please, with consumer key removed? – rustyskates Apr 07 '18 at 20:41
  • @rustyskates sure, here is an example of `$ echo $baseString` `GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FInvoices%2Fe4d08842-29fc-4228-8227-8661e0f93ea3&&` – Zeruno Apr 07 '18 at 21:04
  • and an example of `$ echo $auth_header` `Authorization: OAuth oauth_consumer_key="*", oauth_nonce="1523124975", oauth_signature="*", oauth_signature_method="RSA-SHA1", oauth_timestamp="1523134975", oauth_token="*", oauth_version="1.0"` – Zeruno Apr 07 '18 at 21:05
  • Looks like the params are missing off your $baseString? – rustyskates Apr 07 '18 at 21:06
  • I must point out that in generating baseString, I had a silly error which I have corrected (will update soon) and I can now get a result from Xero - error 500. :/ – Zeruno Apr 07 '18 at 21:06
  • @rustyskates, yes I missed it... now corrected looks like `$ echo $baseString` `GET&https%3A%2F%2Fapi.xero.com%2Fapi.xro%2F2.0%2FInvoices%2Fe4d08842-29fc-4228-8227-8661e0f93ea3&oauth_consumer_key%*%26oauth_nonce%3D1523125307%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1523135308%26oauth_token%*%26oauth_version%3D1.0&` – Zeruno Apr 07 '18 at 21:09
  • It looks like it's having trouble with your signature string - "Invalid length for a Base-64 char array or string". Maybe that `tr -d '\n'` is having an unexpected effect? – rustyskates Apr 07 '18 at 21:28
  • Thanks for pointing it out. May I ask how you got that message? What I hoped to achieve with that code is to remove the new lines that form out of the process, something which I have seen other applications do. I will focus on investigating if it is not acting as intended. – Zeruno Apr 07 '18 at 21:32
  • I work at Xero :-) I'll log that as a place where we could use some better error handling. – rustyskates Apr 07 '18 at 21:33
  • Cool! What luxury to have Xero staff have a look so quickly. I am very grateful. Thanks! :) – Zeruno Apr 07 '18 at 21:41
  • The signature which seems to have thrown that error from your side is 172 characters long with `tr -d '\n'`. This seems right (?); I am reading that base64 encoded string are always a multiple of 4. I have also found a suggestion for that error which suggests urlencode on the signature but that was not helpful either... (signature validation failed) :/ Also tried other ways to remove the new line from base64 decode. – Zeruno Apr 07 '18 at 22:05
  • `echo "$baseString" | openssl dgst -sign ...` adds a newline character to the data being signed, which makes the signature wrong. In addition you have an `&` after the params, which does not agree with the page you linked. – dave_thompson_085 Apr 07 '18 at 22:42
  • @dave_thompson_085 thanks for the catch... should be corrected with `echo -n | ...` and the `&` removed but still Xero returns 500. – Zeruno Apr 07 '18 at 22:46
  • You can't post your base64-encoded signature string here, obvs, but is there any whitespace in it? If so, that won't count in your length and you'll have to add a corresponding number of '=' as padding. – rustyskates Apr 07 '18 at 23:33
  • No whitespace in the signature :/ – Zeruno Apr 07 '18 at 23:49

1 Answers1

1

The last step is to url-encode the oauth_signature value directly before you add it in to the Authorization header. Once you do that, you'll be golden.

rustyskates
  • 856
  • 4
  • 10