This article describes how to sign a request that is sent from Message Service (MNS) to an endpoint. You can retrieve a signature certificate from the x-mns-signing-cert-url header in the request that is sent to the endpoint, and verify whether the request is sent from MNS. This allows you to prevent the impact of malicious requests.

In the request of pushing messages from MNS to the endpoint, MNS generates the signature in the Authorization header by implementing the RSA-SHA1 algorithm on a string-to-sign. The endpoint can use the AccessKey pair to verify the signature. The following sections describe the verification procedure.

Step 1: Retrieve the X509 certificate

In the HTTP request that is sent from MNS to the endpoint, the x-mns-signing-cert-url header specifies a string that indicates the URL of the signature certificate. You must perform Base64 decoding on the string to retrieve the URL of the signature certificate and retrieve the AccessKey pair from the certificate.

Step 2: Calculate the string-to-sign

VERB + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedMNSHeaders
+ CanonicalizedResource 
  • VERB: the HTTP method.
  • CONTENT-MD5: the MD5 hash of the request body.
  • CONTENT-TYPE: the content type of the request body. The value of this parameter must be in lowercase.
  • DATE: the time when the request was sent. This parameter cannot be set to null. The time must be in the UTC format.
  • CanonicalizedMNSHeaders: the HTTP request headers that are prefixed with x-mns-.
  • CanonicalizedResource: the URL of the request. This parameter cannot be set to null.

Example:

POST
ZDgxNjY5ZjFlMDQ5MGM0YWMwMWE5ODlmZDVlYmQxYjI=
text/xml;charset=utf-8
Wed, 25 May 2016 10:46:14 GMT
x-mns-request-id:57458276F0E3D56D7C00****
x-mns-signing-cert-url:aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5w****
x-mns-version:2015-06-06
/notifications       

Step 3: Decrypt the Authorization header

After you perform Base64 decoding on the Authorization header, you can decrypt the decoded value by using the AccessKey pair retrieved in Step 1.

Step 4: Verify the signature

Compare the string-to-sign that is generated in Step 2 with the decrypted string that is generated in Step 3. If the two strings are the same, the request from MNS is verified.

Notice

You must take note of the following naming conventions for the CanonicalizedHeaders parameter (the headers that are prefixed with CanonicalizedMNSHeaders):

  • The header names must be in lowercase.
  • The headers must be sorted in ascending order.
  • Do not add a space before or after the colon (:) that separates a header name and value.
  • Each header is followed by a line feed (\n). Do not specify the CanonicalizedMNSHeaders parameter if no headers that are prefixed with x-mns- exist.

Additional information:

  • The string-to-sign must be in the UTF-8 format.
  • The sha1WithRSAEncryption signature algorithm defined in RFC 3447 must be used.
  • The Base64 algorithm must be used to transcode text.

Sample code in Java

public class SignDemo {
    private Boolean authenticate(String method, String uri, Map<String, String> headers) {
        try {
            // Retrieve the URL of the signature certificate.
            if (! headers.containsKey("x-mns-signing-cert-url")) {
                System.out.println("x-mns-signing-cert-url Header not found");
                return false;
            }
            String cert = headers.get("x-mns-signing-cert-url");
            if (cert.isEmpty()) {
                System.out.println("x-mns-signing-cert-url empty");
                return false;
            }
            cert = new String(Base64.decodeBase64(cert));
            System.out.println("x-mns-signing-cert-url:\t" + cert);

            // Use the URL to retrieve the certificate and retrieve the public key from the certificate.
            URL url = new URL(cert);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            DataInputStream in = new DataInputStream(conn.getInputStream());
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate c = cf.generateCertificate(in);
            PublicKey pk = c.getPublicKey();

            // Retrieve the string-to-sign.
            String str2sign = getSignStr(method, uri, headers);
            System.out.println("String2Sign:\t" + str2sign);

            // Perform Base64 decoding on the Authorization header.
            String signature = headers.get("Authorization");
            byte[] decodedSign = Base64.decodeBase64(signature);

            // Verify the signature.
            java.security.Signature signetcheck = java.security.Signature.getInstance("SHA1withRSA");
            signetcheck.initVerify(pk);
            signetcheck.update(str2sign.getBytes());
            Boolean res = signetcheck.verify(decodedSign);
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private String getSignStr(String method, String uri, Map<String, String> headers) {
        StringBuilder sb = new StringBuilder();
        sb.append(method);
        sb.append("\n");
        sb.append(safeGetHeader(headers, "Content-md5"));
        sb.append("\n");
        sb.append(safeGetHeader(headers, "Content-Type"));
        sb.append("\n");
        sb.append(safeGetHeader(headers, "Date"));
        sb.append("\n");

        List<String> tmp = new ArrayList<String>();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            if (entry.getKey().startsWith("x-mns-")) {
                tmp.add(entry.getKey() + ":" + entry.getValue());
            }
        }
        Collections.sort(tmp);

        for (String kv : tmp) {
            sb.append(kv);
            sb.append("\n");
        }

        sb.append(uri);
        return sb.toString();
    }

    private String safeGetHeader(Map<String, String> headers, String name) {
        if (headers.containsKey(name)) {
            return headers.get(name);
        } else {
            return "";
        }
    }

    public static void main(String[] args) {
        SignDemo sd = new SignDemo();
        Map<String, String> headers = new HashMap<String, String>();
        headers.put("Authorization", "Mko2Azg9fhCw8qR6G7AeAFMyzjO9qn7LDA5/t9E+6X5XURXTqBUuhpK+K55UNhrnlE2UdDkRrwDxsaDP5ajQ****");
        headers.put("Content-md5", "M2ViOTE2ZDEyOTlkODBjMjVkNzM4YjNhNWI3ZWQ1****");
        headers.put("Content-Type", "text/xml;charset=utf-8");
        headers.put("Date", "Tue, 23 Feb 2016 09:41:06 GMT");
        headers.put("x-mns-request-id", "56CC2932F0E3D5BD5306****");
        headers.put("x-mns-signing-cert-url", "aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5w****");
        headers.put("x-mns-version", "2015-06-06");
        Boolean res = sd.authenticate("POST", "/notifications", headers);
        System.out.println("Authenticate result:" + res);
    }
}