HTTP POST Requests using AWS Signature Version 4

pull/203/merge
Michael Wittig 2017-05-05 21:11:54 +02:00 zatwierdzone przez Andrew Gaul
rodzic d3c77384f1
commit 4f14b8607a
1 zmienionych plików z 116 dodań i 37 usunięć

Wyświetl plik

@ -1759,6 +1759,7 @@ public class S3ProxyHandler {
// TODO: handle policy
byte[] policy = null;
String signature = null;
String algorithm = null;
byte[] payload = null;
MultipartStream multipartStream = new MultipartStream(is,
boundary.getBytes(StandardCharsets.UTF_8), 4096, null);
@ -1767,67 +1768,126 @@ public class S3ProxyHandler {
String header = multipartStream.readHeaders();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
multipartStream.readBodyData(baos);
if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"acl\"")) {
if (isField(header, "acl")) {
// TODO: acl
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"AWSAccessKeyId\"")) {
} else if (isField(header, "AWSAccessKeyId") ||
isField(header, "X-Amz-Credential")) {
identity = new String(baos.toByteArray());
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"Content-Type\"")) {
} else if (isField(header, "Content-Type")) {
contentType = new String(baos.toByteArray());
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"file\"")) {
} else if (isField(header, "file")) {
// TODO: buffers entire payload
payload = baos.toByteArray();
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"key\"")) {
} else if (isField(header, "key")) {
blobName = new String(baos.toByteArray());
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"policy\"")) {
} else if (isField(header, "policy")) {
policy = baos.toByteArray();
} else if (startsWithIgnoreCase(header,
"Content-Disposition: form-data;" +
" name=\"signature\"")) {
} else if (isField(header, "signature") ||
isField(header, "X-Amz-Signature")) {
signature = new String(baos.toByteArray());
} else if (isField(header, "X-Amz-Algorithm")) {
algorithm = new String(baos.toByteArray());
}
}
nextPart = multipartStream.readBoundary();
}
if (identity == null || signature == null || blobName == null ||
policy == null) {
if (blobName == null || policy == null) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
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 =
blobStoreLocator.locateBlobStore(identity, null, null);
blobStoreLocator.locateBlobStore(authHeader.identity, null,
null);
if (provider == null) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
String credential = provider.getKey();
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);
}
String expectedSignature = BaseEncoding.base64().encode(
mac.doFinal(policy));
if (!signature.equals(expectedSignature)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
if (signatureVersion4) {
byte[] kSecret = ("AWS4" + credential).getBytes(
StandardCharsets.UTF_8);
byte[] kDate = hmac("HmacSHA256",
authHeader.date.getBytes(StandardCharsets.UTF_8), kSecret);
byte[] kRegion = hmac("HmacSHA256",
authHeader.region.getBytes(StandardCharsets.UTF_8), kDate);
byte[] kService = hmac("HmacSHA256", authHeader.service.getBytes(
StandardCharsets.UTF_8), kRegion);
byte[] kSigning = hmac("HmacSHA256",
"aws4_request".getBytes(StandardCharsets.UTF_8), kService);
String expectedSignature = BaseEncoding.base16().lowerCase().encode(
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
@ -1840,6 +1900,10 @@ public class S3ProxyHandler {
blobStore.putBlob(containerName, blob);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
if (corsAllowAll) {
response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
}
}
private void handleInitiateMultipartUpload(HttpServletRequest request,
@ -2821,6 +2885,21 @@ public class S3ProxyHandler {
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
// which XML 1.0 cannot represent.
private static String encodeBlob(String encodingType, String blobName) {