Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions signature.go
Original file line number Diff line number Diff line change
@@ -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
}
97 changes: 97 additions & 0 deletions signature_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
})
}
}