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.
 
 
 

136 lines
3.0 KiB

  1. package cloudfront
  2. import (
  3. "crypto"
  4. "crypto/rand"
  5. "crypto/rsa"
  6. "crypto/sha1"
  7. "encoding/base64"
  8. "encoding/json"
  9. "fmt"
  10. "net/url"
  11. "strconv"
  12. "strings"
  13. "time"
  14. )
  15. type CloudFront struct {
  16. BaseURL string
  17. keyPairId string
  18. key *rsa.PrivateKey
  19. }
  20. var base64Replacer = strings.NewReplacer("=", "_", "+", "-", "/", "~")
  21. func New(baseurl string, key *rsa.PrivateKey, keyPairId string) *CloudFront {
  22. return &CloudFront{
  23. BaseURL: baseurl,
  24. keyPairId: keyPairId,
  25. key: key,
  26. }
  27. }
  28. type epochTime struct {
  29. EpochTime int64 `json:"AWS:EpochTime"`
  30. }
  31. type condition struct {
  32. DateLessThan epochTime
  33. }
  34. type statement struct {
  35. Resource string
  36. Condition condition
  37. }
  38. type policy struct {
  39. Statement []statement
  40. }
  41. func buildPolicy(resource string, expireTime time.Time) ([]byte, error) {
  42. p := &policy{
  43. Statement: []statement{
  44. statement{
  45. Resource: resource,
  46. Condition: condition{
  47. DateLessThan: epochTime{
  48. EpochTime: expireTime.Truncate(time.Millisecond).Unix(),
  49. },
  50. },
  51. },
  52. },
  53. }
  54. return json.Marshal(p)
  55. }
  56. func (cf *CloudFront) generateSignature(policy []byte) (string, error) {
  57. hash := sha1.New()
  58. if _, err := hash.Write(policy); err != nil {
  59. return "", err
  60. }
  61. hashed := hash.Sum(nil)
  62. signed, err := rsa.SignPKCS1v15(rand.Reader, cf.key, crypto.SHA1, hashed)
  63. if err != nil {
  64. return "", err
  65. }
  66. encoded := base64Replacer.Replace(base64.StdEncoding.EncodeToString(signed))
  67. return encoded, nil
  68. }
  69. // Creates a signed url using RSAwithSHA1 as specified by
  70. // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-signature
  71. func (cf *CloudFront) CannedSignedURL(path, queryString string, expires time.Time) (string, error) {
  72. resource := cf.BaseURL + path
  73. if queryString != "" {
  74. resource = path + "?" + queryString
  75. }
  76. policy, err := buildPolicy(resource, expires)
  77. if err != nil {
  78. return "", err
  79. }
  80. signature, err := cf.generateSignature(policy)
  81. if err != nil {
  82. return "", err
  83. }
  84. // TOOD: Do this once
  85. uri, err := url.Parse(cf.BaseURL)
  86. if err != nil {
  87. return "", err
  88. }
  89. uri.RawQuery = queryString
  90. if queryString != "" {
  91. uri.RawQuery += "&"
  92. }
  93. expireTime := expires.Truncate(time.Millisecond).Unix()
  94. uri.Path = path
  95. uri.RawQuery += fmt.Sprintf("Expires=%d&Signature=%s&Key-Pair-Id=%s", expireTime, signature, cf.keyPairId)
  96. return uri.String(), nil
  97. }
  98. func (cloudfront *CloudFront) SignedURL(path, querystrings string, expires time.Time) string {
  99. policy := `{"Statement":[{"Resource":"` + path + "?" + querystrings + `,"Condition":{"DateLessThan":{"AWS:EpochTime":` + strconv.FormatInt(expires.Truncate(time.Millisecond).Unix(), 10) + `}}}]}`
  100. hash := sha1.New()
  101. hash.Write([]byte(policy))
  102. b := hash.Sum(nil)
  103. he := base64.StdEncoding.EncodeToString(b)
  104. policySha1 := he
  105. url := cloudfront.BaseURL + path + "?" + querystrings + "&Expires=" + strconv.FormatInt(expires.Unix(), 10) + "&Signature=" + policySha1 + "&Key-Pair-Id=" + cloudfront.keyPairId
  106. return url
  107. }