• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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