nox7
By:
nox7

[Spaces API] How to properly create Authorization header in PHP?

November 11, 2017 153 views
PHP API Ubuntu

The Goal

Hello all, my goal is to eventually use DO Spaces to store images for my application. However, I am trying to create an API to upload a simple text document to try.

The Issue

It seems my Authorization header is failing for some reason. It seems fine to me, but an expert is out there who knows what is going on better than I do.

The API tells me to call a hex() function in the pseudo-code of the signature creation of the API docs (Screenshot Authorization Header Pseudo-code)

But as I pointed out in that screenshot, the final signature shown in the example header is clearly not hex, but a hash. So I am confused there. The API call returns XML with an AccessDenied error. (Would be great if these errors were more specific >.>)

Below is my code, and below that is the header I am sending out. I appreciate any help, thank you. The function that makes the header and signature is getAuthorizationSignature()

public function getAuthorizationSignature(
            $http_method, 
            $path, 
            $queryStringParams, 
            $headersNoValues, 
            $headersWithValues, 
            $sha256HashOfBody
        ){
            $canonicalRequest = "PUT\n$path\n$queryString\n$headersWithValues\n$headersNoValues\n$sha256HashOfBody";

            $stringToSign = "AWS4-HMAC-SHA256" 
                . "\n" 
                . date("Y-m-d") 
                . "\n" 
                . date("Ymd") . "/" . $this->region . "/" . "s3/aws4_request" 
                . "\n" 
                . (hash("sha256", $canonicalRequest));

            $dateKey = hash_hmac("sha256", "AWS4" + $this->spaces_private_key, date("Ymd"));
            $dateRegionKey = hash_hmac("sha256", $dateKey, $this->region);
            $dateRegionServiceKey = hash_hmac("sha256", $dateRegionKey, "s3");
            $signingKey = hash_hmac("sha256", $dateRegionServiceKey, "aws4_request");

            $signature = (hash_hmac("sha256", $signingKey, $stringToSign));

            return $signature;
        }

        public function getSignedHeaderString($headers){
            $tools = new tools();
            $newHeaders = '';
            foreach($headers as $header){
                $newHeaders .= $tools->stripWhitespace(strtolower($header)) . "\n";
            }

            return $newHeaders;
        }

        public function getAuthorizationHeader($httpMethod, $path, $queryString, $headersNoValues, $headers, $sha256HashOfBody){
            $headersWithValues = $this->getSignedHeaderString($headers);
            sort($headersNoValues);
            $headersNoValues = implode(";", $headersNoValues);
            $credential = $this->spaces_access_key . "/" . date("Ymd") . "/" . $this->region . "/s3/aws4_request";
            $authHeader = "Authorization: AWS4-HMAC-SHA256 Credential=$credential, SignedHeaders=$headersNoValues, Signature=" . $this->getAuthorizationSignature($httpMethod, $path, $queryString, $headersNoValues, $headersWithValues, $sha256HashOfBody);

            return [$authHeader];
        }

        public function upload($content, $objectKey, $contentType){

            // I am overriding what I originally passed to this function for testing purposes
            $content = "Text file contents";
            $contentType = "text/plain";
            $objectKey = "text.txt";

            $requestBody = $content;
            $requestMethod = "PUT";
            $contentLength = strlen($content);
            $payloadHash = hash("sha256", $content);

            $requestHeaders = array(
                "Host: aamc-cdn.nyc3.digitaloceanspaces.com",
                "Content-Length: $contentLength",
                "x-amz-acl: public-read",
                "x-amz-content-sha256: $payloadHash",
                "Content-Type: $contentType",
            );

            $requestHeaders_NoValues = array(
                "content-length", "x-amz-acl", "content-type", "x-amz-content-sha256"
            );

            $authHeader = $this->getAuthorizationHeader($requestMethod, "/$objectKey", "", $requestHeaders_NoValues, $requestHeaders, $payloadHash);
            $finalHeader = $authHeader + $requestHeaders;

            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $this->host . "/" . $objectKey);
            curl_setopt($curl, CURLOPT_HTTPHEADER, $finalHeader);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
            curl_setopt($curl, CURLOPT_VERBOSE, 1);
            $response = curl_exec($curl);
            print_r($finalHeader);
            return $response;
        }

The header I send out:

Array
(
    [0] => Authorization: AWS4-HMAC-SHA256 Credential=[Access Key]/20171111/nyc3/s3/aws4_request, SignedHeaders=content-length;content-type;x-amz-acl;x-amz-content-sha256, Signature=0dff50d6f0abccf82320ce03922bbc9cd7596b681098c4d1604a0e24e350231a
    [1] => Content-Length: 18
    [2] => x-amz-acl: public-read
    [3] => x-amz-content-sha256: b3a11fb73200f7f32322eb34639c6fb543c3bab8e0e9b161bc8f7d49b5ca7dc8
    [4] => Content-Type: text/plain
)
1 Answer

I am having the same troubles with their Spaces Docs. It seems, as a new product, it is not yet well explained and there aren't enough source on the Internet to go through this. I am getting a "SignatureDoesNotMatcht" error...there is something that is not quite right..

  • Very sorry to hear that, quite annoying that the docs aren't very clear on authorization.

    I even tried using an actual client library for signing requests (AWS SV4) and returned that same error.

    If you use a packet inspector (like Fiddler) and see what is going on when you upload to the Spaces in your browser through their control panel, their signature looks nothing like an SV4 signature.

    DO, can we get a better explanation of this API authorization - or just stop using the bulky, useless Amazon method and create a simpler, robust home-brewed authorization.

    • Yeah, I am still trying to get it working - no success yet. The support told me that the UI for changing CORS will come online in some time (not estimated yet)..

      I am desperately trying to change the CORS settings for my Spaces volume so I disallow file crawling bots..

Have another answer? Share your knowledge.