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

In a request of pushing messages from MNS to an endpoint, the signature in the Authorization header is generated by implementing the RSA-SHA1algorithm on a string to be signed. The client can use the public key to verify the signature. The following section describes the verification procedure.

Step 1: Retrieve the X509 certificate

In an HTTP request that is sent from MNS to an 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 public key from the certificate.

Step 2: Calculate a string to be signed

    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 is required. The time is displayed in UTC.
  • CanonicalizedMNSHeaders: the HTTP request headers that start with x-mns-. For more information, see the note after the procedure section.
  • CanonicalizedResource: the requested URL. This parameter is required.

Example:

Note Empty rows are not allowed. The value of the Content-Type parameter must be in lowercase.
POST
ZDgxNjY5ZjFlMDQ5MGM0YWMwMWE5ODlmZDVlYmQxYjI=
text/xml;charset=utf-8
Wed, 25 May 2016 10:46:14 GMT
x-mns-request-id:57458276F0E3D56D7C00054B
x-mns-signing-cert-url:aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5wZW0=
x-mns-version:2015-06-06
/notifications       

Step 3: Decrypt the Authorization header

After performing Base64 decoding on the Authorization header, you can decrypt the decoded value by using the public key that is retrieved in Step 1.

Step 4: Verify the signature

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

Notice

The following section describes the naming conventions of the CanonicalizedHeaders parameter (the headers that start with x-mns-).

  • 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 start with x-mns- exist.

Other instructions:

  • A string to be signed must be in UTF-8 format.
  • Use the sha1WithRSAEncryption signature algorithm that is defined in RFC 3447. For more information, see http://tools.ietf.org/html/rfc3447.
  • Use the Base64 algorithm to transcode text.

Java sample code

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 (events.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 be signed.
            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+K55UNhrnlE2UdDkRrwDxsaDP5ajQdg==");
        headers.put("Content-md5", "M2ViOTE2ZDEyOTlkODBjMjVkNzM4YjNhNWI3ZWQ1M2E=");
        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", "56CC2932F0E3D5BD530685CB");
        headers.put("x-mns-signing-cert-url", "aHR0cDovL21uc3Rlc3Qub3NzLWNuLWhhbmd6aG91LmFsaXl1bmNzLmNvbS94NTA5X3B1YmxpY19jZXJ0aWZpY2F0ZS5wZW0=");
        headers.put("x-mns-version", "2015-06-06");
        Boolean res = sd.authenticate("POST", "/notifications", headers);
        System.out.println("Authenticate result:" + res);
    }
}