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.
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.
|
CanonicalizedMNSHeaders | The combination of HTTP headers that are prefixed by
|
CanonicalizedResource | The URI of the resource requested by the HTTP request. Example: |
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);
}
}