Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2021 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.auth.oauth2;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.auth.ServiceAccountSigner.SigningException;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.io.BaseEncoding;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Internal utility that signs AWS API requests based on the AWS Signature Version 4 signing
* process.
*
* @see AWS
* Signature V4
*/
class AwsRequestSigner {
// AWS Signature Version 4 signing algorithm identifier.
private static final String HASHING_ALGORITHM = "AWS4-HMAC-SHA256";
// The termination string for the AWS credential scope value as defined in
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
private static final String AWS_REQUEST_TYPE = "aws4_request";
private final AwsSecurityCredentials awsSecurityCredentials;
private final Map additionalHeaders;
private final String httpMethod;
private final String region;
private final String requestPayload;
private final URI uri;
private final AwsDates dates;
/**
* Internal constructor.
*
* @param awsSecurityCredentials AWS security credentials
* @param httpMethod the HTTP request method
* @param url the request URL
* @param region the targeted region
* @param requestPayload the request payload
* @param additionalHeaders a map of additional HTTP headers to be included with the signed
* request
*/
private AwsRequestSigner(
AwsSecurityCredentials awsSecurityCredentials,
String httpMethod,
String url,
String region,
@Nullable String requestPayload,
@Nullable Map additionalHeaders,
@Nullable AwsDates awsDates) {
this.awsSecurityCredentials = checkNotNull(awsSecurityCredentials);
this.httpMethod = checkNotNull(httpMethod);
this.uri = URI.create(url).normalize();
this.region = checkNotNull(region);
this.requestPayload = requestPayload == null ? "" : requestPayload;
this.additionalHeaders =
(additionalHeaders != null)
? new HashMap<>(additionalHeaders)
: new HashMap();
this.dates = awsDates == null ? AwsDates.generateXAmzDate() : awsDates;
}
/**
* Signs the specified AWS API request.
*
* @return the {@link AwsRequestSignature}
*/
AwsRequestSignature sign() {
// Retrieve the service name. For example: iam.amazonaws.com host => iam service.
String serviceName = Splitter.on(".").split(uri.getHost()).iterator().next();
Map canonicalHeaders = getCanonicalHeaders(dates.getOriginalDate());
// Headers must be sorted.
List sortedHeaderNames = new ArrayList<>();
for (String headerName : canonicalHeaders.keySet()) {
sortedHeaderNames.add(headerName.toLowerCase(Locale.US));
}
Collections.sort(sortedHeaderNames);
String canonicalRequestHash = createCanonicalRequestHash(canonicalHeaders, sortedHeaderNames);
String credentialScope =
dates.getFormattedDate() + "/" + region + "/" + serviceName + "/" + AWS_REQUEST_TYPE;
String stringToSign =
createStringToSign(canonicalRequestHash, dates.getXAmzDate(), credentialScope);
String signature =
calculateAwsV4Signature(
serviceName,
awsSecurityCredentials.getSecretAccessKey(),
dates.getFormattedDate(),
region,
stringToSign);
String authorizationHeader =
generateAuthorizationHeader(
sortedHeaderNames, awsSecurityCredentials.getAccessKeyId(), credentialScope, signature);
return new AwsRequestSignature.Builder()
.setSignature(signature)
.setCanonicalHeaders(canonicalHeaders)
.setHttpMethod(httpMethod)
.setSecurityCredentials(awsSecurityCredentials)
.setCredentialScope(credentialScope)
.setUrl(uri.toString())
.setDate(dates.getOriginalDate())
.setRegion(region)
.setAuthorizationHeader(authorizationHeader)
.build();
}
/** Task 1: Create a canonical request for Signature Version 4. */
private String createCanonicalRequestHash(
Map headers, List sortedHeaderNames) {
// Append the HTTP request method.
StringBuilder canonicalRequest = new StringBuilder(httpMethod).append("\n");
// Append the path.
String urlPath = uri.getRawPath().isEmpty() ? "/" : uri.getRawPath();
canonicalRequest.append(urlPath).append("\n");
// Append the canonical query string.
String actionQueryString = uri.getRawQuery() != null ? uri.getRawQuery() : "";
canonicalRequest.append(actionQueryString).append("\n");
// Append the canonical headers.
StringBuilder canonicalHeaders = new StringBuilder();
for (String headerName : sortedHeaderNames) {
canonicalHeaders.append(headerName).append(":").append(headers.get(headerName)).append("\n");
}
canonicalRequest.append(canonicalHeaders).append("\n");
// Append the signed headers.
canonicalRequest.append(Joiner.on(';').join(sortedHeaderNames)).append("\n");
// Append the hashed request payload.
canonicalRequest.append(getHexEncodedSha256Hash(requestPayload.getBytes(UTF_8)));
// Return the hashed canonical request.
return getHexEncodedSha256Hash(canonicalRequest.toString().getBytes(UTF_8));
}
/** Task 2: Create a string to sign for Signature Version 4. */
private String createStringToSign(
String canonicalRequestHash, String xAmzDate, String credentialScope) {
return HASHING_ALGORITHM
+ "\n"
+ xAmzDate
+ "\n"
+ credentialScope
+ "\n"
+ canonicalRequestHash;
}
/**
* Task 3: Calculate the signature for AWS Signature Version 4.
*
* @param date the date used in the hashing process in YYYYMMDD format
*/
private String calculateAwsV4Signature(
String serviceName, String secret, String date, String region, String stringToSign) {
byte[] kDate = sign(("AWS4" + secret).getBytes(UTF_8), date.getBytes(UTF_8));
byte[] kRegion = sign(kDate, region.getBytes(UTF_8));
byte[] kService = sign(kRegion, serviceName.getBytes(UTF_8));
byte[] kSigning = sign(kService, AWS_REQUEST_TYPE.getBytes(UTF_8));
return BaseEncoding.base16().lowerCase().encode(sign(kSigning, stringToSign.getBytes(UTF_8)));
}
/** Task 4: Format the signature to be added to the HTTP request. */
private String generateAuthorizationHeader(
List sortedHeaderNames,
String accessKeyId,
String credentialScope,
String signature) {
return String.format(
"%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
HASHING_ALGORITHM,
accessKeyId,
credentialScope,
Joiner.on(';').join(sortedHeaderNames),
signature);
}
private Map getCanonicalHeaders(String defaultDate) {
Map headers = new HashMap<>();
headers.put("host", uri.getHost());
// Only add the date if it hasn't been specified through the "date" header.
if (!additionalHeaders.containsKey("date")) {
headers.put("x-amz-date", defaultDate);
}
if (awsSecurityCredentials.getSessionToken() != null
&& !awsSecurityCredentials.getSessionToken().isEmpty()) {
headers.put("x-amz-security-token", awsSecurityCredentials.getSessionToken());
}
// Add all additional headers.
for (String key : additionalHeaders.keySet()) {
// Header keys need to be lowercase.
headers.put(key.toLowerCase(Locale.US), additionalHeaders.get(key));
}
return headers;
}
private static byte[] sign(byte[] key, byte[] value) {
try {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(value);
} catch (NoSuchAlgorithmException e) {
// Will not occur as HmacSHA256 is supported. We may allow other algorithms in the future.
throw new RuntimeException("HmacSHA256 must be supported by the JVM.", e);
} catch (InvalidKeyException e) {
throw new SigningException("Invalid key used when calculating the AWS V4 Signature", e);
}
}
private static String getHexEncodedSha256Hash(byte[] bytes) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return BaseEncoding.base16().lowerCase().encode(digest.digest(bytes));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to compute SHA-256 hash.", e);
}
}
static Builder newBuilder(
AwsSecurityCredentials awsSecurityCredentials, String httpMethod, String url, String region) {
return new Builder(awsSecurityCredentials, httpMethod, url, region);
}
static class Builder {
private final AwsSecurityCredentials awsSecurityCredentials;
private final String httpMethod;
private final String url;
private final String region;
@Nullable private String requestPayload;
@Nullable private Map additionalHeaders;
@Nullable private AwsDates dates;
private Builder(
AwsSecurityCredentials awsSecurityCredentials,
String httpMethod,
String url,
String region) {
this.awsSecurityCredentials = awsSecurityCredentials;
this.httpMethod = httpMethod;
this.url = url;
this.region = region;
}
@CanIgnoreReturnValue
Builder setRequestPayload(String requestPayload) {
this.requestPayload = requestPayload;
return this;
}
@CanIgnoreReturnValue
Builder setAdditionalHeaders(Map additionalHeaders) {
if (additionalHeaders.containsKey("date") && additionalHeaders.containsKey("x-amz-date")) {
throw new IllegalArgumentException("One of {date, x-amz-date} can be specified, not both.");
}
try {
if (additionalHeaders.containsKey("date")) {
this.dates = AwsDates.fromDateHeader(additionalHeaders.get("date"));
}
if (additionalHeaders.containsKey("x-amz-date")) {
this.dates = AwsDates.fromXAmzDate(additionalHeaders.get("x-amz-date"));
}
} catch (ParseException e) {
throw new IllegalArgumentException("The provided date header value is invalid.", e);
}
this.additionalHeaders = additionalHeaders;
return this;
}
AwsRequestSigner build() {
return new AwsRequestSigner(
awsSecurityCredentials,
httpMethod,
url,
region,
requestPayload,
additionalHeaders,
dates);
}
}
}