24
24
import java .util .Arrays ;
25
25
import java .util .List ;
26
26
import java .util .Map ;
27
+ import java .util .TreeMap ;
27
28
import software .amazon .awssdk .annotations .SdkInternalApi ;
28
29
import software .amazon .awssdk .auth .credentials .AwsCredentials ;
29
30
import software .amazon .awssdk .auth .credentials .AwsSessionCredentials ;
@@ -75,7 +76,14 @@ protected SdkHttpFullRequest.Builder doSign(SdkHttpFullRequest request,
75
76
.filter (h -> h .equals ("required" ))
76
77
.ifPresent (h -> mutableRequest .putHeader (SignerConstant .X_AMZ_CONTENT_SHA256 , contentSha256 ));
77
78
78
- String canonicalRequest = createCanonicalRequest (mutableRequest , contentSha256 , signingParams .doubleUrlEncode ());
79
+ Map <String , List <String >> canonicalHeaders = canonicalizeSigningHeaders (mutableRequest .headers ());
80
+ String signedHeadersString = getSignedHeadersString (canonicalHeaders );
81
+
82
+ String canonicalRequest = createCanonicalRequest (mutableRequest ,
83
+ canonicalHeaders ,
84
+ signedHeadersString ,
85
+ contentSha256 ,
86
+ signingParams .doubleUrlEncode ());
79
87
80
88
String stringToSign = createStringToSign (canonicalRequest , requestParams );
81
89
@@ -84,7 +92,7 @@ protected SdkHttpFullRequest.Builder doSign(SdkHttpFullRequest request,
84
92
byte [] signature = computeSignature (stringToSign , signingKey );
85
93
86
94
mutableRequest .putHeader (SignerConstant .AUTHORIZATION ,
87
- buildAuthorizationHeader (signature , sanitizedCredentials , requestParams , mutableRequest ));
95
+ buildAuthorizationHeader (signature , sanitizedCredentials , requestParams , signedHeadersString ));
88
96
89
97
processRequestPayload (mutableRequest , signature , signingKey , requestParams , signingParams );
90
98
@@ -110,11 +118,16 @@ protected SdkHttpFullRequest.Builder doPresign(SdkHttpFullRequest request,
110
118
}
111
119
112
120
// Add the important parameters for v4 signing
113
- addPreSignInformationToRequest (mutableRequest , sanitizedCredentials , requestParams , expirationInSeconds );
121
+ Map <String , List <String >> canonicalizedHeaders = canonicalizeSigningHeaders (mutableRequest .headers ());
122
+ String signedHeadersString = getSignedHeadersString (canonicalizedHeaders );
123
+
124
+ addPreSignInformationToRequest (mutableRequest , signedHeadersString , sanitizedCredentials ,
125
+ requestParams , expirationInSeconds );
114
126
115
127
String contentSha256 = calculateContentHashPresign (mutableRequest , signingParams );
116
128
117
- String canonicalRequest = createCanonicalRequest (mutableRequest , contentSha256 , signingParams .doubleUrlEncode ());
129
+ String canonicalRequest = createCanonicalRequest (mutableRequest , canonicalizedHeaders , signedHeadersString ,
130
+ contentSha256 , signingParams .doubleUrlEncode ());
118
131
119
132
String stringToSign = createStringToSign (canonicalRequest , requestParams );
120
133
@@ -191,19 +204,20 @@ protected final byte[] deriveSigningKey(AwsCredentials credentials, Instant sign
191
204
* generate the canonical request.
192
205
*/
193
206
private String createCanonicalRequest (SdkHttpFullRequest .Builder request ,
207
+ Map <String , List <String >> canonicalHeaders ,
208
+ String signedHeadersString ,
194
209
String contentSha256 ,
195
210
boolean doubleUrlEncode ) {
196
-
197
211
String canonicalRequest = request .method ().toString () +
198
212
SignerConstant .LINE_SEPARATOR +
199
213
// This would optionally double url-encode the resource path
200
214
getCanonicalizedResourcePath (request .encodedPath (), doubleUrlEncode ) +
201
215
SignerConstant .LINE_SEPARATOR +
202
216
getCanonicalizedQueryString (request .rawQueryParameters ()) +
203
217
SignerConstant .LINE_SEPARATOR +
204
- getCanonicalizedHeaderString (request . headers () ) +
218
+ getCanonicalizedHeaderString (canonicalHeaders ) +
205
219
SignerConstant .LINE_SEPARATOR +
206
- getSignedHeadersString ( request . headers ()) +
220
+ signedHeadersString +
207
221
SignerConstant .LINE_SEPARATOR +
208
222
contentSha256 ;
209
223
@@ -254,12 +268,11 @@ private byte[] computeSignature(String stringToSign, byte[] signingKey) {
254
268
private String buildAuthorizationHeader (byte [] signature ,
255
269
AwsCredentials credentials ,
256
270
Aws4SignerRequestParams signerParams ,
257
- SdkHttpFullRequest . Builder mutableRequest ) {
271
+ String signedHeadersString ) {
258
272
259
273
String signingCredentials = credentials .accessKeyId () + "/" + signerParams .getScope ();
260
274
String credential = "Credential=" + signingCredentials ;
261
- String signerHeaders = "SignedHeaders=" +
262
- getSignedHeadersString (mutableRequest .headers ());
275
+ String signerHeaders = "SignedHeaders=" + signedHeadersString ;
263
276
String signatureHeader = "Signature=" + BinaryUtils .toHex (signature );
264
277
265
278
return SignerConstant .AWS4_SIGNING_ALGORITHM + " " + credential + ", " + signerHeaders + ", " + signatureHeader ;
@@ -269,6 +282,7 @@ private String buildAuthorizationHeader(byte[] signature,
269
282
* Includes all the signing headers as request parameters for pre-signing.
270
283
*/
271
284
private void addPreSignInformationToRequest (SdkHttpFullRequest .Builder mutableRequest ,
285
+ String signedHeadersString ,
272
286
AwsCredentials sanitizedCredentials ,
273
287
Aws4SignerRequestParams signerParams ,
274
288
long expirationInSeconds ) {
@@ -277,34 +291,39 @@ private void addPreSignInformationToRequest(SdkHttpFullRequest.Builder mutableRe
277
291
278
292
mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_ALGORITHM , SignerConstant .AWS4_SIGNING_ALGORITHM );
279
293
mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_DATE , signerParams .getFormattedRequestSigningDateTime ());
280
- mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_SIGNED_HEADER ,
281
- getSignedHeadersString (mutableRequest .headers ()));
282
- mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_EXPIRES ,
283
- Long .toString (expirationInSeconds ));
294
+ mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_SIGNED_HEADER , signedHeadersString );
295
+ mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_EXPIRES , Long .toString (expirationInSeconds ));
284
296
mutableRequest .putRawQueryParameter (SignerConstant .X_AMZ_CREDENTIAL , signingCredentials );
285
297
}
286
298
299
+ private Map <String , List <String >> canonicalizeSigningHeaders (Map <String , List <String >> headers ) {
300
+ Map <String , List <String >> result = new TreeMap <>();
287
301
288
- private String getCanonicalizedHeaderString (Map <String , List <String >> headers ) {
289
- List <String > sortedHeaders = new ArrayList <>(headers .keySet ());
290
- sortedHeaders .sort (String .CASE_INSENSITIVE_ORDER );
291
-
292
- StringBuilder buffer = new StringBuilder ();
293
- for (String header : sortedHeaders ) {
294
- if (shouldExcludeHeaderFromSigning (header )) {
302
+ for (Map .Entry <String , List <String >> header : headers .entrySet ()) {
303
+ String lowerCaseHeader = lowerCase (header .getKey ());
304
+ if (LIST_OF_HEADERS_TO_IGNORE_IN_LOWER_CASE .contains (lowerCaseHeader )) {
295
305
continue ;
296
306
}
297
- String key = lowerCase (header );
298
307
299
- for (String headerValue : headers .get (header )) {
300
- appendCompactedString (buffer , key );
308
+ result .computeIfAbsent (lowerCaseHeader , x -> new ArrayList <>()).addAll (header .getValue ());
309
+ }
310
+
311
+ return result ;
312
+ }
313
+
314
+ private String getCanonicalizedHeaderString (Map <String , List <String >> canonicalizedHeaders ) {
315
+ StringBuilder buffer = new StringBuilder ();
316
+
317
+ canonicalizedHeaders .forEach ((headerName , headerValues ) -> {
318
+ for (String headerValue : headerValues ) {
319
+ appendCompactedString (buffer , headerName );
301
320
buffer .append (":" );
302
321
if (headerValue != null ) {
303
322
appendCompactedString (buffer , headerValue );
304
323
}
305
324
buffer .append ("\n " );
306
325
}
307
- }
326
+ });
308
327
309
328
return buffer .toString ();
310
329
}
@@ -350,28 +369,17 @@ private boolean isWhiteSpace(final char ch) {
350
369
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\u000b' || ch == '\r' || ch == '\f' ;
351
370
}
352
371
353
- private String getSignedHeadersString (Map <String , List <String >> headers ) {
354
- List <String > sortedHeaders = new ArrayList <>(headers .keySet ());
355
- sortedHeaders .sort (String .CASE_INSENSITIVE_ORDER );
356
-
372
+ private String getSignedHeadersString (Map <String , List <String >> canonicalizedHeaders ) {
357
373
StringBuilder buffer = new StringBuilder ();
358
- for (String header : sortedHeaders ) {
359
- if (shouldExcludeHeaderFromSigning (header )) {
360
- continue ;
361
- }
374
+ for (String header : canonicalizedHeaders .keySet ()) {
362
375
if (buffer .length () > 0 ) {
363
376
buffer .append (";" );
364
377
}
365
- buffer .append (lowerCase ( header ) );
378
+ buffer .append (header );
366
379
}
367
-
368
380
return buffer .toString ();
369
381
}
370
382
371
- private boolean shouldExcludeHeaderFromSigning (String header ) {
372
- return LIST_OF_HEADERS_TO_IGNORE_IN_LOWER_CASE .contains (lowerCase (header ));
373
- }
374
-
375
383
private void addHostHeader (SdkHttpFullRequest .Builder mutableRequest ) {
376
384
// AWS4 requires that we sign the Host header so we
377
385
// have to have it in the request by the time we sign.
0 commit comments