import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.spec.X509EncodedKeySpec;
public class CallbackHandler {
private static final String AUTHORIZATION = "Authorization";
private static final String XTOS_PUBLIC_KEY_URL = "x-tos-pub-key-url";
public void callbackHandler(HttpServletRequest req, HttpServletResponse resp) {
byte[] sign = getSignature(req);
System.out.println("get callback signature error");
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
byte[] publicKey = getPublicKey(req);
if (publicKey == null) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
stringToSign = getStringToSign(req);
} catch (IOException | NoSuchAlgorithmException e) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
verifySignature(publicKey, stringToSign, sign);
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().write("{\"Status\":\"OK\"}");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
public byte[] getSignature(HttpServletRequest req) {
String authorization = req.getHeader(AUTHORIZATION);
if (authorization == null || authorization.isEmpty()) {
return Base64.getDecoder().decode(authorization);
public byte[] getPublicKey(HttpServletRequest req) {
byte[] bytePublicKey = null;
String publicKeyURLBase64 = req.getHeader(XTOS_PUBLIC_KEY_URL);
if (publicKeyURLBase64 == null || publicKeyURLBase64.isEmpty()) {
byte[] publicKeyURLBytes = Base64.getDecoder().decode(publicKeyURLBase64);
URL url = new URL(new String(publicKeyURLBytes));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
throw new IOException("Get public key failed");
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[16384];
while ((nRead = connection.getInputStream().read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
bytePublicKey = buffer.toByteArray();
connection.disconnect();
} catch (IOException e) {
public String getStringToSign(HttpServletRequest req) throws IOException, NoSuchAlgorithmException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] buff = new byte[16384];
while ((nRead = req.getInputStream().read(buff, 0, buff.length)) != -1) {
buffer.write(buff, 0, nRead);
byte[] bodyContent = buffer.toByteArray();
String stringToSign = calcStringToSign(req, bodyContent);
System.out.println(stringToSign);
System.out.println(new String(bodyContent, StandardCharsets.UTF_8));
public String calcStringToSign(HttpServletRequest req, byte[] body) {
StringBuilder buf = new StringBuilder();
String path = req.getRequestURI();
String queryString = req.getQueryString();
Map<String, List<String>> sortedParams = new TreeMap<>();
if (queryString != null && !queryString.isEmpty()) {
String[] pairs = queryString.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key = (idx > 0) ? pair.substring(0, idx) : pair;
String value = (idx > 0 && pair.length() > idx + 1) ? pair.substring(idx + 1) : "";
key = URLDecoder.decode(key, StandardCharsets.UTF_8.name());
value = URLDecoder.decode(value, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
sortedParams.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
if (!sortedParams.isEmpty()) {
boolean firstKey = true;
for (Map.Entry<String, List<String>> entry : sortedParams.entrySet()) {
String key = entry.getKey();
List<String> values = entry.getValue();
Collections.sort(values);
for (String value : values) {
buf.append(key).append('=').append(value);
buf.append(new String(body, StandardCharsets.UTF_8));
public PublicKey loadPublicKey(byte[] publicKeyPEMBytes) throws Exception {
String publicKeyPEM = new String(publicKeyPEMBytes, StandardCharsets.UTF_8);
String publicKeyPEMFormatted = publicKeyPEM
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEMFormatted);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
return keyFactory.generatePublic(keySpec);
public void verifySignature(byte[] publicKey, String stringToSign, byte[] sign) throws Exception {
PublicKey pubKey = loadPublicKey(publicKey);
Signature verifier = Signature.getInstance("MD5withRSA");
verifier.initVerify(pubKey);
verifier.update(stringToSign.getBytes(StandardCharsets.UTF_8));
if (!verifier.verify(sign)) {
throw new SignatureException("Invalid signature");