Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Signed URLs and Signed Cookies for CloudFront in Python with boto
from boto.cloudfront.distribution import Distribution
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
import base64
class BetterThanBoto(Distribution):
def sign_rsa(self, message):
private_key = serialization.load_pem_private_key(self.keyfile, password=None,
backend=default_backend())
signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1())
message = message.encode('utf-8')
signer.update(message)
return signer.finalize()
def _sign_string(self, message, private_key_file=None, private_key_string=None):
if private_key_file:
self.keyfile = open(private_key_file, 'rb').read()
elif private_key_string:
self.keyfile = private_key_string.encode('utf-8')
return self.sign_rsa(message)
@staticmethod
def _url_base64_encode(msg):
"""
Base64 encodes a string using the URL-safe characters specified by
Amazon.
"""
msg_base64 = base64.b64encode(msg).decode('utf-8')
msg_base64 = msg_base64.replace('+', '-')
msg_base64 = msg_base64.replace('=', '_')
msg_base64 = msg_base64.replace('/', '~')
return msg_base64
def generate_signature(self, policy, private_key_file=None):
"""
:param policy: no-whitespace json str (NOT encoded yet)
:param private_key_file: your .pem file with which to sign the policy
:return: encoded signature for use in cookie
"""
# Distribution._create_signing_params()
signature = self._sign_string(policy, private_key_file)
# now base64 encode the signature & make URL safe
encoded_signature = self._url_base64_encode(signature)
return encoded_signature
def create_signed_cookies(self, url, private_key_file=None, keypair_id=None,
expires_at=20, secure=True):
"""
generate the Cloudfront download distirbution signed cookies
:param resource: The object or path of resource.
Examples: 'dir/object.mp4', 'dir/*', '*'
:param private_key_file: Path to the private key file (pem encoded)
:param key_pair_id: ID of the keypair used to sign the cookie
:param expire_minutes: The number of minutes until expiration
:param secure: use https or http protocol for Cloudfront URL - update
to match your distribution settings.
:return: Cookies to be set
"""
# generate no-whitespace json policy,
# then base64 encode & make url safe
policy = self._custom_policy(
url,
expires_at
)
encoded_policy = self._url_base64_encode(policy.encode('utf-8'))
# assemble the 3 Cloudfront cookies
signature = self.generate_signature(
policy, private_key_file=private_key_file
)
cookies = {
"CloudFront-Policy": encoded_policy,
"CloudFront-Signature": signature,
"CloudFront-Key-Pair-Id": keypair_id
}
return cookies
def sign_to_cloudfront(object_url, expires_at):
""" Sign URL to distribute file"""
cf = BetterThanBoto()
url = cf.create_signed_url(url=object_url,
keypair_id="XXXXXXXXXXX",
expire_time=expires_at,
private_key_file="ssl/key.pem")
return url
def create_signed_cookies(object_url, expires_at):
"""
Create a signed cookie
"""
cf = BetterThanBoto()
cookies = cf.create_signed_cookies(url=object_url,
keypair_id=keypair_id="XXXXXXXXXXX",
expires_at=expires_at,
private_key_file="ssl/key.pem")
return cookies
@vedavidhbudimuri

This comment has been minimized.

Copy link

@vedavidhbudimuri vedavidhbudimuri commented Apr 22, 2017

I tried using this script for generating signed cookies but im getting access denied in response. Is there any thing im missing.
below is the issue i posted in stack overflow
http://stackoverflow.com/questions/43540243/unable-to-use-signed-cookies-for-cloudfront

@mikepii

This comment has been minimized.

Copy link

@mikepii mikepii commented Apr 23, 2018

@DataGreed

This comment has been minimized.

Copy link

@DataGreed DataGreed commented May 14, 2020

How do I generate the pem file for this?

@mekza

This comment has been minimized.

Copy link
Owner Author

@mekza mekza commented May 14, 2020

@mekza

This comment has been minimized.

Copy link
Owner Author

@mekza mekza commented May 14, 2020

@DataGreed note that my snippet might be very outdated since it uses boto and not botocore nor boto3. Better way to do it: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudfront.html#generate-a-signed-url-for-amazon-cloudfront

@DataGreed

This comment has been minimized.

Copy link

@DataGreed DataGreed commented May 17, 2020

@mekza thank you very much!

@NabilHunt

This comment has been minimized.

Copy link

@NabilHunt NabilHunt commented Aug 26, 2020

That was helpful! Thanks

@ox0spy

This comment has been minimized.

Copy link

@ox0spy ox0spy commented Oct 23, 2020

@abaptista

This comment has been minimized.

Copy link

@abaptista abaptista commented Oct 23, 2020

Hi there, I was searching for a python version for signing cloudfront cookies and found yours.
@ox0spy I gave it a try with your implementation but I keep getting:
Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>.

I followed the steps you mentioned:

  • created the keys, and upload the public to cloudfront
  • configured a key group associated to the public key, which then is the one used for restricting access to the s3 bucket.
    -the bucket policy is correctly configured to only accept access from the cloudfront distribution (if I disable the restricted access based on the cookies/urls it works fine.)

Do have any idea that might help? Maybe if you could share the values of the variables you are using could give an insight on what I might be doing wrong.
private_key_path=''; key_id = ''; base_url =''; obj_key ='';

Thanks

@ox0spy

This comment has been minimized.

Copy link

@ox0spy ox0spy commented Oct 24, 2020

@abaptista

Hi abaptista, please check my latest code and make sure you choose the correct Trusted Key Group.

# example of variables
private_key_path = 'the-path-of-your-private-key'  # e.g.: './private_key.pem'
key_id = 'KXXXXXXXXXXXQ'
base_url = 'https://xxxx.cloudfront.net'
obj_key = 'the-object-path-of-your-s3'  # e.g.: 'images/avatar.png'

new code remove `base_url`, just using the file's cdn url, e.g.: `url = 'https://xxxx.cloudfront.net/images/avatar.png'`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment