/*************************************************************************
* Copyright 2017 Hewlett-Packard Enterprise, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.objectstorage.pipeline.auth;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.objectstorage.exceptions.s3.AccessDeniedException;
import com.eucalyptus.objectstorage.pipeline.auth.S3V4Authentication.V4AuthComponent;
import com.google.common.collect.ImmutableMap;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.junit.Before;
import org.junit.Test;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import static com.eucalyptus.objectstorage.pipeline.auth.S3V4Authentication.AWS_EXPIRES_PARAM;
import static org.junit.Assert.*;
/**
* Tests {@link S3V4Authentication}.
*/
public class S3V4AuthenticationTest {
static String V4_HEADER = "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, " +
"SignedHeaders=host;range;x-amz-date, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024";
static String CANONICAL_HEADERS = "date:Fri, 24 May 2013 00:00:00 GMT\n" + "host:examplebucket.s3.amazonaws.com\n" +
"x-amz-content-sha256:44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072\n" + "x-amz-date:20130524T000000Z\n" +
"x-amz-storage-class:REDUCED_REDUNDANCY\n";
static String CANONICAL_QUERY_STRING = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1"
+ "%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host";
static String CANONICAL_PRESIGNED_REQUEST = "PUT\n" + "/test.txt\n" +
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date" +
"=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host\n" + "host:examplebucket.s3.amazonaws.com\n" + "\n" + "host\n" +
"UNSIGNED-PAYLOAD";
static String CANONICAL_HEADERS_REQUEST = "PUT\n" + "/test$file.text\n" + "\n" + "date:Fri, 24 May 2013 00:00:00 GMT\n" +
"host:examplebucket.s3.amazonaws.com\n" + "x-amz-content-sha256:44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072\n"
+ "x-amz-date:20130524T000000Z\n" + "x-amz-storage-class:REDUCED_REDUNDANCY\n\n" + "date;host;x-amz-content-sha256;x-amz-date;" +
"x-amz-storage-class\n" + "44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072";
static String SIGNED_HEADERS = "date;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class";
MappingHttpRequest headersRequest;
MappingHttpRequest paramsRequest;
@Before
public void beforeMethod() {
headersRequest = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/test$file.text");
headersRequest.addHeader("Host", "examplebucket.s3.amazonaws.com");
headersRequest.addHeader("Date", "Fri, 24 May 2013 00:00:00 GMT");
headersRequest.addHeader("x-amz-date", "20130524T000000Z");
headersRequest.addHeader("x-amz-storage-class", "REDUCED_REDUNDANCY");
headersRequest.addHeader("x-amz-content-sha256", "44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072");
headersRequest.setContent(ChannelBuffers.copiedBuffer("Welcome to Amazon S3.", Charset.defaultCharset()));
paramsRequest = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/test.txt" +
"?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request" +
"&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=FOO");
paramsRequest.addHeader("Host", "examplebucket.s3.amazonaws.com");
paramsRequest.setContent(ChannelBuffers.copiedBuffer("Welcome to Amazon S3.", Charset.defaultCharset()));
}
@Test
public void testBuildCanonicalResourcePath() throws Throwable {
URL url = new URL("http://eucalyptus:1234/services/objectstorage/test-bucket/test-object?foo=bar");
assertEquals("/services/objectstorage/test-bucket/test-object", S3V4Authentication.buildCanonicalResourcePath(url.getPath()));
}
@Test
public void testBuildCanonicalResourcePathFromMessage() throws Throwable {
MappingHttpRequest request = new MappingHttpRequest(
HttpVersion.HTTP_1_1,
HttpMethod.PUT,
"http://eucalyptus:1234/services/objectstorage/test-bucket/test-object?foo=bar"
);
assertEquals(
"Uri from http request with absolute url",
"/services/objectstorage/test-bucket/test-object",
S3V4Authentication.buildCanonicalResourcePath( request.getServicePath( ) ));
}
@Test
public void testBuildAndVerifyPayloadHash() throws Throwable {
// Unsigned
MappingHttpRequest request = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/test$file.text");
request.addHeader("x-amz-content-sha256", "UNSIGNED-PAYLOAD");
String result = S3V4Authentication.getUnverifiedPayloadHash(request);
assertEquals("UNSIGNED-PAYLOAD", result);
// Signed
request = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/test$file.text");
request.addHeader("x-amz-content-sha256", "44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072");
request.setContent(ChannelBuffers.copiedBuffer("Welcome to Amazon S3.", Charset.defaultCharset()));
result = S3V4Authentication.getUnverifiedPayloadHash(request);
assertEquals("44ce7dd67c959e0d3524ffac1771dfbba87d2b6b4b4e99e42034a8b803f8b072", result);
// Chunked
request = new MappingHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/test$file.text");
request.addHeader("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD");
result = S3V4Authentication.getUnverifiedPayloadHash(request);
assertEquals("STREAMING-AWS4-HMAC-SHA256-PAYLOAD", result);
}
@Test
public void testBuildCanonicalHeaders() {
StringBuilder sb = new StringBuilder();
S3V4Authentication.buildCanonicalHeaders(headersRequest, SIGNED_HEADERS, sb);
assertEquals(CANONICAL_HEADERS, sb.toString());
}
@Test
public void testBuildCanonicalQueryString() {
StringBuilder sb = new StringBuilder();
S3V4Authentication.buildCanonicalQueryString(paramsRequest.getParameters(), sb);
assertEquals(CANONICAL_QUERY_STRING, sb.toString());
}
@Test
public void testBuildCanonicalQueryStringParameterEncoding() {
StringBuilder sb = new StringBuilder();
S3V4Authentication.buildCanonicalQueryString( ImmutableMap.of(
"x", "$",
"A", "B",
"p3", "_",
"p1", "~",
"p2", "-"
), sb);
assertEquals("A=B&p1=~&p2=-&p3=_&x=%26%23%24", sb.toString());
}
@Test
public void testBuildCanonicalRequestForHeaders() {
StringBuilder canonicalRequest = S3V4Authentication.buildCanonicalRequest(headersRequest, SIGNED_HEADERS, headersRequest.getHeader
("x-amz-content-sha256"));
assertEquals(CANONICAL_HEADERS_REQUEST, canonicalRequest.toString());
}
@Test
public void testBuildCanonicalRequestForPresigned() {
StringBuilder canonicalRequest = S3V4Authentication.buildCanonicalRequest(paramsRequest, "host", "UNSIGNED-PAYLOAD");
assertEquals(CANONICAL_PRESIGNED_REQUEST, canonicalRequest.toString());
}
@Test
public void testParseAuthHeader() throws Throwable {
assertEquals(S3V4Authentication.getV4AuthComponents("AWS").size(), 0);
Map<V4AuthComponent, String> auth = S3V4Authentication.getV4AuthComponents(V4_HEADER);
assert auth.get(V4AuthComponent.Credential).equals("AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request");
assert auth.get(V4AuthComponent.SignedHeaders).equals("host;range;x-amz-date");
assert auth.get(V4AuthComponent.Signature).equals("fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024");
}
@Test
public void testParseDateAndAssertNotExpired() throws Throwable {
DateTime dt = DateTime.now();
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
S3Authentication.parseDate(fmt.print(dt));
}
@Test
public void testValidateExpiresFromParams() throws Throwable {
Map<String, String> params = new HashMap<>();
params.put(AWS_EXPIRES_PARAM, "10");
// Current date, future expires
S3V4Authentication.validateExpiresFromParams(params, DateTime.now().toDate());
// Future date
try {
S3V4Authentication.validateExpiresFromParams(params, DateTime.now().plusMinutes(20).toDate());
fail("Date should have been not yet valid");
} catch (AccessDeniedException expected) {
assertTrue(expected.getMessage().contains("not yet valid"));
}
// Past date, not expired
S3V4Authentication.validateExpiresFromParams(params, DateTime.now().minusSeconds(8).toDate());
// Past date, expired
try {
S3V4Authentication.validateExpiresFromParams(params, DateTime.now().minusSeconds(40).toDate());
fail("Date should have been expired");
} catch (AccessDeniedException expected) {
assertTrue(expected.getMessage().toLowerCase().contains("expired"));
}
}
}