1 package sign
2
3 import (
4 "crypto/hmac"
5 "crypto/sha512"
6 "encoding/base64"
7 "fmt"
8 "net/http"
9 "strings"
10 )
11
12 const (
13 AccessKeyPrefix = "AccessKey"
14 DateHeader = "Date"
15 DateTimeFormat = "2006-01-02T15:04:05.000Z"
16 )
17
18
19 type accessKeyHTTPSigner struct {
20 sharedKey string
21 secretKey string
22 }
23
24
25 func NewAccessKeyHTTPSigner(sharedKey, secretKey string) (HTTPSigner, error) {
26 if len(sharedKey) == 0 || len(secretKey) == 0 {
27 return nil, fmt.Errorf("sharedKey or secretKey cannot be an empty string")
28 }
29 return &accessKeyHTTPSigner{sharedKey, secretKey}, nil
30 }
31
32 func (s *accessKeyHTTPSigner) Sign(req *http.Request) (*http.Request, error) {
33 auth := req.Header.Get("Authorization")
34
35
36 if len(auth) > 0 {
37 return req, nil
38 }
39
40 uniqueKey, err := getUniqueKey(s.secretKey, req)
41 if err != nil {
42 return nil, fmt.Errorf("sign: error while generating new unique key for HMAC signing: %v", err)
43 }
44
45 signableContent := getSignableContent(req)
46
47
48
49 hmacContent, err := calculateHMAC(uniqueKey, signableContent)
50 if err != nil {
51 return nil, fmt.Errorf("sign: error while calculating hmac: %v", err)
52 }
53
54 accessKey := s.sharedKey + ":" + hmacContent
55
56 req.Header.Add("Authorization", fmt.Sprintf("%s %s", AccessKeyPrefix, accessKey))
57
58 return req, nil
59 }
60
61 func getUniqueKey(secretKey string, req *http.Request) (string, error) {
62 date := req.Header.Get(DateHeader)
63 if len(date) == 0 {
64 return "", fmt.Errorf("sign: error while access header %s from the request", DateHeader)
65 }
66 parsedDate, err := http.ParseTime(date)
67 if err != nil {
68 return "", fmt.Errorf("sign: error while parsing date to RFC specification: %v", err)
69 }
70 return strings.TrimSpace(secretKey) + parsedDate.Format(DateTimeFormat), nil
71 }
72
73 func getSignableContent(req *http.Request) string {
74 query := req.URL.RawQuery
75 pathAndQuery := ""
76 if len(query) > 0 {
77 pathAndQuery = req.URL.EscapedPath() + "?" + query
78 } else {
79 pathAndQuery = req.URL.EscapedPath()
80 }
81 headers := req.Header
82 contentType := strings.TrimSpace(headers.Get("Content-Type"))
83 contentMD5 := headers.Get("Content-MD5")
84 nepApplicationKey := headers.Get("nep-application-key")
85 nepCorrelationId := headers.Get("nep-correlation-id")
86 nepOrganization := headers.Get("nep-organization")
87 nepServiceVersion := headers.Get("nep-service-version")
88
89 signableContent := []string{
90 req.Method,
91 pathAndQuery,
92 contentType,
93 contentMD5,
94 nepApplicationKey,
95 nepCorrelationId,
96 nepOrganization,
97 nepServiceVersion,
98 }
99
100 signableContent = filterSignableContent(signableContent, func(c string) bool {
101 return len(c) > 0
102 })
103 return strings.Join(signableContent, "\n")
104 }
105
106 func filterSignableContent(content []string, f func(string) bool) []string {
107 cf := make([]string, 0)
108 for _, c := range content {
109 if f(c) {
110 cf = append(cf, c)
111 }
112 }
113 return cf
114 }
115
116 func calculateHMAC(uniqueKey, signableContent string) (string, error) {
117 h := hmac.New(sha512.New, []byte(uniqueKey))
118 _, err := h.Write([]byte(signableContent))
119 if err != nil {
120 return "", fmt.Errorf("sign: error while writing to HMAC writer: %v", err)
121 }
122 sum := h.Sum(nil)
123 return base64.StdEncoding.EncodeToString(sum), nil
124 }
125
View as plain text