1 /*
2  * Copyright 2013-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.auth;
16
17 import com.amazonaws.ReadLimitInfo;
18 import com.amazonaws.SdkClientException;
19 import com.amazonaws.SignableRequest;
20 import com.amazonaws.annotation.SdkTestInternalApi;
21 import com.amazonaws.auth.internal.AWS4SignerRequestParams;
22 import com.amazonaws.auth.internal.AWS4SignerUtils;
23 import com.amazonaws.auth.internal.SignerKey;
24 import com.amazonaws.internal.FIFOCache;
25 import com.amazonaws.log.InternalLogApi;
26 import com.amazonaws.log.InternalLogFactory;
27 import com.amazonaws.util.BinaryUtils;
28 import com.amazonaws.util.DateUtils;
29 import com.amazonaws.util.SdkHttpUtils;
30 import com.amazonaws.util.StringUtils;
31
32 import com.amazonaws.util.endpoint.RegionFromEndpointResolver;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.net.URI;
36 import java.nio.charset.Charset;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Date;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.concurrent.TimeUnit;
44
45 import static com.amazonaws.auth.internal.SignerConstants.AUTHORIZATION;
46 import static com.amazonaws.auth.internal.SignerConstants.AWS4_SIGNING_ALGORITHM;
47 import static com.amazonaws.auth.internal.SignerConstants.AWS4_TERMINATOR;
48 import static com.amazonaws.auth.internal.SignerConstants.HOST;
49 import static com.amazonaws.auth.internal.SignerConstants.LINE_SEPARATOR;
50 import static com.amazonaws.auth.internal.SignerConstants.PRESIGN_URL_MAX_EXPIRATION_SECONDS;
51 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_ALGORITHM;
52 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_CONTENT_SHA256;
53 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_CREDENTIAL;
54 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_DATE;
55 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_EXPIRES;
56 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SECURITY_TOKEN;
57 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SIGNATURE;
58 import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SIGNED_HEADER;
59
60 /**
61  * Signer implementation that signs requests with the AWS4 signing protocol.
62  */

63 public class AWS4Signer extends AbstractAWSSigner implements
64         ServiceAwareSigner, RegionAwareSigner, Presigner, EndpointPrefixAwareSigner, RegionFromEndpointResolverAwareSigner {
65
66     protected static final InternalLogApi log = InternalLogFactory.getLog(AWS4Signer.class);
67     private static final int SIGNER_CACHE_MAX_SIZE = 300;
68     private static final FIFOCache<SignerKey> signerCache = new FIFOCache<SignerKey>(SIGNER_CACHE_MAX_SIZE);
69     private static final List<String> listOfHeadersToIgnoreInLowerCase = Arrays.asList("connection""x-amzn-trace-id");
70
71     private final SdkClock clock;
72
73     /**
74      * Service name override for use when the endpoint can't be used to
75      * determine the service name.
76      */

77     protected String serviceName;
78
79     /**
80      * Endpoint prefix to compute the region name for signing
81      * when the {@link #regionName} is null.
82      */

83     private String endpointPrefix;
84
85     private RegionFromEndpointResolver regionFromEndpointResolver;
86
87     /**
88      * Region name override for use when the endpoint can't be used to determine
89      * the region name.
90      */

91     protected String regionName;
92
93     /** Date override for testing only */
94     protected Date overriddenDate;
95
96     /**
97      * Whether double url-encode the resource path when constructing the
98      * canonical request. By default, we enable double url-encoding.
99      *
100      * TODO: Different sigv4 services seem to be inconsistent on this. So for
101      * services that want to suppress this, they should use new
102      * AWS4Signer(false).
103      */

104     protected boolean doubleUrlEncode;
105
106     /**
107      * Construct a new AWS4 signer instance. By default, enable double
108      * url-encoding.
109      */

110     public AWS4Signer() {
111         this(true);
112     }
113
114     /**
115      * Construct a new AWS4 signer instance.
116      *
117      * @param doubleUrlEncoding
118      *            Whether double url-encode the resource path when constructing
119      *            the canonical request.
120      */

121     public AWS4Signer(boolean doubleUrlEncoding) {
122         this(doubleUrlEncoding, SdkClock.Instance.get());
123     }
124
125     @SdkTestInternalApi
126     public AWS4Signer(SdkClock clock) {
127         this(true, clock);
128     }
129
130     private AWS4Signer(boolean doubleUrlEncode, SdkClock clock) {
131         this.doubleUrlEncode = doubleUrlEncode;
132         this.clock = clock;
133     }
134
135     /**
136      * Sets the service name that this signer should use when calculating
137      * request signatures. This can almost always be determined directly from
138      * the request's end point, so you shouldn't need this method, but it's
139      * provided for the edge case where the information is not in the endpoint.
140      *
141      * @param serviceName
142      *            The service name to use when calculating signatures in this
143      *            signer.
144      */

145     @Override
146     public void setServiceName(String serviceName) {
147         this.serviceName = serviceName;
148     }
149
150     /**
151      * Sets the region name that this signer should use when calculating request
152      * signatures. This can almost always be determined directly from the
153      * request's end point, so you shouldn't need this method, but it's provided
154      * for the edge case where the information is not in the endpoint.
155      *
156      * @param regionName
157      *            The region name to use when calculating signatures in this
158      *            signer.
159      */

160     @Override
161     public void setRegionName(String regionName) {
162         this.regionName = regionName;
163     }
164
165     /**
166      * Sets the endpoint prefix which is used to compute the region that is
167      * used for signing the request.
168      *
169      * This value is passed to {@link AWS4SignerRequestParams} class which
170      * has the logic to compute region.
171      *
172      * @param endpointPrefix The endpoint prefix of the service
173      */

174     @Override
175     public void setEndpointPrefix(String endpointPrefix) {
176         this.endpointPrefix = endpointPrefix;
177     }
178
179     /**
180      * Sets the date that overrides the signing date in the request. This method
181      * is internal and should be used only for testing purposes.
182      */

183     @SdkTestInternalApi
184     public void setOverrideDate(Date overriddenDate) {
185         if (overriddenDate != null) {
186             this.overriddenDate = new Date(overriddenDate.getTime());
187         } else {
188             this.overriddenDate = null;
189         }
190     }
191
192     @Override
193     public void setRegionFromEndpointResolver(RegionFromEndpointResolver resolver) {
194         this.regionFromEndpointResolver = resolver;
195     }
196
197     /**
198      * Returns the region name that is used when calculating the signature.
199      */

200     public String getRegionName() {
201         return regionName;
202     }
203
204     /**
205      * Returns the service name that is used when calculating the signature.
206      */

207     public String getServiceName() {
208         return serviceName;
209     }
210
211     /**
212      * Returns a copy of date that overrides the signing date in the request.
213      * Return null by default.
214      */

215     public Date getOverriddenDate() {
216         return overriddenDate == null ? null : new Date(
217                 overriddenDate.getTime());
218     }
219
220     @Override
221     public void sign(SignableRequest<?> request, AWSCredentials credentials) {
222         // anonymous credentials, don't sign
223         if (isAnonymous(credentials)) {
224             return;
225         }
226
227         AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials);
228         if (sanitizedCredentials instanceof AWSSessionCredentials) {
229             addSessionCredentials(request,
230                     (AWSSessionCredentials) sanitizedCredentials);
231         }
232
233         final AWS4SignerRequestParams signerParams = new AWS4SignerRequestParams(
234                 request, overriddenDate, regionName, serviceName,
235                 AWS4_SIGNING_ALGORITHM, endpointPrefix, regionFromEndpointResolver);
236
237         addHostHeader(request);
238         request.addHeader(X_AMZ_DATE,
239                 signerParams.getFormattedSigningDateTime());
240
241         String contentSha256 = calculateContentHash(request);
242
243         if ("required".equals(request.getHeaders().get(X_AMZ_CONTENT_SHA256))) {
244             request.addHeader(X_AMZ_CONTENT_SHA256, contentSha256);
245         }
246
247         final String canonicalRequest = createCanonicalRequest(request,
248                 contentSha256);
249
250         final String stringToSign = createStringToSign(canonicalRequest,
251                 signerParams);
252
253         final byte[] signingKey = deriveSigningKey(sanitizedCredentials,
254                 signerParams);
255
256         final byte[] signature = computeSignature(stringToSign, signingKey,
257                 signerParams);
258
259         request.addHeader(
260                 AUTHORIZATION,
261                 buildAuthorizationHeader(request, signature,
262                         sanitizedCredentials, signerParams));
263
264         processRequestPayload(request, signature, signingKey,
265                 signerParams);
266     }
267
268     @Override
269     public void presignRequest(SignableRequest<?> request, AWSCredentials credentials,
270             Date userSpecifiedExpirationDate) {
271
272         // anonymous credentials, don't sign
273         if (isAnonymous(credentials)) {
274             return;
275         }
276
277         long expirationInSeconds = generateExpirationDate(userSpecifiedExpirationDate);
278
279         addHostHeader(request);
280
281         AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials);
282         if (sanitizedCredentials instanceof AWSSessionCredentials) {
283             // For SigV4 pre-signing URL, we need to add "X-Amz-Security-Token"
284             // as a query string parameter, before constructing the canonical
285             // request.
286             request.addParameter(X_AMZ_SECURITY_TOKEN,
287                     ((AWSSessionCredentials) sanitizedCredentials)
288                             .getSessionToken());
289         }
290
291         final AWS4SignerRequestParams signerRequestParams = new AWS4SignerRequestParams(
292                 request, overriddenDate, regionName, serviceName,
293                 AWS4_SIGNING_ALGORITHM, endpointPrefix, regionFromEndpointResolver);
294
295         // Add the important parameters for v4 signing
296         final String timeStamp = signerRequestParams.getFormattedSigningDateTime();
297
298         addPreSignInformationToRequest(request, sanitizedCredentials,
299                 signerRequestParams, timeStamp, expirationInSeconds);
300
301         final String contentSha256 = calculateContentHashPresign(request);
302
303         final String canonicalRequest = createCanonicalRequest(request,
304                 contentSha256);
305
306         final String stringToSign = createStringToSign(canonicalRequest,
307                 signerRequestParams);
308
309         final byte[] signingKey = deriveSigningKey(sanitizedCredentials,
310                 signerRequestParams);
311
312         final byte[] signature = computeSignature(stringToSign, signingKey,
313                 signerRequestParams);
314
315         request.addParameter(X_AMZ_SIGNATURE, BinaryUtils.toHex(signature));
316     }
317
318     /**
319      * Step 1 of the AWS Signature version 4 calculation. Refer to
320      * http://docs.aws
321      * .amazon.com/general/latest/gr/sigv4-create-canonical-request.html to
322      * generate the canonical request.
323      */

324     protected String createCanonicalRequest(SignableRequest<?> request,
325             String contentSha256) {
326         /* This would url-encode the resource path for the first time. */
327         final String path = SdkHttpUtils.appendUri(
328                 request.getEndpoint().getPath(), request.getResourcePath());
329
330         final StringBuilder canonicalRequestBuilder = new StringBuilder(request
331                 .getHttpMethod().toString());
332
333         canonicalRequestBuilder.append(LINE_SEPARATOR)
334                 // This would optionally double url-encode the resource path
335                 .append(getCanonicalizedResourcePath(path, doubleUrlEncode))
336                 .append(LINE_SEPARATOR)
337                 .append(getCanonicalizedQueryString(request))
338                 .append(LINE_SEPARATOR)
339                 .append(getCanonicalizedHeaderString(request))
340                 .append(LINE_SEPARATOR)
341                 .append(getSignedHeadersString(request)).append(LINE_SEPARATOR)
342                 .append(contentSha256);
343
344         final String canonicalRequest = canonicalRequestBuilder.toString();
345
346         if (log.isDebugEnabled())
347             log.debug("AWS4 Canonical Request: '\"" + canonicalRequest + "\"");
348
349         return canonicalRequest;
350     }
351
352     /**
353      * Step 2 of the AWS Signature version 4 calculation. Refer to
354      * http://docs.aws
355      * .amazon.com/general/latest/gr/sigv4-create-string-to-sign.html.
356      */

357     protected String createStringToSign(String canonicalRequest,
358             AWS4SignerRequestParams signerParams) {
359
360         final StringBuilder stringToSignBuilder = new StringBuilder(
361                 signerParams.getSigningAlgorithm());
362         stringToSignBuilder.append(LINE_SEPARATOR)
363                 .append(signerParams.getFormattedSigningDateTime())
364                 .append(LINE_SEPARATOR)
365                 .append(signerParams.getScope())
366                 .append(LINE_SEPARATOR)
367                 .append(BinaryUtils.toHex(hash(canonicalRequest)));
368
369         final String stringToSign = stringToSignBuilder.toString();
370
371         if (log.isDebugEnabled())
372             log.debug("AWS4 String to Sign: '\"" + stringToSign + "\"");
373
374         return stringToSign;
375     }
376
377     /**
378      * Step 3 of the AWS Signature version 4 calculation. It involves deriving
379      * the signing key and computing the signature. Refer to
380      * http://docs.aws.amazon
381      * .com/general/latest/gr/sigv4-calculate-signature.html
382      */

383     private final byte[] deriveSigningKey(AWSCredentials credentials,
384             AWS4SignerRequestParams signerRequestParams) {
385
386         final String cacheKey = computeSigningCacheKeyName(credentials,
387                 signerRequestParams);
388         final long daysSinceEpochSigningDate = DateUtils
389                 .numberOfDaysSinceEpoch(signerRequestParams
390                         .getSigningDateTimeMilli());
391
392         SignerKey signerKey = signerCache.get(cacheKey);
393
394         if (signerKey != null) {
395             if (daysSinceEpochSigningDate == signerKey
396                     .getNumberOfDaysSinceEpoch()) {
397                 return signerKey.getSigningKey();
398             }
399         }
400         if (log.isDebugEnabled()) {
401             log.debug("Generating a new signing key as the signing key not available in the cache for the date "
402                     + TimeUnit.DAYS.toMillis(daysSinceEpochSigningDate));
403         }
404         byte[] signingKey = newSigningKey(credentials,
405                 signerRequestParams.getFormattedSigningDate(),
406                 signerRequestParams.getRegionName(),
407                 signerRequestParams.getServiceName());
408         signerCache.add(cacheKey, new SignerKey(
409                 daysSinceEpochSigningDate, signingKey));
410         return signingKey;
411     }
412
413     /**
414      * Computes the name to be used to reference the signing key in the cache.
415      */

416     private final String computeSigningCacheKeyName(AWSCredentials credentials,
417             AWS4SignerRequestParams signerRequestParams) {
418         final StringBuilder hashKeyBuilder = new StringBuilder(
419                 credentials.getAWSSecretKey());
420
421         return hashKeyBuilder.append("-")
422                 .append(signerRequestParams.getRegionName())
423                 .append("-")
424                 .append(signerRequestParams.getServiceName()).toString();
425     }
426
427     /**
428      * Step 3 of the AWS Signature version 4 calculation. It involves deriving
429      * the signing key and computing the signature. Refer to
430      * http://docs.aws.amazon
431      * .com/general/latest/gr/sigv4-calculate-signature.html
432      */

433     protected final byte[] computeSignature(String stringToSign,
434             byte[] signingKey, AWS4SignerRequestParams signerRequestParams) {
435         return sign(stringToSign.getBytes(Charset.forName("UTF-8")), signingKey,
436                 SigningAlgorithm.HmacSHA256);
437     }
438
439     /**
440      * Creates the authorization header to be included in the request.
441      */

442     private String buildAuthorizationHeader(SignableRequest<?> request,
443             byte[] signature, AWSCredentials credentials,
444             AWS4SignerRequestParams signerParams) {
445         final String signingCredentials = credentials.getAWSAccessKeyId() + "/"
446                 + signerParams.getScope();
447
448         final String credential = "Credential="
449                 + signingCredentials;
450         final String signerHeaders = "SignedHeaders="
451                 + getSignedHeadersString(request);
452         final String signatureHeader = "Signature="
453                 + BinaryUtils.toHex(signature);
454
455         final StringBuilder authHeaderBuilder = new StringBuilder();
456
457         authHeaderBuilder.append(AWS4_SIGNING_ALGORITHM)
458                          .append(" ")
459                          .append(credential)
460                          .append(", ")
461                          .append(signerHeaders)
462                          .append(", ")
463                          .append(signatureHeader);
464
465         return authHeaderBuilder.toString();
466     }
467
468     /**
469      * Includes all the signing headers as request parameters for pre-signing.
470      */

471     private void addPreSignInformationToRequest(SignableRequest<?> request,
472             AWSCredentials credentials, AWS4SignerRequestParams signerParams,
473             String timeStamp, long expirationInSeconds) {
474
475         String signingCredentials = credentials.getAWSAccessKeyId() + "/"
476                 + signerParams.getScope();
477
478         request.addParameter(X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM);
479         request.addParameter(X_AMZ_DATE, timeStamp);
480         request.addParameter(X_AMZ_SIGNED_HEADER,
481                 getSignedHeadersString(request));
482         request.addParameter(X_AMZ_EXPIRES,
483                 Long.toString(expirationInSeconds));
484         request.addParameter(X_AMZ_CREDENTIAL, signingCredentials);
485     }
486
487     @Override
488     protected void addSessionCredentials(SignableRequest<?> request,
489             AWSSessionCredentials credentials) {
490         request.addHeader(X_AMZ_SECURITY_TOKEN, credentials.getSessionToken());
491     }
492
493     protected String getCanonicalizedHeaderString(SignableRequest<?> request) {
494         final List<String> sortedHeaders = new ArrayList<String>(request.getHeaders()
495                 .keySet());
496         Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
497
498         final Map<String, String> requestHeaders = request.getHeaders();
499         StringBuilder buffer = new StringBuilder();
500         for (String header : sortedHeaders) {
501             if (shouldExcludeHeaderFromSigning(header)) {
502                 continue;
503             }
504             String key = StringUtils.lowerCase(header);
505             String value = requestHeaders.get(header);
506
507             StringUtils.appendCompactedString(buffer, key);
508             buffer.append(":");
509             if (value != null) {
510                 StringUtils.appendCompactedString(buffer, value);
511             }
512
513             buffer.append("\n");
514         }
515
516         return buffer.toString();
517     }
518
519     protected String getSignedHeadersString(SignableRequest<?> request) {
520         final List<String> sortedHeaders = new ArrayList<String>(request
521                 .getHeaders().keySet());
522         Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
523
524         StringBuilder buffer = new StringBuilder();
525         for (String header : sortedHeaders) {
526             if (shouldExcludeHeaderFromSigning(header)) {
527                 continue;
528             }
529             if (buffer.length() > 0)
530                 buffer.append(";");
531             buffer.append(StringUtils.lowerCase(header));
532         }
533
534         return buffer.toString();
535     }
536
537     protected boolean shouldExcludeHeaderFromSigning(String header) {
538         return listOfHeadersToIgnoreInLowerCase.contains(header.toLowerCase());
539     }
540
541     protected void addHostHeader(SignableRequest<?> request) {
542         // AWS4 requires that we sign the Host header so we
543         // have to have it in the request by the time we sign.
544
545         final URI endpoint = request.getEndpoint();
546
547         if (endpoint.getHost() == null) {
548             throw new IllegalArgumentException("Request endpoint must have a valid hostname, but it did not: " + endpoint);
549         }
550
551         final StringBuilder hostHeaderBuilder = new StringBuilder(endpoint.getHost());
552         if (SdkHttpUtils.isUsingNonDefaultPort(endpoint)) {
553             hostHeaderBuilder.append(":").append(endpoint.getPort());
554         }
555
556         request.addHeader(HOST, hostHeaderBuilder.toString());
557     }
558
559     /**
560      * Calculate the hash of the request's payload. Subclass could override this
561      * method to provide different values for "x-amz-content-sha256" header or
562      * do any other necessary set-ups on the request headers. (e.g. aws-chunked
563      * uses a pre-defined header value, and needs to change some headers
564      * relating to content-encoding and content-length.)
565      */

566     protected String calculateContentHash(SignableRequest<?> request) {
567         InputStream payloadStream = getBinaryRequestPayloadStream(request);
568         ReadLimitInfo info = request.getReadLimitInfo();
569         payloadStream.mark(info == null ? -1 : info.getReadLimit());
570         String contentSha256 = BinaryUtils.toHex(hash(payloadStream));
571         try {
572             payloadStream.reset();
573         } catch (IOException e) {
574             throw new SdkClientException(
575                     "Unable to reset stream after calculating AWS4 signature",
576                     e);
577         }
578         return contentSha256;
579     }
580
581     /**
582      * Subclass could override this method to perform any additional procedure
583      * on the request payload, with access to the result from signing the
584      * header. (e.g. Signing the payload by chunk-encoding). The default
585      * implementation doesn't need to do anything.
586      */

587     protected void processRequestPayload(SignableRequest<?> request, byte[] signature,
588             byte[] signingKey, AWS4SignerRequestParams signerRequestParams) {
589         return;
590     }
591
592     /**
593      * Calculate the hash of the request's payload. In case of pre-sign, the
594      * existing code would generate the hash of an empty byte array and returns
595      * it. This method can be overridden by sub classes to provide different
596      * values (e.g) For S3 pre-signing, the content hash calculation is
597      * different from the general implementation.
598      *
599      */

600     protected String calculateContentHashPresign(SignableRequest<?> request) {
601         return calculateContentHash(request);
602     }
603
604     /**
605      * Checks if the credentials is an instance of
606      * <code>AnonymousAWSCredentials<code>
607      */

608     private boolean isAnonymous(AWSCredentials credentials) {
609         return credentials instanceof AnonymousAWSCredentials;
610     }
611
612     /**
613      * Generates an expiration date for the presigned url. If user has specified
614      * an expiration date, check if it is in the given limit.
615      */

616     private long generateExpirationDate(Date expirationDate) {
617
618         long expirationInSeconds = expirationDate != null ? ((expirationDate
619                 .getTime() - clock.currentTimeMillis()) / 1000L)
620                 : PRESIGN_URL_MAX_EXPIRATION_SECONDS;
621
622         if (expirationInSeconds > PRESIGN_URL_MAX_EXPIRATION_SECONDS) {
623             throw new SdkClientException(
624                     "Requests that are pre-signed by SigV4 algorithm are valid for at most 7 days. "
625                             + "The expiration date set on the current request ["
626                             + AWS4SignerUtils.formatTimestamp(expirationDate
627                                     .getTime()) + "] has exceeded this limit.");
628         }
629         return expirationInSeconds;
630     }
631
632     /**
633      * Generates a new signing key from the given parameters and returns it.
634      */

635     protected byte[] newSigningKey(AWSCredentials credentials,
636             String dateStamp, String regionName, String serviceName) {
637         byte[] kSecret = ("AWS4" + credentials.getAWSSecretKey())
638                 .getBytes(Charset.forName("UTF-8"));
639         byte[] kDate = sign(dateStamp, kSecret, SigningAlgorithm.HmacSHA256);
640         byte[] kRegion = sign(regionName, kDate, SigningAlgorithm.HmacSHA256);
641         byte[] kService = sign(serviceName, kRegion,
642                 SigningAlgorithm.HmacSHA256);
643         return sign(AWS4_TERMINATOR, kService, SigningAlgorithm.HmacSHA256);
644     }
645 }
646