class. * * @param string $endpoint (Required) The endpoint to direct the request to. * @param string $operation (Required) The operation to execute as a result of this request. * @param array $payload (Required) The options to use as part of the payload in the request. * @param CFCredential $credentials (Required) The credentials to use for signing and making requests. * @return void */ public function __construct($endpoint, $operation, $payload, CFCredential $credentials) { parent::__construct($endpoint, $operation, $payload, $credentials); } /** * Generates a cURL handle with all of the required authentication bits set. * * @return resource A cURL handle ready for executing. */ public function authenticate() { // Determine signing values $current_time = time(); $timestamp = gmdate(CFUtilities::DATE_FORMAT_SIGV4, $current_time); // Initialize $x_amz_target = null; $this->headers = array(); $this->signed_headers = array(); $this->canonical_headers = array(); $this->query = array(); // Prepare JSON structure $decoded = json_decode($this->payload, true); $data = (array) (is_array($decoded) ? $decoded : $this->payload); unset($data['curlopts']); unset($data['returnCurlHandle']); $this->body = json_encode($data); if ($this->body === '' || $this->body === '[]') { $this->body = '{}'; } // Do we have an authentication token? if ($this->auth_token) { $this->headers['X-Amz-Security-Token'] = $this->auth_token; $this->query['SecurityToken'] = $this->auth_token; } // Manage the key-value pairs that are used in the query. if (stripos($this->operation, 'x-amz-target') !== false) { $x_amz_target = trim(str_ireplace('x-amz-target:', '', $this->operation)); } else { $this->query['Action'] = $this->operation; } // Only add it if it exists. if ($this->api_version) { $this->query['Version'] = $this->api_version; } // Do a case-sensitive, natural order sort on the array keys. uksort($this->query, 'strcmp'); // Remove the default scheme from the domain. $domain = str_replace(array('http://', 'https://'), '', $this->endpoint); // Parse our request. $parsed_url = parse_url('http://' . $domain); // Set the proper host header. if (isset($parsed_url['port']) && (integer) $parsed_url['port'] !== 80 && (integer) $parsed_url['port'] !== 443) { $host_header = strtolower($parsed_url['host']) . ':' . $parsed_url['port']; } else { $host_header = strtolower($parsed_url['host']); } // Generate the querystring from $this->query $this->querystring = $this->util->to_query_string($this->query); // Gather information to pass along to other classes. $helpers = array( 'utilities' => $this->utilities_class, 'request' => $this->request_class, 'response' => $this->response_class, ); // Compose the request. $request_url = ($this->use_ssl ? 'https://' : 'http://') . $domain; $request_url .= !isset($parsed_url['path']) ? '/' : ''; // Instantiate the request class $request = new $this->request_class($request_url, $this->proxy, $helpers, $this->credentials); $request->set_method('POST'); $request->set_body($this->body); $this->querystring = $this->body; $this->headers['Content-Type'] = 'application/x-amz-json-1.1'; $this->headers['X-Amz-Target'] = $x_amz_target; // Pass along registered stream callbacks if ($this->registered_streaming_read_callback) { $request->register_streaming_read_callback($this->registered_streaming_read_callback); } if ($this->registered_streaming_write_callback) { $request->register_streaming_write_callback($this->registered_streaming_write_callback); } // Add authentication headers $this->headers['X-Amz-Date'] = $timestamp; $this->headers['Content-Length'] = strlen($this->querystring); $this->headers['Host'] = $host_header; // Sort headers uksort($this->headers, 'strnatcasecmp'); // Add headers to request and compute the string to sign foreach ($this->headers as $header_key => $header_value) { // Strip linebreaks from header values as they're illegal and can allow for security issues $header_value = str_replace(array("\r", "\n"), '', $header_value); $request->add_header($header_key, $header_value); $this->canonical_headers[] = strtolower($header_key) . ':' . $header_value; $this->signed_headers[] = strtolower($header_key); } $this->headers['Authorization'] = $this->authorization($timestamp); $request->add_header('Authorization', $this->headers['Authorization']); $request->request_headers = $this->headers; return $request; } /** * Generates the authorization string to use for the request. * * @param string $datetime (Required) The current timestamp. * @return string The authorization string. */ protected function authorization($datetime) { $access_key_id = $this->key; $parts = array(); $parts[] = "AWS4-HMAC-SHA256 Credential=${access_key_id}/" . $this->credential_string($datetime); $parts[] = 'SignedHeaders=' . implode(';', $this->signed_headers); $parts[] = 'Signature=' . $this->hex16($this->signature($datetime)); return implode(',', $parts); } /** * Calculate the signature. * * @param string $datetime (Required) The current timestamp. * @return string The signature. */ protected function signature($datetime) { $k_date = $this->hmac('AWS4' . $this->secret_key, substr($datetime, 0, 8)); $k_region = $this->hmac($k_date, $this->region()); $k_service = $this->hmac($k_region, $this->service()); $k_credentials = $this->hmac($k_service, 'aws4_request'); $signature = $this->hmac($k_credentials, $this->string_to_sign($datetime)); return $signature; } /** * Calculate the string to sign. * * @param string $datetime (Required) The current timestamp. * @return string The string to sign. */ protected function string_to_sign($datetime) { $parts = array(); $parts[] = 'AWS4-HMAC-SHA256'; $parts[] = $datetime; $parts[] = $this->credential_string($datetime); $parts[] = $this->hex16($this->hash($this->canonical_request())); $this->string_to_sign = implode("\n", $parts); return $this->string_to_sign; } /** * Generates the credential string to use for signing. * * @param string $datetime (Required) The current timestamp. * @return string The credential string. */ protected function credential_string($datetime) { $parts = array(); $parts[] = substr($datetime, 0, 8); $parts[] = $this->region(); $parts[] = $this->service(); $parts[] = 'aws4_request'; return implode('/', $parts); } /** * Calculate the canonical request. * * @return string The canonical request. */ protected function canonical_request() { $parts = array(); $parts[] = 'POST'; $parts[] = $this->canonical_uri(); $parts[] = ''; // $parts[] = $this->canonical_querystring(); $parts[] = implode("\n", $this->canonical_headers) . "\n"; $parts[] = implode(';', $this->signed_headers); $parts[] = $this->hex16($this->hash($this->body)); $this->canonical_request = implode("\n", $parts); return $this->canonical_request; } /** * The region ID to use in the signature. * * @return return The region ID. */ protected function region() { $pieces = explode('.', $this->endpoint); // Handle cases with single/no region (i.e. service.region.amazonaws.com vs. service.amazonaws.com) if (count($pieces < 4)) { return 'us-east-1'; } return $pieces[1]; } /** * The service ID to use in the signature. * * @return return The service ID. */ protected function service() { $pieces = explode('.', $this->endpoint); return $pieces[0]; } /** * The request URI path. * * @return string The request URI path. */ protected function canonical_uri() { return '/'; } /** * The canonical query string. * * @return string The canonical query string. */ protected function canonical_querystring() { if (!isset($this->canonical_querystring)) { $this->canonical_querystring = $this->util->to_signable_string($this->query); } return $this->canonical_querystring; } /** * Hex16-pack the data. * * @param string $value (Required) The data to hex16 pack. * @return string The hex16-packed data. */ protected function hex16($value) { $result = unpack('H*', $value); return reset($result); } /** * Applies HMAC SHA-256 encryption to the string, salted by the key. * * @return string Raw HMAC SHA-256 hashed string. */ protected function hmac($key, $string) { return hash_hmac('sha256', $string, $key, true); } /** * SHA-256 hashes the string. * * @return string Raw SHA-256 hashed string. */ protected function hash($string) { return hash('sha256', $string, true); } }