kopia lustrzana https://github.com/gaul/s3proxy
Moving signature creation logic to a separate class
Signed-off-by: Chaithanya Ganta <ganta@adobe.com>pull/239/head
rodzic
3af585e4a6
commit
23705a879b
|
@ -0,0 +1,311 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014-2017 Andrew Gaul <andrew@gaul.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.gaul.s3proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.SortedSetMultimap;
|
||||||
|
import com.google.common.collect.TreeMultimap;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
import com.google.common.net.PercentEscaper;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
final class AwsSignature {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(
|
||||||
|
S3ProxyHandler.class);
|
||||||
|
private static final PercentEscaper AWS_URL_PARAMETER_ESCAPER =
|
||||||
|
new PercentEscaper("-_.~", false);
|
||||||
|
private static final Set<String> SIGNED_SUBRESOURCES = ImmutableSet.of(
|
||||||
|
"acl",
|
||||||
|
"delete",
|
||||||
|
"lifecycle",
|
||||||
|
"location",
|
||||||
|
"logging",
|
||||||
|
"notification",
|
||||||
|
"partNumber",
|
||||||
|
"policy",
|
||||||
|
"requestPayment",
|
||||||
|
"response-cache-control",
|
||||||
|
"response-content-disposition",
|
||||||
|
"response-content-encoding",
|
||||||
|
"response-content-language",
|
||||||
|
"response-content-type",
|
||||||
|
"response-expires",
|
||||||
|
"torrent",
|
||||||
|
"uploadId",
|
||||||
|
"uploads",
|
||||||
|
"versionId",
|
||||||
|
"versioning",
|
||||||
|
"versions",
|
||||||
|
"website"
|
||||||
|
);
|
||||||
|
|
||||||
|
private AwsSignature() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Amazon V2 signature. Reference:
|
||||||
|
* http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
|
||||||
|
*/
|
||||||
|
static String createAuthorizationSignature(
|
||||||
|
HttpServletRequest request, String uri, String credential) {
|
||||||
|
// sort Amazon headers
|
||||||
|
SortedSetMultimap<String, String> canonicalizedHeaders =
|
||||||
|
TreeMultimap.create();
|
||||||
|
for (String headerName : Collections.list(request.getHeaderNames())) {
|
||||||
|
Collection<String> headerValues = Collections.list(
|
||||||
|
request.getHeaders(headerName));
|
||||||
|
headerName = headerName.toLowerCase();
|
||||||
|
if (!headerName.startsWith("x-amz-")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (headerValues.isEmpty()) {
|
||||||
|
canonicalizedHeaders.put(headerName, "");
|
||||||
|
}
|
||||||
|
for (String headerValue : headerValues) {
|
||||||
|
canonicalizedHeaders.put(headerName,
|
||||||
|
Strings.nullToEmpty(headerValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build string to sign
|
||||||
|
StringBuilder builder = new StringBuilder()
|
||||||
|
.append(request.getMethod())
|
||||||
|
.append('\n')
|
||||||
|
.append(Strings.nullToEmpty(request.getHeader(
|
||||||
|
HttpHeaders.CONTENT_MD5)))
|
||||||
|
.append('\n')
|
||||||
|
.append(Strings.nullToEmpty(request.getHeader(
|
||||||
|
HttpHeaders.CONTENT_TYPE)))
|
||||||
|
.append('\n');
|
||||||
|
String expires = request.getParameter("Expires");
|
||||||
|
if (expires != null) {
|
||||||
|
builder.append(expires);
|
||||||
|
} else if (!canonicalizedHeaders.containsKey("x-amz-date")) {
|
||||||
|
builder.append(request.getHeader(HttpHeaders.DATE));
|
||||||
|
}
|
||||||
|
builder.append('\n');
|
||||||
|
for (Map.Entry<String, String> entry : canonicalizedHeaders.entries()) {
|
||||||
|
builder.append(entry.getKey()).append(':')
|
||||||
|
.append(entry.getValue()).append('\n');
|
||||||
|
}
|
||||||
|
builder.append(uri);
|
||||||
|
|
||||||
|
char separator = '?';
|
||||||
|
List<String> subresources = Collections.list(
|
||||||
|
request.getParameterNames());
|
||||||
|
Collections.sort(subresources);
|
||||||
|
for (String subresource : subresources) {
|
||||||
|
if (SIGNED_SUBRESOURCES.contains(subresource)) {
|
||||||
|
builder.append(separator).append(subresource);
|
||||||
|
|
||||||
|
String value = request.getParameter(subresource);
|
||||||
|
if (!"".equals(value)) {
|
||||||
|
builder.append('=').append(value);
|
||||||
|
}
|
||||||
|
separator = '&';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String stringToSign = builder.toString();
|
||||||
|
logger.trace("stringToSign: {}", stringToSign);
|
||||||
|
|
||||||
|
// sign string
|
||||||
|
Mac mac;
|
||||||
|
try {
|
||||||
|
mac = Mac.getInstance("HmacSHA1");
|
||||||
|
mac.init(new SecretKeySpec(credential.getBytes(
|
||||||
|
StandardCharsets.UTF_8), "HmacSHA1"));
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return BaseEncoding.base64().encode(mac.doFinal(
|
||||||
|
stringToSign.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] signMessage(byte[] data, byte[] key, String algorithm)
|
||||||
|
throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
Mac mac = Mac.getInstance(algorithm);
|
||||||
|
mac.init(new SecretKeySpec(key, algorithm));
|
||||||
|
return mac.doFinal(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMessageDigest(byte[] payload, String algorithm)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||||
|
byte[] hash = md.digest(payload);
|
||||||
|
return BaseEncoding.base16().lowerCase().encode(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] extractSignedHeaders(String authorization) {
|
||||||
|
int index = authorization.indexOf("SignedHeaders=");
|
||||||
|
if (index < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int endSigned = authorization.indexOf(',', index);
|
||||||
|
if (endSigned < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int startHeaders = authorization.indexOf('=', index);
|
||||||
|
return authorization.substring(startHeaders + 1, endSigned).split(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildCanonicalHeaders(HttpServletRequest request,
|
||||||
|
String[] signedHeaders) {
|
||||||
|
List<String> headers = new ArrayList<>();
|
||||||
|
for (String header : signedHeaders) {
|
||||||
|
headers.add(header.toLowerCase());
|
||||||
|
}
|
||||||
|
Collections.sort(headers);
|
||||||
|
List<String> headersWithValues = new ArrayList<>();
|
||||||
|
for (String header : headers) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
StringBuilder headerWithValue = new StringBuilder();
|
||||||
|
headerWithValue.append(header);
|
||||||
|
headerWithValue.append(":");
|
||||||
|
for (String value : Collections.list(request.getHeaders(header))) {
|
||||||
|
value = value.trim();
|
||||||
|
if (!value.startsWith("\"")) {
|
||||||
|
value = value.replaceAll("\\s+", " ");
|
||||||
|
}
|
||||||
|
values.add(value);
|
||||||
|
}
|
||||||
|
headerWithValue.append(Joiner.on(",").join(values));
|
||||||
|
headersWithValues.add(headerWithValue.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Joiner.on("\n").join(headersWithValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildCanonicalQueryString(HttpServletRequest request)
|
||||||
|
throws UnsupportedEncodingException {
|
||||||
|
// The parameters are required to be sorted
|
||||||
|
List<String> parameters = Collections.list(request.getParameterNames());
|
||||||
|
Collections.sort(parameters);
|
||||||
|
List<String> queryParameters = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String key : parameters) {
|
||||||
|
if (key.equals("X-Amz-Signature")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// re-encode keys and values in AWS normalized form
|
||||||
|
String value = request.getParameter(key);
|
||||||
|
queryParameters.add(AWS_URL_PARAMETER_ESCAPER.escape(key) +
|
||||||
|
"=" + AWS_URL_PARAMETER_ESCAPER.escape(value));
|
||||||
|
}
|
||||||
|
return Joiner.on("&").join(queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createCanonicalRequest(HttpServletRequest request,
|
||||||
|
String uri, byte[] payload,
|
||||||
|
String hashAlgorithm)
|
||||||
|
throws IOException, NoSuchAlgorithmException {
|
||||||
|
String authorizationHeader = request.getHeader("Authorization");
|
||||||
|
String xAmzContentSha256 = request.getHeader("x-amz-content-sha256");
|
||||||
|
if (xAmzContentSha256 == null) {
|
||||||
|
xAmzContentSha256 = request.getParameter("X-Amz-SignedHeaders");
|
||||||
|
}
|
||||||
|
String digest;
|
||||||
|
if (authorizationHeader == null) {
|
||||||
|
digest = "UNSIGNED-PAYLOAD";
|
||||||
|
} else if ("STREAMING-AWS4-HMAC-SHA256-PAYLOAD".equals(
|
||||||
|
xAmzContentSha256)) {
|
||||||
|
digest = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
|
||||||
|
} else if ("UNSIGNED-PAYLOAD".equals(xAmzContentSha256)) {
|
||||||
|
digest = "UNSIGNED-PAYLOAD";
|
||||||
|
} else {
|
||||||
|
digest = getMessageDigest(payload, hashAlgorithm);
|
||||||
|
}
|
||||||
|
String[] signedHeaders;
|
||||||
|
if (authorizationHeader != null) {
|
||||||
|
signedHeaders = extractSignedHeaders(authorizationHeader);
|
||||||
|
} else {
|
||||||
|
signedHeaders = request.getParameter("X-Amz-SignedHeaders")
|
||||||
|
.split(";");
|
||||||
|
}
|
||||||
|
String canonicalRequest = Joiner.on("\n").join(
|
||||||
|
request.getMethod(),
|
||||||
|
uri,
|
||||||
|
buildCanonicalQueryString(request),
|
||||||
|
buildCanonicalHeaders(request, signedHeaders) + "\n",
|
||||||
|
Joiner.on(';').join(signedHeaders),
|
||||||
|
digest);
|
||||||
|
return getMessageDigest(
|
||||||
|
canonicalRequest.getBytes(StandardCharsets.UTF_8),
|
||||||
|
hashAlgorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create v4 signature. Reference:
|
||||||
|
* http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||||
|
*/
|
||||||
|
static String createAuthorizationSignatureV4(
|
||||||
|
HttpServletRequest request, S3AuthorizationHeader authHeader,
|
||||||
|
byte[] payload, String uri, String credential)
|
||||||
|
throws InvalidKeyException, IOException, NoSuchAlgorithmException,
|
||||||
|
S3Exception {
|
||||||
|
String canonicalRequest = createCanonicalRequest(request, uri, payload,
|
||||||
|
authHeader.hashAlgorithm);
|
||||||
|
String algorithm = authHeader.hmacAlgorithm;
|
||||||
|
byte[] dateKey = signMessage(
|
||||||
|
authHeader.date.getBytes(StandardCharsets.UTF_8),
|
||||||
|
("AWS4" + credential).getBytes(StandardCharsets.UTF_8),
|
||||||
|
algorithm);
|
||||||
|
byte[] dateRegionKey = signMessage(
|
||||||
|
authHeader.region.getBytes(StandardCharsets.UTF_8), dateKey,
|
||||||
|
algorithm);
|
||||||
|
byte[] dateRegionServiceKey = signMessage(
|
||||||
|
authHeader.service.getBytes(StandardCharsets.UTF_8),
|
||||||
|
dateRegionKey, algorithm);
|
||||||
|
byte[] signingKey = signMessage(
|
||||||
|
"aws4_request".getBytes(StandardCharsets.UTF_8),
|
||||||
|
dateRegionServiceKey, algorithm);
|
||||||
|
String date = request.getHeader("x-amz-date");
|
||||||
|
if (date == null) {
|
||||||
|
date = request.getParameter("X-Amz-Date");
|
||||||
|
}
|
||||||
|
String signatureString = "AWS4-HMAC-SHA256\n" +
|
||||||
|
date + "\n" +
|
||||||
|
authHeader.date + "/" + authHeader.region +
|
||||||
|
"/s3/aws4_request\n" +
|
||||||
|
canonicalRequest;
|
||||||
|
byte[] signature = signMessage(
|
||||||
|
signatureString.getBytes(StandardCharsets.UTF_8),
|
||||||
|
signingKey, algorithm);
|
||||||
|
return BaseEncoding.base16().lowerCase().encode(signature);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,6 @@ import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -59,7 +58,6 @@ import javax.xml.stream.XMLStreamWriter;
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
|
@ -68,8 +66,6 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.SortedSetMultimap;
|
|
||||||
import com.google.common.collect.TreeMultimap;
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import com.google.common.hash.HashingInputStream;
|
import com.google.common.hash.HashingInputStream;
|
||||||
|
@ -78,7 +74,6 @@ import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.google.common.net.HttpHeaders;
|
import com.google.common.net.HttpHeaders;
|
||||||
import com.google.common.net.PercentEscaper;
|
|
||||||
|
|
||||||
import org.apache.commons.fileupload.MultipartStream;
|
import org.apache.commons.fileupload.MultipartStream;
|
||||||
import org.jclouds.blobstore.BlobStore;
|
import org.jclouds.blobstore.BlobStore;
|
||||||
|
@ -135,30 +130,6 @@ public class S3ProxyHandler {
|
||||||
.or(CharMatcher.is('.'))
|
.or(CharMatcher.is('.'))
|
||||||
.or(CharMatcher.is('_'))
|
.or(CharMatcher.is('_'))
|
||||||
.or(CharMatcher.is('-'));
|
.or(CharMatcher.is('-'));
|
||||||
private static final Set<String> SIGNED_SUBRESOURCES = ImmutableSet.of(
|
|
||||||
"acl",
|
|
||||||
"delete",
|
|
||||||
"lifecycle",
|
|
||||||
"location",
|
|
||||||
"logging",
|
|
||||||
"notification",
|
|
||||||
"partNumber",
|
|
||||||
"policy",
|
|
||||||
"requestPayment",
|
|
||||||
"response-cache-control",
|
|
||||||
"response-content-disposition",
|
|
||||||
"response-content-encoding",
|
|
||||||
"response-content-language",
|
|
||||||
"response-content-type",
|
|
||||||
"response-expires",
|
|
||||||
"torrent",
|
|
||||||
"uploadId",
|
|
||||||
"uploads",
|
|
||||||
"versionId",
|
|
||||||
"versioning",
|
|
||||||
"versions",
|
|
||||||
"website"
|
|
||||||
);
|
|
||||||
private static final Set<String> UNSUPPORTED_PARAMETERS = ImmutableSet.of(
|
private static final Set<String> UNSUPPORTED_PARAMETERS = ImmutableSet.of(
|
||||||
"accelerate",
|
"accelerate",
|
||||||
"analytics",
|
"analytics",
|
||||||
|
@ -203,8 +174,6 @@ public class S3ProxyHandler {
|
||||||
"bucket-owner-full-control",
|
"bucket-owner-full-control",
|
||||||
"log-delivery-write"
|
"log-delivery-write"
|
||||||
);
|
);
|
||||||
private static final PercentEscaper AWS_URL_PARAMETER_ESCAPER =
|
|
||||||
new PercentEscaper("-_.~", false);
|
|
||||||
|
|
||||||
private final boolean anonymousIdentity;
|
private final boolean anonymousIdentity;
|
||||||
private final AuthenticationType authenticationType;
|
private final AuthenticationType authenticationType;
|
||||||
|
@ -493,8 +462,8 @@ public class S3ProxyHandler {
|
||||||
// When presigned url is generated, it doesn't consider service path
|
// When presigned url is generated, it doesn't consider service path
|
||||||
String uriForSigning = presignedUrl ? uri : this.servicePath + uri;
|
String uriForSigning = presignedUrl ? uri : this.servicePath + uri;
|
||||||
if (authHeader.hmacAlgorithm == null) {
|
if (authHeader.hmacAlgorithm == null) {
|
||||||
expectedSignature = createAuthorizationSignature(request,
|
expectedSignature = AwsSignature.createAuthorizationSignature(
|
||||||
uriForSigning, credential);
|
request, uriForSigning, credential);
|
||||||
} else {
|
} else {
|
||||||
String contentSha256 = request.getHeader(
|
String contentSha256 = request.getHeader(
|
||||||
"x-amz-content-sha256");
|
"x-amz-content-sha256");
|
||||||
|
@ -518,7 +487,8 @@ public class S3ProxyHandler {
|
||||||
}
|
}
|
||||||
is = new ByteArrayInputStream(payload);
|
is = new ByteArrayInputStream(payload);
|
||||||
}
|
}
|
||||||
expectedSignature = createAuthorizationSignatureV4(
|
expectedSignature = AwsSignature
|
||||||
|
.createAuthorizationSignatureV4(
|
||||||
baseRequest, authHeader, payload, uriForSigning,
|
baseRequest, authHeader, payload, uriForSigning,
|
||||||
credential);
|
credential);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
|
@ -2561,237 +2531,6 @@ public class S3ProxyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create Amazon V2 signature. Reference:
|
|
||||||
* http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
|
|
||||||
*/
|
|
||||||
private static String createAuthorizationSignature(
|
|
||||||
HttpServletRequest request, String uri, String credential) {
|
|
||||||
// sort Amazon headers
|
|
||||||
SortedSetMultimap<String, String> canonicalizedHeaders =
|
|
||||||
TreeMultimap.create();
|
|
||||||
for (String headerName : Collections.list(request.getHeaderNames())) {
|
|
||||||
Collection<String> headerValues = Collections.list(
|
|
||||||
request.getHeaders(headerName));
|
|
||||||
headerName = headerName.toLowerCase();
|
|
||||||
if (!headerName.startsWith("x-amz-")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (headerValues.isEmpty()) {
|
|
||||||
canonicalizedHeaders.put(headerName, "");
|
|
||||||
}
|
|
||||||
for (String headerValue : headerValues) {
|
|
||||||
canonicalizedHeaders.put(headerName,
|
|
||||||
Strings.nullToEmpty(headerValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// build string to sign
|
|
||||||
StringBuilder builder = new StringBuilder()
|
|
||||||
.append(request.getMethod())
|
|
||||||
.append('\n')
|
|
||||||
.append(Strings.nullToEmpty(request.getHeader(
|
|
||||||
HttpHeaders.CONTENT_MD5)))
|
|
||||||
.append('\n')
|
|
||||||
.append(Strings.nullToEmpty(request.getHeader(
|
|
||||||
HttpHeaders.CONTENT_TYPE)))
|
|
||||||
.append('\n');
|
|
||||||
String expires = request.getParameter("Expires");
|
|
||||||
if (expires != null) {
|
|
||||||
builder.append(expires);
|
|
||||||
} else if (!canonicalizedHeaders.containsKey("x-amz-date")) {
|
|
||||||
builder.append(request.getHeader(HttpHeaders.DATE));
|
|
||||||
}
|
|
||||||
builder.append('\n');
|
|
||||||
for (Map.Entry<String, String> entry : canonicalizedHeaders.entries()) {
|
|
||||||
builder.append(entry.getKey()).append(':')
|
|
||||||
.append(entry.getValue()).append('\n');
|
|
||||||
}
|
|
||||||
builder.append(uri);
|
|
||||||
|
|
||||||
char separator = '?';
|
|
||||||
List<String> subresources = Collections.list(
|
|
||||||
request.getParameterNames());
|
|
||||||
Collections.sort(subresources);
|
|
||||||
for (String subresource : subresources) {
|
|
||||||
if (SIGNED_SUBRESOURCES.contains(subresource)) {
|
|
||||||
builder.append(separator).append(subresource);
|
|
||||||
|
|
||||||
String value = request.getParameter(subresource);
|
|
||||||
if (!"".equals(value)) {
|
|
||||||
builder.append('=').append(value);
|
|
||||||
}
|
|
||||||
separator = '&';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String stringToSign = builder.toString();
|
|
||||||
logger.trace("stringToSign: {}", stringToSign);
|
|
||||||
|
|
||||||
// sign string
|
|
||||||
Mac mac;
|
|
||||||
try {
|
|
||||||
mac = Mac.getInstance("HmacSHA1");
|
|
||||||
mac.init(new SecretKeySpec(credential.getBytes(
|
|
||||||
StandardCharsets.UTF_8), "HmacSHA1"));
|
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return BaseEncoding.base64().encode(mac.doFinal(
|
|
||||||
stringToSign.getBytes(StandardCharsets.UTF_8)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create v4 signature. Reference:
|
|
||||||
* http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
|
||||||
*/
|
|
||||||
private static String createAuthorizationSignatureV4(
|
|
||||||
HttpServletRequest request, S3AuthorizationHeader authHeader,
|
|
||||||
byte[] payload, String uri, String credential)
|
|
||||||
throws InvalidKeyException, IOException, NoSuchAlgorithmException,
|
|
||||||
S3Exception {
|
|
||||||
String canonicalRequest = createCanonicalRequest(request, uri, payload,
|
|
||||||
authHeader.hashAlgorithm);
|
|
||||||
String algorithm = authHeader.hmacAlgorithm;
|
|
||||||
byte[] dateKey = signMessage(
|
|
||||||
authHeader.date.getBytes(StandardCharsets.UTF_8),
|
|
||||||
("AWS4" + credential).getBytes(StandardCharsets.UTF_8),
|
|
||||||
algorithm);
|
|
||||||
byte[] dateRegionKey = signMessage(
|
|
||||||
authHeader.region.getBytes(StandardCharsets.UTF_8), dateKey,
|
|
||||||
algorithm);
|
|
||||||
byte[] dateRegionServiceKey = signMessage(
|
|
||||||
authHeader.service.getBytes(StandardCharsets.UTF_8),
|
|
||||||
dateRegionKey, algorithm);
|
|
||||||
byte[] signingKey = signMessage(
|
|
||||||
"aws4_request".getBytes(StandardCharsets.UTF_8),
|
|
||||||
dateRegionServiceKey, algorithm);
|
|
||||||
String date = request.getHeader("x-amz-date");
|
|
||||||
if (date == null) {
|
|
||||||
date = request.getParameter("X-Amz-Date");
|
|
||||||
}
|
|
||||||
String signatureString = "AWS4-HMAC-SHA256\n" +
|
|
||||||
date + "\n" +
|
|
||||||
authHeader.date + "/" + authHeader.region +
|
|
||||||
"/s3/aws4_request\n" +
|
|
||||||
canonicalRequest;
|
|
||||||
byte[] signature = signMessage(
|
|
||||||
signatureString.getBytes(StandardCharsets.UTF_8),
|
|
||||||
signingKey, algorithm);
|
|
||||||
return BaseEncoding.base16().lowerCase().encode(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] signMessage(byte[] data, byte[] key, String algorithm)
|
|
||||||
throws InvalidKeyException, NoSuchAlgorithmException {
|
|
||||||
Mac mac = Mac.getInstance(algorithm);
|
|
||||||
mac.init(new SecretKeySpec(key, algorithm));
|
|
||||||
return mac.doFinal(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String createCanonicalRequest(HttpServletRequest request,
|
|
||||||
String uri, byte[] payload, String hashAlgorithm)
|
|
||||||
throws IOException, NoSuchAlgorithmException {
|
|
||||||
String authorizationHeader = request.getHeader("Authorization");
|
|
||||||
String xAmzContentSha256 = request.getHeader("x-amz-content-sha256");
|
|
||||||
if (xAmzContentSha256 == null) {
|
|
||||||
xAmzContentSha256 = request.getParameter("X-Amz-SignedHeaders");
|
|
||||||
}
|
|
||||||
String digest;
|
|
||||||
if (authorizationHeader == null) {
|
|
||||||
digest = "UNSIGNED-PAYLOAD";
|
|
||||||
} else if ("STREAMING-AWS4-HMAC-SHA256-PAYLOAD".equals(
|
|
||||||
xAmzContentSha256)) {
|
|
||||||
digest = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
|
|
||||||
} else if ("UNSIGNED-PAYLOAD".equals(xAmzContentSha256)) {
|
|
||||||
digest = "UNSIGNED-PAYLOAD";
|
|
||||||
} else {
|
|
||||||
digest = getMessageDigest(payload, hashAlgorithm);
|
|
||||||
}
|
|
||||||
String[] signedHeaders;
|
|
||||||
if (authorizationHeader != null) {
|
|
||||||
signedHeaders = extractSignedHeaders(authorizationHeader);
|
|
||||||
} else {
|
|
||||||
signedHeaders = request.getParameter("X-Amz-SignedHeaders")
|
|
||||||
.split(";");
|
|
||||||
}
|
|
||||||
String canonicalRequest = Joiner.on("\n").join(
|
|
||||||
request.getMethod(),
|
|
||||||
uri,
|
|
||||||
buildCanonicalQueryString(request),
|
|
||||||
buildCanonicalHeaders(request, signedHeaders) + "\n",
|
|
||||||
Joiner.on(';').join(signedHeaders),
|
|
||||||
digest);
|
|
||||||
return getMessageDigest(
|
|
||||||
canonicalRequest.getBytes(StandardCharsets.UTF_8),
|
|
||||||
hashAlgorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMessageDigest(byte[] payload, String algorithm)
|
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
|
||||||
byte[] hash = md.digest(payload);
|
|
||||||
return BaseEncoding.base16().lowerCase().encode(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] extractSignedHeaders(String authorization) {
|
|
||||||
int index = authorization.indexOf("SignedHeaders=");
|
|
||||||
if (index < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int endSigned = authorization.indexOf(',', index);
|
|
||||||
if (endSigned < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int startHeaders = authorization.indexOf('=', index);
|
|
||||||
return authorization.substring(startHeaders + 1, endSigned).split(";");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String buildCanonicalHeaders(HttpServletRequest request,
|
|
||||||
String[] signedHeaders) {
|
|
||||||
List<String> headers = new ArrayList<>();
|
|
||||||
for (String header : signedHeaders) {
|
|
||||||
headers.add(header.toLowerCase());
|
|
||||||
}
|
|
||||||
Collections.sort(headers);
|
|
||||||
List<String> headersWithValues = new ArrayList<>();
|
|
||||||
for (String header : headers) {
|
|
||||||
List<String> values = new ArrayList<>();
|
|
||||||
StringBuilder headerWithValue = new StringBuilder();
|
|
||||||
headerWithValue.append(header);
|
|
||||||
headerWithValue.append(":");
|
|
||||||
for (String value : Collections.list(request.getHeaders(header))) {
|
|
||||||
value = value.trim();
|
|
||||||
if (!value.startsWith("\"")) {
|
|
||||||
value = value.replaceAll("\\s+", " ");
|
|
||||||
}
|
|
||||||
values.add(value);
|
|
||||||
}
|
|
||||||
headerWithValue.append(Joiner.on(",").join(values));
|
|
||||||
headersWithValues.add(headerWithValue.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Joiner.on("\n").join(headersWithValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String buildCanonicalQueryString(HttpServletRequest request)
|
|
||||||
throws UnsupportedEncodingException {
|
|
||||||
// The parameters are required to be sorted
|
|
||||||
List<String> parameters = Collections.list(request.getParameterNames());
|
|
||||||
Collections.sort(parameters);
|
|
||||||
List<String> queryParameters = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String key : parameters) {
|
|
||||||
if (key.equals("X-Amz-Signature")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// re-encode keys and values in AWS normalized form
|
|
||||||
String value = request.getParameter(key);
|
|
||||||
queryParameters.add(AWS_URL_PARAMETER_ESCAPER.escape(key) +
|
|
||||||
"=" + AWS_URL_PARAMETER_ESCAPER.escape(value));
|
|
||||||
}
|
|
||||||
return Joiner.on("&").join(queryParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addContentMetdataFromHttpRequest(
|
private static void addContentMetdataFromHttpRequest(
|
||||||
BlobBuilder.PayloadBlobBuilder builder,
|
BlobBuilder.PayloadBlobBuilder builder,
|
||||||
HttpServletRequest request) {
|
HttpServletRequest request) {
|
||||||
|
|
Ładowanie…
Reference in New Issue