You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

358 lines
10 KiB

  1. package aws
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "encoding/base64"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "sort"
  13. "strings"
  14. "time"
  15. )
  16. type V2Signer struct {
  17. auth Auth
  18. service ServiceInfo
  19. host string
  20. }
  21. var b64 = base64.StdEncoding
  22. func NewV2Signer(auth Auth, service ServiceInfo) (*V2Signer, error) {
  23. u, err := url.Parse(service.Endpoint)
  24. if err != nil {
  25. return nil, err
  26. }
  27. return &V2Signer{auth: auth, service: service, host: u.Host}, nil
  28. }
  29. func (s *V2Signer) Sign(method, path string, params map[string]string) {
  30. params["AWSAccessKeyId"] = s.auth.AccessKey
  31. params["SignatureVersion"] = "2"
  32. params["SignatureMethod"] = "HmacSHA256"
  33. if s.auth.Token() != "" {
  34. params["SecurityToken"] = s.auth.Token()
  35. }
  36. // AWS specifies that the parameters in a signed request must
  37. // be provided in the natural order of the keys. This is distinct
  38. // from the natural order of the encoded value of key=value.
  39. // Percent and Equals affect the sorting order.
  40. var keys, sarray []string
  41. for k, _ := range params {
  42. keys = append(keys, k)
  43. }
  44. sort.Strings(keys)
  45. for _, k := range keys {
  46. sarray = append(sarray, Encode(k)+"="+Encode(params[k]))
  47. }
  48. joined := strings.Join(sarray, "&")
  49. payload := method + "\n" + s.host + "\n" + path + "\n" + joined
  50. hash := hmac.New(sha256.New, []byte(s.auth.SecretKey))
  51. hash.Write([]byte(payload))
  52. signature := make([]byte, b64.EncodedLen(hash.Size()))
  53. b64.Encode(signature, hash.Sum(nil))
  54. params["Signature"] = string(signature)
  55. }
  56. // Common date formats for signing requests
  57. const (
  58. ISO8601BasicFormat = "20060102T150405Z"
  59. ISO8601BasicFormatShort = "20060102"
  60. )
  61. type Route53Signer struct {
  62. auth Auth
  63. }
  64. func NewRoute53Signer(auth Auth) *Route53Signer {
  65. return &Route53Signer{auth: auth}
  66. }
  67. // getCurrentDate fetches the date stamp from the aws servers to
  68. // ensure the auth headers are within 5 minutes of the server time
  69. func (s *Route53Signer) getCurrentDate() string {
  70. response, err := http.Get("https://route53.amazonaws.com/date")
  71. if err != nil {
  72. fmt.Print("Unable to get date from amazon: ", err)
  73. return ""
  74. }
  75. response.Body.Close()
  76. return response.Header.Get("Date")
  77. }
  78. // Creates the authorize signature based on the date stamp and secret key
  79. func (s *Route53Signer) getHeaderAuthorize(message string) string {
  80. hmacSha256 := hmac.New(sha256.New, []byte(s.auth.SecretKey))
  81. hmacSha256.Write([]byte(message))
  82. cryptedString := hmacSha256.Sum(nil)
  83. return base64.StdEncoding.EncodeToString(cryptedString)
  84. }
  85. // Adds all the required headers for AWS Route53 API to the request
  86. // including the authorization
  87. func (s *Route53Signer) Sign(req *http.Request) {
  88. date := s.getCurrentDate()
  89. authHeader := fmt.Sprintf("AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=%s,Signature=%s",
  90. s.auth.AccessKey, "HmacSHA256", s.getHeaderAuthorize(date))
  91. req.Header.Set("Host", req.Host)
  92. req.Header.Set("X-Amzn-Authorization", authHeader)
  93. req.Header.Set("X-Amz-Date", date)
  94. req.Header.Set("Content-Type", "application/xml")
  95. }
  96. /*
  97. The V4Signer encapsulates all of the functionality to sign a request with the AWS
  98. Signature Version 4 Signing Process. (http://goo.gl/u1OWZz)
  99. */
  100. type V4Signer struct {
  101. auth Auth
  102. serviceName string
  103. region Region
  104. }
  105. /*
  106. Return a new instance of a V4Signer capable of signing AWS requests.
  107. */
  108. func NewV4Signer(auth Auth, serviceName string, region Region) *V4Signer {
  109. return &V4Signer{auth: auth, serviceName: serviceName, region: region}
  110. }
  111. /*
  112. Sign a request according to the AWS Signature Version 4 Signing Process. (http://goo.gl/u1OWZz)
  113. The signed request will include an "x-amz-date" header with a current timestamp if a valid "x-amz-date"
  114. or "date" header was not available in the original request. In addition, AWS Signature Version 4 requires
  115. the "host" header to be a signed header, therefor the Sign method will manually set a "host" header from
  116. the request.Host.
  117. The signed request will include a new "Authorization" header indicating that the request has been signed.
  118. Any changes to the request after signing the request will invalidate the signature.
  119. */
  120. func (s *V4Signer) Sign(req *http.Request) {
  121. req.Header.Set("host", req.Host) // host header must be included as a signed header
  122. t := s.requestTime(req) // Get requst time
  123. creq := s.canonicalRequest(req) // Build canonical request
  124. sts := s.stringToSign(t, creq) // Build string to sign
  125. signature := s.signature(t, sts) // Calculate the AWS Signature Version 4
  126. auth := s.authorization(req.Header, t, signature) // Create Authorization header value
  127. req.Header.Set("Authorization", auth) // Add Authorization header to request
  128. return
  129. }
  130. /*
  131. requestTime method will parse the time from the request "x-amz-date" or "date" headers.
  132. If the "x-amz-date" header is present, that will take priority over the "date" header.
  133. If neither header is defined or we are unable to parse either header as a valid date
  134. then we will create a new "x-amz-date" header with the current time.
  135. */
  136. func (s *V4Signer) requestTime(req *http.Request) time.Time {
  137. // Get "x-amz-date" header
  138. date := req.Header.Get("x-amz-date")
  139. // Attempt to parse as ISO8601BasicFormat
  140. t, err := time.Parse(ISO8601BasicFormat, date)
  141. if err == nil {
  142. return t
  143. }
  144. // Attempt to parse as http.TimeFormat
  145. t, err = time.Parse(http.TimeFormat, date)
  146. if err == nil {
  147. req.Header.Set("x-amz-date", t.Format(ISO8601BasicFormat))
  148. return t
  149. }
  150. // Get "date" header
  151. date = req.Header.Get("date")
  152. // Attempt to parse as http.TimeFormat
  153. t, err = time.Parse(http.TimeFormat, date)
  154. if err == nil {
  155. return t
  156. }
  157. // Create a current time header to be used
  158. t = time.Now().UTC()
  159. req.Header.Set("x-amz-date", t.Format(ISO8601BasicFormat))
  160. return t
  161. }
  162. /*
  163. canonicalRequest method creates the canonical request according to Task 1 of the AWS Signature Version 4 Signing Process. (http://goo.gl/eUUZ3S)
  164. CanonicalRequest =
  165. HTTPRequestMethod + '\n' +
  166. CanonicalURI + '\n' +
  167. CanonicalQueryString + '\n' +
  168. CanonicalHeaders + '\n' +
  169. SignedHeaders + '\n' +
  170. HexEncode(Hash(Payload))
  171. */
  172. func (s *V4Signer) canonicalRequest(req *http.Request) string {
  173. c := new(bytes.Buffer)
  174. fmt.Fprintf(c, "%s\n", req.Method)
  175. fmt.Fprintf(c, "%s\n", s.canonicalURI(req.URL))
  176. fmt.Fprintf(c, "%s\n", s.canonicalQueryString(req.URL))
  177. fmt.Fprintf(c, "%s\n\n", s.canonicalHeaders(req.Header))
  178. fmt.Fprintf(c, "%s\n", s.signedHeaders(req.Header))
  179. fmt.Fprintf(c, "%s", s.payloadHash(req))
  180. return c.String()
  181. }
  182. func (s *V4Signer) canonicalURI(u *url.URL) string {
  183. canonicalPath := u.RequestURI()
  184. if u.RawQuery != "" {
  185. canonicalPath = canonicalPath[:len(canonicalPath)-len(u.RawQuery)-1]
  186. }
  187. slash := strings.HasSuffix(canonicalPath, "/")
  188. canonicalPath = path.Clean(canonicalPath)
  189. if canonicalPath != "/" && slash {
  190. canonicalPath += "/"
  191. }
  192. return canonicalPath
  193. }
  194. func (s *V4Signer) canonicalQueryString(u *url.URL) string {
  195. var a []string
  196. for k, vs := range u.Query() {
  197. k = Encode(k)
  198. for _, v := range vs {
  199. if v == "" {
  200. a = append(a, k+"=")
  201. } else {
  202. v = Encode(v)
  203. a = append(a, k+"="+v)
  204. }
  205. }
  206. }
  207. sort.Strings(a)
  208. return strings.Join(a, "&")
  209. }
  210. func (s *V4Signer) canonicalHeaders(h http.Header) string {
  211. i, a := 0, make([]string, len(h))
  212. for k, v := range h {
  213. for j, w := range v {
  214. v[j] = strings.Trim(w, " ")
  215. }
  216. sort.Strings(v)
  217. a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
  218. i++
  219. }
  220. sort.Strings(a)
  221. return strings.Join(a, "\n")
  222. }
  223. func (s *V4Signer) signedHeaders(h http.Header) string {
  224. i, a := 0, make([]string, len(h))
  225. for k, _ := range h {
  226. a[i] = strings.ToLower(k)
  227. i++
  228. }
  229. sort.Strings(a)
  230. return strings.Join(a, ";")
  231. }
  232. func (s *V4Signer) payloadHash(req *http.Request) string {
  233. var b []byte
  234. if req.Body == nil {
  235. b = []byte("")
  236. } else {
  237. var err error
  238. b, err = ioutil.ReadAll(req.Body)
  239. if err != nil {
  240. // TODO: I REALLY DON'T LIKE THIS PANIC!!!!
  241. panic(err)
  242. }
  243. }
  244. req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
  245. return s.hash(string(b))
  246. }
  247. /*
  248. stringToSign method creates the string to sign accorting to Task 2 of the AWS Signature Version 4 Signing Process. (http://goo.gl/es1PAu)
  249. StringToSign =
  250. Algorithm + '\n' +
  251. RequestDate + '\n' +
  252. CredentialScope + '\n' +
  253. HexEncode(Hash(CanonicalRequest))
  254. */
  255. func (s *V4Signer) stringToSign(t time.Time, creq string) string {
  256. w := new(bytes.Buffer)
  257. fmt.Fprint(w, "AWS4-HMAC-SHA256\n")
  258. fmt.Fprintf(w, "%s\n", t.Format(ISO8601BasicFormat))
  259. fmt.Fprintf(w, "%s\n", s.credentialScope(t))
  260. fmt.Fprintf(w, "%s", s.hash(creq))
  261. return w.String()
  262. }
  263. func (s *V4Signer) credentialScope(t time.Time) string {
  264. return fmt.Sprintf("%s/%s/%s/aws4_request", t.Format(ISO8601BasicFormatShort), s.region.Name, s.serviceName)
  265. }
  266. /*
  267. signature method calculates the AWS Signature Version 4 according to Task 3 of the AWS Signature Version 4 Signing Process. (http://goo.gl/j0Yqe1)
  268. signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
  269. */
  270. func (s *V4Signer) signature(t time.Time, sts string) string {
  271. h := s.hmac(s.derivedKey(t), []byte(sts))
  272. return fmt.Sprintf("%x", h)
  273. }
  274. /*
  275. derivedKey method derives a signing key to be used for signing a request.
  276. kSecret = Your AWS Secret Access Key
  277. kDate = HMAC("AWS4" + kSecret, Date)
  278. kRegion = HMAC(kDate, Region)
  279. kService = HMAC(kRegion, Service)
  280. kSigning = HMAC(kService, "aws4_request")
  281. */
  282. func (s *V4Signer) derivedKey(t time.Time) []byte {
  283. h := s.hmac([]byte("AWS4"+s.auth.SecretKey), []byte(t.Format(ISO8601BasicFormatShort)))
  284. h = s.hmac(h, []byte(s.region.Name))
  285. h = s.hmac(h, []byte(s.serviceName))
  286. h = s.hmac(h, []byte("aws4_request"))
  287. return h
  288. }
  289. /*
  290. authorization method generates the authorization header value.
  291. */
  292. func (s *V4Signer) authorization(header http.Header, t time.Time, signature string) string {
  293. w := new(bytes.Buffer)
  294. fmt.Fprint(w, "AWS4-HMAC-SHA256 ")
  295. fmt.Fprintf(w, "Credential=%s/%s, ", s.auth.AccessKey, s.credentialScope(t))
  296. fmt.Fprintf(w, "SignedHeaders=%s, ", s.signedHeaders(header))
  297. fmt.Fprintf(w, "Signature=%s", signature)
  298. return w.String()
  299. }
  300. // hash method calculates the sha256 hash for a given string
  301. func (s *V4Signer) hash(in string) string {
  302. h := sha256.New()
  303. fmt.Fprintf(h, "%s", in)
  304. return fmt.Sprintf("%x", h.Sum(nil))
  305. }
  306. // hmac method calculates the sha256 hmac for a given slice of bytes
  307. func (s *V4Signer) hmac(key, data []byte) []byte {
  308. h := hmac.New(sha256.New, key)
  309. h.Write(data)
  310. return h.Sum(nil)
  311. }