diff --git a/.gitignore b/.gitignore index feb3ede744a..33a707d8cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ packages/**/esm/ packages/**/cjs/ **/.DS_Store .vscode +.kiro .idea *.log .npm/ diff --git a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.test.ts b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.test.ts index db7289da109..ec9914bdea3 100644 --- a/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.test.ts +++ b/packages/core/__tests__/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.test.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { getHashedPayload } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload'; +import { UNSIGNED_PAYLOAD } from '../../../../../../../src/clients/middleware/signing/signer/signatureV4/constants'; describe('getHashedPayload', () => { test('returns empty hash if nullish', () => { @@ -34,4 +35,10 @@ describe('getHashedPayload', () => { expect(getHashedPayload(scalar as any)).toStrictEqual('UNSIGNED-PAYLOAD'); } }); + + test('returns UNSIGNED_PAYLOAD constant without hashing when passed as body', () => { + expect(getHashedPayload(UNSIGNED_PAYLOAD as any)).toStrictEqual( + 'UNSIGNED-PAYLOAD', + ); + }); }); diff --git a/packages/core/src/clients/index.ts b/packages/core/src/clients/index.ts index f1c4d671223..743d62cdc5b 100644 --- a/packages/core/src/clients/index.ts +++ b/packages/core/src/clients/index.ts @@ -13,7 +13,10 @@ export { PresignUrlOptions, SignRequestOptions, } from './middleware/signing/signer/signatureV4'; -export { EMPTY_HASH as EMPTY_SHA256_HASH } from './middleware/signing/signer/signatureV4/constants'; +export { + EMPTY_HASH as EMPTY_SHA256_HASH, + UNSIGNED_PAYLOAD, +} from './middleware/signing/signer/signatureV4/constants'; export { extendedEncodeURIComponent } from './middleware/signing/utils/extendedEncodeURIComponent'; export { signingMiddlewareFactory, diff --git a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts index 8fa93dc2136..7dd22cdc0bf 100644 --- a/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts +++ b/packages/core/src/clients/middleware/signing/signer/signatureV4/utils/getHashedPayload.ts @@ -34,7 +34,9 @@ export const getHashedPayload = (body: HttpRequest['body']): string => { }; const isSourceData = (body: HttpRequest['body']): body is SourceData => - typeof body === 'string' || ArrayBuffer.isView(body) || isArrayBuffer(body); + // Exclude UNSIGNED_PAYLOAD constant to prevent it from being hashed as a string + body !== UNSIGNED_PAYLOAD && + (typeof body === 'string' || ArrayBuffer.isView(body) || isArrayBuffer(body)); const isArrayBuffer = (arg: any): arg is ArrayBuffer => (typeof ArrayBuffer === 'function' && arg instanceof ArrayBuffer) || diff --git a/packages/storage/__tests__/providers/s3/utils/client/S3/getPresignedGetObjectUrl.test.ts b/packages/storage/__tests__/providers/s3/utils/client/S3/getPresignedGetObjectUrl.test.ts index a208859a7c8..403a6162010 100644 --- a/packages/storage/__tests__/providers/s3/utils/client/S3/getPresignedGetObjectUrl.test.ts +++ b/packages/storage/__tests__/providers/s3/utils/client/S3/getPresignedGetObjectUrl.test.ts @@ -1,7 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { presignUrl } from '@aws-amplify/core/internals/aws-client-utils'; +import { + UNSIGNED_PAYLOAD, + presignUrl, +} from '@aws-amplify/core/internals/aws-client-utils'; import { getPresignedGetObjectUrl } from '../../../../../../src/providers/s3/utils/client/s3data'; @@ -42,9 +45,6 @@ describe('serializeGetObjectRequest', () => { ); expect(actualUrl.pathname).toEqual('/key'); expect(actualUrl.searchParams.get('X-Amz-Expires')).toEqual('900'); - expect(actualUrl.searchParams.get('x-amz-content-sha256')).toEqual( - expect.any(String), - ); expect(actualUrl.searchParams.get('x-amz-user-agent')).toEqual('UA'); }); @@ -99,9 +99,6 @@ describe('serializeGetObjectRequest', () => { ); expect(actual.searchParams.get('X-Amz-Expires')).toBe('900'); - expect(actual.searchParams.get('x-amz-content-sha256')).toEqual( - expect.any(String), - ); expect(actual.searchParams.get('response-content-disposition')).toBe( 'attachment; filename="filename.jpg"', ); @@ -110,4 +107,32 @@ describe('serializeGetObjectRequest', () => { ); expect(actual.searchParams.get('x-amz-user-agent')).toBe('UA'); }); + + it('should use UNSIGNED-PAYLOAD for presigned URLs', async () => { + mockPresignUrl.mockClear(); + + const result = await getPresignedGetObjectUrl( + { + ...defaultConfigWithStaticCredentials, + signingRegion: defaultConfigWithStaticCredentials.region, + signingService: 's3', + expiration: 900, + }, + { + Bucket: 'bucket', + Key: 'key', + }, + ); + + // Verify UNSIGNED-PAYLOAD is used in the signature calculation + expect(mockPresignUrl).toHaveBeenCalledWith( + expect.objectContaining({ + body: UNSIGNED_PAYLOAD, + }), + expect.anything(), + ); + + // Verify x-amz-content-sha256 is NOT in the presigned URL query parameters + expect(result.searchParams.get('x-amz-content-sha256')).toBeNull(); + }); }); diff --git a/packages/storage/src/providers/s3/utils/client/s3data/getObject.ts b/packages/storage/src/providers/s3/utils/client/s3data/getObject.ts index 40d8e067892..223c671cb58 100644 --- a/packages/storage/src/providers/s3/utils/client/s3data/getObject.ts +++ b/packages/storage/src/providers/s3/utils/client/s3data/getObject.ts @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { - EMPTY_SHA256_HASH, Endpoint, HttpRequest, HttpResponse, PresignUrlOptions, + UNSIGNED_PAYLOAD, UserAgentOptions, parseMetadata, presignUrl, @@ -18,7 +18,6 @@ import { } from '@aws-amplify/core/internals/utils'; import { - CONTENT_SHA256_HEADER, assignStringVariables, buildStorageServiceError, deserializeBoolean, @@ -172,10 +171,6 @@ export const getPresignedGetObjectUrl = async ( const endpoint = defaultConfig.endpointResolver(config, input); const { url, headers, method } = await getObjectSerializer(input, endpoint); - // TODO: set content sha256 query parameter with value of UNSIGNED-PAYLOAD instead of empty hash. - // It requires changes in presignUrl. Without this change, the generated url still works, - // but not the same as other tools like AWS SDK and CLI. - url.searchParams.append(CONTENT_SHA256_HEADER, EMPTY_SHA256_HASH); if (config.userAgentValue) { url.searchParams.append( config.userAgentHeader ?? USER_AGENT_HEADER, @@ -199,7 +194,7 @@ export const getPresignedGetObjectUrl = async ( } return presignUrl( - { method, url, body: undefined }, + { method, url, body: UNSIGNED_PAYLOAD }, { signingService: defaultConfig.service, signingRegion: config.region,