github.com/aavshr/aws-sdk-go@v1.41.3/service/cloudfront/sign/sign_url.go (about)

     1  // Package sign provides utilities to generate signed URLs for Amazon CloudFront.
     2  //
     3  // More information about signed URLs and their structure can be found at:
     4  // http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html
     5  //
     6  // To sign a URL create a URLSigner with your private key and credential pair key ID.
     7  // Once you have a URLSigner instance you can call Sign or SignWithPolicy to
     8  // sign the URLs.
     9  //
    10  // Example:
    11  //
    12  //    // Sign URL to be valid for 1 hour from now.
    13  //    signer := sign.NewURLSigner(keyID, privKey)
    14  //    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
    15  //    if err != nil {
    16  //        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
    17  //    }
    18  //
    19  package sign
    20  
    21  import (
    22  	"crypto/rsa"
    23  	"fmt"
    24  	"net/url"
    25  	"strings"
    26  	"time"
    27  )
    28  
    29  // An URLSigner provides URL signing utilities to sign URLs for Amazon CloudFront
    30  // resources. Using a private key and Credential Key Pair key ID the URLSigner
    31  // only needs to be created once per Credential Key Pair key ID and private key.
    32  //
    33  // The signer is safe to use concurrently.
    34  type URLSigner struct {
    35  	keyID   string
    36  	privKey *rsa.PrivateKey
    37  }
    38  
    39  // NewURLSigner constructs and returns a new URLSigner to be used to for signing
    40  // Amazon CloudFront URL resources with.
    41  func NewURLSigner(keyID string, privKey *rsa.PrivateKey) *URLSigner {
    42  	return &URLSigner{
    43  		keyID:   keyID,
    44  		privKey: privKey,
    45  	}
    46  }
    47  
    48  // Sign will sign a single URL to expire at the time of expires sign using the
    49  // Amazon CloudFront default Canned Policy. The URL will be signed with the
    50  // private key and Credential Key Pair Key ID previously provided to URLSigner.
    51  //
    52  // This is the default method of signing Amazon CloudFront URLs. If extra policy
    53  // conditions are need other than URL expiry use SignWithPolicy instead.
    54  //
    55  // Example:
    56  //
    57  //    // Sign URL to be valid for 1 hour from now.
    58  //    signer := sign.NewURLSigner(keyID, privKey)
    59  //    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
    60  //    if err != nil {
    61  //        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
    62  //    }
    63  //
    64  func (s URLSigner) Sign(url string, expires time.Time) (string, error) {
    65  	scheme, cleanedURL, err := cleanURLScheme(url)
    66  	if err != nil {
    67  		return "", err
    68  	}
    69  
    70  	resource, err := CreateResource(scheme, url)
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	return signURL(scheme, cleanedURL, s.keyID, NewCannedPolicy(resource, expires), false, s.privKey)
    76  }
    77  
    78  // SignWithPolicy will sign a URL with the Policy provided.  The URL will be
    79  // signed with the private key and Credential Key Pair Key ID previously provided to URLSigner.
    80  //
    81  // Use this signing method if you are looking to sign a URL with more than just
    82  // the URL's expiry time, or reusing Policies between multiple URL signings.
    83  // If only the expiry time is needed you can use Sign and provide just the
    84  // URL's expiry time. A minimum of at least one policy statement is required for a signed URL.
    85  //
    86  // Note: It is not safe to use Polices between multiple signers concurrently
    87  //
    88  // Example:
    89  //
    90  //     // Sign URL to be valid for 30 minutes from now, expires one hour from now, and
    91  //     // restricted to the 192.0.2.0/24 IP address range.
    92  //     policy := &sign.Policy{
    93  //         Statements: []sign.Statement{
    94  //             {
    95  //                 Resource: rawURL,
    96  //                 Condition: sign.Condition{
    97  //                     // Optional IP source address range
    98  //                     IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
    99  //                     // Optional date URL is not valid until
   100  //                     DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
   101  //                     // Required date the URL will expire after
   102  //                     DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
   103  //                 },
   104  //             },
   105  //         },
   106  //     }
   107  //
   108  //     signer := sign.NewURLSigner(keyID, privKey)
   109  //     signedURL, err := signer.SignWithPolicy(rawURL, policy)
   110  //     if err != nil {
   111  //         log.Fatalf("Failed to sign url, err: %s\n", err.Error())
   112  //     }
   113  //
   114  func (s URLSigner) SignWithPolicy(url string, p *Policy) (string, error) {
   115  	scheme, cleanedURL, err := cleanURLScheme(url)
   116  	if err != nil {
   117  		return "", err
   118  	}
   119  
   120  	return signURL(scheme, cleanedURL, s.keyID, p, true, s.privKey)
   121  }
   122  
   123  func signURL(scheme, url, keyID string, p *Policy, customPolicy bool, privKey *rsa.PrivateKey) (string, error) {
   124  	// Validation URL elements
   125  	if err := validateURL(url); err != nil {
   126  		return "", err
   127  	}
   128  
   129  	b64Signature, b64Policy, err := p.Sign(privKey)
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	// build and return signed URL
   135  	builtURL := buildSignedURL(url, keyID, p, customPolicy, b64Policy, b64Signature)
   136  	if scheme == "rtmp" {
   137  		return buildRTMPURL(builtURL)
   138  	}
   139  
   140  	return builtURL, nil
   141  }
   142  
   143  func buildSignedURL(baseURL, keyID string, p *Policy, customPolicy bool, b64Policy, b64Signature []byte) string {
   144  	pred := "?"
   145  	if strings.Contains(baseURL, "?") {
   146  		pred = "&"
   147  	}
   148  	signedURL := baseURL + pred
   149  
   150  	if customPolicy {
   151  		signedURL += "Policy=" + string(b64Policy)
   152  	} else {
   153  		signedURL += fmt.Sprintf("Expires=%d", p.Statements[0].Condition.DateLessThan.UTC().Unix())
   154  	}
   155  	signedURL += fmt.Sprintf("&Signature=%s&Key-Pair-Id=%s", string(b64Signature), keyID)
   156  
   157  	return signedURL
   158  }
   159  
   160  func buildRTMPURL(u string) (string, error) {
   161  	parsed, err := url.Parse(u)
   162  	if err != nil {
   163  		return "", fmt.Errorf("unable to parse rtmp signed URL, err: %s", err)
   164  	}
   165  
   166  	rtmpURL := strings.TrimLeft(parsed.Path, "/")
   167  	if parsed.RawQuery != "" {
   168  		rtmpURL = fmt.Sprintf("%s?%s", rtmpURL, parsed.RawQuery)
   169  	}
   170  
   171  	return rtmpURL, nil
   172  }
   173  
   174  func cleanURLScheme(u string) (scheme, cleanedURL string, err error) {
   175  	parts := strings.SplitN(u, "://", 2)
   176  	if len(parts) != 2 {
   177  		return "", "", fmt.Errorf("invalid URL, missing scheme and domain/path")
   178  	}
   179  	scheme = strings.Replace(parts[0], "*", "", 1)
   180  	cleanedURL = fmt.Sprintf("%s://%s", scheme, parts[1])
   181  
   182  	return strings.ToLower(scheme), cleanedURL, nil
   183  }
   184  
   185  var illegalQueryParms = []string{"Expires", "Policy", "Signature", "Key-Pair-Id"}
   186  
   187  func validateURL(u string) error {
   188  	parsed, err := url.Parse(u)
   189  	if err != nil {
   190  		return fmt.Errorf("unable to parse URL, err: %s", err.Error())
   191  	}
   192  
   193  	if parsed.Scheme == "" {
   194  		return fmt.Errorf("URL missing valid scheme, %s", u)
   195  	}
   196  
   197  	q := parsed.Query()
   198  	for _, p := range illegalQueryParms {
   199  		if _, ok := q[p]; ok {
   200  			return fmt.Errorf("%s cannot be a query parameter for a signed URL", p)
   201  		}
   202  	}
   203  
   204  	return nil
   205  }