sign_cookie.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package sign
  2. import (
  3. "crypto/rsa"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "time"
  8. )
  9. const (
  10. // CookiePolicyName name of the policy cookie
  11. CookiePolicyName = "CloudFront-Policy"
  12. // CookieSignatureName name of the signature cookie
  13. CookieSignatureName = "CloudFront-Signature"
  14. // CookieKeyIDName name of the signing Key ID cookie
  15. CookieKeyIDName = "CloudFront-Key-Pair-Id"
  16. )
  17. // A CookieOptions optional additional options that can be applied to the signed
  18. // cookies.
  19. type CookieOptions struct {
  20. Path string
  21. Domain string
  22. Secure bool
  23. }
  24. // apply will integration the options provided into the base cookie options
  25. // a new copy will be returned. The base CookieOption will not be modified.
  26. func (o CookieOptions) apply(opts ...func(*CookieOptions)) CookieOptions {
  27. if len(opts) == 0 {
  28. return o
  29. }
  30. for _, opt := range opts {
  31. opt(&o)
  32. }
  33. return o
  34. }
  35. // A CookieSigner provides signing utilities to sign Cookies for Amazon CloudFront
  36. // resources. Using a private key and Credential Key Pair key ID the CookieSigner
  37. // only needs to be created once per Credential Key Pair key ID and private key.
  38. //
  39. // More information about signed Cookies and their structure can be found at:
  40. // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html
  41. //
  42. // To sign a Cookie, create a CookieSigner with your private key and credential
  43. // pair key ID. Once you have a CookieSigner instance you can call Sign or
  44. // SignWithPolicy to sign the URLs.
  45. //
  46. // The signer is safe to use concurrently, but the optional cookies options
  47. // are not safe to modify concurrently.
  48. type CookieSigner struct {
  49. keyID string
  50. privKey *rsa.PrivateKey
  51. Opts CookieOptions
  52. }
  53. // NewCookieSigner constructs and returns a new CookieSigner to be used to for
  54. // signing Amazon CloudFront URL resources with.
  55. func NewCookieSigner(keyID string, privKey *rsa.PrivateKey, opts ...func(*CookieOptions)) *CookieSigner {
  56. signer := &CookieSigner{
  57. keyID: keyID,
  58. privKey: privKey,
  59. Opts: CookieOptions{}.apply(opts...),
  60. }
  61. return signer
  62. }
  63. // Sign returns the cookies needed to allow user agents to make arbetrary
  64. // requests to cloudfront for the resource(s) defined by the policy.
  65. //
  66. // Sign will create a CloudFront policy with only a resource and condition of
  67. // DateLessThan equal to the expires time provided.
  68. //
  69. // The returned slice cookies should all be added to the Client's cookies or
  70. // server's response.
  71. //
  72. // Example:
  73. // s := NewCookieSigner(keyID, privKey)
  74. //
  75. // // Get Signed cookies for a resource that will expire in 1 hour
  76. // cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour))
  77. // if err != nil {
  78. // fmt.Println("failed to create signed cookies", err)
  79. // return
  80. // }
  81. //
  82. // // Or get Signed cookies for a resource that will expire in 1 hour
  83. // // and set path and domain of cookies
  84. // cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour), func(o *sign.CookieOptions) {
  85. // o.Path = "/"
  86. // o.Domain = ".example.com"
  87. // })
  88. // if err != nil {
  89. // fmt.Println("failed to create signed cookies", err)
  90. // return
  91. // }
  92. //
  93. // // Server Response via http.ResponseWriter
  94. // for _, c := range cookies {
  95. // http.SetCookie(w, c)
  96. // }
  97. //
  98. // // Client request via the cookie jar
  99. // if client.CookieJar != nil {
  100. // for _, c := range cookies {
  101. // client.Cookie(w, c)
  102. // }
  103. // }
  104. func (s CookieSigner) Sign(u string, expires time.Time, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
  105. scheme, err := cookieURLScheme(u)
  106. if err != nil {
  107. return nil, err
  108. }
  109. resource, err := CreateResource(scheme, u)
  110. if err != nil {
  111. return nil, err
  112. }
  113. p := NewCannedPolicy(resource, expires)
  114. return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
  115. }
  116. // Returns and validates the URL's scheme.
  117. // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html#private-content-custom-policy-statement-cookies
  118. func cookieURLScheme(u string) (string, error) {
  119. parts := strings.SplitN(u, "://", 2)
  120. if len(parts) != 2 {
  121. return "", fmt.Errorf("invalid cookie URL, missing scheme")
  122. }
  123. scheme := strings.ToLower(parts[0])
  124. if scheme != "http" && scheme != "https" && scheme != "http*" {
  125. return "", fmt.Errorf("invalid cookie URL scheme. Expect http, https, or http*. Go, %s", scheme)
  126. }
  127. return scheme, nil
  128. }
  129. // SignWithPolicy returns the cookies needed to allow user agents to make
  130. // arbetrairy requets to cloudfront for the resource(s) defined by the policy.
  131. //
  132. // The returned slice cookies should all be added to the Client's cookies or
  133. // server's response.
  134. //
  135. // Example:
  136. // s := NewCookieSigner(keyID, privKey)
  137. //
  138. // policy := &sign.Policy{
  139. // Statements: []sign.Statement{
  140. // {
  141. // // Read the provided documentation on how to set this
  142. // // correctly, you'll probably want to use wildcards.
  143. // Resource: RawCloudFrontURL,
  144. // Condition: sign.Condition{
  145. // // Optional IP source address range
  146. // IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
  147. // // Optional date URL is not valid until
  148. // DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
  149. // // Required date the URL will expire after
  150. // DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
  151. // },
  152. // },
  153. // },
  154. // }
  155. //
  156. // // Get Signed cookies for a resource that will expire in 1 hour
  157. // cookies, err := s.SignWithPolicy(policy)
  158. // if err != nil {
  159. // fmt.Println("failed to create signed cookies", err)
  160. // return
  161. // }
  162. //
  163. // // Or get Signed cookies for a resource that will expire in 1 hour
  164. // // and set path and domain of cookies
  165. // cookies, err := s.Sign(policy, func(o *sign.CookieOptions) {
  166. // o.Path = "/"
  167. // o.Domain = ".example.com"
  168. // })
  169. // if err != nil {
  170. // fmt.Println("failed to create signed cookies", err)
  171. // return
  172. // }
  173. //
  174. // // Server Response via http.ResponseWriter
  175. // for _, c := range cookies {
  176. // http.SetCookie(w, c)
  177. // }
  178. //
  179. // // Client request via the cookie jar
  180. // if client.CookieJar != nil {
  181. // for _, c := range cookies {
  182. // client.Cookie(w, c)
  183. // }
  184. // }
  185. func (s CookieSigner) SignWithPolicy(p *Policy, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
  186. return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
  187. }
  188. // Prepares the cookies to be attached to the header. An (optional) options
  189. // struct is provided in case people don't want to manually edit their cookies.
  190. func createCookies(p *Policy, keyID string, privKey *rsa.PrivateKey, opt CookieOptions) ([]*http.Cookie, error) {
  191. b64Sig, b64Policy, err := p.Sign(privKey)
  192. if err != nil {
  193. return nil, err
  194. }
  195. // Creates proper cookies
  196. cPolicy := &http.Cookie{
  197. Name: CookiePolicyName,
  198. Value: string(b64Policy),
  199. HttpOnly: true,
  200. }
  201. cSignature := &http.Cookie{
  202. Name: CookieSignatureName,
  203. Value: string(b64Sig),
  204. HttpOnly: true,
  205. }
  206. cKey := &http.Cookie{
  207. Name: CookieKeyIDName,
  208. Value: keyID,
  209. HttpOnly: true,
  210. }
  211. cookies := []*http.Cookie{cPolicy, cSignature, cKey}
  212. // Applie the cookie options
  213. for _, c := range cookies {
  214. c.Path = opt.Path
  215. c.Domain = opt.Domain
  216. c.Secure = opt.Secure
  217. }
  218. return cookies, nil
  219. }