| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package sign
- import (
- "crypto/rsa"
- "fmt"
- "net/http"
- "strings"
- "time"
- )
- const (
- // CookiePolicyName name of the policy cookie
- CookiePolicyName = "CloudFront-Policy"
- // CookieSignatureName name of the signature cookie
- CookieSignatureName = "CloudFront-Signature"
- // CookieKeyIDName name of the signing Key ID cookie
- CookieKeyIDName = "CloudFront-Key-Pair-Id"
- )
- // A CookieOptions optional additional options that can be applied to the signed
- // cookies.
- type CookieOptions struct {
- Path string
- Domain string
- Secure bool
- }
- // apply will integration the options provided into the base cookie options
- // a new copy will be returned. The base CookieOption will not be modified.
- func (o CookieOptions) apply(opts ...func(*CookieOptions)) CookieOptions {
- if len(opts) == 0 {
- return o
- }
- for _, opt := range opts {
- opt(&o)
- }
- return o
- }
- // A CookieSigner provides signing utilities to sign Cookies for Amazon CloudFront
- // resources. Using a private key and Credential Key Pair key ID the CookieSigner
- // only needs to be created once per Credential Key Pair key ID and private key.
- //
- // More information about signed Cookies and their structure can be found at:
- // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html
- //
- // To sign a Cookie, create a CookieSigner with your private key and credential
- // pair key ID. Once you have a CookieSigner instance you can call Sign or
- // SignWithPolicy to sign the URLs.
- //
- // The signer is safe to use concurrently, but the optional cookies options
- // are not safe to modify concurrently.
- type CookieSigner struct {
- keyID string
- privKey *rsa.PrivateKey
- Opts CookieOptions
- }
- // NewCookieSigner constructs and returns a new CookieSigner to be used to for
- // signing Amazon CloudFront URL resources with.
- func NewCookieSigner(keyID string, privKey *rsa.PrivateKey, opts ...func(*CookieOptions)) *CookieSigner {
- signer := &CookieSigner{
- keyID: keyID,
- privKey: privKey,
- Opts: CookieOptions{}.apply(opts...),
- }
- return signer
- }
- // Sign returns the cookies needed to allow user agents to make arbetrary
- // requests to cloudfront for the resource(s) defined by the policy.
- //
- // Sign will create a CloudFront policy with only a resource and condition of
- // DateLessThan equal to the expires time provided.
- //
- // The returned slice cookies should all be added to the Client's cookies or
- // server's response.
- //
- // Example:
- // s := NewCookieSigner(keyID, privKey)
- //
- // // Get Signed cookies for a resource that will expire in 1 hour
- // cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour))
- // if err != nil {
- // fmt.Println("failed to create signed cookies", err)
- // return
- // }
- //
- // // Or get Signed cookies for a resource that will expire in 1 hour
- // // and set path and domain of cookies
- // cookies, err := s.Sign("*", time.Now().Add(1 * time.Hour), func(o *sign.CookieOptions) {
- // o.Path = "/"
- // o.Domain = ".example.com"
- // })
- // if err != nil {
- // fmt.Println("failed to create signed cookies", err)
- // return
- // }
- //
- // // Server Response via http.ResponseWriter
- // for _, c := range cookies {
- // http.SetCookie(w, c)
- // }
- //
- // // Client request via the cookie jar
- // if client.CookieJar != nil {
- // for _, c := range cookies {
- // client.Cookie(w, c)
- // }
- // }
- func (s CookieSigner) Sign(u string, expires time.Time, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
- scheme, err := cookieURLScheme(u)
- if err != nil {
- return nil, err
- }
- resource, err := CreateResource(scheme, u)
- if err != nil {
- return nil, err
- }
- p := NewCannedPolicy(resource, expires)
- return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
- }
- // Returns and validates the URL's scheme.
- // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html#private-content-custom-policy-statement-cookies
- func cookieURLScheme(u string) (string, error) {
- parts := strings.SplitN(u, "://", 2)
- if len(parts) != 2 {
- return "", fmt.Errorf("invalid cookie URL, missing scheme")
- }
- scheme := strings.ToLower(parts[0])
- if scheme != "http" && scheme != "https" && scheme != "http*" {
- return "", fmt.Errorf("invalid cookie URL scheme. Expect http, https, or http*. Go, %s", scheme)
- }
- return scheme, nil
- }
- // SignWithPolicy returns the cookies needed to allow user agents to make
- // arbetrairy requets to cloudfront for the resource(s) defined by the policy.
- //
- // The returned slice cookies should all be added to the Client's cookies or
- // server's response.
- //
- // Example:
- // s := NewCookieSigner(keyID, privKey)
- //
- // policy := &sign.Policy{
- // Statements: []sign.Statement{
- // {
- // // Read the provided documentation on how to set this
- // // correctly, you'll probably want to use wildcards.
- // Resource: RawCloudFrontURL,
- // Condition: sign.Condition{
- // // Optional IP source address range
- // IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
- // // Optional date URL is not valid until
- // DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
- // // Required date the URL will expire after
- // DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
- // },
- // },
- // },
- // }
- //
- // // Get Signed cookies for a resource that will expire in 1 hour
- // cookies, err := s.SignWithPolicy(policy)
- // if err != nil {
- // fmt.Println("failed to create signed cookies", err)
- // return
- // }
- //
- // // Or get Signed cookies for a resource that will expire in 1 hour
- // // and set path and domain of cookies
- // cookies, err := s.Sign(policy, func(o *sign.CookieOptions) {
- // o.Path = "/"
- // o.Domain = ".example.com"
- // })
- // if err != nil {
- // fmt.Println("failed to create signed cookies", err)
- // return
- // }
- //
- // // Server Response via http.ResponseWriter
- // for _, c := range cookies {
- // http.SetCookie(w, c)
- // }
- //
- // // Client request via the cookie jar
- // if client.CookieJar != nil {
- // for _, c := range cookies {
- // client.Cookie(w, c)
- // }
- // }
- func (s CookieSigner) SignWithPolicy(p *Policy, opts ...func(*CookieOptions)) ([]*http.Cookie, error) {
- return createCookies(p, s.keyID, s.privKey, s.Opts.apply(opts...))
- }
- // Prepares the cookies to be attached to the header. An (optional) options
- // struct is provided in case people don't want to manually edit their cookies.
- func createCookies(p *Policy, keyID string, privKey *rsa.PrivateKey, opt CookieOptions) ([]*http.Cookie, error) {
- b64Sig, b64Policy, err := p.Sign(privKey)
- if err != nil {
- return nil, err
- }
- // Creates proper cookies
- cPolicy := &http.Cookie{
- Name: CookiePolicyName,
- Value: string(b64Policy),
- HttpOnly: true,
- }
- cSignature := &http.Cookie{
- Name: CookieSignatureName,
- Value: string(b64Sig),
- HttpOnly: true,
- }
- cKey := &http.Cookie{
- Name: CookieKeyIDName,
- Value: keyID,
- HttpOnly: true,
- }
- cookies := []*http.Cookie{cPolicy, cSignature, cKey}
- // Applie the cookie options
- for _, c := range cookies {
- c.Path = opt.Path
- c.Domain = opt.Domain
- c.Secure = opt.Secure
- }
- return cookies, nil
- }
|