I'm using the ApiAuth gem to authenticate API requests. Now I need to write a shell script that uses cURL to send test requests. So I need to generate an MD5 of the POST body and base64 encode it so that it matches what ApiAuth does on the server:
My shell script:
query="{\"document\":{\"recipient_id\":\"$ACCESS_ID\",\"data\":{\"id\":\"$ACCESS_ID\"}},\"vendor_string\":\"test\",\"patient\":{\"document\":{\"recipient_id\":\"$ACCESS_ID\",\"data\":{\"id\":\"$ACCESS_ID\"}}}}"
# need to figure how to get a base64 encoded md5 the same way Ruby does
content_md5=$(echo -n "$query" | openssl md5 -binary | base64)
content_type='application/json'
request_uri="$API_BASE/test"
httpdate=$(date -u +"%a, %_d %b %Y %H:%M:%S GMT")
accept_header='application/vnd.test+json; version=1'
canonical_string="$content_type,$content_md5,$request_uri,$httpdate"
signature=$(echo -n "$canonical_string" | openssl dgst -sha1 -hmac "$SECRET_KEY" -binary | base64)
curl -H "Authorization: APIAuth $ACCESS_ID:$signature"\
-H "Content-MD5: $content_md5" \
-H "Date: $httpdate" \
-H "Accept: $accept_header" \
-H "Content-type: $content_type" \
-d $query \
-v \
$request_uri
The first thing that fails is comparing the Content-MD5 that I send with the content MD5 that ApiAuth calculates:
https://github.com/mgomes/api_auth/blob/master/lib/api_auth/base.rb#L37
def authentic?(request, secret_key)
return false if secret_key.nil?
return !md5_mismatch?(request) && signatures_match?(request, secret_key) && !request_too_old?(request)
end
Here the md5_mismatch?(request) method returns false. it uses these methods to calculate the MD5:
https://github.com/mgomes/api_auth/blob/master/lib/api_auth/request_drivers/action_controller.rb
def calculated_md5
if @request.env.has_key?('RAW_POST_DATA')
body = @request.raw_post
else
body = ''
end
md5_base64digest(body)
end
https://github.com/mgomes/api_auth/blob/master/lib/api_auth/helpers.rb
def b64_encode(string)
if Base64.respond_to?(:strict_encode64)
Base64.strict_encode64(string)
else
# Fall back to stripping out newlines on Ruby 1.8.
Base64.encode64(string).gsub(/\n/, '')
end
end
def md5_base64digest(string)
if Digest::MD5.respond_to?(:base64digest)
Digest::MD5.base64digest(string)
else
b64_encode(Digest::MD5.digest(string))
end
end
So I'm thinking it boils down to matching exactly what's going on with:
Digest::MD5.base64digest
My attempt was:
content_md5=$(echo -n "$query" | openssl md5 -binary | base64)
How can I make the bash script equivalent to the ruby method?
I've tried with and without the -binary
flag.
I've checked that the $query
in bash is the exact same as @request.raw_post
in Ruby and there's no trailing newlines since I'm using echo -n
.
Update:
Output from bash:
echo $query
{"document":{"recipient_id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o","data":{"id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o"}},"vendor_string":"kipusystems","patient":{"document":{"recipient_id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o","data":{"id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o"}}}}
echo $content_md5
Lsb/vxJKHUxyRAqMhOMeOw==
Output from ruby:
puts body
{"document":{"recipient_id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o","data":{"id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o"}},"vendor_string":"kipusystems","patient":{"document":{"recipient_id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o","data":{"id":"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o"}}}}
puts md5_base64digest(body)
/DdffT+N+sZZjaTC5TJNcg==
I selected and copied the $query
and body
strings out of the terminals that ran the bash script and rails server respectively. They're both exactly the same in that sense, how can I further narrow down this problem?
Update 2: Maybe some character encoding issue?
I pasted this literal text into the (mac bash) shell prompt:
echo -n "{\"document\":{\"recipient_id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\",\"data\":{\"id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\"}},\"vendor_string\":\"kipusystems\",\"patient\":{\"document\":{\"recipient_id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\",\"data\":{\"id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\"}}}}" | openssl dgst -md5 -binary | base64
And that outputs: /DdffT+N+sZZjaTC5TJNcg==
which is good! That's what the Ruby side outputs. Ok cool.
But when I run my shell script with that exact literal command I just pasted above, it outputs: Lsb/vxJKHUxyRAqMhOMeOw==
which is the same as the content-md5 I originally started with (script originally posted).
When I run echo $LANG
I get en_US.UTF-8
.
Update 3:
I run the shell script with:
sh script.sh
And that outputs Lsb/vxJKHUxyRAqMhOMeOw==
when I echo out this command:
echo -n "{\"document\":{\"recipient_id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\",\"data\":{\"id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\"}},\"vendor_string\":\"kipusystems\",\"patient\":{\"document\":{\"recipient_id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\",\"data\":{\"id\":\"lwzvZixLvVLL50qasfoO2YvMz9UzNlxg8HBOEj8NV_o\"}}}}" | openssl dgst -md5 -binary | base64
Update 4:
Weird! So, I've been running (the above posted shell script) using sh script.sh
and that has shown me an md5 result that was different from what I was seeing in Ruby. Now, I chmod +x
'ed the script and ran it directly: script.sh
and now I get the correct md5!!
But, now the signatures_match?
method in ApiAuth returns false still :'(