• 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.internal.utils;
17 
18 import static java.nio.charset.StandardCharsets.UTF_8;
19 
20 import java.io.InputStream;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.security.InvalidKeyException;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.PrivateKey;
26 import java.security.SecureRandom;
27 import java.security.Signature;
28 import java.security.SignatureException;
29 import java.time.Instant;
30 import java.util.Base64;
31 import software.amazon.awssdk.annotations.SdkInternalApi;
32 import software.amazon.awssdk.core.exception.SdkClientException;
33 import software.amazon.awssdk.services.cloudfront.internal.auth.Pem;
34 import software.amazon.awssdk.services.cloudfront.internal.auth.Rsa;
35 import software.amazon.awssdk.utils.IoUtils;
36 import software.amazon.awssdk.utils.StringUtils;
37 
38 @SdkInternalApi
39 public final class SigningUtils {
40 
SigningUtils()41     private SigningUtils() {
42     }
43 
44     /**
45      * Returns a "canned" policy for the given parameters.
46      * For more information, see
47      * <a href =
48      * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html"
49      * >Creating a signed URL using a canned policy</a>
50      * or
51      * <a href=
52      * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html"
53      * >Setting signed cookies using a canned policy</a>.
54      */
buildCannedPolicy(String resourceUrl, Instant expirationDate)55     public static String buildCannedPolicy(String resourceUrl, Instant expirationDate) {
56         return "{\"Statement\":[{\"Resource\":\""
57                + resourceUrl
58                + "\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":"
59                + expirationDate.getEpochSecond()
60                + "}}}]}";
61     }
62 
63     /**
64      * Returns a custom policy for the given parameters.
65      * For more information, see <a href=
66      * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html"
67      * >Creating a signed URL using a custom policy</a>
68      * or
69      * <a href=
70      * "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html"
71      * >Setting signed cookies using a custom policy</a>.
72      */
buildCustomPolicy(String resourceUrl, Instant activeDate, Instant expirationDate, String ipAddress)73     public static String buildCustomPolicy(String resourceUrl, Instant activeDate, Instant expirationDate,
74                                             String ipAddress) {
75         return "{\"Statement\": [{"
76                + "\"Resource\":\""
77                + resourceUrl
78                + "\""
79                + ",\"Condition\":{"
80                + "\"DateLessThan\":{\"AWS:EpochTime\":"
81                + expirationDate.getEpochSecond()
82                + "}"
83                + (ipAddress == null
84                   ? ""
85                   : ",\"IpAddress\":{\"AWS:SourceIp\":\"" + ipAddress + "\"}"
86                )
87                + (activeDate == null
88                   ? ""
89                   : ",\"DateGreaterThan\":{\"AWS:EpochTime\":" + activeDate.getEpochSecond() + "}"
90                )
91                + "}}]}";
92     }
93 
94     /**
95      * Converts the given data to be safe for use in signed URLs for a private
96      * distribution by using specialized Base64 encoding.
97      */
makeBytesUrlSafe(byte[] bytes)98     public static String makeBytesUrlSafe(byte[] bytes) {
99         byte[] encoded = Base64.getEncoder().encode(bytes);
100         for (int i = 0; i < encoded.length; i++) {
101             switch (encoded[i]) {
102                 case '+':
103                     encoded[i] = '-';
104                     continue;
105                 case '=':
106                     encoded[i] = '_';
107                     continue;
108                 case '/':
109                     encoded[i] = '~';
110                     continue;
111                 default:
112             }
113         }
114         return new String(encoded, UTF_8);
115     }
116 
117     /**
118      * Converts the given string to be safe for use in signed URLs for a private
119      * distribution.
120      */
makeStringUrlSafe(String str)121     public static String makeStringUrlSafe(String str) {
122         return makeBytesUrlSafe(str.getBytes(UTF_8));
123     }
124 
125     /**
126      * Signs the data given with the private key given, using the SHA1withRSA
127      * algorithm provided by bouncy castle.
128      */
signWithSha1Rsa(byte[] dataToSign, PrivateKey privateKey)129     public static byte[] signWithSha1Rsa(byte[] dataToSign, PrivateKey privateKey) throws InvalidKeyException {
130         try {
131             Signature signature = Signature.getInstance("SHA1withRSA");
132             SecureRandom random = new SecureRandom();
133             signature.initSign(privateKey, random);
134             signature.update(dataToSign);
135             return signature.sign();
136         } catch (NoSuchAlgorithmException | SignatureException e) {
137             throw new IllegalStateException(e);
138         }
139     }
140 
141     /**
142      * Generate a policy document that describes custom access permissions to
143      * apply via a private distribution's signed URL.
144      *
145      * @param resourceUrl
146      *            The HTTP/S resource path that restricts which distribution and
147      *            S3 objects will be accessible in a signed URL, i.e.,
148      *            <tt>"https://" + distributionName + "/" + objectKey</tt> (may
149      *            also include URL parameters). The '*' and '?' characters can
150      *            be used as a wildcards to allow multi-character or single-character
151      *            matches respectively:
152      *            <ul>
153      *            <li><tt>*</tt> : All distributions/objects will be accessible</li>
154      *            <li><tt>a1b2c3d4e5f6g7.cloudfront.net/*</tt> : All objects
155      *            within the distribution a1b2c3d4e5f6g7 will be accessible</li>
156      *            <li><tt>a1b2c3d4e5f6g7.cloudfront.net/path/to/object.txt</tt>
157      *            : Only the S3 object named <tt>path/to/object.txt</tt> in the
158      *            distribution a1b2c3d4e5f6g7 will be accessible.</li>
159      *            </ul>
160      * @param activeDate
161      *            An optional UTC time and date when the signed URL will become
162      *            active. If null, the signed URL will be active as soon as it
163      *            is created.
164      * @param expirationDate
165      *            The UTC time and date when the signed URL will expire. REQUIRED.
166      * @param limitToIpAddressCidr
167      *            An optional range of client IP addresses that will be allowed
168      *            to access the distribution, specified as an IPv4 CIDR range
169      *            (IPv6 format is not supported). If null, the CIDR will be omitted
170      *            and any client will be permitted.
171      * @return A policy document describing the access permission to apply when
172      *         generating a signed URL.
173      */
buildCustomPolicyForSignedUrl(String resourceUrl, Instant activeDate, Instant expirationDate, String limitToIpAddressCidr)174     public static String buildCustomPolicyForSignedUrl(String resourceUrl,
175                                                        Instant activeDate,
176                                                        Instant expirationDate,
177                                                        String limitToIpAddressCidr) {
178         if (expirationDate == null) {
179             throw SdkClientException.create("Expiration date must be provided to sign CloudFront URLs");
180         }
181         if (resourceUrl == null) {
182             resourceUrl = "*";
183         }
184         return buildCustomPolicy(resourceUrl, activeDate, expirationDate, limitToIpAddressCidr);
185     }
186 
187     /**
188      * Creates a private key from the file given, either in pem or der format.
189      * Other formats will cause an exception to be thrown.
190      */
loadPrivateKey(Path keyFile)191     public static PrivateKey loadPrivateKey(Path keyFile) throws Exception {
192         if (StringUtils.lowerCase(keyFile.toString()).endsWith(".pem")) {
193             try (InputStream is = Files.newInputStream(keyFile)) {
194                 return Pem.readPrivateKey(is);
195             }
196         }
197         if (StringUtils.lowerCase(keyFile.toString()).endsWith(".der")) {
198             try (InputStream is = Files.newInputStream(keyFile)) {
199                 return Rsa.privateKeyFromPkcs8(IoUtils.toByteArray(is));
200             }
201         }
202         throw SdkClientException.create("Unsupported file type for private key");
203     }
204 
205 }
206