1

I am attempting to follow the documentation per Access Control and interrogating code like azure-documentdb-node SDK and I am unable to do so.

I get the following error: 401 Unauthorized: {"code":"Unauthorized","message":"The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\ndbs\n\n13 april 2015 18:21:05 gmt\n\n'\r\nActivityId: ...

My ruby code looks like the following:

require 'openssl'
require 'rest-client'
require 'base64'
require 'uri'
require 'json'
require 'time'

def get_databases url, master_key
  time = Time.now.httpdate
  authorization = get_master_auth_token "get", "", "dbs", time, master_key
  header = { "authorization" => authorization, "x-ms-date" => time, "x-ms-version" => "2015-04-08" }
  RestClient.get url, header
end

def get_master_auth_token verb, resource_id, resource_type, date, master_key
  digest = OpenSSL::Digest::SHA256.new
  key = Base64.decode64 master_key
  text = verb + "\n" +
    resource_type + "\n" +
    resource_id + "\n" +
    date + "\n" +
    "\n"
  hmac = OpenSSL::HMAC.digest digest, key, text.downcase
  auth_string = "type=" + "master" + "&ver=" + "1.0" + "&sig=" + hmac
  URI.escape auth_string
end

Thanks!

EDIT: After Ryan's advice and example I've simplified the code down to the following snippit that should be a match for the node code he has posted BUT it still fails in ruby:

def hard_coded_get_databases master_key, url
  verb = "get"
  resource_type = "dbs"
  resource_id = ""
  date = Time.now.httpdate
  serv_version = '2014-08-21'
  master_token = "master"
  token_version = "1.0"
  key = Base64.decode64 master_key
  text = verb + "\n" + resource_type + "\n" + resource_id + "\n" + date + "\n\n"
  body = text.downcase.force_encoding "utf-8"
  signature = OpenSSL::HMAC.digest OpenSSL::Digest::SHA256.new, key, body
  auth_token = URI.escape("type="+master_token + "&ver="+token_version + "&sig="+signature)

  header = { "accept" => "application/json", "x-ms-version" => serv_version, "x-ms-date" => date, "authorization" => auth_token }
  RestClient.get url, header
end

EDIT2: I believe I've isolated the problem to how I am doing the master key authentication.

Taking Ryan's example we can trim his node code down the following:

var crypto = require("crypto")

function encode_message(masterKey, message) {
    var key = new Buffer(masterKey, "base64"); // encode/decode? base64 the masterKey
    var body = new Buffer(message.toLowerCase(), "utf8"); // convert message to "utf8" and lower case
    return crypto.createHmac("sha256", key).update(body).digest("base64"); // encrypt the message using key
 }

If I call this node code I can produce the following key:

encode_message("blah", 'get\ncolls\n\nTue, 14 Apr 2015 13:34:22 GMT\n\n')
'IYlLuyZtVLx5ANkGMAxviDHgC/DJJXSj1gUGLvN0oM8='

If I produce the equivalent ruby code to create the authentication my ruby code looks like the following:

require 'base64'
require 'openssl'

def encode_message master_key, message
  key = Base64.urlsafe_decode64 master_key
  hmac = OpenSSL::HMAC.digest 'sha256', key, message
  Base64.urlsafe_encode64 hmac
end

If I call this code I get the following:

2.2.1 :021 > encode_message("blah", "get\ncolls\n\nTue, 14 Apr 2015 13:34:22 GMT\n\n")
 => "N6BL3n4eSvYA8dIL1KzlTIvR3TcYpdqW2UNPtKWrjP8="

Clearly the 2 encoded auth tokens are not the same. (Ryan again thanks so much for the help to get this far).

Y. Adam Martin
  • 368
  • 2
  • 13
  • As a note I also referenced the following post from msdn on the topic: https://social.msdn.microsoft.com/Forums/azure/en-US/542152e6-cd8f-4953-813f-9fd35dc37c62/unable-to-access-azure-documentdb-via-rest-api?forum=AzureDocumentDB – Y. Adam Martin Apr 13 '15 at 18:38

2 Answers2

1

I have found the answer. Thanks to Magnus Stahre ... he is the man for helping me figure it out.

It was the encoding as I thought and the trick is this:

def encode_message master_key, message
  key = Base64.urlsafe_decode64 master_key
  hmac = OpenSSL::HMAC.digest 'sha256', key, message.downcase
  Base64.encode64(hmac).strip
end

I was downcasing in my code too early AND my Base64.encode64 was failing to strip away a newline character that ruby was adding on the end.

Y. Adam Martin
  • 368
  • 2
  • 13
0

i'll start off by apologizing for my limited Ruby knowledge but let me try assist here;

in your get_master_auth_token function it appears you are decoding the key before using it. is this correct? if so, why?

here is a node.js sample that uses the master key, builds up the auth header value and does a simple http call to list the collections in a database

var crypto = require("crypto");
var https = require("https");

https.globalAgent.options.secureProtocol = "TLSv1_client_method";

var verb = 'get'; 
var resourceType = 'dbs'; //the resource you are trying to get. dbs, colls, docs etc. 
var resourceId = ''; //the parent resource id. note: not the id, but the _rid. but for you, because you are trying to lookup list of databases there is no parent
var masterKey = '...'; //your masterkey 
var date = new Date().toUTCString();

var auth = getAuthorizationTokenUsingMasterKey(verb, resourceId, resourceType, date, masterKey);

var options = {
hostname: '...', //your hostname (without https://)
port: 443,
path: '/dbs/',
method: 'GET',
    headers: {
        accept: 'application/json',
        'x-ms-version': '2014-08-21',
        'x-ms-date': date,
        authorization: auth,
    }
};

for (var i = 0; i < 1000; i++) {
    var req = https.request(options, function (res) {
        process.stdout.write(new Date().toUTCString() + " - statusCode: " + res.statusCode + "\n");
        res.on('data', function (d) {
        }).on('error', function (e) {
        })
    });

    //console.log(req);

    req.end();
}

function getAuthorizationTokenUsingMasterKey(verb, resourceId, resourceType, date, masterKey) {
    var key = new Buffer(masterKey, "base64");

    var text = (verb || "") + "\n" + 
        (resourceType || "") + "\n" + 
        (resourceId || "") + "\n" + 
        (date || "") + "\n" + 
        ("") + "\n";

    var body = new Buffer(text.toLowerCase(), "utf8");
    var signature = crypto.createHmac("sha256", key).update(body).digest("base64");

    var MasterToken = "master";    
    var TokenVersion = "1.0";

    return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}

In your example, the resourceId passed to the getAuthorizationTokenUsingMasterKey method should be "" and the resourceType should be "dbs" as you have it.

I did notice that in some cases you have to URI Encode the value, but I think you are doing that already as the very last line of the func.

the only difference I can spot in your code vs my code is that you appear to be decoding the master_key which I don't do.

what I would recommend you do is run this node sample and compare the values of the strings we have in body & signature to the your values. they need to match.

Ryan CrawCour
  • 2,704
  • 1
  • 19
  • 25
  • Thanks so much for your help. My understanding from http://stackoverflow.com/questions/6182315/how-to-do-base64-encoding-in-node-js is that when you do: var key= new Buffer(masterkey, "base64") is that you are decoding the master key is this not correct? – Y. Adam Martin Apr 14 '15 at 12:53