Skip to content

Commit

Permalink
Update presigner so host is set and content-hash is excluded
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenbaker committed Oct 6, 2023
1 parent f36f672 commit 29a6d11
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package software.amazon.awssdk.http.auth.aws.internal.signer;

import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS4_SIGNING_ALGORITHM;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.addHostHeader;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.deriveSigningKey;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.hashCanonicalRequest;
Expand All @@ -42,15 +41,16 @@ public final class DefaultV4RequestSigner implements V4RequestSigner {
private static final Logger LOG = Logger.loggerFor(DefaultV4RequestSigner.class);

private final V4Properties properties;
private final String contentHash;

public DefaultV4RequestSigner(V4Properties properties) {
public DefaultV4RequestSigner(V4Properties properties, String contentHash) {
this.properties = properties;
this.contentHash = contentHash;
}

@Override
public V4Context sign(SdkHttpRequest.Builder requestBuilder) {
// Step 0: Pre-requisites
String contentHash = getContentHash(requestBuilder);
addHostHeader(requestBuilder);

// Step 1: Create a canonical request
Expand All @@ -71,12 +71,6 @@ public V4Context sign(SdkHttpRequest.Builder requestBuilder) {
return new V4Context(contentHash, signingKey, signature, canonicalRequest, requestBuilder);
}

private String getContentHash(SdkHttpRequest.Builder requestBuilder) {
return requestBuilder.firstMatchingHeader(X_AMZ_CONTENT_SHA256).orElseThrow(
() -> new IllegalArgumentException("Content hash must be present in the '" + X_AMZ_CONTENT_SHA256 + "' header!")
);
}

private V4CanonicalRequest createCanonicalRequest(SdkHttpRequest request, String contentHash) {
return new V4CanonicalRequest(request, contentHash, new V4CanonicalRequest.Options(
properties.shouldDoubleUrlEncode(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getCanonicalHeaders;
import static software.amazon.awssdk.http.auth.aws.internal.signer.V4CanonicalRequest.getSignedHeadersString;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AWS4_SIGNING_ALGORITHM;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.addDateHeader;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.addHostHeader;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.formatDateTime;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils.getContentHash;

import java.time.Duration;
import java.util.List;
Expand All @@ -38,8 +41,8 @@ public interface V4RequestSigner {
/**
* Retrieve an implementation of a V4RequestSigner, which signs the request, but does not add authentication to the request.
*/
static V4RequestSigner create(V4Properties properties) {
return new DefaultV4RequestSigner(properties);
static V4RequestSigner create(V4Properties properties, String contentHash) {
return new DefaultV4RequestSigner(properties, contentHash);
}

/**
Expand All @@ -54,7 +57,7 @@ static V4RequestSigner header(V4Properties properties) {
}
addDateHeader(requestBuilder, formatDateTime(properties.getCredentialScope().getInstant()));

V4Context ctx = create(properties).sign(requestBuilder);
V4Context ctx = create(properties, getContentHash(requestBuilder)).sign(requestBuilder);

// Add the signature within an authorization header
String authHeader = AWS4_SIGNING_ALGORITHM
Expand All @@ -77,6 +80,8 @@ static V4RequestSigner query(V4Properties properties) {
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SECURITY_TOKEN,
((AwsSessionCredentialsIdentity) properties.getCredentials()).sessionToken());
}
// We have to add the host-header here explicitly, since query-signed request requires it in the signed-header param
addHostHeader(requestBuilder);

List<Pair<String, List<String>>> canonicalHeaders = getCanonicalHeaders(requestBuilder.build());
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM);
Expand All @@ -85,7 +90,7 @@ static V4RequestSigner query(V4Properties properties) {
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_CREDENTIAL,
properties.getCredentialScope().scope(properties.getCredentials()));

V4Context ctx = create(properties).sign(requestBuilder);
V4Context ctx = create(properties, getContentHash(requestBuilder)).sign(requestBuilder);

// Add the signature
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNATURE, ctx.getSignature());
Expand All @@ -100,11 +105,33 @@ static V4RequestSigner query(V4Properties properties) {
*/
static V4RequestSigner presigned(V4Properties properties, Duration expirationDuration) {
return requestBuilder -> {
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_EXPIRES,
Long.toString(expirationDuration.getSeconds())
);
// Add pre-requisites
if (properties.getCredentials() instanceof AwsSessionCredentialsIdentity) {
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SECURITY_TOKEN,
((AwsSessionCredentialsIdentity) properties.getCredentials()).sessionToken());
}
// We have to add the host-header here explicitly, since pre-signed request requires it in the signed-header param
addHostHeader(requestBuilder);

// Pre-signed requests shouldn't have the content-hash header
String contentHash = getContentHash(requestBuilder);
requestBuilder.removeHeader(X_AMZ_CONTENT_SHA256);

List<Pair<String, List<String>>> canonicalHeaders = getCanonicalHeaders(requestBuilder.build());
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_ALGORITHM, AWS4_SIGNING_ALGORITHM);
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_DATE, properties.getCredentialScope().getDatetime());
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNED_HEADERS, getSignedHeadersString(canonicalHeaders));
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_CREDENTIAL,
properties.getCredentialScope().scope(properties.getCredentials()));
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_EXPIRES, Long.toString(expirationDuration.getSeconds()));

V4Context ctx = create(properties, contentHash).sign(requestBuilder);

// Add the signature
requestBuilder.putRawQueryParameter(SignerConstant.X_AMZ_SIGNATURE, ctx.getSignature());

return ctx;

return query(properties).sign(requestBuilder);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package software.amazon.awssdk.http.auth.aws.internal.signer.util;

import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_CONTENT_SHA256;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_DECODED_CONTENT_LENGTH;

import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -275,4 +276,10 @@ private static int readAll(InputStream inputStream) {
throw new RuntimeException("Could not finish reading stream: ", e);
}
}

public static String getContentHash(SdkHttpRequest.Builder requestBuilder) {
return requestBuilder.firstMatchingHeader(X_AMZ_CONTENT_SHA256).orElseThrow(
() -> new IllegalArgumentException("Content hash must be present in the '" + X_AMZ_CONTENT_SHA256 + "' header!")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ public class DefaultRequestSignerTest {
.normalizePath(true)
.build();

V4RequestSigner requestSigner = new DefaultV4RequestSigner(v4Properties);
DefaultV4RequestSigner requestSigner = new DefaultV4RequestSigner(v4Properties, "quux");

@Test
public void requestSigner_sign_shouldReturnSignedContext_butNotAddAnyAuthInfoToRequest() {
SdkHttpRequest.Builder request = SdkHttpRequest
.builder()
.uri(URI.create("https://localhost"))
.method(SdkHttpMethod.GET)
.putHeader("x-amz-content-sha256", "quux");
.method(SdkHttpMethod.GET);
String expectedContentHash = "quux";
String expectedSigningKeyHex = "3d558b7a87b67996abc908071e0771a31b2a7977ab247144e60a6cba3356be1f";
String expectedSignature = "7557839280ea0ef5c4acc66e5670d0857ac4f491884b1b8031d4dea2fc33483c";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static software.amazon.awssdk.http.auth.aws.TestUtils.TickingClock;
import static software.amazon.awssdk.http.auth.aws.internal.signer.V4RequestSigner.header;

import java.net.URI;
import java.time.Clock;
Expand All @@ -38,21 +39,12 @@ public class V4RequestSignerTest {
private static final AwsSessionCredentialsIdentity sessionCreds =
AwsSessionCredentialsIdentity.create("access", "secret", "token");

@Test
public void sign_computesSignatureAndAddsHostHeader() {
String expectedSignature = "9f7da47c7fe7989712658509580c725430af16a2dccb6bf38b3506bd9606642e";
V4Context ctx = V4RequestSigner.create(getProperties(creds)).sign(getRequest());

assertEquals(expectedSignature, ctx.getSignature());
assertThat(ctx.getSignedRequest().firstMatchingHeader("Host")).hasValue("test.com");
}

@Test
public void sign_withHeader_addsAuthHeaders() {
String expectedAuthorization = "AWS4-HMAC-SHA256 Credential=access/19700101/us-east-1/demo/aws4_request, " +
"SignedHeaders=host;x-amz-archive-description;x-amz-content-sha256;x-amz-date, " +
"Signature=0fafd04465eb6201e868a80f72d15d50731512298f554684ce6627c0619f429a";
V4Context ctx = V4RequestSigner.header(getProperties(creds)).sign(getRequest());
V4Context ctx = header(getProperties(creds)).sign(getRequest());

assertThat(ctx.getSignedRequest().firstMatchingHeader("X-Amz-Date")).hasValue("19700101T000000Z");
assertThat(ctx.getSignedRequest().firstMatchingHeader("Authorization")).hasValue(expectedAuthorization);
Expand All @@ -64,7 +56,7 @@ public void sign_withHeaderAndSessionCredentials_addsAuthHeadersAndTokenHeader()
"SignedHeaders=host;x-amz-archive-description;x-amz-content-sha256;x-amz-date;"
+ "x-amz-security-token, " +
"Signature=cda79272f6d258c2cb2f04ac84a5f9515440e0158bf39e212c3dcf88b3a477a9";
V4Context ctx = V4RequestSigner.header(getProperties(sessionCreds)).sign(getRequest());
V4Context ctx = header(getProperties(sessionCreds)).sign(getRequest());

assertThat(ctx.getSignedRequest().firstMatchingHeader("X-Amz-Date")).hasValue("19700101T000000Z");
assertThat(ctx.getSignedRequest().firstMatchingHeader("Authorization")).hasValue(expectedAuthorization);
Expand All @@ -77,11 +69,11 @@ public void sign_withQuery_addsAuthQueryParams() {

assertEquals("AWS4-HMAC-SHA256", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Algorithm").get(0));
assertEquals("19700101T000000Z", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Date").get(0));
assertEquals("x-amz-archive-description;x-amz-content-sha256",
assertEquals("host;x-amz-archive-description;x-amz-content-sha256",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0));
assertEquals("access/19700101/us-east-1/demo/aws4_request", ctx.getSignedRequest().rawQueryParameters().get(
"X-Amz-Credential").get(0));
assertEquals("448f105ad26c5adfdf07b482b0f46ff294032c7bc72e10bb944e2f45929bf468",
assertEquals("bb3ddb98bc32b85c8aa484bfaf321171a22ad802baa03ee9d5fcda9842b769c9",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Signature").get(0));
}

Expand All @@ -91,12 +83,12 @@ public void sign_withQueryAndSessionCredentials_addsAuthQueryParamsAndTokenParam

assertEquals("AWS4-HMAC-SHA256", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Algorithm").get(0));
assertEquals("19700101T000000Z", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Date").get(0));
assertEquals("x-amz-archive-description;x-amz-content-sha256",
assertEquals("host;x-amz-archive-description;x-amz-content-sha256",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0));
assertEquals(
"access/19700101/us-east-1/demo/aws4_request",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Credential").get(0));
assertEquals("f8a7d5ed62c2095240dd847ea48ebb1f1471b3fccb8d8165f8c5dbd8c5a670da",
assertEquals("2ffe9562fefd57e14f43bf1937b6b85cc0f0180d63789254bddec25498e14a29",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Signature").get(0));
assertEquals("token", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Security-Token").get(0));
}
Expand All @@ -106,15 +98,17 @@ public void sign_withPresigned_addsExpirationParam() {
V4Context ctx = V4RequestSigner.presigned(getProperties(creds), Duration.ZERO).sign(getRequest());

assertEquals("0", ctx.getSignedRequest().rawQueryParameters().get("X-Amz-Expires").get(0));
assertEquals("45d00c9c0eb2c05cfe7e42231a572aef30d96d1afb2a205e27d9c6b89867a865", ctx.getSignature());
assertEquals("host;x-amz-archive-description",
ctx.getSignedRequest().rawQueryParameters().get("X-Amz-SignedHeaders").get(0));
assertEquals("691f39caa2064fe4fb897976dfb4b09df54749c825a5fcd1e2f0b3fcd1bcc600", ctx.getSignature());
}

@Test
public void sign_withNoChecksumHeader_throws() {
public void sign_withNoContentHashHeader_throws() {
SdkHttpRequest.Builder request = getRequest().removeHeader("x-amz-content-sha256");

assertThrows(IllegalArgumentException.class,
() -> V4RequestSigner.create(getProperties(sessionCreds)).sign(request)
() -> header(getProperties(sessionCreds)).sign(request)
);
}

Expand Down

0 comments on commit 29a6d11

Please sign in to comment.