13
13
# See the License for the specific language governing permissions and
14
14
# limitations under the License.
15
15
16
+ import collections
16
17
import hashlib
17
18
import hmac
18
19
import binascii
19
20
20
21
from datetime import datetime
21
- from .compat import urlsplit , strtype
22
+ from .error import InvalidArgumentError
23
+ from .compat import urlsplit , strtype , urlencode
22
24
from .helpers import get_region
23
25
26
+ def presign_v4 (method , url , headers = None , access_key = None , secret_key = None , expires = None ):
27
+ if not access_key or not secret_key :
28
+ raise InvalidArgumentError ('invalid access/secret id' )
29
+
30
+ # verify only if 'None' not on expires with 0 value which should
31
+ # be an InvalidArgument is handled later below
32
+ if expires is None :
33
+ expires = 604800
34
+
35
+ if expires < 1 or expires > 604800 :
36
+ raise InvalidArgumentError ('expires param valid values are between 1 secs to 604800 secs' )
37
+
38
+ if headers is None :
39
+ headers = {}
40
+
41
+ parsed_url = urlsplit (url )
42
+ content_hash_hex = 'UNSIGNED-PAYLOAD'
43
+ host = parsed_url .netloc
44
+ headers ['host' ] = host
45
+ date = datetime .utcnow ()
46
+ iso8601Date = date .strftime ("%Y%m%dT%H%M%SZ" )
47
+ region = get_region (parsed_url .hostname )
48
+
49
+ headers_to_sign = dict (headers )
50
+ ignored_headers = ['Authorization' , 'Content-Length' , 'Content-Type' ,
51
+ 'User-Agent' ]
52
+
53
+ for ignored_header in ignored_headers :
54
+ if ignored_header in headers_to_sign :
55
+ del headers_to_sign [ignored_header ]
56
+
57
+ query = {}
58
+ query ['X-Amz-Algorithm' ] = 'AWS4-HMAC-SHA256'
59
+ query ['X-Amz-Credential' ] = generate_credential_string (access_key , date , region )
60
+ query ['X-Amz-Date' ] = iso8601Date
61
+ query ['X-Amz-Expires' ] = expires
62
+ query ['X-Amz-SignedHeaders' ] = ';' .join (get_signed_headers (headers_to_sign ))
63
+
64
+ url_components = [parsed_url .geturl ()]
65
+ if query is not None :
66
+ ordered_query = collections .OrderedDict (sorted (query .items ()))
67
+ query_components = []
68
+ for component_key in ordered_query :
69
+ single_component = [component_key ]
70
+ if ordered_query [component_key ] is not None :
71
+ single_component .append ('=' )
72
+ single_component .append (
73
+ urlencode (str (ordered_query [component_key ])).replace ('/' , '%2F' ))
74
+ query_components .append ('' .join (single_component ))
75
+
76
+ query_string = '&' .join (query_components )
77
+ if query_string :
78
+ url_components .append ('?' )
79
+ url_components .append (query_string )
80
+ new_url = '' .join (url_components )
81
+ new_parsed_url = urlsplit (new_url )
82
+ canonical_request = generate_canonical_request (method ,
83
+ new_parsed_url ,
84
+ headers_to_sign ,
85
+ content_hash_hex )
86
+
87
+ canonical_request_hasher = hashlib .sha256 ()
88
+ canonical_request_hasher .update (canonical_request .encode ('utf-8' ))
89
+ canonical_request_sha256 = canonical_request_hasher .hexdigest ()
90
+
91
+ string_to_sign = generate_string_to_sign (date , region ,
92
+ canonical_request_sha256 )
93
+ signing_key = generate_signing_key (date , region , secret_key )
94
+ signature = hmac .new (signing_key , string_to_sign .encode ('utf-8' ),
95
+ hashlib .sha256 ).hexdigest ()
96
+
97
+ new_parsed_url = urlsplit (new_url + "&X-Amz-Signature=" + signature )
98
+ return new_parsed_url .geturl ()
99
+
100
+ def get_signed_headers (headers ):
101
+ headers_to_sign = dict (headers )
102
+ ignored_headers = ['Authorization' , 'Content-Length' , 'Content-Type' ,
103
+ 'User-Agent' ]
104
+
105
+ for ignored_header in ignored_headers :
106
+ if ignored_header in headers_to_sign :
107
+ del headers_to_sign [ignored_header ]
108
+
109
+ signed_headers = []
110
+ for header in headers :
111
+ signed_headers .append (header )
112
+ signed_headers .sort ()
113
+
114
+ return signed_headers
115
+
24
116
def sign_v4 (method , url , headers = None , access_key = None , secret_key = None ,
25
117
content_hash = None ):
26
118
if not access_key or not secret_key :
@@ -36,7 +128,10 @@ def sign_v4(method, url, headers=None, access_key=None, secret_key=None,
36
128
37
129
host = parsed_url .netloc
38
130
headers ['host' ] = host
39
- headers ['x-amz-date' ] = datetime .utcnow ().strftime ("%Y%m%dT%H%M%SZ" )
131
+ region = get_region (parsed_url .hostname )
132
+
133
+ date = datetime .utcnow ()
134
+ headers ['x-amz-date' ] = date .strftime ("%Y%m%dT%H%M%SZ" )
40
135
headers ['x-amz-content-sha256' ] = content_hash_hex
41
136
42
137
headers_to_sign = dict (headers )
@@ -75,14 +170,11 @@ def sign_v4(method, url, headers=None, access_key=None, secret_key=None,
75
170
if ignored_header in headers_to_sign :
76
171
del headers_to_sign [ignored_header ]
77
172
78
- canonical_request , signed_headers = generate_canonical_request (method ,
79
- parsed_url ,
80
- headers_to_sign ,
81
- content_hash_hex )
82
-
83
- region = get_region (parsed_url .hostname )
84
-
85
- date = datetime .utcnow ()
173
+ signed_headers = get_signed_headers (headers_to_sign )
174
+ canonical_request = generate_canonical_request (method ,
175
+ parsed_url ,
176
+ headers_to_sign ,
177
+ content_hash_hex )
86
178
87
179
canonical_request_hasher = hashlib .sha256 ()
88
180
canonical_request_hasher .update (canonical_request .encode ('utf-8' ))
@@ -91,12 +183,12 @@ def sign_v4(method, url, headers=None, access_key=None, secret_key=None,
91
183
string_to_sign = generate_string_to_sign (date , region ,
92
184
canonical_request_sha256 )
93
185
signing_key = generate_signing_key (date , region , secret_key )
94
- signed_request = hmac .new (signing_key , string_to_sign .encode ('utf-8' ),
95
- hashlib .sha256 ).hexdigest ()
186
+ signature = hmac .new (signing_key , string_to_sign .encode ('utf-8' ),
187
+ hashlib .sha256 ).hexdigest ()
96
188
97
189
authorization_header = generate_authorization_header (access_key , date , region ,
98
190
signed_headers ,
99
- signed_request )
191
+ signature )
100
192
101
193
headers ['authorization' ] = authorization_header
102
194
@@ -134,21 +226,15 @@ def generate_canonical_request(method, parsed_url, headers, content_hash_hex):
134
226
lines .append (';' .join (signed_headers ))
135
227
lines .append (str (content_hash_hex ))
136
228
137
- return '\n ' .join (lines ), signed_headers
229
+ return '\n ' .join (lines )
138
230
139
231
140
232
def generate_string_to_sign (date , region , request_hash ):
141
233
formatted_date_time = date .strftime ("%Y%m%dT%H%M%SZ" )
142
- formatted_date = date .strftime ("%Y%m%d" )
143
-
144
- scope = '/' .join ([formatted_date ,
145
- region ,
146
- 's3' ,
147
- 'aws4_request' ])
148
234
149
235
return '\n ' .join (['AWS4-HMAC-SHA256' ,
150
236
formatted_date_time ,
151
- scope ,
237
+ generate_scope_string ( date , region ) ,
152
238
request_hash ])
153
239
154
240
@@ -165,15 +251,21 @@ def generate_signing_key(date, region, secret):
165
251
return hmac .new (key4 , 'aws4_request' .encode ('utf-8' ),
166
252
hashlib .sha256 ).digest ()
167
253
254
+ def generate_scope_string (date , region ):
255
+ formatted_date = date .strftime ("%Y%m%d" )
256
+ scope = '/' .join ([formatted_date ,
257
+ region ,
258
+ 's3' ,
259
+ 'aws4_request' ])
260
+ return scope
261
+
262
+ def generate_credential_string (access_key , date , region ):
263
+ return access_key + '/' + generate_scope_string (date , region )
168
264
169
265
def generate_authorization_header (access_key , date , region , signed_headers ,
170
- signed_request ):
171
- formatted_date = date .strftime ("%Y%m%d" )
266
+ signature ):
172
267
signed_headers_string = ';' .join (signed_headers )
173
- auth_header = "AWS4-HMAC-SHA256 Credential=" + access_key + "/" + \
174
- formatted_date + "/" + region + \
175
- "/s3/aws4_request, SignedHeaders=" + \
176
- signed_headers_string + \
177
- ", Signature=" + \
178
- signed_request
268
+ credential = generate_credential_string (access_key , date , region )
269
+ auth_header = "AWS4-HMAC-SHA256 Credential=" + credential + ", SignedHeaders=" + \
270
+ signed_headers_string + ", Signature=" + signature
179
271
return auth_header
0 commit comments