kopia lustrzana https://github.com/gaul/s3proxy
HTTP POST Requests using AWS Signature Version 4
rodzic
d3c77384f1
commit
4f14b8607a
|
@ -1759,6 +1759,7 @@ public class S3ProxyHandler {
|
||||||
// TODO: handle policy
|
// TODO: handle policy
|
||||||
byte[] policy = null;
|
byte[] policy = null;
|
||||||
String signature = null;
|
String signature = null;
|
||||||
|
String algorithm = null;
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
MultipartStream multipartStream = new MultipartStream(is,
|
MultipartStream multipartStream = new MultipartStream(is,
|
||||||
boundary.getBytes(StandardCharsets.UTF_8), 4096, null);
|
boundary.getBytes(StandardCharsets.UTF_8), 4096, null);
|
||||||
|
@ -1767,67 +1768,126 @@ public class S3ProxyHandler {
|
||||||
String header = multipartStream.readHeaders();
|
String header = multipartStream.readHeaders();
|
||||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
multipartStream.readBodyData(baos);
|
multipartStream.readBodyData(baos);
|
||||||
if (startsWithIgnoreCase(header,
|
if (isField(header, "acl")) {
|
||||||
"Content-Disposition: form-data;" +
|
|
||||||
" name=\"acl\"")) {
|
|
||||||
// TODO: acl
|
// TODO: acl
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "AWSAccessKeyId") ||
|
||||||
"Content-Disposition: form-data;" +
|
isField(header, "X-Amz-Credential")) {
|
||||||
" name=\"AWSAccessKeyId\"")) {
|
|
||||||
identity = new String(baos.toByteArray());
|
identity = new String(baos.toByteArray());
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "Content-Type")) {
|
||||||
"Content-Disposition: form-data;" +
|
|
||||||
" name=\"Content-Type\"")) {
|
|
||||||
contentType = new String(baos.toByteArray());
|
contentType = new String(baos.toByteArray());
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "file")) {
|
||||||
"Content-Disposition: form-data;" +
|
|
||||||
" name=\"file\"")) {
|
|
||||||
// TODO: buffers entire payload
|
// TODO: buffers entire payload
|
||||||
payload = baos.toByteArray();
|
payload = baos.toByteArray();
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "key")) {
|
||||||
"Content-Disposition: form-data;" +
|
|
||||||
" name=\"key\"")) {
|
|
||||||
blobName = new String(baos.toByteArray());
|
blobName = new String(baos.toByteArray());
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "policy")) {
|
||||||
"Content-Disposition: form-data;" +
|
|
||||||
" name=\"policy\"")) {
|
|
||||||
policy = baos.toByteArray();
|
policy = baos.toByteArray();
|
||||||
} else if (startsWithIgnoreCase(header,
|
} else if (isField(header, "signature") ||
|
||||||
"Content-Disposition: form-data;" +
|
isField(header, "X-Amz-Signature")) {
|
||||||
" name=\"signature\"")) {
|
|
||||||
signature = new String(baos.toByteArray());
|
signature = new String(baos.toByteArray());
|
||||||
|
} else if (isField(header, "X-Amz-Algorithm")) {
|
||||||
|
algorithm = new String(baos.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nextPart = multipartStream.readBoundary();
|
nextPart = multipartStream.readBoundary();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identity == null || signature == null || blobName == null ||
|
|
||||||
policy == null) {
|
if (blobName == null || policy == null) {
|
||||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String headerAuthorization = null;
|
||||||
|
S3AuthorizationHeader authHeader = null;
|
||||||
|
boolean signatureVersion4;
|
||||||
|
if (algorithm == null) {
|
||||||
|
if (identity == null || signature == null) {
|
||||||
|
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
signatureVersion4 = false;
|
||||||
|
headerAuthorization = "AWS " + identity + ":" + signature;
|
||||||
|
} else if (algorithm.equals("AWS4-HMAC-SHA256")) {
|
||||||
|
if (identity == null || signature == null) {
|
||||||
|
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
signatureVersion4 = true;
|
||||||
|
headerAuthorization = "AWS4-HMAC-SHA256" +
|
||||||
|
" Credential=" + identity +
|
||||||
|
", Signature=" + signature;
|
||||||
|
} else {
|
||||||
|
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
authHeader = new S3AuthorizationHeader(headerAuthorization);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT, iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (authHeader.authenticationType) {
|
||||||
|
case AWS_V2:
|
||||||
|
switch (authenticationType) {
|
||||||
|
case AWS_V2:
|
||||||
|
case AWS_V2_OR_V4:
|
||||||
|
case NONE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AWS_V4:
|
||||||
|
switch (authenticationType) {
|
||||||
|
case AWS_V4:
|
||||||
|
case AWS_V2_OR_V4:
|
||||||
|
case NONE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unhandled type: " +
|
||||||
|
authHeader.authenticationType);
|
||||||
|
}
|
||||||
|
|
||||||
Map.Entry<String, BlobStore> provider =
|
Map.Entry<String, BlobStore> provider =
|
||||||
blobStoreLocator.locateBlobStore(identity, null, null);
|
blobStoreLocator.locateBlobStore(authHeader.identity, null,
|
||||||
|
null);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String credential = provider.getKey();
|
String credential = provider.getKey();
|
||||||
|
|
||||||
Mac mac;
|
if (signatureVersion4) {
|
||||||
try {
|
byte[] kSecret = ("AWS4" + credential).getBytes(
|
||||||
mac = Mac.getInstance("HmacSHA1");
|
StandardCharsets.UTF_8);
|
||||||
mac.init(new SecretKeySpec(credential.getBytes(
|
byte[] kDate = hmac("HmacSHA256",
|
||||||
StandardCharsets.UTF_8), "HmacSHA1"));
|
authHeader.date.getBytes(StandardCharsets.UTF_8), kSecret);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
byte[] kRegion = hmac("HmacSHA256",
|
||||||
throw new RuntimeException(e);
|
authHeader.region.getBytes(StandardCharsets.UTF_8), kDate);
|
||||||
}
|
byte[] kService = hmac("HmacSHA256", authHeader.service.getBytes(
|
||||||
String expectedSignature = BaseEncoding.base64().encode(
|
StandardCharsets.UTF_8), kRegion);
|
||||||
mac.doFinal(policy));
|
byte[] kSigning = hmac("HmacSHA256",
|
||||||
if (!signature.equals(expectedSignature)) {
|
"aws4_request".getBytes(StandardCharsets.UTF_8), kService);
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
String expectedSignature = BaseEncoding.base16().lowerCase().encode(
|
||||||
return;
|
hmac("HmacSHA256", policy, kSigning));
|
||||||
|
if (!signature.equals(expectedSignature)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String expectedSignature = BaseEncoding.base64().encode(
|
||||||
|
hmac("HmacSHA1", policy,
|
||||||
|
credential.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
if (!signature.equals(expectedSignature)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobBuilder.PayloadBlobBuilder builder = blobStore
|
BlobBuilder.PayloadBlobBuilder builder = blobStore
|
||||||
|
@ -1840,6 +1900,10 @@ public class S3ProxyHandler {
|
||||||
blobStore.putBlob(containerName, blob);
|
blobStore.putBlob(containerName, blob);
|
||||||
|
|
||||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||||
|
|
||||||
|
if (corsAllowAll) {
|
||||||
|
response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInitiateMultipartUpload(HttpServletRequest request,
|
private void handleInitiateMultipartUpload(HttpServletRequest request,
|
||||||
|
@ -2821,6 +2885,21 @@ public class S3ProxyHandler {
|
||||||
return string.toLowerCase().startsWith(prefix.toLowerCase());
|
return string.toLowerCase().startsWith(prefix.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isField(String string, String field) {
|
||||||
|
return startsWithIgnoreCase(string,
|
||||||
|
"Content-Disposition: form-data; name=\"" + field + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hmac(String algorithm, byte[] data, byte[] key) {
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance(algorithm);
|
||||||
|
mac.init(new SecretKeySpec(key, algorithm));
|
||||||
|
return mac.doFinal(data);
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encode blob name if client requests it. This allows for characters
|
// Encode blob name if client requests it. This allows for characters
|
||||||
// which XML 1.0 cannot represent.
|
// which XML 1.0 cannot represent.
|
||||||
private static String encodeBlob(String encodingType, String blobName) {
|
private static String encodeBlob(String encodingType, String blobName) {
|
||||||
|
|
Ładowanie…
Reference in New Issue