Oracle Cloud Infrastructure Documentation

Request Signatures

This topic describes how to sign Oracle Cloud Infrastructure API requests.

Signing samples are included for the following:

Signature Version 1

The signature described here is version 1 of the Oracle Cloud Infrastructure API signature. In the future, if Oracle modifies the method for signing requests, the version number will be incremented and your company will be notified.

Required Credentials and OCIDs

You need an API signing key in the correct format. See Required Keys and OCIDs.

Warning

Client Clock Skew

If the client's clock is skewed more than 5 minutes, a 401 (NotAuthenticated) HTTP status code is returned. This will affect your API requests. For more information, see Maximum Allowed Client Clock Skew.

You also need the OCIDs for your tenancy and user. See Where to Get the Tenancy's OCID and User's OCID.

Summary of Signing Steps

In general, these are the steps required to sign a request:

  1. Form the HTTPS request (SSL protocol TLS 1.2 is required).
  2. Create the signing string, which is based on parts of the request.
  3. Create the signature from the signing string, using your private key and the RSA-SHA256 algorithm.
  4. Add the resulting signature and other required information to the Authorization header in the request.

See the remaining sections in this topic for details about these steps.

Specification You Need to Be Familiar With

To learn how to perform steps 2-4 in the process above, refer to draft-cavage-http-signatures-08. It's a draft specification that forms the basis for how Oracle handles request signatures. It describes generally how to form the signing string, how to create the signature, and how to add the signature and required information to the request. The remaining sections in this topic assume you're familiar with it. Important details of the Oracle Cloud Infrastructure implementation of the reference are listed in the next section.

Special Implementation Details

The following sections describe important items to note about the Oracle Cloud Infrastructure implementation of the spec.

Authorization Header

The Oracle Cloud Infrastructure signature uses the "Signature" Authentication scheme (with an Authorization header), and not the Signature HTTP header.

Required Headers

This section describes the headers that must be included in the signing string.

Note

Error if Required Header is Missing

If a required header is missing, your client will receive a 401 "Unauthorized" response.

For GET and DELETE requests (when there's no content in the request body), the signing string must include at least these headers:

For PUT and POST requests (when there's content in the request body), the signing string must include at least these headers:

  • (request-target)
  • host
  • date or x-date (if both are included, Oracle uses x-date)
  • x-content-sha256 (except for Object Storage PUT requests; see the next section)
  • content-type
  • content-length
Warning

For PUT and POST requests, your client must compute the x-content-sha256 and include it in the request and signing string, even if the body is an empty string. Also, the content-length is always required in the request and signing string, even if the body is empty. Some HTTP clients will not send the content-length if the body is empty, so you must explicitly ensure your client sends it. If date and x-date are both included, Oracle uses x-date. The x-date is used to protect against the reuse of the signed portion of the request (replay attacks).

The one exception is for Object Storage PUT requests on objects (see the next section).

Special Instructions for Object Storage PUT

For Object Storage PutObject and UploadPart PUT requests, the signing string must include at least these headers:

  • (request-target)
  • host
  • date or x-date (if both are included, Oracle uses x-date)

If the request also includes any of the other headers that are normally required for PUT requests (see the list above), then those headers must also be included in the signing string.

Case and Order of Headers

The headers must be all lowercase in the signing string.

The order of the headers in the signing string does not matter. Just make sure to specify the order in the headers parameter in the Authorization header, as described in the draft-cavage-http-signatures-05.

Warning

The (request-target) includes the path and query string from the request. Oracle expects that you will create the signing string with the query parameters in the same order as they appear in the request. If the request query parameters change order after signing occurs, authentication will fail.

URL Encoding of Path and Query String

When forming the signing string, you must URL encode all parameters in the path and query string (but not the headers) according to RFC 3986.

Key Identifier

You must set keyId="<TENANCY OCID>/<USER OCID>/<KEY FINGERPRINT>" in the Authorization header that you add to the request. To get those values, see Where to Get the Tenancy's OCID and User's OCID. An example keyId looks like this (wrapped to better fit the page):

ocid1.tenancy.oc1..exampleuwjnv47knr7uuuvqar5bshnspi6xoxsfebh3vy72fi4swgrkvuvq/ocid1.user.oc1..exampleba3pv6wkcr4jqae5f44n2b2m2yt2j6rx32uzr4h25vqstifsfdsq/40:a4:f8:a0:40:4f:a3:2f:e0:fd:4e:b9:25:72:81:5f

Signing Algorithm

The signing algorithm must be RSA-SHA256, and you must set algorithm="rsa-sha256" in the Authorization header (notice the quotation marks).

Signature Version

You should include version="1" in the Authorization header (notice the quotation marks). If you do not, it's assumed that you're using whatever the current version is (which is version 1 at this time).

Example Header

Here's an example of the general syntax of the Authorization header (for a request with content in the body):

Authorization: Signature version="1",keyId="<tenancy_ocid>/<user_ocid>/<key_fingerprint>",algorithm="rsa-sha256",headers="(request-target) date x-content-sha256 content-type content-length",signature="Base64(RSA-SHA256(<signing_string>))"

Test Values

Here's an example key pair, two example requests, and the resulting Authorization header for each.

Warning

The example signatures use the RSA 2048-bit keys below. Use these keys only for testing your signing code, not for sending production requests.

View examples in full screen for easier reading


-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
oYi+1hqp1fIekaxsyQIDAQAB
-----END PUBLIC KEY-----
						
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
-----END RSA PRIVATE KEY-----


The public key is stored under keyId:

ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/73:61:a2:21:67:e0:df:be:7e:4b:93:1e:15:98:a5:b7



For the following GET request (line breaks inserted between query parameters for easier reading; also notice the URL encoding as mentioned earlier):
			
GET https://iaas.us-phoenix-1.oraclecloud.com/20160918/instances
?availabilityDomain=Pjwf%3A%20PHX-AD-1
&compartmentId=ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa
&displayName=TeamXInstances
&volumeId=ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q
Date: Thu, 05 Jan 2014 21:31:40 GMT
			
The signing string would be (line breaks inserted into the (request-target) header for easier reading):
			
date: Thu, 05 Jan 2014 21:31:40 GMT
(request-target): get /20160918/instances?availabilityDomain=Pjwf%3A%20PH
X-AD-1&compartmentId=ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2i
dnccdflvjsnog7mlr6rtdb25gilchfeyjxa&displayName=TeamXInstances&
volumeId=ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h
4lgvyndsdsnoiwr5q
host: iaas.us-phoenix-1.oraclecloud.com

The Authorization header would be:
Signature version="1",headers="date (request-target) host",keyId="ocid1.t
enancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/
ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3ryn
jq/73:61:a2:21:67:e0:df:be:7e:4b:93:1e:15:98:a5:b7",algorithm="rsa-sha256
",signature="GBas7grhyrhSKHP6AVIj/h5/Vp8bd/peM79H9Wv8kjoaCivujVXlpbKLjMPe
DUhxkFIWtTtLBj3sUzaFj34XE6YZAHc9r2DmE4pMwOAy/kiITcZxa1oHPOeRheC0jP2dqbTll
8fmTZVwKZOKHYPtrLJIJQHJjNvxFWeHQjMaR7M="

For the following POST request:
			
POST https://iaas.us-phoenix-1.oraclecloud.com/20160918/volumeAttachments
Date: Thu, 05 Jan 2014 21:31:40 GMT
{
   "compartmentId": "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa",
   "instanceId": "ocid1.instance.oc1.phx.abuw4ljrlsfiqw6vzzxb43vyypt4pkodawglp3wqxjqofakrwvou52gb6s5a",
   "volumeId": "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q"
}

The signing string would be:
			
date: Thu, 05 Jan 2014 21:31:40 GMT
(request-target): post /20160918/volumeAttachments
host: iaas.us-phoenix-1.oraclecloud.com
content-length: 316
content-type: application/json
x-content-sha256: V9Z20UJTvkvpJ50flBzKE32+6m2zJjweHpDMX/U4Uy0=
			
The Authorization header would be:	

Signature version="1",headers="date (request-target) host content-length c
ontent-type x-content-sha256",keyId="ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr
4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcn
a5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/73:61:a2:21:67:e0:df:be:7e:4b
:93:1e:15:98:a5:b7",algorithm="rsa-sha256",signature="Mje8vIDPlwIHmD/cTDwR
xE7HaAvBg16JnVcsuqaNRim23fFPgQfLoOOxae6WqKb1uPjYEl0qIdazWaBy/Ml8DRhqlocMwo
SXv0fbukP8J5N80LCmzT/FFBvIvTB91XuXI3hYfP9Zt1l7S6ieVadHUfqBedWH0itrtPJBgKmrWso="

Sample Code

This section shows the basic code for signing API requests.

Bash

View the Bash sample in full screen for easier reading.


# Version: 1.0.2
# Usage:
# oci-curl <host> <method> [file-to-send-as-body] <request-target> [extra-curl-args]
#
# ex:
# oci-curl iaas.us-ashburn-1.oraclecloud.com get "/20160918/instances?compartmentId=some-compartment-ocid"
# oci-curl iaas.us-ashburn-1.oraclecloud.com post ./request.json "/20160918/vcns"

function oci-curl {
	# TODO: update these values to your own
		local tenancyId="ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq";
		local authUserId="ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq";
		local keyFingerprint="20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34";
		local privateKeyPath="/Users/someuser/.oci/oci_api_key.pem";

	local alg=rsa-sha256
	local sigVersion="1"
	local now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")"
	local host=$1
	local method=$2
	local extra_args
	local keyId="$tenancyId/$authUserId/$keyFingerprint"
	
	case $method in
				
		"get" | "GET")
		local target=$3
		extra_args=("${@: 4}")
		local curl_method="GET";
		local request_method="get";
		;;				
				
		"delete" | "DELETE")
		local target=$3
		extra_args=("${@: 4}")
		local curl_method="DELETE";
		local request_method="delete";
		;;		
				
		"head" | "HEAD")
		local target=$3
		extra_args=("--head" "${@: 4}")
		local curl_method="HEAD";
		local request_method="head";
		;;
				
		"post" | "POST")
		local body=$3
		local target=$4
		extra_args=("${@: 5}")
		local curl_method="POST";
		local request_method="post";
		local content_sha256="$(openssl dgst -binary -sha256 < $body | openssl enc -e -base64)";
		local content_type="application/json";
		local content_length="$(wc -c < $body | xargs)";
		;;		
		
		"put" | "PUT")
		local body=$3
		local target=$4
		extra_args=("${@: 5}")
		local curl_method="PUT"
		local request_method="put"
		local content_sha256="$(openssl dgst -binary -sha256 < $body | openssl enc -e -base64)";
		local content_type="application/json";
		local content_length="$(wc -c < $body | xargs)";
		;;				
		
		*) echo "invalid method"; return;;
esac

# This line will url encode all special characters in the request target except "/", "?", "=", and "&", since those characters are used 
# in the request target to indicate path and query string structure. If you need to encode any of "/", "?", "=", or "&", such as when
# used as part of a path value or query string key or value, you will need to do that yourself in the request target you pass in.

local escaped_target="$(echo $( rawurlencode "$target" ))"	
local request_target="(request-target): $request_method $escaped_target"
local date_header="date: $now"
local host_header="host: $host"
local content_sha256_header="x-content-sha256: $content_sha256"
local content_type_header="content-type: $content_type"
local content_length_header="content-length: $content_length"
local signing_string="$request_target\n$date_header\n$host_header"
local headers="(request-target) date host"
local curl_header_args
curl_header_args=(-H "$date_header")
local body_arg
body_arg=()
				
if [ "$curl_method" = "PUT" -o "$curl_method" = "POST" ]; then
	signing_string="$signing_string\n$content_sha256_header\n$content_type_header\n$content_length_header"
	headers=$headers" x-content-sha256 content-type content-length"
	curl_header_args=("${curl_header_args[@]}" -H "$content_sha256_header" -H "$content_type_header" -H "$content_length_header")
	body_arg=(--data-binary @${body})
fi
				
local sig=$(printf '%b' "$signing_string" | \
			openssl dgst -sha256 -sign $privateKeyPath | \
			openssl enc -e -base64 | tr -d '\n')

curl "${extra_args[@]}" "${body_arg[@]}" -X $curl_method -sS https://${host}${escaped_target} "${curl_header_args[@]}" \
	-H "Authorization: Signature version=\"$sigVersion\",keyId=\"$keyId\",algorithm=\"$alg\",headers=\"${headers}\",signature=\"$sig\""
}				
# url encode all special characters except "/", "?", "=", and "&"
function rawurlencode {
  local string="${1}"
  local strlen=${#string}
  local encoded=""
  local pos c o	

  for (( pos=0 ; pos<strlen ; pos++ )); do
	c=${string:$pos:1}
	case "$c" in
		[-_.~a-zA-Z0-9] | "/" | "?" | "=" | "&" ) o="${c}" ;;
		* )               printf -v o '%%%02x' "'$c"
	esac
	encoded+="${o}"
	done

	echo "${encoded}"
}

An example of a request.json file that could be used with the preceding Bash code is shown next:

{
   "compartmentId": "some-compartment-id",
   "displayName": "some-vcn-display-name",
   "cidrBlock": "10.0.0.0/16"
}

PowerShell

The following is an example for creating a request signature for an Oracle Cloud Infrastructure REST API call using a PowerShell script (oci-rest.ps1). The example uses the Bouncy Castle library .dll file to enable crypto functionality. Download the DLL from https://www.bouncycastle.org and place it in the same directory as the PowerShell script file.

View the PowerShell sample in full screen for easier reading.

param (
    [Parameter(Mandatory=$true)][string]$method,
    [Parameter(Mandatory=$true)][string]$ocihost,
    [Parameter(Mandatory=$true)][string]$target,

    [bool]$echo_debug = $false,

    # TODO: Update these defaults or override them on the command line.
    [string]$tenancyId = 'ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq',
    [string]$authUserId= 'ocid1.user.oc1..aaaaaaaalj3z3isgtuqd5uqft424he7r3cuqfr3e5gpidgnmqsxwd5qevkha',
    [string]$keyFingerprint = '29:f3:01:46:07:b8:dc:8c:16:c3:2b:b3:8d:dc:26:c5',
    [string]$privateKeyPath = $PSScriptRoot + '/oci_api_key.pem',

    [Parameter(Mandatory=$false)][string]$body,
    [Parameter(Mandatory=$false)][string]$bouncycastlelib
)

##############################################################################
# This is a powershell example of how to create request signatures for an
# Oracle Cloud Infra REST API call.  It was modeled after the bash example.
#
# Note that it utilizes the Bouncy Castle library dll for crypto functionality.  
# It is assumed to be in the same directory as this script, 
# but can be changed via commandline argument.
# See https://www.bouncycastle.org for more details.
#
# Usage:
# oci-rest.ps1 -host <host> -method <method> -body [file-to-send-as-body] -target <request-target> -bouncycastlelib [BouncyCastle.Crypto.dll]
#
# Examples:
# ./oci-rest.ps1 -method get -ocihost iaas.us-ashburn-1.oraclecloud.com -target "/20160918/instances?compartmentId=ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa" 
# ./oci-rest.ps1 -method post -ocihost iaas.us-ashburn-1.oraclecloud.com -target "/20160918/vcns" -body ./request.json
#
##############################################################################


##############################################################################
# Creates a message digest for the request body.
##############################################################################
function Digest($body_file_path) {
    $sha256digest = New-Object org.bouncycastle.crypto.digests.SHA256Digest
    $content = Get-Item $body_file_path
    $bytes = $null
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        [byte[]]$bytes = Get-Content $body_file_path -AsByteStream
    } else {
        [byte[]]$bytes = Get-Content $body_file_path -Encoding byte
    }
    $sha256digest.BlockUpdate($bytes, 0, $bytes.Length)
    $result_size = $sha256digest.GetDigestSize()
    $result_bytes = New-Object Byte[] $result_size
    $sha256digest.DoFinal($result_bytes, 0) | Out-Null
    $content_sha256 = [Convert]::ToBase64String($result_bytes)
    return $content_sha256
}

##############################################################################
# Creates the signature to be put in the Authorization request header.
##############################################################################
function Sign($signing_string, $privateKeyPath) {
    $sha256digest = New-Object org.bouncycastle.crypto.digests.SHA256Digest  
    $signer = New-Object Org.BouncyCastle.Crypto.Signers.RSADigestSigner $sha256digest 

    $privateKeyFile = [System.IO.File]::OpenText($privateKeyPath)
    $pemReader = New-Object Org.BouncyCastle.OpenSsl.PemReader $privateKeyFile
    $keyPair = [Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair]$pemReader.ReadObject()
    #$keyParameter = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($keyPair.Private)
    $keyParameter = $keyPair.Private
    $signer.Init($true, $keyParameter)

    $encoding = [System.Text.Encoding]::UTF8
    [byte[]]$bytes = $encoding.GetBytes($signing_string)
    $signer.BlockUpdate($bytes, 0, $bytes.Length)
    $signature = $signer.GenerateSignature()
    $signedString = [Convert]::ToBase64String($signature)

    return $signedString
}

##############################################################################
# Makes the Oracle Cloud API REST call.
##############################################################################
function RestCall($method, $ocihost, $target, $privateKeyPath, $keyId, 
                  $body_file_path='', $echo_debug=$false) {
    $alg = 'rsa-sha256'
    $sigVersion = '1'
    $now = Get-Date
    $now = $now.ToUniversalTime()
    $now_string = $now.ToString("ddd, dd MMM yyyy HH:mm:ss") + " GMT"
    
	$content_type = ''
	$content_length = 0
	$content_sha256 = ''
    $request_method = $method.ToLower()
    If ($request_method -eq "get") {
        $method = "Get"
    } ElseIf ($request_method -eq "delete") {
        $method = "Delete"
    } ElseIf ($request_method -eq "head") {
        $method = "Head"
    } ElseIf ($request_method -eq "post") {
        if ($body_file_path.Length -eq 0) {
            echo "body parameter must be specified and point to valid json body file."
            Exit 1
        }
        $method = "Post"
		$content_type = 'application/json'
		$content_length = (Get-Item $body_file_path).length
        $content_sha256 = Digest $body_file_path
        if ($echo_debug) {
            output_debug "digest=$content_sha256"
        }
    } ElseIf ($request_method -eq "put") {
        if ($body_file_path.Length -eq 0) {
            echo "body parameter must be specified and point to valid json body file."
            Exit 1
        }
        $method = "Put"
		$content_type = 'application/json'
		$content_length = (Get-Item $body_file_path).length
        $content_sha256 = Digest $body_file_path
        if ($echo_debug) {
            output_debug "digest=$content_sha256"
        }
    } Else {
        echo "invalid method"
        Exit 1
	}

    $escaped_target = rawurlencode $target
    $request_target = $request_method + " " + $escaped_target
    if ($echo_debug) {
        output_debug "escaped target=$escaped_target"
    }

    $headers = @{}
    $header_list = "(request-target) date host"
    $headers["date"] = $now_string
    $headers["host"] = $ocihost
    
    #$nl = [Environment]::NewLine   # This doesn't work in windows environments
    $nl = "`n"
	$signing_string = "(request-target): " + $request_target + $nl + "date: " + $now_string + $nl + "host: " + $ocihost

    if (($request_method -eq "put") -or ($request_method -eq "post")) {
		$headers["x-content-sha256"] = $content_sha256
        $headers["content-type"] = $content_type
        $headers["content-length"] = $content_length
		$header_list = $header_list + " x-content-sha256 content-type content-length"
        $signing_string = $signing_string + $nl + "x-content-sha256: " + $content_sha256 + $nl + 
                          "content-type: " + $content_type + $nl + "content-length: " + $content_length
	}

    if ($echo_debug) {
        output_debug "signing string=$signing_string"
    }
    $sig = Sign $signing_string $privateKeyPath
    if ($echo_debug) {
        output_debug "signature=$sig"
    }
    $authorization = 'Signature version="' + $sigVersion + '",keyId="' + $keyId + '",algorithm="' + 
                     $alg + '",headers="' + $header_list + '",signature="' + $sig + '"'
    $headers["Authorization"] = $authorization

    $url = "https://" + $ocihost + $escaped_target    
    if ($echo_debug) {
        output_debug "authorization=$authorization"
        output_debug "url=$url"
        output_debug "headers=$headers"
        $headers.getenumerator() | Out-String
    }

    # Without this setting, Invoke-RestMethod was failing on windows with a connection error.
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

	if ($body_file_path.Length -gt 0) {
        PostPutRequest $method $url $headers $body_file_path
	} else {
        Invoke-RestMethod -Method $method -Uri $url -Headers $headers | ConvertTo-Json
	}	
}

##############################################################################
# Makes a Post or Put call.
# We do this b/c Invoke-RestMethod doesn't seem to give the granular control
# needed, but HttpWebRequest works well.
##############################################################################
Function PostPutRequest($method, $url, $headers, $body_file_path) {
    $junk = [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
	$request = [System.Net.HttpWebRequest]::Create($url)
    $request.Method = $method.ToUpper()
    $request.Accept = "application/json";
	$request.ProtocolVersion = "1.0"
    $request.ContentType = $headers["content-type"]
    $request.ContentLength = $headers["content-length"]
    $request.Date = $headers["date"]
    $request.Host = $headers["host"]
    $request.Headers["x-content-sha256"] = $headers["x-content-sha256"]
    $request.Headers["authorization"] = $headers["authorization"]
    #$request.Headers["(request-target)"] = $headers["(request-target)"]

    # Create the input stream to the REST API
	$requestInputStream = $request.GetRequestStream()

	# Create a stream writer to write the json
	$writer = New-Object System.IO.StreamWriter($requestInputStream)
	$writer.AutoFlush = $true

	# Write the json
	Try {
        $bytes = $null
        if ($PSVersionTable.PSVersion.Major -ge 6) {
            [byte[]]$bytes = Get-Content $body_file_path -AsByteStream
        } else {
            [byte[]]$bytes = Get-Content $body_file_path -Encoding byte
        }
		$writer.Write($bytes, 0, $bytes.Length)
	} Catch [System.IO.IOException] {
		Throw "Cannot write to stream. Exception $($_.Exception)"
	} Catch [System.Exception] {
		Throw "Some other weird error caught...$($_.Exception)"
	} Finally {
		$writer.Close()
	}
	Get-WebResponseOutput $request
}

##############################################################################
# Gets the response output for a request.
##############################################################################
Function Get-WebResponseOutput($request) {
	$junk = [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
    
    $response = $null
	Try {
		$response = $request.GetResponse()
	} Catch [System.Net.WebException] {
        echo "Exception from server: " $_.Exception.Message
        $ex = $_.Exception.Response.StatusCode
        if ($response -ne $null) {
            $response.Close()
        }
		Throw "Exception from server: $ex"
	} Catch [System.Exception] {
        if ($response -ne $null) {
            $response.Close()
        }
        echo "Some other random error: " $_.Exception.Message
		Throw "Some other random error..$($_.Exception)"
	}
	
	$readStream = $response.GetResponseStream()
	$reader = New-Object System.IO.StreamReader($readStream)
	
	Try {
		$output = $reader.readtoend()
	} Catch {
        echo "Exception reading stream from server.  Exception: " $_.Exception.Message
		Throw "Exception reading stream from server.  Exception: $($_.Exception)"
	} Finally {
        if ($response -ne $null) {
            $response.Close()
        }
		$reader.Close()
	}
	return $output
}


##############################################################################
# url encode all special characters except "/", "?", "=", and "&"
# This will url encode all special characters in the request target except "/", "?", "=", and "&", 
# since those characters are used in the request target to indicate path and query string structure. 
# If you need to encode any of "/", "?", "=", or "&", such as, when used 
# as part of a path value or query string key or value, 
# you will need to do that yourself in the request target you pass in.
##############################################################################
function rawurlencode($target) {
	Add-Type -AssemblyName System.Web
	$chars_to_skip = "-_.~abcdefghijklmnopqrstuvwxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/?=&";
    $encoded = ""
	for ($i=0; $i -lt $target.Length; $i++) {
		$ch = $target[$i]
		$o = $ch
		if ($chars_to_skip.IndexOf($ch) -lt 0) {
            $o = [System.Web.HttpUtility]::UrlEncode($ch)
		}
		$encoded = $encoded + $o
    }
    return $encoded
}


##############################################################################
# Trivial function to output debug messages to console.
##############################################################################
function output_debug($msg) {
    echo "[debug] $msg"
    #Write-Verbose $msg
}

##############################################################################
# Main entry point logic.
##############################################################################
if ($bouncycastlelib.Length -eq 0) {
    $bouncycastlelib = $PSScriptRoot + "/BouncyCastle.Crypto.dll"
}
if ($echo_debug) {
    [Reflection.Assembly]::LoadFile($bouncycastlelib)
    output_debug "Bouncy Castle loaded and ready"
} else {
    [Reflection.Assembly]::LoadFile($bouncycastlelib) | Out-Null
}    

$keyId = $tenancyId + "/" + $authUserId + "/" + $keyFingerprint
if ($echo_debug) {
    output_debug "keyId=$keyId"
}

RestCall $method $ocihost $target $privateKeyPath $keyId $body $echo_debug

Following is an example of a request.json file that you can use with the preceding PowerShell code:

{
    "compartmentId": "some-compartment-id",
    "displayName": "some-vcn-display-name",
    "cidrBlock": "10.0.0.0/16"
}

C#

View the C# sample in full screen for easier reading.


// Version 1.0.1
namespace Oracle.Oci
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography;
    using System.Text;

    //
    //  Nuget Package Manager Console: Install-Package BouncyCastle
    //  Nuget CLI: nuget install BouncyCastle
    //
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.OpenSsl;
    using Org.BouncyCastle.Security;

    public class Signing
    {
        public static void Main(string[] args)
        {
            var tenancyId = "ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq";
            var compartmentId = "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa";
            var userId = "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq";
            var fingerprint = "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34";
            var privateKeyPath = "private.pem";
            var privateKeyPassphrase = "password";

            var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

            // Oracle Cloud Infrastructure APIs require TLS 1.2
            // uncomment the line below if targeting < .NET Framework 4.6 (HttpWebRequest does not enable TLS 1.2 by default in earlier versions)
            // ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            // GET with query parameters (gets user)
            var uri = new Uri($"https://identity.us-phoenix-1.oraclecloud.com/20160918/users/{userId}");
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Accept = "application/json";

            signer.SignRequest(request);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            ExecuteRequest(request);

            // POST with body (creates a VCN)
            uri = new Uri($"https://iaas.us-phoenix-1.oraclecloud.com/20160918/vcns");
            var body = string.Format(@"{{""cidrBlock"" : ""10.0.0.0/16"",""compartmentId"" : ""{0}"",""displayName"" : ""MyVcn""}}", compartmentId);
            var bytes = Encoding.UTF8.GetBytes(body);

            request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.Accept = "application/json";
            request.ContentType = "application/json";
            request.Headers["x-content-sha256"] = Convert.ToBase64String(SHA256.Create().ComputeHash(bytes));

            using (var stream = request.GetRequestStream())
            {
                stream.Write(bytes, 0, bytes.Length);
            }

            signer.SignRequest(request);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            ExecuteRequest(request);



            // GET with query parameters (gets namespace)
            uri = new Uri($"https://objectstorage.us-phoenix-1.oraclecloud.com/n/");
            request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Accept = "application/json";

            signer.SignRequest(request);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            string namespaceName = ExecuteRequest(request);

            namespaceName = JsonConvert.DeserializeObject<string>(namespaceName);

            // POST with body (creates a bucket)
            uri = new Uri($"https://objectstorage.us-phoenix-1.oraclecloud.com/n/{namespaceName}/b/" );
            body = string.Format(@"{{""name"" : ""bucket01"",""compartmentId"" : ""{0}"",""publicAccessType"" : ""ObjectRead""}}", compartmentId);
            bytes = Encoding.UTF8.GetBytes(body);

            request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.Accept = "application/json";
            request.ContentType = "application/json";
            request.Headers["x-content-sha256"] = Convert.ToBase64String(SHA256.Create().ComputeHash(bytes));

            using (var stream = request.GetRequestStream())
            {
                stream.Write(bytes, 0, bytes.Length);
            }

            signer.SignRequest(request);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            ExecuteRequest(request);


            // PUT with body (puts a object)
            uri = new Uri($"https://objectstorage.us-phoenix-1.oraclecloud.com/n/{namespaceName}/b/bucket01/o/object01");
            body = "Hello Object Storage Service!!!";
            bytes = Encoding.UTF8.GetBytes(body);

            request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "PUT";
            request.Accept = "application/json";
            request.ContentType = "application/json";

            using (var stream = request.GetRequestStream())
            {
                stream.Write(bytes, 0, bytes.Length);
            }

            signer.SignRequest(request, true);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            ExecuteRequest(request);

            // POST with body (create multipart upload)
            uri = new Uri($"https://objectstorage.us-phoenix-1.oraclecloud.com/n/{namespaceName}/b/bucket01/u");
            body = "{\"object\" : \"object02\"}";
            bytes = Encoding.UTF8.GetBytes(body);

            request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.Accept = "application/json";
            request.ContentType = "application/json";
            request.Headers["x-content-sha256"] = Convert.ToBase64String(SHA256.Create().ComputeHash(bytes));

            using (var stream = request.GetRequestStream())
            {
                stream.Write(bytes, 0, bytes.Length);
            }

            signer.SignRequest(request);

            Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

            ExecuteRequest(request);

            Console.ReadKey();
        }

        private static string ExecuteRequest(HttpWebRequest request)
        {
            try
            {
                var webResponse = (HttpWebResponse)request.GetResponse();
                var response = new StreamReader(webResponse.GetResponseStream()).ReadToEnd();
                Console.WriteLine($"Response: {response}");

                return response;
            }
            catch (WebException e)
            {
                Console.WriteLine($"Exception occurred: {e.Message}");
                Console.WriteLine($"Response: {new StreamReader(e.Response.GetResponseStream()).ReadToEnd()}");

                return String.Empty;
            }
        }

        public class RequestSigner
        {
            private static readonly IDictionary<string, List<string>> RequiredHeaders = new Dictionary<string, List<string>>
            {
                { "GET", new List<string>{"date", "(request-target)", "host" }},
                { "HEAD", new List<string>{"date", "(request-target)", "host" }},
                { "DELETE", new List<string>{"date", "(request-target)", "host" }},
                { "PUT", new List<string>{"date", "(request-target)", "host", "content-length", "content-type", "x-content-sha256" }},
                { "POST", new List<string>{"date", "(request-target)", "host", "content-length", "content-type", "x-content-sha256" }},
                { "PUT-LESS", new List<string>{"date", "(request-target)", "host" }}
            };

            private readonly string keyId;
            private readonly ISigner signer;

            /// <summary>
            /// Adds the necessary authorization header for signed requests to Oracle Cloud Infrastructure services.
            /// Documentation for request signatures can be found here: https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/signingrequests.htm
            /// </summary>
            /// <param name="tenancyId">The tenancy OCID</param>
            /// <param name="userId">The user OCID</param>
            /// <param name="fingerprint">The fingerprint corresponding to the provided key</param>
            /// <param name="privateKeyPath">Path to a PEM file containing a private key</param>
            /// <param name="privateKeyPassphrase">An optional passphrase for the private key</param>
            public RequestSigner(string tenancyId, string userId, string fingerprint, string privateKeyPath, string privateKeyPassphrase="")
            {
                // This is the keyId for a key uploaded through the console
                this.keyId = $"{tenancyId}/{userId}/{fingerprint}";

                AsymmetricCipherKeyPair keyPair;
                using (var fileStream = File.OpenText(privateKeyPath))
                {
                    try {
                        keyPair = (AsymmetricCipherKeyPair)new PemReader(fileStream, new Password(privateKeyPassphrase.ToCharArray())).ReadObject();
                    }
                    catch (InvalidCipherTextException) {
                        throw new ArgumentException("Incorrect passphrase for private key");
                    }
                }

                RsaKeyParameters privateKeyParams = (RsaKeyParameters)keyPair.Private;
                this.signer = SignerUtilities.GetSigner("SHA-256withRSA");
                this.signer.Init(true, privateKeyParams);
            }

            public void SignRequest(HttpWebRequest request, bool useLessHeadersForPut = false)
            {
                if (request == null) { throw new ArgumentNullException(nameof(request)); }

                // By default, request.Date is DateTime.MinValue, so override to DateTime.UtcNow, but preserve the value if caller has already set the Date
                if (request.Date == DateTime.MinValue) { request.Date = DateTime.UtcNow; }

                var requestMethodUpper = request.Method.ToUpperInvariant();
                var requestMethodKey = useLessHeadersForPut ? requestMethodUpper + "-LESS" : requestMethodUpper;

                List<string> headers;
                if (!RequiredHeaders.TryGetValue(requestMethodKey, out headers)) {
                    throw new ArgumentException($"Don't know how to sign method: {request.Method}");
                }

                // for PUT and POST, if the body is empty we still must explicitly set content-length = 0 and x-content-sha256
                // the caller may already do this, but we shouldn't require it since we can determine it here
                if (request.ContentLength <= 0 && (string.Equals(requestMethodUpper, "POST") || string.Equals(requestMethodUpper, "PUT")))
                {
                    request.ContentLength = 0;
                    request.Headers["x-content-sha256"] = Convert.ToBase64String(SHA256.Create().ComputeHash(new byte[0]));
                }

                var signingStringBuilder = new StringBuilder();
                var newline = string.Empty;
                foreach (var headerName in headers)
                {
                    string value = null;
                    switch (headerName)
                    {
                        case "(request-target)":
                            value = buildRequestTarget(request);
                            break;
                        case "host":
                            value = request.Host;
                            break;
                        case "content-length":
                            value = request.ContentLength.ToString();
                            break;
                        default:
                            value = request.Headers[headerName];
                            break;
                    }

                    if (value == null) { throw new ArgumentException($"Request did not contain required header: {headerName}"); }
                    signingStringBuilder.Append(newline).Append($"{headerName}: {value}");
                    newline = "\n";
                }

                // generate signature using the private key
                var bytes = Encoding.UTF8.GetBytes(signingStringBuilder.ToString());
                this.signer.BlockUpdate(bytes, 0, bytes.Length);
                var signature = Convert.ToBase64String(this.signer.GenerateSignature());
                var authorization = $@"Signature version=""1"",headers=""{string.Join(" ", headers)}"",keyId=""{keyId}"",algorithm=""rsa-sha256"",signature=""{signature}""";
                request.Headers["authorization"] = authorization;
            }

            private static string buildRequestTarget(HttpWebRequest request)
            {
                // ex. get /20160918/instances
                return $"{request.Method.ToLowerInvariant()} {request.RequestUri.PathAndQuery}";
            }
        }

        /// <summary>
        /// Implements Bouncy Castle's IPasswordFinder interface to allow opening password protected private keys.
        /// </summary>
        public class Password : IPasswordFinder
        {
            private readonly char[] password;

            public Password(char[] password) { this.password = password; }

            public char[] GetPassword() { return (char[])password.Clone(); }
        }
    }
}

Java

This sample omits the optional version field in the Authorization header.

View the Java sample in full screen for easier reading

/*
* @version 1.0.1
*
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
</dependency>
 */
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
/*
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5</version>
</dependency>
*/
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
/*
<dependency>
    <groupId>org.tomitribe</groupId>
    <artifactId>tomitribe-http-signatures</artifactId>
    <version>1.0</version>
</dependency>
*/
import org.apache.http.entity.StringEntity;
import org.tomitribe.auth.signatures.MissingRequiredHeaderException;
import org.tomitribe.auth.signatures.PEM;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * This example creates a {@link RequestSigner}, then prints out the Authorization header
 * that is inserted into the HttpGet object.
 *
 * <p>
 * apiKey is the identifier for a key uploaded through the console.
 * privateKeyFilename is the location of your private key (that matches the uploaded public key for apiKey).
 * </p>
 *
 * The signed HttpGet request is not executed, since instanceId does not map to a real instance.
 */
public class Signing {
    public static void main(String[] args) throws UnsupportedEncodingException {
        HttpRequestBase request;

        // This is the keyId for a key uploaded through the console
        String apiKey = ("ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/"
                                + "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/"
                                + "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34");
        String privateKeyFilename = "../sample-private-key";
        PrivateKey privateKey = loadPrivateKey(privateKeyFilename);
        RequestSigner signer = new RequestSigner(apiKey, privateKey);

        // GET with query parameters
        String uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances?availabilityDomain=%s&compartmentId=%s&displayName=%s&volumeId=%s";
        uri = String.format(uri,
                "Pjwf%3A%20PHX-AD-1",
                // Older ocid formats included ":" which must be escaped
                "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa".replace(":", "%3A"),
                "TeamXInstances",
                "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q".replace(":", "%3A")
        );

        request = new HttpGet(uri);
        // Uncomment to use a fixed date
        // request.setHeader("Date", "Thu, 05 Jan 2014 21:31:40 GMT");

        signer.signRequest(request);
        System.out.println(uri);
        System.out.println(request.getFirstHeader("Authorization"));


        // POST with body
        uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/volumeAttachments";
        request = new HttpPost(uri);
        // Uncomment to use a fixed date
        // request.setHeader("Date", "Thu, 05 Jan 2014 21:31:40 GMT");
        HttpEntity entity = new StringEntity("{\n" +
                "    \"compartmentId\": \"ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa\",\n" +
                "    \"instanceId\": \"ocid1.instance.oc1.phx.abuw4ljrlsfiqw6vzzxb43vyypt4pkodawglp3wqxjqofakrwvou52gb6s5a\",\n" +
                "    \"volumeId\": \"ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q\"\n" +
                "}");
        ((HttpPost)request).setEntity(entity);

        signer.signRequest(request);
        System.out.println("\n" + uri);
        System.out.println(request.getFirstHeader("Authorization"));

    }

    /**
     * Load a {@link PrivateKey} from a file.
     */
    private static PrivateKey loadPrivateKey(String privateKeyFilename) {
        try (InputStream privateKeyStream = Files.newInputStream(Paths.get(privateKeyFilename))){
            return PEM.readPrivateKey(privateKeyStream);
        } catch (InvalidKeySpecException e) {
                throw new RuntimeException("Invalid format for private key");
        } catch (IOException e) {
            throw new RuntimeException("Failed to load private key");
        }
    }

    /**
     * A light wrapper around https://github.com/tomitribe/http-signatures-java
     */
    public static class RequestSigner {
        private static final SimpleDateFormat DATE_FORMAT;
        private static final String SIGNATURE_ALGORITHM = "rsa-sha256";
        private static final Map<String, List<String>> REQUIRED_HEADERS;
        static {
            DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
            DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
            REQUIRED_HEADERS = ImmutableMap.<String, List<String>>builder()
                    .put("get", ImmutableList.of("date", "(request-target)", "host"))
                    .put("head", ImmutableList.of("date", "(request-target)", "host"))
                    .put("delete", ImmutableList.of("date", "(request-target)", "host"))
                    .put("put", ImmutableList.of("date", "(request-target)", "host", "content-length", "content-type", "x-content-sha256"))
                    .put("post", ImmutableList.of("date", "(request-target)", "host", "content-length", "content-type", "x-content-sha256"))
            .build();
        }
        private final Map<String, Signer> signers;

        /**
         * @param apiKey The identifier for a key uploaded through the console.
         * @param privateKey The private key that matches the uploaded public key for the given apiKey.
         */
        public RequestSigner(String apiKey, Key privateKey) {
            this.signers = REQUIRED_HEADERS
                    .entrySet().stream()
                    .collect(Collectors.toMap(
                            entry -> entry.getKey(),
                            entry -> buildSigner(apiKey, privateKey, entry.getKey())));
        }

        /**
         * Create a {@link Signer} that expects the headers for a given method.
         * @param apiKey The identifier for a key uploaded through the console.
         * @param privateKey The private key that matches the uploaded public key for the given apiKey.
         * @param method HTTP verb for this signer
         * @return
         */
        protected Signer buildSigner(String apiKey, Key privateKey, String method) {
            final Signature signature = new Signature(
                    apiKey, SIGNATURE_ALGORITHM, null, REQUIRED_HEADERS.get(method.toLowerCase()));
            return new Signer(privateKey, signature);
        }

        /**
         * Sign a request, optionally including additional headers in the signature.
         *
         * <ol>
         * <li>If missing, insert the Date header (RFC 2822).</li>
         * <li>If PUT or POST, insert any missing content-type, content-length, x-content-sha256</li>
         * <li>Verify that all headers to be signed are present.</li>
         * <li>Set the request's Authorization header to the computed signature.</li>
         * </ol>
         *
         * @param request The request to sign
         */
        public void signRequest(HttpRequestBase request) {
            final String method = request.getMethod().toLowerCase();
            // nothing to sign for options
            if (method.equals("options")) {
                return;
            }

            final String path = extractPath(request.getURI());

            // supply date if missing
            if (!request.containsHeader("date")) {
                request.addHeader("date", DATE_FORMAT.format(new Date()));
            }

            // supply host if mossing
            if (!request.containsHeader("host")) {
                request.addHeader("host", request.getURI().getHost());
            }

            // supply content-type, content-length, and x-content-sha256 if missing (PUT and POST only)
            if (method.equals("put") || method.equals("post")) {
                if (!request.containsHeader("content-type")) {
                    request.addHeader("content-type", "application/json");
                }
                if (!request.containsHeader("content-length") || !request.containsHeader("x-content-sha256")) {
                    byte[] body = getRequestBody((HttpEntityEnclosingRequestBase) request);
                    if (!request.containsHeader("content-length")) {
                        request.addHeader("content-length", Integer.toString(body.length));
                    }
                    if (!request.containsHeader("x-content-sha256")) {
                        request.addHeader("x-content-sha256", calculateSHA256(body));
                    }
                }
            }

            final Map<String, String> headers = extractHeadersToSign(request);
            final String signature = this.calculateSignature(method, path, headers);
            request.setHeader("Authorization", signature);
        }

        /**
         * Extract path and query string to build the (request-target) pseudo-header.
         * For the URI "http://www.host.com/somePath?example=path" return "/somePath?example=path"
         */
        private static String extractPath(URI uri) {
            String path = uri.getRawPath();
            String query = uri.getRawQuery();
            if (query != null && !query.trim().isEmpty()) {
                path = path + "?" + query;
            }
            return path;
        }

        /**
         * Extract the headers required for signing from a {@link HttpRequestBase}, into a Map
         * that can be passed to {@link RequestSigner#calculateSignature}.
         *
         * <p>
         * Throws if a required header is missing, or if there are multiple values for a single header.
         * </p>
         *
         * @param request The request to extract headers from.
         */
        private static Map<String, String> extractHeadersToSign(HttpRequestBase request) {
            List<String> headersToSign = REQUIRED_HEADERS.get(request.getMethod().toLowerCase());
            if (headersToSign == null) {
                throw new RuntimeException("Don't know how to sign method " + request.getMethod());
            }
            return headersToSign.stream()
                    // (request-target) is a pseudo-header
                    .filter(header -> !header.toLowerCase().equals("(request-target)"))
                    .collect(Collectors.toMap(
                    header -> header,
                    header -> {
                        if (!request.containsHeader(header)) {
                            throw new MissingRequiredHeaderException(header);
                        }
                        if (request.getHeaders(header).length > 1) {
                            throw new RuntimeException(
                                    String.format("Expected one value for header %s", header));
                        }
                        return request.getFirstHeader(header).getValue();
                    }));
        }

        /**
         * Wrapper around {@link Signer#sign}, returns the {@link Signature} as a String.
         *
         * @param method Request method (GET, POST, ...)
         * @param path The path + query string for forming the (request-target) pseudo-header
         * @param headers Headers to include in the signature.
         */
        private String calculateSignature(String method, String path, Map<String, String> headers) {
            Signer signer = this.signers.get(method);
            if (signer == null) {
                throw new RuntimeException("Don't know how to sign method " + method);
            }
            try {
                return signer.sign(method, path, headers).toString();
            } catch (IOException e) {
                throw new RuntimeException("Failed to generate signature", e);
            }
        }

        /**
         * Calculate the Base64-encoded string representing the SHA256 of a request body
         * @param body The request body to hash
         */
        private String calculateSHA256(byte[] body) {
            byte[] hash = Hashing.sha256().hashBytes(body).asBytes();
            return Base64.getEncoder().encodeToString(hash);
        }

        /**
         * Helper to safely extract a request body.  Because an {@link HttpEntity} may not be repeatable,
         * this function ensures the entity is reset after reading.  Null entities are treated as an empty string.
         *
         * @param request A request with a (possibly null) {@link HttpEntity}
         */
        private byte[] getRequestBody(HttpEntityEnclosingRequestBase request) {
            HttpEntity entity = request.getEntity();
            // null body is equivalent to an empty string
            if (entity == null) {
                return "".getBytes(StandardCharsets.UTF_8);
            }
            // May need to replace the request entity after consuming
            boolean consumed = !entity.isRepeatable();
            ByteArrayOutputStream content = new ByteArrayOutputStream();
            try {
                entity.writeTo(content);
            } catch (IOException e) {
                throw new RuntimeException("Failed to copy request body", e);
            }
            // Replace the now-consumed body with a copy of the content stream
            byte[] body = content.toByteArray();
            if (consumed) {
                request.setEntity(new ByteArrayEntity(body));
            }
            return body;
        }
    }
}

NodeJS

View the NodeJS sample in full screen for easier reading


/*
    Version 1.0.1
    Before running this example, install necessary dependencies by running:
    npm install http-signature jssha
*/

var fs = require('fs');
var https = require('https');
var os = require('os');
var httpSignature = require('http-signature');
var jsSHA = require("jssha");


// TODO: update these values to your own
var tenancyId = "ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq";
var authUserId = "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq";
var keyFingerprint = "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34";
var privateKeyPath = "~/.oci/oci_api_key.pem";


var identityDomain = "identity.us-ashburn-1.oraclecloud.com";
var coreServicesDomain = "iaas.us-ashburn-1.oraclecloud.com";


if(privateKeyPath.indexOf("~/") === 0) {
    privateKeyPath = privateKeyPath.replace("~", os.homedir())
}
var privateKey = fs.readFileSync(privateKeyPath, 'ascii');


// signing function as described at https://docs.cloud.oracle.com/Content/API/Concepts/signingrequests.htm
				function sign(request, options) {

    var apiKeyId = options.tenancyId + "/" + options.userId + "/" + options.keyFingerprint;

    var headersToSign = [
        "host",
        "date",
        "(request-target)"
    ];

    var methodsThatRequireExtraHeaders = ["POST", "PUT"];

    if(methodsThatRequireExtraHeaders.indexOf(request.method.toUpperCase()) !== -1) {
        options.body = options.body || "";

        var shaObj = new jsSHA("SHA-256", "TEXT");
        shaObj.update(options.body);

        request.setHeader("Content-Length", options.body.length);
        request.setHeader("x-content-sha256", shaObj.getHash('B64'));

        headersToSign = headersToSign.concat([
            "content-type",
            "content-length",
            "x-content-sha256"
        ]);
    }

    httpSignature.sign(request, {
        key: options.privateKey,
        keyId: apiKeyId,
        headers: headersToSign
    });

    var newAuthHeaderValue = request.getHeader("Authorization").replace("Signature ", "Signature version=\"1\",");
    request.setHeader("Authorization", newAuthHeaderValue);
}

// generates a function to handle the https.request response object
function handleRequest(callback) {

    return function(response) {
        var responseBody = "";

        response.on('data', function(chunk) {
        responseBody += chunk;
    });

        response.on('end', function() {
            callback(JSON.parse(responseBody));
        });
    }
}

// gets the user with the specified id
function getUser(userId, callback) {

    var options = {
        host: identityDomain,
        path: "/20160918/users/" + encodeURIComponent(userId),
    };

    var request = https.request(options, handleRequest(callback));

    sign(request, {
        privateKey: privateKey,
        keyFingerprint: keyFingerprint,
        tenancyId: tenancyId,
        userId: authUserId
    });

    request.end();
};

// creates a Oracle Cloud Infrastructure VCN in the specified compartment
function createVCN(compartmentId, displayName, cidrBlock, callback) {
    
    var body = JSON.stringify({
        compartmentId: compartmentId,
        displayName: displayName,
        cidrBlock: cidrBlock
    });

    var options = {
        host: coreServicesDomain,
        path: '/20160918/vcns',
        method: 'POST',
        headers: {
            "Content-Type": "application/json",
        }
    };

    var request = https.request(options, handleRequest(callback));

    sign(request, {
        body: body,
        privateKey: privateKey,
        keyFingerprint: keyFingerprint,
        tenancyId: tenancyId,
        userId: authUserId
    });

    request.end(body);
};

// test the above functions
console.log("GET USER:");

getUser(authUserId, function(data) {
    console.log(data);
        
    console.log("\nCREATING VCN:");

    // TODO: replace this with a compartment you have access to
    var compartmentIdToCreateVcnIn = tenancyId;

    createVCN(compartmentIdToCreateVcnIn, "Test-VCN", "10.0.0.0/16", function(data) {
        console.log(data);
    });
});

Perl

This sample omits the optional version field in the Authorization header.

View the Perl sample in full screen for easier reading

#!/usr/bin/perl
# Version 1.0.1
# Need the following:
# Modules - Authen::HTTP::Signature, DateTime, DateTime::Format::HTTP, Mozilla::CA, File::Slurp, LWP::UserAgent, LWP::Protocol::https
# LWP::UserAgent and LWP::Protoco::https >= 6.06
# OpenSSL >= 1.0.1

use strict;
use warnings;

{
   package OCISigner;

   use Authen::HTTP::Signature;
   use Digest::SHA qw(sha256_base64);
   use DateTime;
   use DateTime::Format::HTTP;

   my @generic_headers = (
     'date', '(request-target)', 'host'
   );
   my @body_headers = (
     'content-length', 'content-type', 'x-content-sha256'
   );
   my @all_headers = (@generic_headers, @body_headers);
   my %required_headers = (
     get => \@generic_headers,
     delete => \@generic_headers,
     head => \@generic_headers,
     post => \@all_headers,
     put => \@all_headers
   );

   sub new {   
      my ( $class, $api_key, $private_key) = @_;
      my $self = {
          _api_key => $api_key,
          _private_key  => $private_key
      };
      bless $self, $class;
      return $self;
   }

   sub sign_request {
      my ( $self, $request ) = @_;
      my $verb = lc($request->method);
      my $sign_body = grep(/^$verb$/, ('post', 'put'));
      $self->inject_missing_headers($request, $sign_body);
      my $headers = $required_headers{$verb};

      my $all_auth = Authen::HTTP::Signature->new(
        key => $self->{_private_key},
        request => $request,
        key_id => $self->{_api_key},
        headers => $headers,
      );
      $all_auth->sign();
   }

   sub inject_missing_headers {
      my ( $self, $request, $sign_body ) = @_;
      $request->header('content-type', 'application/json') unless $request->header('content-type');
      $request->header('accept', '*/*') unless $request->header('accept');
      my $class = 'DateTime::Format::HTTP';
      $request->header('date', $class->format_datetime(DateTime->now)) unless $request->header('date');

      $request->header('host', $request->uri->host) unless $request->header('host');
      if ($sign_body) {
        $request->content('') unless $request->content;
        $request->header('content-length', length($request->content)) unless $request->header('content-length');
        $request->header('x-content-sha256', $self->compute_sha256($request->content)) unless $request->header('x-content-sha256');
      }
   }

   sub compute_sha256 {
      my ( $self, $content ) = @_;
      my $digest = sha256_base64($content);
      while (length($digest) % 4) {
         $digest .= '=';
      }
      return $digest;
   }
} # OCISigner

{
   package OCIClient;

   use LWP::UserAgent;
   use Mozilla::CA;

   sub new {
      my ( $class, $api_key, $private_key ) = @_;
      my $ua = LWP::UserAgent->new;
      $ua->ssl_opts(
        verify_hostname => 1,
        SSL_ca_file => Mozilla::CA::SSL_ca_file()
      );
      my $self = {
        _signer => OCISigner->new($api_key, $private_key),
        _ua => $ua
      };
      bless $self, $class;
      return $self;
   }

   sub make_request {
     my ( $self, $request ) = @_;
     print "Sending request\n";
     $self->{_signer}->sign_request($request);

     my $response = $self->{_ua}->request($request);
     if ($response->is_success) {
       my $message = $response->decoded_content;
       print "Received reply: $message\n";
     }
     else {
       print "HTTP GET error code: ", $response->code, "\n";
       print "HTTP GET error message: ", $response->message, "\n";
     }
   }
} # OCIClient

use File::Slurp qw(read_file);

my $api_key = "ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/" .
              "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/" .
              "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34";

my $private_key = read_file('../sample-private-key') or die $!;

my $client = OCIClient->new($api_key, $private_key);

# Uncomment to use a fixed date
#my $fixed_date = 'Thu, 05 Jan 2014 21:31:40 GMT';
my $fixed_date;

# GET with query parameters
# Note: Older ocid formats included ":" which must be escaped
my %query_args = (
   availability_domain => "Pjwf%3A%20PHX-AD-1",
   compartment_id => "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa",
   display_name => "TeamXInstances",
   volume_id => "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q"
);
my $uri = "https://iaas.us-phoenix-1.oraclecloud.com/20160918/instances?availabilityDomain=" .
          $query_args{availability_domain} .
          "&compartmentId=" .
          $query_args{compartment_id} .
          "&displayName=" .
          $query_args{display_name} .
          "&volumeId=" .
          $query_args{volume_id};
my $request = HTTP::Request->new(GET => $uri);
$request->header('date', $fixed_date) if $fixed_date;
$client->make_request($request);

# POST with body
$uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/volumeAttachments";
my $body = q|{
    "compartmentId": "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa",
    "instanceId": "ocid1.instance.oc1.phx.abuw4ljrlsfiqw6vzzxb43vyypt4pkodawglp3wqxjqofakrwvou52gb6s5a",
    "volumeId": "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q"
}|;
$request = HTTP::Request->new(POST => $uri);
$request->header('date', $fixed_date) if $fixed_date;
$request->content($body);
$client->make_request($request);

PHP

View the PHP sample in full screen for easier reading.

<? php

// Version 1.0.0
//
//  Dependencies:
//  - PHP curl extension
//  - Guzzle (composer require guzzlehttp/guzzle)
//
				
require 'vendor/autoload.php';

use Psr\Http\Message\RequestInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Client;
use GuzzleHttp\Middleware;

// TODO: Update these for your tenancy
$tenancy_id = 'ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq';
$user_id = 'ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq';
$thumbprint = '20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34';
$region = 'us-phoenix-1';
$key_location = 'file://private.pem';
$key_passphrase = 'password';

$namespace = 'MyNamespace';
$bucket_name = 'MyBucket';
$file_to_upload = 'myfile.txt';

$key_id = "$tenancy_id/$user_id/$thumbprint";

function sign_string($data, $key_path, $passphrase){
    $pkeyid = openssl_pkey_get_private($key_path, $passphrase);
    if (!$pkeyid) {
       exit('Error reading private key');
	}

	openssl_sign($data, $signature, $pkeyid, OPENSSL_ALGO_SHA256);

	// free the key from memory
	openssl_free_key($pkeyid);

	return base64_encode($signature);
}

function oci_signer_middleware(RequestInterface $request) {
	global $key_id;
	global $key_location;
	global $key_passphrase;

	// headers required for all HTTP verbs
	$headers = "date (request-target) host";

	// example: Thu, 05 Jan 2014 21:31:40 GMT
	$date=gmdate("D, d M Y H:i:s T", time());
	$method = strtolower($request->getMethod());
	$request_target = $request->getRequestTarget();
	$host = $request->getHeader('Host')[0];

	$request = $request->withHeader('Date', $date);

	$signing_string = "date: $date\n(request-target): $method $request_target\nhost: $host";

	// additional required headers for POST and PUT requests
	if ($method == 'post' || $method == 'put') {
	   $content_length = $request->getHeader('Content-Length')[0];

	   // if content length is 0 we still need to explicitly send the Content-Length header
	   if (!$content_length){
	       $content_length = 0;
		   $request = $request->withHeader('Content-Length', 0);
       }

	   $content_type = $request->getHeader('Content-Type')[0];
	   $content_sha256 = base64_encode(hex2bin(hash("sha256", $request->getBody())));

	   $request = $request->withHeader('x-content-sha256', $content_sha256);

	   $headers = $headers . " content-length content-type x-content-sha256";
	   $signing_string = $signing_string . "\ncontent-length: $content_length\ncontent-type: $content_type\nx-content-sha256: $content_sha256";
	}

	echo "Signing string:\n$signing_string".PHP_EOL;

	$signature = sign_string($signing_string, $key_location, $key_passphrase);

	$authorization_header = "Signature version=\"1\",keyId=\"$key_id\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
	$request = $request->withHeader('Authorization', $authorization_header);
    
	echo "\nRequest headers:".PHP_EOL;
	foreach ($request->getHeaders() as $name => $values) {
	   echo $name . ': ' . implode(', ', $values) . "\n";
	}

	return $request;
}

// EXAMPLE REQUESTS
$handler = new CurlHandler();
$stack = HandlerStack::create($handler);

// place signing middleware after prepare-body so it can access Content-Length header
$stack->after('prepare_body', Middleware::mapRequest('oci_signer_middleware'));

$client = new Client([
    'handler' => $stack
]);

// GET current user
echo "************************************".PHP_EOL;
echo "Getting user: $user_id...".PHP_EOL;
echo "************************************".PHP_EOL;
$response = $client->get("https://identity.$region.oraclecloud.com/20160918/users/$user_id");
echo "\nResponse:\n";
echo $response->getStatusCode().PHP_EOL;
echo $response->getBody().PHP_EOL.PHP_EOL;

// Create a VCN
echo "************************************".PHP_EOL;
echo "Creating VCN...".PHP_EOL;
echo "************************************".PHP_EOL;
$body = "{\"cidrBlock\" : \"10.0.0.0/16\",\"compartmentId\" : \"$tenancy_id\",\"displayName\" : \"MyPhpVcn\"}";
$response = $client->post("https://iaas.$region.oraclecloud.com/20160918/vcns", [ "body" => $body, 'headers' => ['Content-Type' => 'application/json']]);
echo "\nResponse:".PHP_EOL;
echo $response->getStatusCode().PHP_EOL;
echo $response->getBody().PHP_EOL.PHP_EOL;

// PUT object with no content
echo "************************************".PHP_EOL;
echo "Putting object 'NewObject'...".PHP_EOL;
echo "************************************".PHP_EOL;
$body = '';
$response = $client->put("https://objectstorage.$region.oraclecloud.com/n/$namespace/b/$bucket_name/o/NewObject", [ "body" => $body, 'headers' => ['Content-Type' => 'application/json']]);
echo "\nResponse:\n";
echo $response->getStatusCode().PHP_EOL;
echo $response->getBody().PHP_EOL;

// PUT object with content
echo "************************************".PHP_EOL;
echo "Putting object 'NewObject2'...".PHP_EOL;
echo "************************************".PHP_EOL;

$file_handle = fopen($file_to_upload, "rb");
$body = "";
while (!feof($file_handle)) {
   $body = $body . fgets($file_handle);
}
fclose($file_handle);

$response = $client->put("https://objectstorage.$region.oraclecloud.com/n/$namespace/b/$bucket_name/o/NewObject2", [ "body" => $body, 'headers' => ['Content-Type' => 'application/octet-stream']]);
echo "\nResponse:\n";
echo $response->getStatusCode().PHP_EOL;
echo $response->getBody().PHP_EOL;
				
?>

Python

This sample omits the optional version field in the Authorization header.

Important

This Python sample code requires TLS 1.2, which is not included with the default Python on Mac OS X.

View the Python sample in full screen for easier reading

import base64
import email.utils
import hashlib

# pip install httpsig_cffi requests six
import httpsig_cffi.sign
import requests
import six
# Version 1.0.1

class SignedRequestAuth(requests.auth.AuthBase):
    """A requests auth instance that can be reused across requests"""
    generic_headers = [
        "date",
        "(request-target)",
        "host"
    ]
    body_headers = [
        "content-length",
        "content-type",
        "x-content-sha256",
    ]
    required_headers = {
        "get": generic_headers,
        "head": generic_headers,
        "delete": generic_headers,
        "put": generic_headers + body_headers,
        "post": generic_headers + body_headers
    }

    def __init__(self, key_id, private_key):
        # Build a httpsig_cffi.requests_auth.HTTPSignatureAuth for each
        # HTTP method's required headers
        self.signers = {}
        for method, headers in six.iteritems(self.required_headers):
            signer = httpsig_cffi.sign.HeaderSigner(
                key_id=key_id, secret=private_key,
                algorithm="rsa-sha256", headers=headers[:])
            use_host = "host" in headers
            self.signers[method] = (signer, use_host)

    def inject_missing_headers(self, request, sign_body):
        # Inject date, content-type, and host if missing
        request.headers.setdefault(
            "date", email.utils.formatdate(usegmt=True))
        request.headers.setdefault("content-type", "application/json")
        request.headers.setdefault(
            "host", six.moves.urllib.parse.urlparse(request.url).netloc)

        # Requests with a body need to send content-type,
        # content-length, and x-content-sha256
        if sign_body:
            body = request.body or ""
            if "x-content-sha256" not in request.headers:
                m = hashlib.sha256(body.encode("utf-8"))
                base64digest = base64.b64encode(m.digest())
                base64string = base64digest.decode("utf-8")
                request.headers["x-content-sha256"] = base64string
            request.headers.setdefault("content-length", len(body))

    def __call__(self, request):
        verb = request.method.lower()
        # nothing to sign for options
        if verb == "options":
            return request
        signer, use_host = self.signers.get(verb, (None, None))
        if signer is None:
            raise ValueError(
                "Don't know how to sign request verb {}".format(verb))

        # Inject body headers for put/post requests, date for all requests
        sign_body = verb in ["put", "post"]
        self.inject_missing_headers(request, sign_body=sign_body)

        if use_host:
            host = six.moves.urllib.parse.urlparse(request.url).netloc
        else:
            host = None

        signed_headers = signer.sign(
            request.headers, host=host,
            method=request.method, path=request.path_url)
        request.headers.update(signed_headers)
        return request


# -----BEGIN RSA PRIVATE KEY-----
# ...
# -----END RSA PRIVATE KEY-----
with open("../sample-private-key") as f:
    private_key = f.read().strip()

# This is the keyId for a key uploaded through the console
api_key = "/".join([
    "ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq",
    "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq",
    "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34"
])

auth = SignedRequestAuth(api_key, private_key)

headers = {
    "content-type": "application/json",
    "date": email.utils.formatdate(usegmt=True),
    # Uncomment to use a fixed date
    # "date": "Thu, 05 Jan 2014 21:31:40 GMT"
}


# GET with query parameters
uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances?availabilityDomain={availability_domain}&compartmentId={compartment_id}&displayName={display_name}&volumeId={volume_id}"
uri = uri.format(
    availability_domain="Pjwf%3A%20PHX-AD-1",
    # Older ocid formats included ":" which must be escaped
    compartment_id="ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa".replace(":", "%3A"),
    display_name="TeamXInstances",
    volume_id="ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q".replace(":", "%3A")
)
response = requests.get(uri, auth=auth, headers=headers)
print(uri)
print(response.request.headers["Authorization"])


# POST with body
uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/volumeAttachments"
body = """{
    "compartmentId": "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa",
    "instanceId": "ocid1.instance.oc1.phx.abuw4ljrlsfiqw6vzzxb43vyypt4pkodawglp3wqxjqofakrwvou52gb6s5a",
    "volumeId": "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q"
}"""
response = requests.post(uri, auth=auth, headers=headers, data=body)
print("\n" + uri)
print(response.request.headers["Authorization"])

Ruby

View the Ruby sample in full screen for easier reading

require 'base64'
require 'digest'
require 'openssl'
require 'time'
require 'uri'

# gem 'httparty', '~> 0.13.0'
require 'httparty'

# Version 1.0.1
class Client
    include HTTParty
    attr_reader :signer

    def initialize(key_id, private_key)
        @signer = Signer.new(key_id, private_key)
    end

    # nothing to sign for :options

    [:get, :head, :delete].each do |method|
        define_method(method) do |uri, headers: {}|
            self.signer.sign(method, uri, headers, body: nil)
            self.class.send(method, uri, :headers => headers)
        end
    end

    [:put, :post].each do |method|
        define_method(method) do |uri, headers: {}, body: ""|
            self.signer.sign(method, uri, headers, body)
            self.class.send(method, uri, :headers => headers, :body => body)
        end
    end
end


class Signer
    class << self
        attr_reader :headers
    end

    attr_reader :key_id, :private_key

    generic_headers = [:"date", :"(request-target)", :"host"]
    body_headers = [
        :"content-length", :"content-type", :"x-content-sha256"]
    @headers = {
        get: generic_headers,
        head: generic_headers,
        delete: generic_headers,
        put: generic_headers + body_headers,
        post: generic_headers + body_headers
    }

    def initialize(key_id, private_key)
        @key_id = key_id
        @private_key = private_key
    end

    def sign(method, uri, headers, body)
        uri = URI(uri)
        path = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}"
        self.inject_missing_headers(headers, method, body, uri)
        signature = self.compute_signature(headers, method, path)
        unless signature.nil?
            self.inject_authorization_header(headers, method, signature)
        end
    end

    def inject_missing_headers(headers, method, body, uri)
        headers["content-type"] ||= "application/json"
        headers["date"] ||= Time.now.utc.httpdate
        headers["accept"] ||= "*/*"
        headers["host"] ||= uri.host
        if method == :put or method == :post
            body ||= ""
            headers["content-length"] ||= body.length.to_s
            headers["x-content-sha256"] ||= Digest::SHA256.base64digest(body)
        end
    end

    def inject_authorization_header(headers, method, signature)
        signed_headers = self.class.headers[method].map(&:to_s).join(" ")
        headers["authorization"] = [
            %(Signature version="1"),
            %(headers="#{signed_headers}"),
            %(keyId="#{self.key_id}"),
            %(algorithm="rsa-sha256"),
            %(signature="#{signature}")
        ].join(",")
    end

    def compute_signature(headers, method, path)
        return if self.class.headers[method].empty?
        signing_string = self.class.headers[method].map do |header|
            if header == :"(request-target)"
                "#{header}: #{method.downcase} #{path}"
            else
                "#{header}: #{headers[header.to_s]}"
            end
        end.join("\n")
        signature = self.private_key.sign(
            OpenSSL::Digest::SHA256.new,
            signing_string.encode("ascii"))
        Base64.strict_encode64(signature)
    end
end


api_key = [
    "ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq",
    "ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq",
    "20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34"
].join("/")
private_key = OpenSSL::PKey::RSA.new(File.read("../sample-private-key"))
client = Client.new(api_key, private_key)


headers = {
    # Uncomment to use a fixed date
    # "date" => "Thu, 05 Jan 2014 21:31:40 GMT"
}

# GET with query parameters
uri = "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances?availabilityDomain=%{availability_domain}&compartmentId=%{compartment_id}&displayName=%{display_name}&volumeId=%{volume_id}"
uri = uri % {
    :availability_domain => "Pjwf%3A%20PHX-AD-1",
    # Older ocid formats included ":" which must be escaped
    :compartment_id => "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa".sub(":", "%3A"),
    :display_name => "TeamXInstances",
    :volume_id => "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q".sub(":", "%3A")
}
response = client.get(uri, headers: headers)
puts uri
puts response.request.options[:headers]["authorization"]
puts response.response

# POST with body
uri = "https://iaas.us-ashburn.oraclecloud.com/20160918/volumeAttachments"
body = %q({
    "compartmentId": "ocid1.compartment.oc1..aaaaaaaam3we6vgnherjq5q2idnccdflvjsnog7mlr6rtdb25gilchfeyjxa",
    "instanceId": "ocid1.instance.oc1.phx.abuw4ljrlsfiqw6vzzxb43vyypt4pkodawglp3wqxjqofakrwvou52gb6s5a",
    "volumeId": "ocid1.volume.oc1.phx.abyhqljrgvttnlx73nmrwfaux7kcvzfs3s66izvxf2h4lgvyndsdsnoiwr5q"
})
response = client.post(uri, headers: headers, body: body)
puts "\n" + uri
puts response.request.options[:headers]["authorization"]
puts response.response

Go

The following example shows how to create a default signer.

Note

The Go SDK exposes a stand-alone signer that you can use to sign custom requests. You can find related code at http_signer.go.

client := http.Client{}
var request http.Request
request = ... // some custom request

// Set the Date header
request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))

// And a provider of cryptographic keys
provider := common.DefaultConfigProvider()

// Build the signer
signer := common.DefaultSigner(provider)

// Sign the request
signer.Sign(&request)

// Execute the request
client.Do(request)

The following example shows how the signer can allow more granular control on the headers used for signing:

client := http.Client{}
var request http.Request
request = ... // some custom request

// Set the Date header
request.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))

// Mandatory headers to be used in the sign process
defaultGenericHeaders    = []string{"date", "(request-target)", "host"}

// Optional headers
optionalHeaders = []string{"content-length", "content-type", "x-content-sha256"}

// A predicate that specifies when to use the optional signing headers
optionalHeadersPredicate := func (r *http.Request) bool {
	return r.Method == http.MethodPost
}

// And a provider of cryptographic keys
provider := common.DefaultConfigProvider()

// Build the signer
signer := common.RequestSigner(provider, defaultGenericHeaders, optionalHeaders, optionalHeadersPredicate)

// Sign the request
signer.Sign(&request)

// Execute the request
c.Do(request)