All Products
Search
Document Center

Simple Message Queue (formerly MNS):Verify a signature on an HTTP server

Last Updated:Oct 28, 2024

In a request for pushing messages from Simple Message Queue (formerly MNS) to an endpoint, the signature in the Authorization header is generated by implementing the RSA-SHA1 algorithm on a string-to-sign. This topic describes how to verify a signature on an HTTP server by using the public key.

Step 1: Retrieve the X509 certificate

In an HTTP request that is sent from SMQ 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.

Note

The signature certificate is valid only if the URL prefix of the signature certificate is https://mnstest.oss-cn-hangzhou.aliyuncs.com/. Otherwise, the certificate is invalid. For more information, see How do I check whether the URL of a public key certificate is provided by Alibaba Cloud?

Step 2: Construct a string-to-sign

Construct a string-to-sign based on the following pseudocode:

StringToSign = HttpMethod + "\n" 
         + CONTENT-MD5 + "\n"     
         + CONTENT-TYPE + "\n" 
         + DATE + "\n" 
         + CanonicalizedMNSHeaders
         + CanonicalizedResource;

The following table describes the parameters.

Parameter

Description

HttpMethod

The HTTP method in uppercase letters, such as PUT, GET, POST, or DELETE.

Content-MD5

The MD5 hash value of the request body. If the Content-MD5 header is not specified, leave this parameter empty.

CONTENT-TYPE

The type of the request body. If the Content-Type header is not specified, leave this parameter empty.

DATE

The time when the request was sent.

  • Example: Thu, 07 Mar 2012 18:49:58 GMT. If you use the x-mns-date header instead of the Date header, set this parameter to the value of x-mns-date header.

  • This parameter is required and the value must be in the GMT format.

  • If SMQ does not receive a request within 15 minutes after the request is sent, SMQ considers the request is invalid and returns the error code 400. For more information, see Error codes.

CanonicalizedMNSHeaders

The combination of HTTP headers that are prefixed by x-mns-. The value of this parameter must meet the following requirements:

  • The header names must be in lowercase.

  • The headers must be sorted in alphabetical order.

  • The headers are concatenated.

    // The original request headers.
    Map<String, String> httpHeaders = request.getHeaders();
    // Sort the headers and convert the headers to lowercase.
    sortHeadersKeyAndToLowerCase(httpHeaders);
    // Concatenate the headers.
    Set<String> keySet = httpHeaders.keySet();
    for (String key : keySet) {
        if (key.startsWith("x-mns-")) {
            CanonicalizedMNSHeaders.append(key).append(":")
                .append(httpHeaders.get(key)).append("\n");
        }
    }

CanonicalizedResource

The URI of the resource requested by the HTTP request. Example: /queues/$queueName?metaOverride=true.

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 public key 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 SMQ is valid. Otherwise, reject the request.

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 (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);

            // Decode the Authorization header in Base64. 
            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);
    }
}