From 826f72f64a69e0048ed23a423732a3032dde1d88 Mon Sep 17 00:00:00 2001 From: parsaaes Date: Mon, 11 Apr 2022 00:02:14 +0430 Subject: [PATCH] Add generate sign function --- signature.go | 75 ++++++++++++++++++++++++++++++++++++ signature_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 signature.go create mode 100644 signature_test.go diff --git a/signature.go b/signature.go new file mode 100644 index 0000000..383bc54 --- /dev/null +++ b/signature.go @@ -0,0 +1,75 @@ +package vonage + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "fmt" + "hash" + "sort" + "strings" +) + +type SignMethod string + +const ( + MD5HASH SignMethod = "md5hash" + MD5HMAC SignMethod = "md5hmac" + SHA1HMAC SignMethod = "sha1hmac" + SHA256HMAC SignMethod = "sha256hmac" + SHA512HMAC SignMethod = "sha512hmac" +) + +// GenerateSign generates the signature based on https://developer.vonage.com/concepts/guides/signing-messages +// Params needs to contain all body and query parameters +func GenerateSign(method SignMethod, secret string, params map[string]interface{}) ([]byte, error) { + if params == nil { + params = map[string]interface{}{} + } + + delete(params, "sig") + + keys := []string{} + + for k := range params { + keys = append(keys, k) + } + + sort.Strings(keys) + + queryToSign := "" + + for _, k := range keys { + queryToSign += "&" + k + "=" + + strings.NewReplacer("&", "_", "=", "_").Replace(fmt.Sprintf("%v", params[k])) + } + + var h hash.Hash + + switch method { + case MD5HASH: + queryToSign += secret + + result := md5.Sum([]byte(queryToSign)) + + return result[:], nil + case MD5HMAC: + h = hmac.New(md5.New, []byte(secret)) + case SHA1HMAC: + h = hmac.New(sha1.New, []byte(secret)) + case SHA256HMAC: + h = hmac.New(sha256.New, []byte(secret)) + case SHA512HMAC: + h = hmac.New(sha512.New, []byte(secret)) + default: + return nil, fmt.Errorf("invalid method: %s", method) + } + _, err := h.Write([]byte(queryToSign)) + if err != nil { + return nil, err + } + + return h.Sum(nil), nil +} diff --git a/signature_test.go b/signature_test.go new file mode 100644 index 0000000..d82239a --- /dev/null +++ b/signature_test.go @@ -0,0 +1,97 @@ +package vonage + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestGenerate(t *testing.T) { + cases := []struct { + name string + method SignMethod + params map[string]interface{} + secret string + expectedResult string + errorExpected bool + }{ + { + name: "invalid method", + method: "random method", + errorExpected: true, + }, + { + name: "empty params", + method: MD5HASH, + params: map[string]interface{}{}, + secret: "secret", + expectedResult: "5ebe2294ecd0e0f08eab7690d2a6ee69", + }, + { + name: "only sig param", + method: MD5HASH, + params: map[string]interface{}{ + "sig": "signature", + }, + secret: "secret", + expectedResult: "5ebe2294ecd0e0f08eab7690d2a6ee69", + }, + { + name: "custom param", + method: MD5HASH, + params: map[string]interface{}{ + "from": "NEXMO", + }, + secret: "secret", + expectedResult: "2cdd20a2a0f7270545a98b3ccb87ba51", + }, + { + name: "empty params but with md5 hmac", + method: MD5HMAC, + params: map[string]interface{}{}, + secret: "secret", + expectedResult: "5c8db03f04cec0f43bcb060023914190", + }, + { + name: "empty params but with sha1 hmac", + method: SHA1HMAC, + params: map[string]interface{}{}, + secret: "secret", + expectedResult: "25af6174a0fcecc4d346680a72b7ce644b9a88e8", + }, + { + name: "empty params but with sha256 hmac", + method: SHA256HMAC, + params: map[string]interface{}{}, + secret: "secret", + expectedResult: "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", + }, + { + name: "empty params but with sha512 hmac", + method: SHA512HMAC, + params: map[string]interface{}{}, + secret: "secret", + expectedResult: "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result, err := GenerateSign(tc.method, tc.secret, tc.params) + if tc.errorExpected { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Error("expected no error but got one") + } + + h, _ := hex.DecodeString(tc.expectedResult) + + if !bytes.Equal(result, h) { + t.Error("invalid sign generated") + } + } + }) + } +}