diff --git a/webapp/api.php b/webapp/api.php index 4ce9936..56ff2ab 100644 --- a/webapp/api.php +++ b/webapp/api.php @@ -2483,44 +2483,72 @@ function handlePtrLookup($db) { /** * Make AWS Route53 API request with Signature Version 4 + * Note: Route53 is a global service, always uses us-east-1 for signing */ function awsRoute53Request($method, $path, $body, $accessKeyId, $secretAccessKey, $region) { $service = 'route53'; $host = 'route53.amazonaws.com'; - $endpoint = "https://{$host}/{$path}"; + // Route53 is a global service, always use us-east-1 for signing + $signingRegion = 'us-east-1'; $algorithm = 'AWS4-HMAC-SHA256'; $timestamp = gmdate('Ymd\THis\Z'); $datestamp = gmdate('Ymd'); - // Create canonical request - $canonicalUri = '/' . $path; + // Parse path and query string + $canonicalUri = '/' . ltrim($path, '/'); $canonicalQuerystring = ''; - // Parse query string if present if (strpos($path, '?') !== false) { - list($canonicalUri, $queryString) = explode('?', '/' . $path, 2); + list($pathPart, $queryString) = explode('?', $path, 2); + $canonicalUri = '/' . ltrim($pathPart, '/'); parse_str($queryString, $queryParams); ksort($queryParams); - $canonicalQuerystring = http_build_query($queryParams); + // Build query string with proper encoding + $pairs = []; + foreach ($queryParams as $k => $v) { + $pairs[] = rawurlencode($k) . '=' . rawurlencode($v); + } + $canonicalQuerystring = implode('&', $pairs); } - $payloadHash = hash('sha256', is_array($body) ? json_encode($body) : ''); + $endpoint = "https://{$host}{$canonicalUri}" . ($canonicalQuerystring ? "?{$canonicalQuerystring}" : ''); + // For GET requests, body is empty + $payload = ''; + if ($method === 'POST' && !empty($body)) { + $payload = is_array($body) ? json_encode($body) : $body; + } + $payloadHash = hash('sha256', $payload); + + // Build canonical headers - must be sorted alphabetically and lowercase $canonicalHeaders = "host:{$host}\n" . "x-amz-date:{$timestamp}\n"; $signedHeaders = 'host;x-amz-date'; - $canonicalRequest = "{$method}\n{$canonicalUri}\n{$canonicalQuerystring}\n{$canonicalHeaders}\n{$signedHeaders}\n{$payloadHash}"; + // Build canonical request + $canonicalRequest = implode("\n", [ + $method, + $canonicalUri, + $canonicalQuerystring, + $canonicalHeaders, + $signedHeaders, + $payloadHash + ]); // Create string to sign - $credentialScope = "{$datestamp}/{$region}/{$service}/aws4_request"; - $stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest); + $credentialScope = "{$datestamp}/{$signingRegion}/{$service}/aws4_request"; + $stringToSign = implode("\n", [ + $algorithm, + $timestamp, + $credentialScope, + hash('sha256', $canonicalRequest) + ]); - // Calculate signature + // Calculate signature using HMAC-SHA256 $kSecret = 'AWS4' . $secretAccessKey; $kDate = hash_hmac('sha256', $datestamp, $kSecret, true); - $kRegion = hash_hmac('sha256', $region, $kDate, true); + $kRegion = hash_hmac('sha256', $signingRegion, $kDate, true); $kService = hash_hmac('sha256', $service, $kRegion, true); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); $signature = hash_hmac('sha256', $stringToSign, $kSigning); @@ -2537,14 +2565,13 @@ function awsRoute53Request($method, $path, $body, $accessKeyId, $secretAccessKey CURLOPT_HTTPHEADER => [ "Host: {$host}", "X-Amz-Date: {$timestamp}", - "Authorization: {$authorizationHeader}", - "Content-Type: application/xml" + "Authorization: {$authorizationHeader}" ] ]); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($body) ? json_encode($body) : $body); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); } $response = curl_exec($ch);