fix: add region validation, fix presigned URL canonical request (add method, fix headers), normalizeUri decode

This commit is contained in:
asepharyana
2026-07-06 15:31:06 +07:00
parent ee758aa939
commit 084ff3fb9d
2 changed files with 20 additions and 9 deletions
+14 -7
View File
@@ -97,7 +97,8 @@ const buildCanonicalRequest = (
const normalizeUri = (uri: string): string => { const normalizeUri = (uri: string): string => {
if (!uri || uri === '') return '/'; if (!uri || uri === '') return '/';
return uri; // AWS SigV4 requires URI-decoded paths in the canonical request
return decodeURIComponent(uri);
}; };
const buildCanonicalQueryString = (searchParams: URLSearchParams): string => { const buildCanonicalQueryString = (searchParams: URLSearchParams): string => {
@@ -144,6 +145,10 @@ export const verifySignature = async (
return { isValid: false, credential: null, errorCode: 'SignatureDoesNotMatch' }; return { isValid: false, credential: null, errorCode: 'SignatureDoesNotMatch' };
} }
if (parsed.region !== region) {
return { isValid: false, credential: null, errorCode: 'SignatureDoesNotMatch' };
}
const parsedUrl = new URL(url, 'http://localhost'); const parsedUrl = new URL(url, 'http://localhost');
const canonicalUri = normalizeUri(parsedUrl.pathname); const canonicalUri = normalizeUri(parsedUrl.pathname);
const canonicalQueryString = buildCanonicalQueryString(parsedUrl.searchParams); const canonicalQueryString = buildCanonicalQueryString(parsedUrl.searchParams);
@@ -164,7 +169,7 @@ export const verifySignature = async (
const amzDate = headers['x-amz-date'] || ''; const amzDate = headers['x-amz-date'] || '';
const dateStamp = parsed.date; const dateStamp = parsed.date;
const credentialScope = `${dateStamp}/${parsed.region}/${parsed.service}/${parsed.termination}`; const credentialScope = `${dateStamp}/${region}/${parsed.service}/${parsed.termination}`;
const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${hashedCanonicalRequest}`; const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${hashedCanonicalRequest}`;
@@ -188,6 +193,7 @@ export const verifySignature = async (
export const verifyPresignedUrl = async ( export const verifyPresignedUrl = async (
url: string, url: string,
method: string,
s3AccessKey: string, s3AccessKey: string,
s3SecretKey: string, s3SecretKey: string,
region: string, region: string,
@@ -245,14 +251,15 @@ export const verifyPresignedUrl = async (
} }
const canonicalQueryString = buildCanonicalQueryString(sortedParams); const canonicalQueryString = buildCanonicalQueryString(sortedParams);
const canonicalHeaders = signedHeaders // Build canonical headers for presigned URL — only 'host' is typically signed
.split(';') const signedHeaderList = signedHeaders.split(';').filter(Boolean);
.map((h) => `${h}:host\n`) const canonicalHeaders = signedHeaderList
.map((h) => `${h.toLowerCase()}:${h === 'host' ? parsedUrl.host : ''}\n`)
.join(''); .join('');
const signedHeadersStr = signedHeaders;
const hashedPayload = 'UNSIGNED-PAYLOAD'; const hashedPayload = 'UNSIGNED-PAYLOAD';
const canonicalRequest = `${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeadersStr}\n${hashedPayload}`; const canonicalRequest = `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${hashedPayload}`;
const hashedCanonicalRequest = await sha256Hex(canonicalRequest); const hashedCanonicalRequest = await sha256Hex(canonicalRequest);
const credentialScope = `${dateStamp}/${region}/s3/aws4_request`; const credentialScope = `${dateStamp}/${region}/s3/aws4_request`;
+6 -2
View File
@@ -188,10 +188,14 @@ export const s3ErrorResponse = (
message: string, message: string,
resource: string, resource: string,
status: number, status: number,
requestId: string = '',
): Response => ): Response =>
new Response(s3ErrorXml(code, message, resource, ''), { new Response(s3ErrorXml(code, message, resource, requestId), {
status, status,
headers: { 'content-type': 'application/xml' }, headers: {
'content-type': 'application/xml',
...(requestId ? { 'x-amz-request-id': requestId } : {}),
},
}); });
// ─────── DeleteObjects XML parser ─────── // ─────── DeleteObjects XML parser ───────