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 }