1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.cloudfront; 17 18 import static java.nio.charset.StandardCharsets.UTF_8; 19 20 import java.net.URI; 21 import java.security.InvalidKeyException; 22 import java.util.function.Consumer; 23 import software.amazon.awssdk.annotations.Immutable; 24 import software.amazon.awssdk.annotations.SdkPublicApi; 25 import software.amazon.awssdk.annotations.ThreadSafe; 26 import software.amazon.awssdk.core.exception.SdkClientException; 27 import software.amazon.awssdk.services.cloudfront.cookie.CookiesForCannedPolicy; 28 import software.amazon.awssdk.services.cloudfront.cookie.CookiesForCustomPolicy; 29 import software.amazon.awssdk.services.cloudfront.internal.cookie.DefaultCookiesForCannedPolicy; 30 import software.amazon.awssdk.services.cloudfront.internal.cookie.DefaultCookiesForCustomPolicy; 31 import software.amazon.awssdk.services.cloudfront.internal.url.DefaultSignedUrl; 32 import software.amazon.awssdk.services.cloudfront.internal.utils.SigningUtils; 33 import software.amazon.awssdk.services.cloudfront.model.CannedSignerRequest; 34 import software.amazon.awssdk.services.cloudfront.model.CustomSignerRequest; 35 import software.amazon.awssdk.services.cloudfront.url.SignedUrl; 36 37 /** 38 * 39 * Utilities for working with CloudFront distributions 40 * <p> 41 * To securely serve private content by using CloudFront, you can require that users access your private content by using 42 * special CloudFront signed URLs or signed cookies. You then develop your application either to create and distribute signed 43 * URLs to authenticated users or to send Set-Cookie headers that set signed cookies for authenticated users. 44 * <p> 45 * Signed URLs take precedence over signed cookies. If you use both signed URLs and signed cookies to control access to the 46 * same files and a viewer uses a signed URL to request a file, CloudFront determines whether to return the file to the 47 * viewer based only on the signed URL. 48 * 49 */ 50 @Immutable 51 @ThreadSafe 52 @SdkPublicApi 53 public final class CloudFrontUtilities { 54 55 private static final String KEY_PAIR_ID_KEY = "CloudFront-Key-Pair-Id"; 56 private static final String SIGNATURE_KEY = "CloudFront-Signature"; 57 private static final String EXPIRES_KEY = "CloudFront-Expires"; 58 private static final String POLICY_KEY = "CloudFront-Policy"; 59 CloudFrontUtilities()60 private CloudFrontUtilities() { 61 } 62 create()63 public static CloudFrontUtilities create() { 64 return new CloudFrontUtilities(); 65 } 66 67 /** 68 * Returns a signed URL with a canned policy that grants universal access to 69 * private content until a given date. 70 * For more information, see <a href= 71 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html" 72 * >Creating a signed URL using a canned policy</a>. 73 * 74 * <p> 75 * This is a convenience which creates an instance of the {@link CannedSignerRequest.Builder} avoiding the need to 76 * create one manually via {@link CannedSignerRequest#builder()} 77 * 78 * @param request 79 * A {@link Consumer} that will call methods on {@link CannedSignerRequest.Builder} to create a request. 80 * @return A signed URL that will permit access to a specific distribution 81 * and S3 object. 82 * 83 * <p><b>Example Usage</b> 84 * <p> 85 * {@snippet : 86 * //Generates signed URL String with canned policy, valid for 7 days 87 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 88 * 89 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 90 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 91 * String keyPairId = "myKeyPairId"; 92 * PrivateKey privateKey = myPrivateKey; 93 * 94 * SignedUrl signedUrl = utilities.getSignedUrlWithCannedPolicy(r -> r.resourceUrl(resourceUrl) 95 * .privateKey(privateKey) 96 * .keyPairId(keyPairId) 97 * .expirationDate(expirationDate)); 98 * String url = signedUrl.url(); 99 * } 100 */ getSignedUrlWithCannedPolicy(Consumer<CannedSignerRequest.Builder> request)101 public SignedUrl getSignedUrlWithCannedPolicy(Consumer<CannedSignerRequest.Builder> request) { 102 return getSignedUrlWithCannedPolicy(CannedSignerRequest.builder().applyMutation(request).build()); 103 } 104 105 /** 106 * Returns a signed URL with a canned policy that grants universal access to 107 * private content until a given date. 108 * For more information, see <a href= 109 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html" 110 * >Creating a signed URL using a canned policy</a>. 111 * 112 * @param request 113 * A {@link CannedSignerRequest} configured with the following values: 114 * resourceUrl, privateKey, keyPairId, expirationDate 115 * @return A signed URL that will permit access to a specific distribution 116 * and S3 object. 117 * 118 * <p><b>Example Usage</b> 119 * <p> 120 * {@snippet : 121 * //Generates signed URL String with canned policy, valid for 7 days 122 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 123 * 124 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 125 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 126 * String keyPairId = "myKeyPairId"; 127 * Path keyFile = myKeyFile; 128 * 129 * CannedSignerRequest cannedRequest = CannedSignerRequest.builder() 130 * .resourceUrl(resourceUrl) 131 * .privateKey(keyFile) 132 * .keyPairId(keyPairId) 133 * .expirationDate(expirationDate) 134 * .build(); 135 * SignedUrl signedUrl = utilities.getSignedUrlWithCannedPolicy(cannedRequest); 136 * String url = signedUrl.url(); 137 * } 138 */ getSignedUrlWithCannedPolicy(CannedSignerRequest request)139 public SignedUrl getSignedUrlWithCannedPolicy(CannedSignerRequest request) { 140 try { 141 String resourceUrl = request.resourceUrl(); 142 String cannedPolicy = SigningUtils.buildCannedPolicy(resourceUrl, request.expirationDate()); 143 byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey()); 144 String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes); 145 URI uri = URI.create(resourceUrl); 146 String protocol = uri.getScheme(); 147 String domain = uri.getHost(); 148 String encodedPath = uri.getRawPath() 149 + (uri.getQuery() != null ? "?" + uri.getRawQuery() + "&" : "?") 150 + "Expires=" + request.expirationDate().getEpochSecond() 151 + "&Signature=" + urlSafeSignature 152 + "&Key-Pair-Id=" + request.keyPairId(); 153 return DefaultSignedUrl.builder().protocol(protocol).domain(domain).encodedPath(encodedPath) 154 .url(protocol + "://" + domain + encodedPath).build(); 155 } catch (InvalidKeyException e) { 156 throw SdkClientException.create("Could not sign url", e); 157 } 158 } 159 160 /** 161 * Returns a signed URL that provides tailored access to private content 162 * based on an access time window and an ip range. The custom policy itself 163 * is included as part of the signed URL (For a signed URL with canned 164 * policy, there is no policy included in the signed URL). 165 * For more information, see <a href= 166 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html" 167 * >Creating a signed URL using a custom policy</a>. 168 * 169 * <p> 170 * This is a convenience which creates an instance of the {@link CustomSignerRequest.Builder} avoiding the need to 171 * create one manually via {@link CustomSignerRequest#builder()} 172 * 173 * @param request 174 * A {@link Consumer} that will call methods on {@link CustomSignerRequest.Builder} to create a request. 175 * @return A signed URL that will permit access to distribution and S3 176 * objects as specified in the policy document. 177 * 178 * <p><b>Example Usage</b> 179 * <p> 180 * {@snippet : 181 * //Generates signed URL String with custom policy, with an access window that begins in 2 days and ends in 7 days, 182 * //for a specified IP range 183 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 184 * 185 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 186 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 187 * String keyPairId = "myKeyPairId"; 188 * PrivateKey privateKey = myPrivateKey; 189 * Instant activeDate = Instant.now().plus(Duration.ofDays(2)); 190 * String ipRange = "192.168.0.1/24"; 191 * 192 * SignedUrl signedUrl = utilities.getSignedUrlWithCustomPolicy(r -> r.resourceUrl(resourceUrl) 193 * .privateKey(privateKey) 194 * .keyPairId(keyPairId) 195 * .expirationDate(expirationDate) 196 * .activeDate(activeDate) 197 * .ipRange(ipRange)); 198 * String url = signedUrl.url(); 199 * } 200 */ getSignedUrlWithCustomPolicy(Consumer<CustomSignerRequest.Builder> request)201 public SignedUrl getSignedUrlWithCustomPolicy(Consumer<CustomSignerRequest.Builder> request) { 202 return getSignedUrlWithCustomPolicy(CustomSignerRequest.builder().applyMutation(request).build()); 203 } 204 205 /** 206 * Returns a signed URL that provides tailored access to private content 207 * based on an access time window and an ip range. The custom policy itself 208 * is included as part of the signed URL (For a signed URL with canned 209 * policy, there is no policy included in the signed URL). 210 * For more information, see <a href= 211 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html" 212 * >Creating a signed URL using a custom policy</a>. 213 * 214 * @param request 215 * A {@link CustomSignerRequest} configured with the following values: 216 * resourceUrl, privateKey, keyPairId, expirationDate, activeDate (optional), ipRange (optional) 217 * @return A signed URL that will permit access to distribution and S3 218 * objects as specified in the policy document. 219 * 220 * <p><b>Example Usage</b> 221 * <p> 222 * {@snippet : 223 * //Generates signed URL String with custom policy, with an access window that begins in 2 days and ends in 7 days, 224 * //for a specified IP range 225 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 226 * 227 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 228 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 229 * String keyPairId = "myKeyPairId"; 230 * Path keyFile = myKeyFile; 231 * Instant activeDate = Instant.now().plus(Duration.ofDays(2)); 232 * String ipRange = "192.168.0.1/24"; 233 * 234 * CustomSignerRequest customRequest = CustomSignerRequest.builder() 235 * .resourceUrl(resourceUrl) 236 * .privateKey(keyFile) 237 * .keyPairId(keyPairId) 238 * .expirationDate(expirationDate) 239 * .activeDate(activeDate) 240 * .ipRange(ipRange) 241 * .build(); 242 * SignedUrl signedUrl = utilities.getSignedUrlWithCustomPolicy(customRequest); 243 * String url = signedUrl.url(); 244 * } 245 */ getSignedUrlWithCustomPolicy(CustomSignerRequest request)246 public SignedUrl getSignedUrlWithCustomPolicy(CustomSignerRequest request) { 247 try { 248 String resourceUrl = request.resourceUrl(); 249 String policy = SigningUtils.buildCustomPolicyForSignedUrl(request.resourceUrl(), request.activeDate(), 250 request.expirationDate(), request.ipRange()); 251 byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey()); 252 String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy); 253 String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes); 254 URI uri = URI.create(resourceUrl); 255 String protocol = uri.getScheme(); 256 String domain = uri.getHost(); 257 String encodedPath = uri.getRawPath() 258 + (uri.getQuery() != null ? "?" + uri.getRawQuery() + "&" : "?") 259 + "Policy=" + urlSafePolicy 260 + "&Signature=" + urlSafeSignature 261 + "&Key-Pair-Id=" + request.keyPairId(); 262 return DefaultSignedUrl.builder().protocol(protocol).domain(domain).encodedPath(encodedPath) 263 .url(protocol + "://" + domain + encodedPath).build(); 264 } catch (InvalidKeyException e) { 265 throw SdkClientException.create("Could not sign url", e); 266 } 267 } 268 269 /** 270 * Generate signed cookies that allows access to a specific distribution and 271 * resource path by applying access restrictions from a "canned" (simplified) 272 * policy document. 273 * For more information, see <a href= 274 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html" 275 * >Setting signed cookies using a canned policy</a>. 276 * 277 * <p> 278 * This is a convenience which creates an instance of the {@link CannedSignerRequest.Builder} avoiding the need to 279 * create one manually via {@link CannedSignerRequest#builder()} 280 * 281 * @param request 282 * A {@link Consumer} that will call methods on {@link CannedSignerRequest.Builder} to create a request. 283 * @return The signed cookies with canned policy. 284 * 285 * <p><b>Example Usage</b> 286 * <p> 287 * {@snippet : 288 * //Generates signed Cookie for canned policy, valid for 7 days 289 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 290 * 291 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 292 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 293 * String keyPairId = "myKeyPairId"; 294 * PrivateKey privateKey = myPrivateKey; 295 * 296 * CookiesForCannedPolicy cookies = utilities.getSignedCookiesForCannedPolicy(r -> r.resourceUrl(resourceUrl) 297 * .privateKey(privateKey) 298 * .keyPairId(keyPairId) 299 * .expirationDate(expirationDate)); 300 * // Generates Set-Cookie header values to send to the viewer to allow access 301 * String signatureHeaderValue = cookies.signatureHeaderValue(); 302 * String keyPairIdHeaderValue = cookies.keyPairIdHeaderValue(); 303 * String expiresHeaderValue = cookies.expiresHeaderValue(); 304 * } 305 */ getCookiesForCannedPolicy(Consumer<CannedSignerRequest.Builder> request)306 public CookiesForCannedPolicy getCookiesForCannedPolicy(Consumer<CannedSignerRequest.Builder> request) { 307 return getCookiesForCannedPolicy(CannedSignerRequest.builder().applyMutation(request).build()); 308 } 309 310 /** 311 * Generate signed cookies that allows access to a specific distribution and 312 * resource path by applying access restrictions from a "canned" (simplified) 313 * policy document. 314 * For more information, see <a href= 315 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html" 316 * >Setting signed cookies using a canned policy</a>. 317 * 318 * @param request 319 * A {@link CannedSignerRequest} configured with the following values: 320 * resourceUrl, privateKey, keyPairId, expirationDate 321 * @return The signed cookies with canned policy. 322 * 323 * <p><b>Example Usage</b> 324 * <p> 325 * {@snippet : 326 * //Generates signed Cookie for canned policy, valid for 7 days 327 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 328 * 329 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 330 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 331 * String keyPairId = "myKeyPairId"; 332 * Path keyFile = myKeyFile; 333 * 334 * CannedSignerRequest cannedRequest = CannedSignerRequest.builder() 335 * .resourceUrl(resourceUrl) 336 * .privateKey(keyFile) 337 * .keyPairId(keyPairId) 338 * .expirationDate(expirationDate) 339 * .build(); 340 * CookiesForCannedPolicy cookies = utilities.getCookiesForCannedPolicy(cannedRequest); 341 * // Generates Set-Cookie header values to send to the viewer to allow access 342 * String signatureHeaderValue = cookies.signatureHeaderValue(); 343 * String keyPairIdHeaderValue = cookies.keyPairIdHeaderValue(); 344 * String expiresHeaderValue = cookies.expiresHeaderValue(); 345 * } 346 */ getCookiesForCannedPolicy(CannedSignerRequest request)347 public CookiesForCannedPolicy getCookiesForCannedPolicy(CannedSignerRequest request) { 348 try { 349 String cannedPolicy = SigningUtils.buildCannedPolicy(request.resourceUrl(), request.expirationDate()); 350 byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey()); 351 String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes); 352 String expiry = String.valueOf(request.expirationDate().getEpochSecond()); 353 return DefaultCookiesForCannedPolicy.builder() 354 .resourceUrl(request.resourceUrl()) 355 .keyPairIdHeaderValue(KEY_PAIR_ID_KEY + "=" + request.keyPairId()) 356 .signatureHeaderValue(SIGNATURE_KEY + "=" + urlSafeSignature) 357 .expiresHeaderValue(EXPIRES_KEY + "=" + expiry).build(); 358 } catch (InvalidKeyException e) { 359 throw SdkClientException.create("Could not sign canned policy cookie", e); 360 } 361 } 362 363 /** 364 * Returns signed cookies that provides tailored access to private content based on an access time window and an ip range. 365 * For more information, see <a href= 366 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html" 367 * >Setting signed cookies using a custom policy</a>. 368 * 369 * <p> 370 * This is a convenience which creates an instance of the {@link CustomSignerRequest.Builder} avoiding the need to 371 * create one manually via {@link CustomSignerRequest#builder()} 372 * 373 * @param request 374 * A {@link Consumer} that will call methods on {@link CustomSignerRequest.Builder} to create a request. 375 * @return The signed cookies with custom policy. 376 * 377 * <p><b>Example Usage</b> 378 * <p> 379 * {@snippet : 380 * //Generates signed Cookie for custom policy, with an access window that begins in 2 days and ends in 7 days, 381 * //for a specified IP range 382 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 383 * 384 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 385 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 386 * String keyPairId = "myKeyPairId"; 387 * PrivateKey privateKey = myPrivateKey; 388 * Instant activeDate = Instant.now().plus(Duration.ofDays(2)); 389 * String ipRange = "192.168.0.1/24"; 390 * 391 * CookiesForCustomPolicy cookies = utilities.getCookiesForCustomPolicy(r -> r.resourceUrl(resourceUrl) 392 * .privateKey(privateKey) 393 * .keyPairId(keyPairId) 394 * .expirationDate(expirationDate) 395 * .activeDate(activeDate) 396 * .ipRange(ipRange)); 397 * // Generates Set-Cookie header values to send to the viewer to allow access 398 * String signatureHeaderValue = cookies.signatureHeaderValue(); 399 * String keyPairIdHeaderValue = cookies.keyPairIdHeaderValue(); 400 * String policyHeaderValue = cookies.policyHeaderValue(); 401 * } 402 */ getCookiesForCustomPolicy(Consumer<CustomSignerRequest.Builder> request)403 public CookiesForCustomPolicy getCookiesForCustomPolicy(Consumer<CustomSignerRequest.Builder> request) { 404 return getCookiesForCustomPolicy(CustomSignerRequest.builder().applyMutation(request).build()); 405 } 406 407 /** 408 * Returns signed cookies that provides tailored access to private content based on an access time window and an ip range. 409 * For more information, see <a href= 410 * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html" 411 * >Setting signed cookies using a custom policy</a>. 412 * 413 * @param request 414 * A {@link CustomSignerRequest} configured with the following values: 415 * resourceUrl, privateKey, keyPairId, expirationDate, activeDate (optional), ipRange (optional) 416 * @return The signed cookies with custom policy. 417 * 418 * <p><b>Example Usage</b> 419 * <p> 420 * {@snippet : 421 * //Generates signed Cookie for custom policy, with an access window that begins in 2 days and ends in 7 days, 422 * //for a specified IP range 423 * CloudFrontUtilities utilities = CloudFrontUtilities.create(); 424 * 425 * Instant expirationDate = Instant.now().plus(Duration.ofDays(7)); 426 * String resourceUrl = "https://d111111abcdef8.cloudfront.net/s3ObjectKey"; 427 * String keyPairId = "myKeyPairId"; 428 * Path keyFile = myKeyFile; 429 * Instant activeDate = Instant.now().plus(Duration.ofDays(2)); 430 * String ipRange = "192.168.0.1/24"; 431 * 432 * CustomSignerRequest customRequest = CustomSignerRequest.builder() 433 * .resourceUrl(resourceUrl) 434 * .privateKey(keyFile) 435 * .keyPairId(keyFile) 436 * .expirationDate(expirationDate) 437 * .activeDate(activeDate) 438 * .ipRange(ipRange) 439 * .build(); 440 * CookiesForCustomPolicy cookies = utilities.getCookiesForCustomPolicy(customRequest); 441 * // Generates Set-Cookie header values to send to the viewer to allow access 442 * String signatureHeaderValue = cookies.signatureHeaderValue(); 443 * String keyPairIdHeaderValue = cookies.keyPairIdHeaderValue(); 444 * String policyHeaderValue = cookies.policyHeaderValue(); 445 * } 446 */ getCookiesForCustomPolicy(CustomSignerRequest request)447 public CookiesForCustomPolicy getCookiesForCustomPolicy(CustomSignerRequest request) { 448 try { 449 String policy = SigningUtils.buildCustomPolicy(request.resourceUrl(), request.activeDate(), request.expirationDate(), 450 request.ipRange()); 451 byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey()); 452 String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy); 453 String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes); 454 return DefaultCookiesForCustomPolicy.builder() 455 .resourceUrl(request.resourceUrl()) 456 .keyPairIdHeaderValue(KEY_PAIR_ID_KEY + "=" + request.keyPairId()) 457 .signatureHeaderValue(SIGNATURE_KEY + "=" + urlSafeSignature) 458 .policyHeaderValue(POLICY_KEY + "=" + urlSafePolicy).build(); 459 } catch (InvalidKeyException e) { 460 throw SdkClientException.create("Could not sign custom policy cookie", e); 461 } 462 } 463 464 } 465