sign_v4.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. package tos
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "time"
  14. )
  15. const (
  16. emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  17. unsignedPayload = "UNSIGNED-PAYLOAD"
  18. signPrefix = "TOS4-HMAC-SHA256"
  19. iso8601Layout = "20060102T150405Z"
  20. yyMMdd = "20060102"
  21. serverTimeFormat = "2006-01-02T15:04:05Z"
  22. authorization = "Authorization"
  23. v4Algorithm = "X-Tos-Algorithm"
  24. v4Credential = "X-Tos-Credential"
  25. v4Date = "X-Tos-Date"
  26. v4Expires = "X-Tos-Expires"
  27. v4SignedHeaders = "X-Tos-SignedHeaders"
  28. v4Signature = "X-Tos-Signature"
  29. v4SignatureLower = "x-tos-signature"
  30. v4ContentSHA256 = "X-Tos-Content-Sha256"
  31. v4SecurityToken = "X-Tos-Security-Token"
  32. v4Prefix = "x-tos"
  33. )
  34. func defaultSigningQueryV4(key string) bool {
  35. return key != v4SignatureLower
  36. }
  37. func defaultSigningHeaderV4(key string, isSigningQuery bool) bool {
  38. return (key == "content-type" && !isSigningQuery) || strings.HasPrefix(key, v4Prefix)
  39. }
  40. func UTCNow() time.Time {
  41. return time.Now().UTC()
  42. }
  43. type Signer interface {
  44. SignHeader(req *Request) http.Header
  45. SignQuery(req *Request, ttl time.Duration) url.Values
  46. }
  47. type SigningKeyInfo struct {
  48. Date string
  49. Region string
  50. Credential *Credential
  51. }
  52. type SignV4 struct {
  53. credentials Credentials
  54. region string
  55. signingHeader func(key string, isSigningQuery bool) bool
  56. signingQuery func(key string) bool
  57. now func() time.Time
  58. signingKey func(*SigningKeyInfo) []byte
  59. logger Logger
  60. }
  61. type signedRes struct {
  62. CanonicalString string
  63. StringToSign string
  64. Sign string
  65. }
  66. type signedHeader struct {
  67. CanonicalString string
  68. StringToSign string
  69. Header http.Header
  70. }
  71. type signedQuery struct {
  72. CanonicalString string
  73. StringToSign string
  74. Query url.Values
  75. }
  76. type SignV4Option func(*SignV4)
  77. func (sv *SignV4) WithSignLogger(logger Logger) {
  78. sv.logger = logger
  79. }
  80. // NewSignV4 create SignV4
  81. // use WithSignKey to set self-defined sign-key generator
  82. // use WithSignLogger to set logger
  83. func NewSignV4(credentials Credentials, region string) *SignV4 {
  84. signV4 := &SignV4{
  85. credentials: credentials,
  86. region: region,
  87. signingHeader: defaultSigningHeaderV4,
  88. signingQuery: defaultSigningQueryV4,
  89. now: UTCNow,
  90. signingKey: SigningKey,
  91. }
  92. return signV4
  93. }
  94. // WithSigningKey for self-defined sign-key generator
  95. func (sv *SignV4) WithSigningKey(signingKey func(*SigningKeyInfo) []byte) {
  96. sv.signingKey = signingKey
  97. }
  98. func (sv *SignV4) signedHeader(header http.Header, isSignedQuery bool) KVs {
  99. var signed = make(KVs, 0, 10)
  100. for key, values := range header {
  101. kk := strings.ToLower(key)
  102. if sv.signingHeader(kk, isSignedQuery) {
  103. vv := make([]string, 0, len(values))
  104. for _, value := range values {
  105. vv = append(vv, strings.Join(strings.Fields(value), " "))
  106. }
  107. signed = append(signed, KV{Key: kk, Values: vv})
  108. }
  109. }
  110. return signed
  111. }
  112. func (sv *SignV4) signedQuery(query url.Values, extra url.Values) KVs {
  113. var signed = make(KVs, 0, len(query)+len(extra))
  114. for key, values := range query {
  115. if sv.signingQuery(strings.ToLower(key)) {
  116. signed = append(signed, KV{Key: key, Values: values})
  117. }
  118. }
  119. for key, values := range extra {
  120. if sv.signingQuery(strings.ToLower(key)) {
  121. signed = append(signed, KV{Key: key, Values: values})
  122. }
  123. }
  124. return signed
  125. }
  126. func (sv *SignV4) canonicalRequest(method, path, contentSha256 string, header, query KVs) string {
  127. const split = byte('\n')
  128. var buf bytes.Buffer
  129. buf.Grow(512)
  130. // Method
  131. buf.WriteString(method)
  132. buf.WriteByte(split)
  133. // URI
  134. buf.Write(encodePath(path))
  135. buf.WriteByte(split)
  136. // query
  137. buf.Write(encodeQuery(query))
  138. buf.WriteByte(split)
  139. // canonical headers
  140. keys := make([]string, 0, len(header))
  141. for _, kv := range header {
  142. keys = append(keys, kv.Key)
  143. buf.WriteString(kv.Key)
  144. buf.WriteByte(':')
  145. buf.WriteString(strings.Join(kv.Values, ","))
  146. buf.WriteByte('\n')
  147. }
  148. buf.WriteByte(split)
  149. // signed headers
  150. buf.WriteString(strings.Join(keys, ";"))
  151. buf.WriteByte(split)
  152. if len(contentSha256) > 0 {
  153. buf.WriteString(contentSha256)
  154. } else {
  155. buf.WriteString(emptySHA256)
  156. }
  157. return buf.String()
  158. }
  159. func SigningKey(info *SigningKeyInfo) []byte {
  160. date := hmacSHA256([]byte(info.Credential.AccessKeySecret), []byte(info.Date))
  161. region := hmacSHA256(date, []byte(info.Region))
  162. service := hmacSHA256(region, []byte("tos"))
  163. return hmacSHA256(service, []byte("request"))
  164. }
  165. func (sv *SignV4) doSign(method, path, contentSha256 string, header, query KVs, now time.Time, cred *Credential) signedRes {
  166. const split = byte('\n')
  167. canonicalStr := sv.canonicalRequest(method, path, contentSha256, header, query)
  168. var buf bytes.Buffer
  169. buf.Grow(len(signPrefix) + 128)
  170. buf.WriteString(signPrefix)
  171. buf.WriteByte(split)
  172. buf.WriteString(now.Format(iso8601Layout))
  173. buf.WriteByte(split)
  174. date := now.Format(yyMMdd)
  175. buf.WriteString(date) // yyMMdd + '/' + region + '/' + service + '/' + request
  176. buf.WriteByte('/')
  177. buf.WriteString(sv.region)
  178. buf.WriteString("/tos/request")
  179. buf.WriteByte(split)
  180. sum := sha256.Sum256([]byte(canonicalStr))
  181. buf.WriteString(hex.EncodeToString(sum[:]))
  182. signK := sv.signingKey(&SigningKeyInfo{Date: date, Region: sv.region, Credential: cred})
  183. sign := hmacSHA256(signK, buf.Bytes())
  184. return signedRes{
  185. CanonicalString: canonicalStr,
  186. StringToSign: buf.String(),
  187. Sign: hex.EncodeToString(sign),
  188. }
  189. }
  190. func (sv *SignV4) SignHeader(req *Request) http.Header {
  191. signed := make(http.Header, 4)
  192. now := sv.now()
  193. date := now.Format(iso8601Layout)
  194. contentSha256 := req.Header.Get(v4ContentSHA256)
  195. signedHeader := sv.signedHeader(req.Header, false)
  196. signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4Date), Values: []string{date}})
  197. signedHeader = append(signedHeader, KV{Key: "date", Values: []string{date}})
  198. signedHeader = append(signedHeader, KV{Key: "host", Values: []string{req.Host}})
  199. // if len(contentSha256) == 0 {
  200. // signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4ContentSHA256), Values: []string{unsignedPayload}})
  201. // signed.Set(v4ContentSHA256, unsignedPayload)
  202. // }
  203. cred := sv.credentials.Credential()
  204. if sts := cred.SecurityToken; len(sts) > 0 {
  205. signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4SecurityToken), Values: []string{sts}})
  206. signed.Set(v4SecurityToken, sts)
  207. }
  208. sort.Sort(signedHeader)
  209. signedQuery := sv.signedQuery(req.Query, nil)
  210. signRes := sv.doSign(req.Method, req.Path, contentSha256, signedHeader, signedQuery, now, &cred)
  211. credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, now.Format(yyMMdd), sv.region)
  212. auth := fmt.Sprintf("TOS4-HMAC-SHA256 Credential=%s,SignedHeaders=%s,Signature=%s", credential, joinKeys(signedHeader), signRes.Sign)
  213. signed.Set(authorization, auth)
  214. signed.Set(v4Date, date)
  215. signed.Set("Date", date)
  216. if sv.logger != nil {
  217. sv.logger.Debug("[tos] CanonicalString:" + "\n" + signRes.CanonicalString + "\n")
  218. sv.logger.Debug("[tos] StringToSign:" + "\n" + signRes.StringToSign + "\n")
  219. }
  220. return signed
  221. }
  222. func (sv *SignV4) SignQuery(req *Request, ttl time.Duration) url.Values {
  223. now := sv.now()
  224. date := now.Format(iso8601Layout)
  225. query := req.Query
  226. extra := make(url.Values)
  227. cred := sv.credentials.Credential()
  228. credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, now.Format(yyMMdd), sv.region)
  229. extra.Add(v4Algorithm, signPrefix)
  230. extra.Add(v4Credential, credential)
  231. extra.Add(v4Date, date)
  232. extra.Add(v4Expires, strconv.FormatInt(ttl.Milliseconds()/1000, 10))
  233. if sts := cred.SecurityToken; len(sts) > 0 {
  234. extra.Add(v4SecurityToken, sts)
  235. }
  236. signedHeader := sv.signedHeader(req.Header, true)
  237. signedHeader = append(signedHeader, KV{Key: "host", Values: []string{req.Host}})
  238. sort.Sort(signedHeader)
  239. extra.Add(v4SignedHeaders, joinKeys(signedHeader))
  240. signedQuery := sv.signedQuery(query, extra)
  241. signRes := sv.doSign(req.Method, req.Path, unsignedPayload, signedHeader, signedQuery, now, &cred)
  242. extra.Add(v4Signature, signRes.Sign)
  243. if sv.logger != nil {
  244. sv.logger.Debug("[tos] CanonicalString:" + "\n" + signRes.CanonicalString + "\n")
  245. sv.logger.Debug("[tos] StringToSign:" + "\n" + signRes.StringToSign + "\n")
  246. }
  247. return extra
  248. }
  249. type KV struct {
  250. Key string
  251. Values []string
  252. }
  253. type KVs []KV
  254. func (kvs KVs) Len() int { return len(kvs) }
  255. func (kvs KVs) Swap(i, j int) { kvs[i], kvs[j] = kvs[j], kvs[i] }
  256. func (kvs KVs) Less(i, j int) bool { return kvs[i].Key < kvs[j].Key }
  257. func joinKeys(kvs KVs) string {
  258. keys := make([]string, 0, len(kvs))
  259. for i := range kvs {
  260. keys = append(keys, kvs[i].Key)
  261. }
  262. sort.Strings(keys)
  263. return strings.Join(keys, ";")
  264. }
  265. func hmacSHA256(key []byte, value []byte) []byte {
  266. h := hmac.New(sha256.New, key)
  267. h.Write(value)
  268. return h.Sum(nil)
  269. }
  270. var (
  271. nonEscape [256]bool
  272. )
  273. // ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.')
  274. func init() {
  275. for i := 'a'; i <= 'z'; i++ {
  276. nonEscape[i] = true
  277. }
  278. for i := 'A'; i <= 'Z'; i++ {
  279. nonEscape[i] = true
  280. }
  281. for i := '0'; i <= '9'; i++ {
  282. nonEscape[i] = true
  283. }
  284. nonEscape['-'] = true
  285. nonEscape['_'] = true
  286. nonEscape['.'] = true
  287. nonEscape['~'] = true
  288. }
  289. func encodePath(path string) []byte {
  290. if len(path) == 0 {
  291. return []byte{'/'}
  292. }
  293. return URIEncode(path, false)
  294. }
  295. func encodeQuery(query KVs) []byte {
  296. if len(query) == 0 {
  297. return make([]byte, 0)
  298. }
  299. var buf bytes.Buffer
  300. buf.Grow(512)
  301. sort.Sort(query)
  302. for _, kv := range query {
  303. keyEscaped := URIEncode(kv.Key, true)
  304. for _, v := range kv.Values {
  305. if buf.Len() > 0 {
  306. buf.WriteByte('&')
  307. }
  308. buf.Write(keyEscaped)
  309. buf.WriteByte('=')
  310. buf.Write(URIEncode(v, true))
  311. }
  312. }
  313. return buf.Bytes()
  314. }
  315. func URIEncode(in string, encodeSlash bool) []byte {
  316. hexCount := 0
  317. for i := 0; i < len(in); i++ {
  318. c := uint8(in[i])
  319. if c == '/' {
  320. if encodeSlash {
  321. hexCount++
  322. }
  323. } else if !nonEscape[c] {
  324. hexCount++
  325. }
  326. }
  327. encoded := make([]byte, len(in)+2*hexCount)
  328. for i, j := 0, 0; i < len(in); i++ {
  329. c := uint8(in[i])
  330. if c == '/' {
  331. if encodeSlash {
  332. encoded[j] = '%'
  333. encoded[j+1] = '2'
  334. encoded[j+2] = 'F'
  335. j += 3
  336. } else {
  337. encoded[j] = c
  338. j++
  339. }
  340. } else if !nonEscape[c] {
  341. encoded[j] = '%'
  342. encoded[j+1] = "0123456789ABCDEF"[c>>4]
  343. encoded[j+2] = "0123456789ABCDEF"[c&15]
  344. j += 3
  345. } else {
  346. encoded[j] = c
  347. j++
  348. }
  349. }
  350. return encoded
  351. }