Signature Specifications

Introduction

All requests to the Xiaomi Cloud-ML service are required to accompany with the request and a signature. After the client signed the request, the server would sign again to authenticate the identity of the user.

Signature Algorithm

The signature method is similar to AWS, requiring attention to the parameters and the priority. After implementing the signature, it needs to pass the following unit test.

def test_sign(self):
  url = 'https://api.github.com/user?a=b'
  timestamp = '1474203860'
  content_md5 = 'd41d8cd98f00b204e9800998ecf8427e'
  app_secret = "sk"

  self.assertEquals(
      self.signer._sign(url, timestamp, content_md5, app_secret),
      '\x10\xe1pv\x96\x1c\x96\xfb\xc7\xe2\x16\x9d\xf4Ma5\x1dO\x86f')

def test_sign_to_base64(self):
  url = 'https://api.github.com/user?a=b'
  timestamp = '1474203860'
  content_md5 = 'd41d8cd98f00b204e9800998ecf8427e'
  app_secret = "sk"

  self.assertEquals(
      self.signer._sign_to_base64(url, timestamp, content_md5, app_secret),
      'EOFwdpYclvvH4had9E1hNR1PhmY=')

Python Implementation

The server-side signature is in the cloud_ml_common directory and the code is as follows.

import base64
import time
import hmac
import hashlib
from hashlib import sha1
from requests.auth import AuthBase
from urllib import unquote
from urlparse import urlparse
from constant import Constant

class Signer(AuthBase):
  ''' The signer class used to sign the request. '''
  def __init__(self, app_key, app_secret):
    self._app_key = str(app_key)
    self._app_secret = str(app_secret)
  def __call__(self, request):
    url = request.url
    if not request.body:
      request.body = ""
    timestamp = request.headers.get(Constant.TIMESTAMP, str(int(time.time())))
    content_md5 = request.headers.get(Constant.CONTENT_MD5,
                                      hashlib.md5(request.body).hexdigest())

    request.headers[Constant.TIMESTAMP] = timestamp
    request.headers[Constant.CONTENT_MD5] = content_md5
    request.headers[Constant.AUTHORIZATION] = self._sign_to_base64(
        url, timestamp, content_md5, self._app_secret)
    request.headers[Constant.SECRET_KEY_ID] = self._app_key
    return request

  def _sign(self, url, timestamp, content_md5, app_secret):
    ''' Sign the specified http request. '''

    string_to_sign = "{}\n{}\n{}\n".format(url, timestamp, content_md5)
    digest = hmac.new(app_secret, string_to_sign, digestmod=sha1)
    return digest.digest()

  def _sign_to_base64(self, url, timestamp, content_md5, app_secret):
    ''' Sign the specified request to base64 encoded result. '''

    signature = self._sign(url, timestamp, content_md5, app_secret)
    return base64.encodestring(signature).strip()

  def _get_header_value(self, http_headers, name):
    if http_headers is not None and name in http_headers:
      value = http_headers[name]
      if type(value) is list:
        return http_headers[name][0]
      else:
        return value
    return ""