1"""
2Provides AWS4SigningKey class for generating Amazon Web Services
3authentication version 4 signing keys.
4
5"""
6
7# Licensed under the MIT License:
8# http://opensource.org/licenses/MIT
9
10
11from __future__ import unicode_literals
12
13import hmac
14import hashlib
15from warnings import warn
16from datetime import datetime
17from six import text_type
18
19
20class AWS4SigningKey:
21    """
22    AWS signing key. Used to sign AWS authentication strings.
23
24    The secret key is stored in the instance after instantiation, this can be
25    changed via the store_secret_key argument, see below for details.
26
27    Methods:
28    generate_key() -- Generate AWS4 Signing Key string
29    sign_sha256()  -- Generate SHA256 HMAC signature, encoding message to bytes
30                      first if required
31
32    Attributes:
33    region   -- AWS region the key is scoped for
34    service  -- AWS service the key is scoped for
35    date     -- Date the key is scoped for
36    scope    -- The AWS scope string for this key, calculated from the above
37                attributes
38    key      -- The signing key string itself
39
40    amz_date -- Deprecated name for 'date'. Use the 'date' attribute instead.
41                amz_date will be removed in a future version.
42
43    """
44
45    def __init__(self, secret_key, region, service, date=None,
46                 store_secret_key=True):
47        """
48        >>> AWS4SigningKey(secret_key, region, service[, date]
49        ...                [, store_secret_key])
50
51        secret_key -- This is your AWS secret access key
52        region     -- The region you're connecting to, as per list at
53                      http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
54                      e.g. us-east-1. For services which don't require a
55                      region (e.g. IAM), use us-east-1.
56        service    -- The name of the service you're connecting to, as per
57                      endpoints at:
58                      http://docs.aws.amazon.com/general/latest/gr/rande.html
59                      e.g. elasticbeanstalk
60        date       -- 8-digit date of the form YYYYMMDD. Key is only valid for
61                      requests with a Date or X-Amz-Date header matching this
62                      date. If date is not supplied the current date is
63                      used.
64        store_secret_key
65                   -- Whether the secret key is stored in the instance. By
66                      default this is True, meaning the key is stored in
67                      the secret_key property and is available to any
68                      code the instance is passed to. Having the secret
69                      key retained makes it easier to regenerate the key
70                      if a scope parameter changes (usually the date).
71                      This is used by the AWS4Auth class to perform its
72                      automatic key updates when a request date/scope date
73                      mismatch is encountered.
74
75                      If you are passing instances to untrusted code you can
76                      set this to False. This will cause the secret key to be
77                      discarded as soon as the signing key has been generated.
78                      Note though that you will need to manually regenerate
79                      keys when needed (or if you use the regenerate_key()
80                      method on an AWS4Auth instance you will need to pass it
81                      the secret key).
82
83        All arguments should be supplied as strings.
84
85        """
86
87        self.region = region
88        self.service = service
89        self.date = date or datetime.utcnow().strftime('%Y%m%d')
90        self.scope = '{}/{}/{}/aws4_request'.format(self.date, self.region, self.service)
91        self.store_secret_key = store_secret_key
92        self.secret_key = secret_key if self.store_secret_key else None
93        self.key = self.generate_key(secret_key, self.region, self.service, self.date)
94
95    @classmethod
96    def generate_key(cls, secret_key, region, service, date,
97                     intermediates=False):
98        """
99        Generate the signing key string as bytes.
100
101        If intermediate is set to True, returns a 4-tuple containing the key
102        and the intermediate keys:
103
104        ( signing_key, date_key, region_key, service_key )
105
106        The intermediate keys can be used for testing against examples from
107        Amazon.
108
109        """
110        init_key = ('AWS4' + secret_key).encode('utf-8')
111        date_key = cls.sign_sha256(init_key, date)
112        region_key = cls.sign_sha256(date_key, region)
113        service_key = cls.sign_sha256(region_key, service)
114        key = cls.sign_sha256(service_key, 'aws4_request')
115        if intermediates:
116            return (key, date_key, region_key, service_key)
117        else:
118            return key
119
120    @staticmethod
121    def sign_sha256(key, msg):
122        """
123        Generate an SHA256 HMAC, encoding msg to UTF-8 if not
124        already encoded.
125
126        key -- signing key. bytes.
127        msg -- message to sign. unicode or bytes.
128
129        """
130        if isinstance(msg, text_type):
131            msg = msg.encode('utf-8')
132        return hmac.new(key, msg, hashlib.sha256).digest()
133
134    @property
135    def amz_date(self):
136        msg = ("This attribute has been renamed to 'date'. 'amz_date' is "
137               "deprecated and will be removed in a future version.")
138        warn(msg, DeprecationWarning)
139        return self.date
140