@@ -93,7 +93,7 @@ def hmac_sha256(key, msg):
9393 return hmac .new (key , msg .encode ('utf-8' ), hashlib .sha256 ).digest ()
9494
9595 @staticmethod
96- def sign_url (path , method , query , ak , sk , region , service , session_token = None ):
96+ def sign_url (path , method , query , ak , sk , region , service , session_token = None , host = None ):
9797 """
9898 Generate presigned URL query string (AWS Signature V4)
9999
@@ -105,6 +105,7 @@ def sign_url(path, method, query, ak, sk, region, service, session_token=None):
105105 :param region: Service region
106106 :param service: Service name
107107 :param session_token: Optional session token
108+ :param host: Optional host header to sign
108109 :return: Query string with signature
109110 """
110111 format_date = datetime .datetime .utcnow ().strftime ("%Y%m%dT%H%M%SZ" )
@@ -113,31 +114,46 @@ def sign_url(path, method, query, ak, sk, region, service, session_token=None):
113114 # Build credential scope
114115 credential_scope = '/' .join ([date , region , service , 'request' ])
115116
117+ # Determine if host header should be signed
118+ sign_host = host is not None and host != ''
119+
116120 # Add required query parameters
117121 query = dict (query ) # Make a copy to avoid modifying original
118122 query ['X-Date' ] = format_date
119123 query ['X-NotSignBody' ] = ''
120124 query ['X-Credential' ] = ak + '/' + credential_scope
121125 query ['X-Algorithm' ] = 'HMAC-SHA256'
122- query ['X-SignedHeaders' ] = ''
126+ query ['X-SignedHeaders' ] = 'host' if sign_host else ' '
123127 query ['X-SignedQueries' ] = ''
124128
129+ # Generate X-SignedQueries BEFORE adding X-Security-Token
130+ query ['X-SignedQueries' ] = ';' .join (sorted (query .keys ()))
131+
132+ # X-Security-Token must be added AFTER X-SignedQueries calculation
125133 if session_token :
126134 query ['X-Security-Token' ] = session_token
127135
128- # Generate X-SignedQueries with all query parameter names
129- query ['X-SignedQueries' ] = ';' .join (sorted (query .keys ()))
130-
131- # Build canonical request with empty body
136+ # Build canonical request
132137 body_hash = hashlib .sha256 (b'' ).hexdigest ()
133- canonical_request = '\n ' .join ([
134- method ,
135- path ,
136- SignerV4 .canonical_query (query ),
137- '\n ' , # Empty line for signed headers section
138- '' , # Empty signed headers list
139- body_hash
140- ])
138+
139+ if sign_host :
140+ canonical_request = '\n ' .join ([
141+ method ,
142+ path ,
143+ SignerV4 .canonical_query (query ),
144+ 'host:' + host + '\n ' ,
145+ 'host' ,
146+ body_hash
147+ ])
148+ else :
149+ canonical_request = '\n ' .join ([
150+ method ,
151+ path ,
152+ SignerV4 .canonical_query (query ),
153+ '\n ' ,
154+ '' ,
155+ body_hash
156+ ])
141157 sdk_core_logger .debug_sign ("[sign_url] canonical_request:\n %s" , canonical_request )
142158
143159 # Build string to sign
0 commit comments