Creating a mini PHP SDK for signing requests to Oracle Cloud Infrastructure API

The idea to write this library came about when I wanted to take full advantage of the  free  Oracle Cloud Infrastructure offering , namely 10 GB of Object Storage and 10 TB of outgoing traffic per month. The difference with AWS S3 is  huge . Unfortunately, Oracle Cloud  does not have  an SDK available for the still most popular programming language for website development. The good news is that the service is  partially compatible  with Amazon S3, which means that you can use existing and well-documented  developer tools , including for PHP.





For those who are anxious to see the code, welcome to  https://github.com/hitrov/oci-api-php-request-sign .





Indeed, with the available tools, you can perform almost every operation imaginable - to create, read, and delete buckets and objects (files). Recycle bins can be both public (with or without listing files) and private. It is possible to upload files to a private basket, having only a "secret" URL (generated manually using the  CLI  or the web interface - Oracle Cloud console). In fact, this may already be enough for many scenarios, especially if you generate brute-force filenames in case you don't want to expose them to the public.





I was interested in the ability to "share" files, that is, share public links to files, and, of course, restrict access if necessary. With a small number of files, you can do it manually, but we are gathered here to have programmatic access. AWS S3 calls this a Pre-Signed URL , while Oracle  calls it  Pre-Authenticated Request .





Installing AWS PHP SDK





composer require aws/aws-sdk-php
      
      



Below it will be shown where to get access ( AWS_ACCESS_KEY_ID



and AWS_SECRET_ACCESS_KEY



.





Namespace can be seen





require('vendor/autoload.php');

$namespaceName = 'frpegp***';
$bucketName = 'test******05';
$region = 'eu-frankfurt-1';
$endpoint = "https://$namespaceName.compat.objectstorage.$region.oraclecloud.com";

$s3 = new Aws\S3\S3Client([
    'version' => 'latest',
    'region'  => $region,
    'endpoint' => $endpoint,
    'signature_version' => 'v4',
    'use_path_style_endpoint' => true,
    'credentials' => [
        'key'    => 'AKI***YYJ', // remove if you have env var AWS_ACCESS_KEY_ID
        'secret' => 'ndK***cIf', // remove if you have env var AWS_SECRET_ACCESS_KEY
    ],
]);

$cmd = $s3->getCommand('GetObject', [
    'Bucket' => $bucketName,
    'Key' => 'fff.txt'
]);

$request = $s3->createPresignedRequest($cmd, '+20 minutes');
var_dump($request);
      
      



object(GuzzleHttp\Psr7\Request)#146 (7) {
  ["method":"GuzzleHttp\Psr7\Request":private]=>
  string(3) "GET"
  ["uri":"GuzzleHttp\Psr7\Request":private]=>
  object(GuzzleHttp\Psr7\Uri)#148 (7) {
    ["scheme":"GuzzleHttp\Psr7\Uri":private]=>
    string(5) "https"
    ...
    ["host":"GuzzleHttp\Psr7\Uri":private]=>
    string(64) "{namespace}.compat.objectstorage.eu-frankfurt-1.oraclecloud.com"
    ...
    ["path":"GuzzleHttp\Psr7\Uri":private]=>
    string(21) "/{bucket}/fff.txt"
    ["query":"GuzzleHttp\Psr7\Uri":private]=>
    string(329) "X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=&&&%2F20210211%2Feu-frankfurt-1%2Fs3%2Faws4_request&X-Amz-Date=20210211T093350Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1200&X-Amz-Signature=1e1b1***6ac992"
    ...
  }
}
      
      



This operation returns a PSR-7 request in response  , from which you can generate a URL of the form





https://{namespace}.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/{bucket}/fff.txt?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***%2F20210210%2Feu-frankfurt-1%2Fs3%2Faws4_request&X-Amz-Date=20210210T185244Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1200&X-Amz-Signature=a167a***9a857







But, unfortunately, this does not allow revoking access, for example, if the link is "long-playing".





,  API,  , .





, \\  ,  DNS   Email. API Reference and Endpoints.





, , , , - () Oracle Cloud  User Settings





Oracle Cloud Profile Actions
Oracle Cloud

API Keys — Add API Key





API Keys - Add API Key
API Keys - Add API Key

Download private key ( ),  Add





Download Private Key and Add
Download Private Key and Add

,





Configuration File example
Configuration File example

, AWS PHP SDK, Customer Secret Keys ( AWS_ACCESS_KEY_ID



AWS_SECRET_ACCESS_KEY



Amazon.





Oracle Cloud Infrastructure mini PHP SDK ( !)





composer require hitrov/oci-api-php-request-sign
      
      



 PSR-4 .





require 'vendor/autoload.php';
use Hitrov\OCI\Signer;
      
      



( , , ).





OCI_TENANCY_ID=ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq
OCI_USER_ID=ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq
OCI_KEY_FINGERPRINT=20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34
OCI_PRIVATE_KEY_FILENAME=/path/to/privatekey.pem
      
      



.





$signer = new Signer;
      
      



 https://github.com/hitrov/oci-api-php-request-sign#alternatives-for-providing-credentials , .





   CreatePreauthenticatedRequest.





( )





public function getHeaders(
    string $url, string $method = 'GET', ?string $body = null, ?string $contentType = 'application/json', string $dateString = null
): array
      
      







$curl = curl_init();
$url = 'https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}/p/';
$method = 'POST';
$body = '{"accessType": "ObjectRead", "name": "read-access-to-image.png", "objectName": "path/to/image.png", "timeExpires": "2021-03-01T00:00:00-00:00"}';

$headers = $signer->getHeaders($url, $method, $body, 'application/json');
var_dump($headers);

$curlOptions = [
  CURLOPT_URL => $url,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 5,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => $method,
  CURLOPT_HTTPHEADER => $headers,
];

if ($body) {
  // not needed for GET or HEAD requests
  $curlOptions[CURLOPT_POSTFIELDS] = $body;
}

curl_setopt_array($curl, $curlOptions);
$response = curl_exec($curl);
echo $response;
curl_close($curl);
      
      



array(6) {
  [0]=>
  string(35) "date: Mon, 08 Feb 2021 20:49:22 GMT"
  [1]=>
  string(50) "host: objectstorage.eu-frankfurt-1.oraclecloud.com"
  [2]=>
  string(18) "content-length: 76"
  [3]=>
  string(30) "content-type: application/json"
  [4]=>
  string(62) "x-content-sha256: X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="
  [5]=>
  string(538) "Authorization: Signature version=\"1\",keyId=\"ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34\",algorithm=\"rsa-sha256\",headers=\"date (request-target) host content-length content-type x-content-sha256\",signature=\"LXWXDA8VmXXc1NRbMmXtW61IS97DfIOMAnlj+Gm+oBPNc2svXYdhcXNJ+oFPoi9qJHLnoUiHqotTzuVPXSG5iyXzFntvkAn3lFIAja52iwwwcJflEIXj/b39eG2dCsOTmmUJguut0FsLhCRSX0eylTSLgxTFGoQi7K/m18nafso=\""
}
      
      



{
  "accessUri": "/p/AlIlOEsMok7oE7YkN30KJUDjDKQjk493BKbuM-ANUNGdBBAHzHT_5lFlzYC9CQiA/n/{namespaceName}/b/{bucketName}/o/path/to/image.png",
  "id": "oHJQWGxpD+2PhDqtoewvLCf8/lYNlaIpbZHYx+mBryAad/q0LnFy37Me/quKhxEi:path/to/image.png",
  "name": "read-access-to-image.png",
  "accessType": "ObjectRead",
  "objectName": "path/to/image.png",
  "timeCreated": "2021-02-09T11:52:45.053Z",
  "timeExpires": "2021-03-01T00:00:00Z"
}
      
      



!





, . , – .





1) , « » (SIGNING_HEADERS_NAMES).  





  •  date





  • · (request-target)





  • · host





 POST|PUT|PATCH 





  • · content-length





  • · content-type





  • · x-content-sha256





$signingHeadersNames = $signer->getSigningHeadersNames('POST');
      
      



2) SHA256 «» –  base64





$bodyHashBase64 = $signer->getBodyHashBase64($body);
      
      



3) ,





date: Mon, 08 Feb 2021 20:51:33 GMT
(request-target): post /n/{namespaceName}/b/{bucketName}/p/
host: objectstorage.eu-frankfurt-1.oraclecloud.com
content-length: 76
content-type: application/json
x-content-sha256: X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
      
      



$signingString = $signer->getSigningString($url, $method, $body, 'application/json');
      
      



(2). , , 5 .





4) (3)  RSA-SHA256





$signature = $signer->calculateSignature($signingString, $privateKeyString);
      
      



5) KEY_ID , API Key, ,





"{OCITENANCYID}/{OCIUSERID}/{OCIKEY_FINGERPRINT}"







$keyId = $signer->getKeyId();
      
      



6)  ( 1



 Oracle)





Authorization: Signature version=\"1\",keyId=\"{KEY_ID}\",algorithm=\"rsa-sha256\",headers=\"{SIGNING_HEADERS_NAMES_STRING}\",signature=\"{SIGNATURE}\"







 SIGNING_HEADERS_NAMES_STRING – (1), .





date (request-target) host content-length content-type x-content-sha256







$signingHeadersNamesString = implode(' ', $signingHeadersNames);
$authorizationHeader = $signer->getAuthorizationHeader($keyId, $signingHeadersNamesString, $signature);
      
      







Authorization: Signature version=\"1\",keyId=\"ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34\",algorithm=\"rsa-sha256\",headers=\"date (request-target) host content-length content-type x-content-sha256\",signature=\"LXWXDA8VmXXc1NRbMmXtW61IS97DfIOMAnlj+Gm+oBPNc2svXYdhcXNJ+oFPoi9qJHLnoUiHqotTzuVPXSG5iyXzFntvkAn3lFIAja52iwwwcJflEIXj/b39eG2dCsOTmmUJguut0FsLhCRSX0eylTSLgxTFGoQi7K/m18nafso=\"
      
      



- .  var_dump()



- (3),  (request-target)  . , , (6).





I was helped by the  Oracle Cloud Infrastructure (OCI) REST call walkthrough with curl article . Some of the method names are borrowed from the official  GoLang SDK . Test cases -  from the same place .








All Articles