const Authorization = "Authorization"
const XTosPublicKeyUrl = "x-tos-pub-key-url"
func calcStringToSign(req *http.Request, body []byte) []byte {
buf := bytes.NewBuffer(nil)
buf.WriteString(req.URL.Path)
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
for _, key := range keys {
for _, value := range values {
buf.Truncate(buf.Len() - 1)
func getSignature(r *http.Request) ([]byte, error) {
authorization := r.Header.Get(Authorization)
if authorization == "" {
return base64.StdEncoding.DecodeString(authorization)
func getPublicKey(r *http.Request) ([]byte, error) {
var bytePublicKey []byte
publicKeyURLBase64 := r.Header.Get(XTosPublicKeyUrl)
if publicKeyURLBase64 == "" {
return bytePublicKey, errors.New("no x-tos-pub-key-url field in request header ")
publicKeyURL, _ := base64.StdEncoding.DecodeString(publicKeyURLBase64)
resp, err := http.Get(string(publicKeyURL))
return bytePublicKey, err
bytePublicKey, err = ioutil.ReadAll(resp.Body)
return bytePublicKey, err
if resp.StatusCode >= http.StatusBadRequest {
return nil, errors.New("get public key failed")
return bytePublicKey, nil
func getContentAndSignMD5(r *http.Request) ([]byte, []byte, error) {
bodyContent, err := ioutil.ReadAll(r.Body)
stringToSign := calcStringToSign(r, bodyContent)
authMd5.Write(stringToSign)
signMd5 := authMd5.Sum(nil)
return signMd5, bodyContent, nil
func verifySignature(publicKey []byte, signMd5 []byte, sign []byte) error {
pubBlock, _ := pem.Decode(publicKey)
return errors.New("invalid public key pem")
pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
if err != nil || pubInterface == nil {
return errors.New("invalid public key pem")
pub := pubInterface.(*rsa.PublicKey)
err = rsa.VerifyPKCS1v15(pub, crypto.MD5, signMd5, sign)
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
sign, err := getSignature(r)
fmt.Println("get callback signature err:", err.Error())
w.WriteHeader(http.StatusBadRequest)
publicKey, err := getPublicKey(r)
w.WriteHeader(http.StatusBadRequest)
signMd5, body, err := getContentAndSignMD5(r)
w.WriteHeader(http.StatusBadRequest)
if err = verifySignature(publicKey, signMd5, sign); err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"msg":"ok"}`))