143 lines
3.2 KiB
Go
143 lines
3.2 KiB
Go
package cloudfront
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/AdRoll/goamz/aws"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type CloudFront struct {
|
|
BaseURL string
|
|
keyPairId string
|
|
key *rsa.PrivateKey
|
|
}
|
|
|
|
var base64Replacer = strings.NewReplacer("=", "_", "+", "-", "/", "~")
|
|
|
|
func NewKeyLess(auth aws.Auth, baseurl string) *CloudFront {
|
|
return &CloudFront{keyPairId: auth.AccessKey, BaseURL: baseurl}
|
|
}
|
|
|
|
func New(baseurl string, key *rsa.PrivateKey, keyPairId string) *CloudFront {
|
|
return &CloudFront{
|
|
BaseURL: baseurl,
|
|
keyPairId: keyPairId,
|
|
key: key,
|
|
}
|
|
}
|
|
|
|
type epochTime struct {
|
|
EpochTime int64 `json:"AWS:EpochTime"`
|
|
}
|
|
|
|
type condition struct {
|
|
DateLessThan epochTime
|
|
}
|
|
|
|
type statement struct {
|
|
Resource string
|
|
Condition condition
|
|
}
|
|
|
|
type policy struct {
|
|
Statement []statement
|
|
}
|
|
|
|
func buildPolicy(resource string, expireTime time.Time) ([]byte, error) {
|
|
p := &policy{
|
|
Statement: []statement{
|
|
statement{
|
|
Resource: resource,
|
|
Condition: condition{
|
|
DateLessThan: epochTime{
|
|
EpochTime: expireTime.Truncate(time.Millisecond).Unix(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return json.Marshal(p)
|
|
}
|
|
|
|
func (cf *CloudFront) generateSignature(policy []byte) (string, error) {
|
|
hash := sha1.New()
|
|
_, err := hash.Write(policy)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
hashed := hash.Sum(nil)
|
|
var signed []byte
|
|
if cf.key.Validate() == nil {
|
|
signed, err = rsa.SignPKCS1v15(nil, cf.key, crypto.SHA1, hashed)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
} else {
|
|
signed = hashed
|
|
}
|
|
encoded := base64Replacer.Replace(base64.StdEncoding.EncodeToString(signed))
|
|
|
|
return encoded, nil
|
|
}
|
|
|
|
// Creates a signed url using RSAwithSHA1 as specified by
|
|
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-signature
|
|
func (cf *CloudFront) CannedSignedURL(path, queryString string, expires time.Time) (string, error) {
|
|
resource := cf.BaseURL + path
|
|
if queryString != "" {
|
|
resource = path + "?" + queryString
|
|
}
|
|
|
|
policy, err := buildPolicy(resource, expires)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
signature, err := cf.generateSignature(policy)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// TOOD: Do this once
|
|
uri, err := url.Parse(cf.BaseURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
uri.RawQuery = queryString
|
|
if queryString != "" {
|
|
uri.RawQuery += "&"
|
|
}
|
|
|
|
expireTime := expires.Truncate(time.Millisecond).Unix()
|
|
|
|
uri.Path = path
|
|
uri.RawQuery += fmt.Sprintf("Expires=%d&Signature=%s&Key-Pair-Id=%s", expireTime, signature, cf.keyPairId)
|
|
|
|
return uri.String(), nil
|
|
}
|
|
|
|
func (cloudfront *CloudFront) SignedURL(path, querystrings string, expires time.Time) string {
|
|
policy := `{"Statement":[{"Resource":"` + path + "?" + querystrings + `,"Condition":{"DateLessThan":{"AWS:EpochTime":` + strconv.FormatInt(expires.Truncate(time.Millisecond).Unix(), 10) + `}}}]}`
|
|
|
|
hash := sha1.New()
|
|
hash.Write([]byte(policy))
|
|
b := hash.Sum(nil)
|
|
he := base64.StdEncoding.EncodeToString(b)
|
|
|
|
policySha1 := he
|
|
|
|
url := cloudfront.BaseURL + path + "?" + querystrings + "&Expires=" + strconv.FormatInt(expires.Unix(), 10) + "&Signature=" + policySha1 + "&Key-Pair-Id=" + cloudfront.keyPairId
|
|
|
|
return url
|
|
}
|