""" Provides AWS4SigningKey class for generating Amazon Web Services authentication version 4 signing keys. """ # Licensed under the MIT License: # http://opensource.org/licenses/MIT from __future__ import unicode_literals import hmac import hashlib from warnings import warn from datetime import datetime from six import text_type class AWS4SigningKey: """ AWS signing key. Used to sign AWS authentication strings. The secret key is stored in the instance after instantiation, this can be changed via the store_secret_key argument, see below for details. Methods: generate_key() -- Generate AWS4 Signing Key string sign_sha256() -- Generate SHA256 HMAC signature, encoding message to bytes first if required Attributes: region -- AWS region the key is scoped for service -- AWS service the key is scoped for date -- Date the key is scoped for scope -- The AWS scope string for this key, calculated from the above attributes key -- The signing key string itself amz_date -- Deprecated name for 'date'. Use the 'date' attribute instead. amz_date will be removed in a future version. """ def __init__(self, secret_key, region, service, date=None, store_secret_key=True): """ >>> AWS4SigningKey(secret_key, region, service[, date] ... [, store_secret_key]) secret_key -- This is your AWS secret access key region -- The region you're connecting to, as per list at http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region e.g. us-east-1. For services which don't require a region (e.g. IAM), use us-east-1. service -- The name of the service you're connecting to, as per endpoints at: http://docs.aws.amazon.com/general/latest/gr/rande.html e.g. elasticbeanstalk date -- 8-digit date of the form YYYYMMDD. Key is only valid for requests with a Date or X-Amz-Date header matching this date. If date is not supplied the current date is used. store_secret_key -- Whether the secret key is stored in the instance. By default this is True, meaning the key is stored in the secret_key property and is available to any code the instance is passed to. Having the secret key retained makes it easier to regenerate the key if a scope parameter changes (usually the date). This is used by the AWS4Auth class to perform its automatic key updates when a request date/scope date mismatch is encountered. If you are passing instances to untrusted code you can set this to False. This will cause the secret key to be discarded as soon as the signing key has been generated. Note though that you will need to manually regenerate keys when needed (or if you use the regenerate_key() method on an AWS4Auth instance you will need to pass it the secret key). All arguments should be supplied as strings. """ self.region = region self.service = service self.date = date or datetime.utcnow().strftime('%Y%m%d') self.scope = '{}/{}/{}/aws4_request'.format(self.date, self.region, self.service) self.store_secret_key = store_secret_key self.secret_key = secret_key if self.store_secret_key else None self.key = self.generate_key(secret_key, self.region, self.service, self.date) @classmethod def generate_key(cls, secret_key, region, service, date, intermediates=False): """ Generate the signing key string as bytes. If intermediate is set to True, returns a 4-tuple containing the key and the intermediate keys: ( signing_key, date_key, region_key, service_key ) The intermediate keys can be used for testing against examples from Amazon. """ init_key = ('AWS4' + secret_key).encode('utf-8') date_key = cls.sign_sha256(init_key, date) region_key = cls.sign_sha256(date_key, region) service_key = cls.sign_sha256(region_key, service) key = cls.sign_sha256(service_key, 'aws4_request') if intermediates: return (key, date_key, region_key, service_key) else: return key @staticmethod def sign_sha256(key, msg): """ Generate an SHA256 HMAC, encoding msg to UTF-8 if not already encoded. key -- signing key. bytes. msg -- message to sign. unicode or bytes. """ if isinstance(msg, text_type): msg = msg.encode('utf-8') return hmac.new(key, msg, hashlib.sha256).digest() @property def amz_date(self): msg = ("This attribute has been renamed to 'date'. 'amz_date' is " "deprecated and will be removed in a future version.") warn(msg, DeprecationWarning) return self.date