이두잉의 AWS 세상

AWS CloudFront SignedURL/Cookie 사용

2017.02.28 15:11 - leedoing leedoing

WS CloudFront를 사용할 때 컨텐츠 보안을 위해서 Signed URL 및 Cookie를 사용합니다.


Signed URL/Cookie 사용 방법에 대해서 알아봅니다.


1. 구성


2. CloudFront Signed URL/Cookie를 위한 Key Pair 생성

[Root 계정 로그인] > [우측 계정명] > [My Security Credentials] > [CloudFront Key Pairs] > [Create New Key Pair] 


Private, Public Key 그리고 Key Pair ID를 확인할 수 있습니다.


3. CloudFront Signed URL/Cookie 미리 준비된 정책 vs 사용자 정책

(https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html)


둘의 차이는 아래 그림과 같습니다.

사용자 정책을 만들면 IP Base 기반이라든지 좀 더 다양한 옵션이 있습니다. 다만 URL, Cookie의 길이가 조금 더 길어집니다.

코드상에 둘은 거의 차이가 없습니다. 다만 Policy 생성을 추가하면 사용자 지정 정책이 됩니다.


4. CloudFront Singed URL vs Cookie 

https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/private-content-choosing-signed-urls-cookies.html


Signed URL의 경우 일반적인 컨텐츠 및 RTMP 서비스 시 권장합니다. Signed Cookie의 경우 HLS와 같이 브라우저 URL 변경 없이 파일을 서비스할 때 사용합니다.


Signed URL의 경우 Expires, Signature, Key-Pair-Id 등이 기존 도메인에 추가되어 Signed URL을 생성합니다.


서비스 UR

http://d1e5lqevy0hhfg.cloudfront.net/test.mp


미리 준비된 정책 Signed URL

http://d1e5lqevy0hhfg.cloudfront.net/test.mp4?Expires=1488263708&Signature=eRGDtIzaj-a~T~xf8nleM2vpdjaqya~vzFC8m5elA-NBU6i6WVSPv9phnaN5rfuWpIJt~zqSnzDY4CLbjmWJbGt24BEv2zi-m-zjkNDzYQ0vDhGr3zu5R8U~oA4K8M~CG4ynN8CfOE8B7-fI4sCSzxc8HBo62sD9s1nXKHXErOuBcs-GoVpgW6ktxwNoTSSzXNWOSv1mA47DFAb~8Ln6nu8LbCcDYjX94T7GVqERsMr0xuMS6axnAuaSc8~62ZXb0e8YOgkLcWW6vn8ZmYAJI4NVlmuAmA01pmc9gSh-rzvlKL2VJx9gseSklUpstwf1gEmbTf~zuHOoMgne~gBo6Q__&Key-Pair-Id=APKAICJI44KY7SDYRFQQ


사용자 정책 Signed URL

http://d1e5lqevy0hhfg.cloudfront.net/test.mp4?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cDovL2QxZTVscWV2eTBoaGZnLmNsb3VkZnJvbnQubmV0L3Rlc3QubXA0IiwiQ29uZGl0aW9uIjp7IklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIyMTguMjM2Ljg0LjQzXC8yNCJ9LCJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTQ4ODI2MzU5NX19fV19&Signature=CMrBVA7b~j91UvI9XLm5MeOfFiPfLX-AncEepvTc9g~ZE8DEZuHYlkm5HzBq5hGNjvzdiHu-sZM6ZUXx0hmkkeyfw-L6pWcz0KD58k~X3h9MreLUswmNhoSsrTnL-5njmboYUyScmenF-pl17lZee-4pJJG-tNcEktVCwo9QGC1rDrv1sEbn9mqyb9UNXa3bqp74tk8b~6palnPPQM9rO2-Uj~fws6lbaEFii5mT7gzua87hSLc6NpNm0C8mUL6oDg9q5cmNcanGOFlpPMRX8-tEgvuXML7m6TBuZH8etnhanZaFa0-fogKnr~s4y4jpkAKVpMPMa56m7zNrdbW4cA__&Key-Pair-Id=APKAICJI44KY7SDYRFQQ


Cookie의 경우 Value가 Cookie 값으로 전달됩니다. expire 시간이 지나면 AcceessDenied 메시지가 출력되며 403 코드를 리턴합니다. (Cookie의 경우 당연히 도메인을 CNAME 처리해줘야 합니다.)


5. 사용 방법 

https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html

1. Document를 따라 Policy json 파일을 생성(공란 제거)

2. AWS Web Console에서 다운받은 private key를 이용하여 Policy json을 RSA 또는 SHA1 해쉬 생성

3. 해쉬 값 Base64 인코딩

4. URL에 영향을 끼치는 +, =, / 을 -, _, ~로 값 치환

5. URL/Cookie에 Policy, Signature, Key-Pair-Id 값 등록


AWS에서는 Java, PHP 등 SDK를 제공하고 있습니다. SDK를 사용할 경우 library(CloudFrontClient)에서 3, 4, 5에 대한 작업을 합니다. (https://docs.aws.amazon.com/aws-sdk-php/v3/guide/service/cloudfront-signed-url.html)

Key File 권한 확인


Signed URL Sample(PHP 5.5+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
function createSignedURL($streamHostUrl$resourceKey$timeout){
    $keyPairId = "APKAJPWDZQR7UI2ARAYQ"// Key Pair
    $expires = time() + $timeout// Expire Time
    $url = $streamHostUrl . '/' . $resourceKey// Service URL
    $ip=$_SERVER["REMOTE_ADDR"] . "\/24"// IP
    $json = '{"Statement":[{"Resource":"'.$url.'","Condition":{"IpAddress":{"AWS:SourceIp":"'.$ip.'"},"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
 
    $fp=fopen("/home/ec2-user/pk-APKAJPWDZQR7UI2ARAYQ.pem""r");
    $priv_key=fread($fp8192);
    fclose($fp);
 
    $key = openssl_get_privatekey($priv_key);
    if(!$key){
        echo "<p>Failed to load private key!</p>";
        return;
    }
    if(!openssl_sign($json$signed_policy$key, OPENSSL_ALGO_SHA1)){
        echo '<p>Failed to sign policy: '.opeenssl_error_string().'</p>';
        return;
    }
 
    $base64_signed_policy = base64_encode($signed_policy);
 
    $policy = strtr(base64_encode($json), '+=/''-_~'); //Custom Policy
 
    $signature = str_replace(array('+','=','/'), array('-','_','~'), $base64_signed_policy);
 
    //Construct the URL
    //$signedUrl = $url.'?Expires='.$expires.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId; //Manual Policy
    $signedUrl = $url.'?Policy='.$policy.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId;   //Custom Policy
 
    return $signedUrl;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8" />
        <title>Signed URL Test</title>
</head>
<body>
        <div>
        <?php $signedUrl = createSignedUrl('http://d3cqtg62iu3s25.cloudfront.net''test.mp4'30);?>
                <?php echo $signedUrl ?>
        </div>
 
        <video controls autoplay>
                <source src="<?php echo $signedUrl ?>" type="video/mp4">
        </video>
</body>
</html>
cs


Signed URL Sample(PHP 5.5+, PHP v3 SDK)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
require 'vendor/autoload.php'// include autoloader of composer.
use Aws\CloudFront\CloudFrontClient;
 
function createSignedUrl($streamHostUrl$resourceKey$timeout){
$cloudFront = new Aws\CloudFront\CloudFrontClient([
    'region'  => 'ap-northeast-2',
    'version' => '2014-11-06'
]);
$url = $streamHostUrl . '/' . $resourceKey;
$expires = time() + $timeout;
$ip = $_SERVER['REMOTE_ADDR'] . "\/24";
 
$json = '{"Statement":[{"Resource":"'.$url.'","Condition":{"IpAddress":{"AWS:SourceIp":"'.$ip.'"},"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
 
$signedUrlCustomPolicy = $cloudFront->getSignedUrl([
    'url'         => $streamHostUrl . '/' . $resourceKey,
//  'expires'     => $expires, //Manual Policy
    'policy'      => $json//Custom Policy
    'private_key' => '/home/ec2-user/pk-APKAJPWDZQR7UI2ARAYQ.pem',
    'key_pair_id' => 'APKAJPWDZQR7UI2ARAYQ'
]);
    return $signedUrlCustomPolicy;
}
error_reporting(E_ALL);
ini_set("display_errors"1);
?>
 
<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8" />
        <title>Signed URL Test</title>
</head>
<body>
        <div>
        <?php $signedUrl = createSignedUrl('http://d3cqtg62iu3s25.cloudfront.net''test.mp4'30);?>
                <?php echo $signedUrl ?>
        </div>
 
        <video controls autoplay>
                <source src="<?php echo $signedUrl ?>" type="video/mp4">
        </video>
</body>
</html>
cs


Policy에 사용자가 만든 json 파일을 expires 대신 추가하면 사용자 지정 정책이 됩니다. json 파일은 공백이 없어야 하며, rsa로 암호화 한 뒤에 base64로 인코딩한 뒤에 URL에 영향을 끼치는 몇몇 문자를 변경해야 합니다. 이런 잡다한 작업은 SDK를 사용하면 CloudFrontClient.php라는 라이브러리에서 알아서 해줍니다. 


Signed Cookie Sample(PHP 5.5+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
function createSignedCookie($streamHostUrl$resourceKey$timeout){
    $keyPairId = "APKAJPWDZQR7UI2ARAYQ"// Key Pair
    $expires = time() + $timeout// Expire Time
    $url = $streamHostUrl . '/' . $resourceKey// Service URL
    $ip=$_SERVER["REMOTE_ADDR"] . "\/24"// IP
    $json = '{"Statement":[{"Resource":"'.$url.'","Condition":{"IpAddress":{"AWS:SourceIp":"'.$ip.'"},"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
 
    $fp=fopen("/home/ec2-user/pk-APKAJPWDZQR7UI2ARAYQ.pem""r");
    $priv_key=fread($fp8192);
    fclose($fp);
 
    $key = openssl_get_privatekey($priv_key);
    if(!$key){
        echo "<p>Failed to load private key!</p>";
        return;
    }
    if(!openssl_sign($json$signed_policy$key, OPENSSL_ALGO_SHA1)){
        echo '<p>Failed to sign policy: '.opeenssl_error_string().'</p>';
        return;
    }
 
    $base64_signed_policy = base64_encode($signed_policy);
 
    $policy = strtr(base64_encode($json), '+=/''-_~'); //Custom Policy
 
    $signature = str_replace(array('+','=','/'), array('-','_','~'), $base64_signed_policy);
 
    //Construct the URL
    //$signedUrl = $url.'?Expires='.$expires.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId; //Manual Policy
    $signedUrl = $url.'?Policy='.$policy.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId;   //Custom Policy
 
    return $signeCookie;
}
?>
 
<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8" />
        <title>Signed cookie Test</title>
</head>
<body>
    <?php
    $signedCookieCustomPolicy = createSignedCookie('http://cf.leedoing.com''vod/*'300);
    foreach ($signedCookieCustomPolicy as $name => $value) {
        setcookie($name$value0"""leedoing.com"falsetrue);
    }
    print_r($signedCookieCustomPolicy);
    ?>
 
</body>
</html>
 
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video"></video>
<script>
  if(Hls.isSupported()) {
    var video = document.getElementById('video');
    var hls = new Hls();
    hls.loadSource('http://cf.leedoing.com/vod/mp4:sample.mp4/playlist.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 }
</script>
 
 
cs


Signed Cookie Sample(PHP 5.5+, PHP v3 SDK)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
require 'vendor/autoload.php'// include autoloader of composer.
use Aws\CloudFront\CloudFrontClient;
function createSignedCookie($streamHostUrl$resourceKey$timeout){
$cloudFront = new Aws\CloudFront\CloudFrontClient([
    'region'  => 'ap-northeast-2',
    'version' => '2014-11-06'
]);
$url = $streamHostUrl . "/" . $resourceKey;
$ip = $_SERVER['REMOTE_ADDR'] . "\/24";
$expires = time() + $timeout;
 
$json = '{"Statement":[{"Resource":"'.$url.'","Condition":{"IpAddress":{"AWS:SourceIp":"0.0.0.0\/0"},"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
 
$signedCookieCustomPolicy = $cloudFront->getSignedCookie([
    'url'         => $url,  //Manual Policy
//  'expires'     => $expires, //Manual Policy
    'policy'      => $json//Custom Policy
    'private_key' => '/home/ec2-user/pk-APKAJPWDZQR7UI2ARAYQ.pem',
    'key_pair_id' => 'APKAJPWDZQR7UI2ARAYQ'
]);
        return $signedCookieCustomPolicy;
}
?>
 
<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8" />
        <title>Signed cookie Test</title>
</head>
<body>
    <?php
    $signedCookieCustomPolicy = createSignedCookie('http://cf.leedoing.com''vod/*'300);
    foreach ($signedCookieCustomPolicy as $name => $value) {
        setcookie($name$value0"""leedoing.com"falsetrue);
    }
    print_r($signedCookieCustomPolicy);
    ?>
 
</body>
</html>
 
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video"></video>
<script>
  if(Hls.isSupported()) {
    var video = document.getElementById('video');
    var hls = new Hls();
    hls.loadSource('http://cf.leedoing.com/vod/mp4:sample.mp4/playlist.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 }
</script>
 
 
cs

Signed Cookie 또한 방식은 URL과 거의 동일합니다. 그러나 Path 별로 Singed Cookie를 사용할 수 있습니다.


만약 Signed URL/Cookie를 통해 CloudFront 접근 시에 MalformedPolicy 라는 값이 출력되면 Policy에 문제가 있으니 수정해주시길 바랍니다. 공백은 없는지, RSA 암호화는 되어 있는지, base64 인코딩은 되어 있는지 확인해봅니다.


Java Signed URL 샘플코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
 
import org.apache.commons.codec.binary.Base64;
 
 
public class CFSignedUrl {
 
    /**
     * Reads binary data from an input stream and returns it as a byte array.
     *
     * @param is
     * input stream from which data is read.
     *
     * @return
     * byte array containing data read from the input stream.
     *
     * @throws IOException
     */
    public static byte[] readInputStreamToBytes(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b = -1;
        while ((b = is.read()) != -1) {
            baos.write(b);
        }
        return baos.toByteArray();
    }
    
    /**
     * Converts byte data to a Base64-encoded string.
     *
     * @param data
     * data to Base64 encode.
     * @return
     * encoded Base64 string.
     */
    public static String toBase64(byte[] data) {
        byte[] b64 = Base64.encodeBase64(data);
        try {
            return new String(b64, "UTF-8");
        }
        catch(UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    
    /**
     * Convert the given data to be safe for use in signed URLs for a private distribution by
     * using specialized Base64 encoding.
     *
     * @param bytes
     * @return a URL-safe Base64 encoded version of the data.
     * @throws UnsupportedEncodingException
     */
    protected static String makeBytesUrlSafe(byte[] bytes) throws UnsupportedEncodingException {
        return toBase64(bytes)
                .replace('+''-')
                .replace('=''_')
                .replace('/''~');
    }
    
    /**
     * Generate an RSA SHA1 signature of the given data using the given private
     * key DER certificate.
     *
     * Based on example code from:
     * http://www.java2s.com/Tutorial/Java/0490__Security/RSASignatureGeneration.htm
     * http://forums.sun.com/thread.jspa?threadID=5175986
     *
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws InvalidKeySpecException
     * @throws NoSuchProviderException
     */
    public static byte[] signWithRsaSha1(byte[] derPrivateKeyBytes, byte[] dataToSign)
        throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
        InvalidKeySpecException, NoSuchProviderException
    {
        // Build an RSA private key from private key data
        PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(derPrivateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
 
        // Sign data
        /*Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Signature signature = Signature.getInstance("SHA1withRSA", "BC");*/
        Signature signature = Signature.getInstance("SHA1withRSA""SunJSSE");
        signature.initSign(privateKey, new SecureRandom());
        signature.update(dataToSign);
 
        byte[] signatureBytes = signature.sign();
        return signatureBytes;
    }
    
    /**
     * Generate a signed URL that allows access to a specific distribution and
     * S3 object by applying a access restrictions from a "canned" (simplified)
     * policy document.
     *
     * @param resourceUrlOrPath The URL or path that uniquely identifies a resource within a distribution.
     *                          For standard distributions the resource URL will be
     *                          <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
     *                          parameters. For distributions with the HTTPS required protocol, the resource URL
     *                          must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
     *                          and instead the resource path is nothing but the stream's name.
     * @param keyPairId         Identifier of a public/private certificate keypair already configured in your
     *                          Amazon Web Services account.
     * @param derPrivateKey     The RSA private key data that corresponding to the certificate keypair identified by
     *                          keyPairId, in DER format. To convert a standard PEM private key file into this format
     *                          use the utility method
     * @param epochDateLessThan The time and date when the signed URL will expire. REQUIRED.
     * @return A signed URL that will permit access to a specific distribution and S3 object.
     * @throws eException exception
     */
    public static String signUrlCanned(String resourceUrlOrPath,
                                       String keyPairId, byte[] derPrivateKey, Date epochDateLessThan)
            throws Exception {
        try {
            String cannedPolicy =
                    "{\"Statement\":[{\"Resource\":\"" + resourceUrlOrPath
                            + "\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":"
                            + epochDateLessThan.getTime() / 1000 + "}}}]}";
 
            byte[] signatureBytes = signWithRsaSha1(derPrivateKey,
                    cannedPolicy.getBytes("UTF-8"));
 
            String urlSafeSignature = makeBytesUrlSafe(signatureBytes);
 
            return resourceUrlOrPath
                    + (resourceUrlOrPath.indexOf('?'>= 0 ? "&" : "?")
                    + "Expires=" + epochDateLessThan.getTime() / 1000
                    + "&Signature=" + urlSafeSignature
                    + "&Key-Pair-Id=" + keyPairId;
        }
        catch(RuntimeException e) {
            throw e;
        }
        catch(Exception e) {
            throw e;
        }
    }
    
    /**
     * Generate a policy document that describes custom access permissions to apply
     * via a private distribution's signed URL.
     *
     * @param resourcePath         An optional HTTP/S or RTMP resource path that restricts which distribution and S3 objects
     *                             will be accessible in a signed URL. For standard distributions the resource URL will be
     *                             <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
     *                             parameters. For distributions with the HTTPS required protocol, the resource URL
     *                             must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
     *                             and instead the resource path is nothing but the stream's name.
     *                             <p>
     *                             The '*' and '?' characters can be used as a wildcards to allow multi-character or
     *                             single-character matches respectively:
     *                             <ul>
     *                             <li><tt>*</tt> : All distributions/objects will be accessible</li>
     *                             <li><tt>a1b2c3d4e5f6g7.cloudfront.net/*</tt> : All objects within the distribution
     *                             a1b2c3d4e5f6g7 will be accessible</li>
     *                             <li><tt>a1b2c3d4e5f6g7.cloudfront.net/path/to/object.txt</tt> : Only the S3 object
     *                             named <tt>path/to/object.txt</tt> in the distribution a1b2c3d4e5f6g7 will be
     *                             accessible.</li>
     *                             </ul>
     *                             If this parameter is null the policy will permit access to all distributions and S3
     *                             objects associated with the certificate keypair used to generate the signed URL.
     * @param epochDateLessThan    The time and date when the signed URL will expire. REQUIRED.
     * @param limitToIpAddressCIDR An optional range of client IP addresses that will be allowed to access the distribution,
     *                             specified as a CIDR range. If null, the CIDR will be <tt>0.0.0.0/0</tt> and any
     *                             client will be permitted.
     * @param epochDateGreaterThan An optional time and date when the signed URL will become active. If null, the signed
     *                             URL will be active as soon as it is created.
     * @return A policy document describing the access permission to apply when generating a signed URL.
     * @throws CloudFrontServiceException exception
     */
    public static String buildPolicyForSignedUrl(
            String resourcePath, Date epochDateLessThan,
            String limitToIpAddressCIDR, Date epochDateGreaterThan)
            throws Exception {
        if(epochDateLessThan == null) {
            throw new Exception(
                    "epochDateLessThan must be provided to sign CloudFront URLs");
        }
        if(resourcePath == null) {
            resourcePath = "*";
        }
        String ipAddress = (limitToIpAddressCIDR == null
                ? "0.0.0.0/0"  // No IP restriction
                : limitToIpAddressCIDR);
        return "{\"Statement\": [{" +
                "\"Resource\":\"" + resourcePath + "\"" +
                ",\"Condition\":{" +
                "\"DateLessThan\":{\"AWS:EpochTime\":"
                + epochDateLessThan.getTime() / 1000 + "}" +
                ",\"IpAddress\":{\"AWS:SourceIp\":\"" + ipAddress + "\"}" +
                (epochDateGreaterThan == null ? ""
                        : ",\"DateGreaterThan\":{\"AWS:EpochTime\":"
                        + epochDateGreaterThan.getTime() / 1000 + "}"+
                "}}]}";
    }
    
    /**
     * Convert the given string to be safe for use in signed URLs for a private distribution.
     *
     * @param str
     * @return a URL-safe Base64 encoded version of the data.
     * @throws UnsupportedEncodingException
     */
    protected static String makeStringUrlSafe(String str) throws UnsupportedEncodingException {
        return toBase64(str.getBytes("UTF-8"))
                .replace('+''-')
                .replace('=''_')
                .replace('/''~');
    }
 
    /**
     * Generate a signed URL that allows access to distribution and S3 objects by
     * applying access restrictions specified in a custom policy document.
     *
     * @param resourceUrlOrPath The URL or path that uniquely identifies a resource within a distribution.
     *                          For standard distributions the resource URL will be
     *                          <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
     *                          parameters. For distributions with the HTTPS required protocol, the resource URL
     *                          must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
     *                          and instead the resource path is nothing but the stream's name.
     * @param keyPairId         Identifier of a public/private certificate keypair already configured in your
     *                          Amazon Web Services account.
     * @param derPrivateKey     The RSA private key data that corresponding to the certificate keypair identified by
     *                          keyPairId, in DER format. To convert a standard PEM private key file into this format
     *                          use the utility method {@link EncryptionUtil#convertRsaPemToDer(java.io.InputStream)}
     * @param policy            A policy document that describes the access permissions that will be applied by the
     *                          signed URL. To generate a custom policy use
     *                          {@link #buildPolicyForSignedUrl(String, Date, String, Date)}.
     * @return A signed URL that will permit access to distribution and S3 objects as specified
     *         in the policy document.
     * @throws CloudFrontServiceException exception
     */
    public static String signUrl(String resourceUrlOrPath,
                                 String keyPairId, byte[] derPrivateKey, String policy)
            throws Exception {
        try {
            byte[] signatureBytes = signWithRsaSha1(derPrivateKey,
                    policy.getBytes("UTF-8"));
 
            String urlSafePolicy = makeStringUrlSafe(policy);
            String urlSafeSignature = makeBytesUrlSafe(signatureBytes);
 
            return resourceUrlOrPath
                    + (resourceUrlOrPath.indexOf('?'>= 0 ? "&" : "?")
                    + "Policy=" + urlSafePolicy
                    + "&Signature=" + urlSafeSignature
                    + "&Key-Pair-Id=" + keyPairId;
        }
        catch(RuntimeException e) {
            throw e;
        }
        catch(Exception e) {
            throw new Exception(e);
        }
    }
 
    public static void main(String[] args) throws Exception {
    
/*        AWSCredentials awsCredentials = new AWSCredentials(
                "AKIAI3VAMX24BBXRTDHA", "ksIUBBmG9COjn2MiTPViO4sRnhauicPVuwEOeNoU");
        CloudFrontService cloudFrontService = new CloudFrontService(awsCredentials);
    
        String distributionId = "EREU5ZDWIEXUK";
        
        Distribution distribution = cloudFrontService.getDistributionInfo(distributionId);
        System.out.println("Active trusted signers: " + distribution.toString());
        System.out.println("Active trusted signers: " + distribution.getActiveTrustedSigners());*/
    
        // Obtain one of your own (Self) keypair ids that can sign URLs for the distribution
        
        /*List selfKeypairIds = (List) distribution.getActiveTrustedSigners().get("Self");
        String keyPairId = (String) selfKeypairIds.get(0);
        System.out.println("Keypair ID: " + keyPairId); */
    
        
        // Signed URLs for a private distribution
        // Note that Java only supports SSL certificates in DER format, 
        // so you will need to convert your PEM-formatted file to DER format. 
        // To do this, you can use openssl:
        // openssl pkcs8 -topk8 -nocrypt -in origin.pem -inform PEM -out new.der -outform DER 
        // So the encoder works correctly, you should also add the bouncy castle jar
        // to your project and then add the provider.
    
        //Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    
        String distributionDomain = "d1lw5iruksdx6g.cloudfront.net";
        //String privateKeyFilePath = "D:\\Hoon\\workspace\\test\\src\\pk-APKAICXPW2IVRXISJ2YQ.der";
        String privateKeyFilePath = "D:\\Hoon\\workspace\\test\\src\\pk-APKAJY54CBXY7H526TLA.der";
        String objectKey = "test/index2.html";
        String policyResourcePath = "http://" + distributionDomain + "/" + objectKey;
    
        // Convert your DER file into a byte array.
    
        byte[] derPrivateKey = readInputStreamToBytes(new FileInputStream(privateKeyFilePath));
    
        // Generate a "canned" signed URL to allow access to a 
        // specific distribution and object
 
        long start = 0L;
        long end = 0L;
        long temp = 0L;
        
        start = System.currentTimeMillis();
        temp = start;
        //for(int i=0; i<100*1000; i++){
        String signedUrlCanned = CFSignedUrl.signUrlCanned(
            "http://" + distributionDomain + "/" + objectKey, // Resource URL or Path
            //"APKAICXPW2IVRXISJ2YQ",     // Certificate identifier,
            "APKAJY54CBXY7H526TLA",     // Certificate identifier, 
                           // an active trusted signer for the distribution
            derPrivateKey, // DER Private key data
            new Date(System.currentTimeMillis()+3000000)//ServiceUtils.parseIso8601Date("2014-08-22T10:40:00.000Z") // DateLessThan
            );
        /*if(i%10000==0){
            end = System.currentTimeMillis();
            System.out.println("=====\t"+i + "\tXXXXX "+(end - temp));
            temp = end;
        }*/
        System.out.println(signedUrlCanned);
        //}
        end = System.currentTimeMillis();
        System.out.println("total"+(end -start));
    
        // Build a policy document to define custom restrictions for a signed URL.
    
/*        String policy = CloudFrontService.buildPolicyForSignedUrl(
            // Resource path (optional, may include '*' and '?' wildcards)
            policyResourcePath, 
            // DateLessThan
            ServiceUtils.parseIso8601Date("2014-09-31T22:20:00.000Z"), 
            // CIDR IP address restriction (optional, 0.0.0.0/0 means everyone)
            "0.0.0.0/0", 
            // DateGreaterThan (optional)
            ServiceUtils.parseIso8601Date("2014-01-01T06:31:56.000Z")
            );
    
        // Generate a signed URL using a custom policy document.
    
        String signedUrl = CloudFrontService.signUrl(
            // Resource URL or Path
            "http://" + distributionDomain + "/" + objectKey, 
            // Certificate identifier, an active trusted signer for the distribution
            "IS2P2OZJZTW6VZJYLSR5R75ZVJIGFTUQ",     
            // DER Private key data
            derPrivateKey, 
            // Access control policy
            policy 
            );
        System.out.println(signedUrl);
        
        
*/    
        // Build a policy document to define custom restrictions for a signed URL
        String policy = buildPolicyForSignedUrl(
            "http://d3lsfm7awh826s.cloudfront.net/kmnet/_definst_/kmnet_480p.stream/*"// Resource path (optional, may include '*' and '?' wildcards)
            //ServiceUtils.parseIso8601Date("2009-11-14T22:20:00.000Z"), // DateLessThan
            new Date(System.currentTimeMillis()+30000000),//ServiceUtils.parseIso8601Date("2014-08-22T10:40:00.000Z") // DateLessThan
            "0.0.0.0/0"// CIDR IP address restriction (optional, 0.0.0.0/0 means everyone)
            //ServiceUtils.parseIso8601Date("2009-10-16T06:31:56.000Z")  // DateGreaterThan (optional)
            new Date(System.currentTimeMillis())//ServiceUtils.parseIso8601Date("2014-08-22T10:40:00.000Z") // DateLessThan
            );
 
        // Generate a signed URL using a custom policy document
        String signedUrl = CFSignedUrl.signUrl(
            "http://d3lsfm7awh826s.cloudfront.net/kmnet/_definst_/kmnet_480p.stream/playlist.m3u8"// Domain name
            "APKAICXPW2IVRXISJ2YQ",     // Certificate identifier, an active trusted signer for the distribution
            derPrivateKey, // DER Private key data
            policy // Access control policy
            );
        System.out.println(signedUrl);
 
        }
}
cs


감사합니다.




저작자 표시
신고