Oracle Cloud Infrastructureドキュメント

シグネチャのリクエスト

このトピックでは、Oracle Cloud Infrastructure APIリクエストに署名する方法について説明します。

署名サンプルは、以下のものに含まれています:

シグネチャ・バージョン1

ここで説明するシグネチャは、Oracle Cloud Infrastructure APIシグネチャの「バージョン1」です。 将来、Oracleがリクエストに署名するメソッドを変更すると、バージョン番号が増え、会社に通知されます。

必須の資格証明とOCID

API署名キーが正しい形式で必要です。 「必要なキーとOCID」を参照してください。

警告

クライアント・クロック・スキュー

クライアント・クロックが5分を超えて歪んだ場合、401 (NotAuthenticated)HTTPステータス・コードが返されます。 これはAPIリクエストに影響します。 詳細は、「許容される最大クライアント・クロック・スキュー」を参照してください。

テナンシとユーザーのOCIDも必要です。 「テナンシのOCIDとユーザーのOCIDの入手場所」を参照してください。

署名ステップの概要

一般的に、これらはリクエストに署名するために必要なステップです:

  1. HTTPSリクエストを作成します(SSLプロトコルTLS 1.2が必要です)。
  2. リクエストの一部に基づいて署名文字列を作成します。
  3. 秘密キーとRSA-SHA256アルゴリズムを使用して、シグネチャ文字列からシグネチャを作成します。
  4. 結果のシグネチャとその他の必要な情報をリクエストのAuthorizationヘッダーに追加します。

これらのステップの詳細については、このトピックの残りのセクションを参照してください。

慣れ親しんでいる必要がある仕様

上記のステップでステップ2〜4を実行する方法については、draft-cavage-http-signatures-08を参照してください。 これは、オラクルがリクエスト・シグネチャを処理する方法の基礎を形成するドラフト仕様です。 一般的に、シグネチャ文字列の作成方法、シグネチャの作成方法、およびシグネチャと必要な情報をリクエストに追加する方法について説明します。 このトピックの残りのセクションでは、それに精通していると仮定しています。 リファレンスのOracle Cloud Infrastructure実装の重要な詳細は、次のセクションにリストされています。

特別実施の詳細

以下のセクションでは、仕様のOracle Cloud Infrastructure実装に関する重要なアイテムについて説明します。

認可ヘッダー

Oracle Cloud Infrastructureシグネチャは、署名HTTPヘッダーではなく、署名認証スキーム(Authorizationヘッダー付き)を使用します。

必須ヘッダー

このセクションでは、署名文字列に含める必要があるヘッダーについて説明します。

ノート

必要なヘッダーが見つからない場合のエラー

必要なヘッダーが見つからない場合、クライアントは401 "Unauthorized"レスポンスを受け取ります。

GETおよびDELETEリクエスト(リクエスト本文にコンテンツがない場合)の場合、署名文字列には少なくとも次のヘッダーが含まれている必要があります:

  • (request-target) (draft-cavage-http-signatures-08で説明されているように)
  • host
  • dateまたはx-date (両方が含まれている場合、Oracleはx-dateを使用します)

PUTリクエストとPOSTリクエスト(リクエスト本文にコンテンツがある場合)の場合、署名文字列には少なくとも次のヘッダーが含まれている必要があります:

  • (request-target)
  • host
  • dateまたはx-date (両方が含まれている場合、Oracleはx-dateを使用します)
  • x-content-sha256 (オブジェクト・ストレージ PUTリクエストを除く、次のセクションを参照)
  • content-type
  • content-length
警告

PUTおよびPOSTリクエストの場合、クライアントはx-content-sha256をコンピュートし、本文が空の文字列であってもリクエストおよび署名文字列にそれを組み込む必要があります。 また、本文が空であっても、リクエストと署名の文字列には常にcontent-lengthが必要です。 一部のHTTPクライアントは、本文が空の場合はcontent-lengthを送信しないため、クライアントが明示的に送信するようにする必要があります。 datex-dateの両方が含まれている場合、Oracleはx-dateを使用します。 x-dateは、リクエストの署名部分の再利用(リプレイ攻撃)から保護するために使用されます。

1つの例外は、オブジェクトに対するオブジェクト・ストレージ PUTリクエストです(次のセクションを参照)。

オブジェクト・ストレージ PUTの特別な手順

オブジェクト・ストレージ PutObjectUploadPart PUTリクエストの場合、署名文字列には少なくとも次のヘッダーが含まれている必要があります:

  • (request-target)
  • host
  • dateまたはx-date (両方が含まれている場合、Oracleはx-dateを使用します)

リクエストにもPUTリクエスト(通常は上記のリストを参照)に必要な他のヘッダーも含まれている場合は、これらのヘッダーも署名文字列に含める必要があります。

ヘッダーのケースと順序

署名文字列のヘッダーはすべて小文字でなければなりません。

署名文字列内のヘッダーの順序は関係ありません。 Authorizationヘッダーのheadersパラメータで順序を指定するようにしてください(draft-cavage-http-signatures-05の説明を参照)。

警告

(request-target)には、リクエストからのパスと問合せ文字列が含まれています。
Oracleは、リクエストに現れる「同じ順序」の問合せパラメータを使用して署名文字列を作成することを想定しています。 署名後にリクエスト問合せのパラメータが変更された場合、認証は失敗します。

パスと問合せ文字列のURLエンコーディング

署名文字列を作成するときは、RFC 3986に従って、パス内のすべてのパラメータと問合せ文字列(ヘッダーは除く)をURLエンコードする必要があります。

キー識別子

リクエストに追加するAuthorizationヘッダーにkeyId="<TENANCY OCID>/<USER OCID>/<KEY FINGERPRINT>"を設定する必要があります。 これらの値を取得するには、「テナンシのOCIDとユーザーのOCIDの入手場所」を参照してください。 keyIdの例は次のようになります(ページに合わせるためにラップされています):

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

署名アルゴリズム

署名アルゴリズムはRSA-SHA256でなければならず、Authorizationヘッダーにalgorithm="rsa-sha256"を設定する必要があります(引用符に注意)。

シグネチャ・バージョン

Authorizationヘッダーにversion="1"を含める必要があります(引用符に注意してください)。 そうしないと、現在のバージョン(現時点ではバージョン1)を使用しているものとみなされます。

ヘッダーの例

Authorizationヘッダーの一般的な構文の例を示します(本文に内容が含まれているリクエストの場合)::

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>))"

テスト値

キー・ペアの例、2つのリクエスト例、それぞれの結果のAuthorizationヘッダーを示します。

警告

サンプルのシグネチャは、以下のRSA 2048ビットのキーを使用します。
これらのキーは、プロダクション・リクエストを送信するためではなく、署名コードのテストにのみ使用してください。

読みやすいように例をフル・スクリーンで表示


-----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="

サンプル・コード

このセクションでは、APIリクエストに署名するための基本的なコードを示します。

Bash

「読みやすいようにBashサンプルをフル・スクリーンで表示」


# 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}"
}

直前のBashコードで使用できるrequest.jsonファイルの例を次に示します:

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

PowerShell

以下は、PowerShellスクリプト(oci-rest.ps1)を使用してOracle Cloud Infrastructure REST API呼び出しのリクエスト・シグネチャを作成する例です。 この例では、Bouncy Castleライブラリの.dllファイルを使用して、暗号化機能を有効にしています。 https://www.bouncycastle.orgからDLLをダウンロードし、PowerShellスクリプト・ファイルと同じディレクトリに置きます。

「読みやすいようにPowerShellサンプルをフル・スクリーンで表示」

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

以下は、上記のPowerShellコードで使用できるrequest.jsonファイルの例です:

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

C#

「読みやすいようにC#サンプルをフル・スクリーンで表示」


// 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.cloud.oracle.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

このサンプルでは、Authorizationヘッダーのオプションのversionフィールドは省略されています。

読みやすいようにJavaサンプルをフル・スクリーンで表示

/*
* @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>httpcore</artifactId>
    <version>4.4.1</version>
</dependency>

(or transitively via org.apache.httpcomponents:httpclient:jar:4.5)
*/
import org.apache.http.HttpEntity;

/*
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5</version>
</dependency>
*/
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

読みやすいようにNodeJSサンプルをフル・スクリーンで表示


/*
    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

このサンプルでは、Authorizationヘッダーのオプションのversionフィールドは省略されています。

読みやすいようにPerlサンプルをフル・スクリーンで表示

#!/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

「読みやすいようにPHPサンプルをフル・スクリーンで表示」

<? 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

このサンプルでは、Authorizationヘッダーのオプションのversionフィールドは省略されています。

重要

このPythonのサンプル・コードにはTLS 1.2が必要ですが、これはMac OS XのデフォルトのPythonには含まれていません。

読みやすいようにPythonサンプルをフル・スクリーンで表示

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

読みやすいようにRubyサンプルをフル・スクリーンで表示

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 SDKは、カスタム・リクエストに署名するために使用できるスタンドアロンの署名者を公開します。
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)

次の例は、署名者が署名に使用されるヘッダーをより詳細に制御できるようにする方法を示しています:

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)