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.
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:
- Form the HTTPS request (SSL protocol TLS 1.2 is required).
- Create the signing string, which is based on parts of the request.
- Create the signature from the signing string, using your private key and the RSA-SHA256 algorithm.
- 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.
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:
(request-target)
(as described in draft-cavage-http-signatures-08)host
date
orx-date
(if both are included, Oracle usesx-date
)
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
orx-date
(if both are included, Oracle usesx-date
)x-content-sha256
(except for Object Storage PUT requests; see the next section)content-type
content-length
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
orx-date
(if both are included, Oracle usesx-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.
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..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>
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.
The example signatures use the RSA 2048-bit keys below. Use these keys only for testing your signing code, not for sending production requests.
-----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..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>
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...<unique_ID>
&displayName=TeamXInstances
&volumeId=ocid1.volume.oc1.phx.<unique_ID>
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..<unique_ID>/ocid1.user.oc1..<unique_ID>/<key_fingerprint>,algorithm="rsa-sha256
",signature="<your_signature>"
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..<unique_id>",
"instanceId": "ocid1.instance.oc1.phx.<unique_id>",
"volumeId": "ocid1.volume.oc1.phx.<unique_id>"
}
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..<unique_id>/ocid1.user.oc1.<unique_id>/<your_fingerprint>",
algorithm="rsa-sha256",signature="<your_signature>"
Sample Code
This section shows the basic code for signing API requests.
Java
/**
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl
* or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import com.google.common.net.UrlEscapers;
import com.oracle.bmc.http.signing.RequestSigningFilter;
public class RawRestCallExample {
public static void main(String[] args) throws Exception {
// TODO: fill this out
String instanceId = null;
String configurationFilePath = "~/.oci/config";
String profile = "DEFAULT";
// Pre-Requirement: Allow setting of restricted headers. This is required to allow the SigningFilter
// to set the host header that gets computed during signing of the request.
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
// 1) Create a request signing filter instance
RequestSigningFilter requestSigningFilter =
RequestSigningFilter.fromConfigFile(configurationFilePath, profile);
// 2) Create a Jersey client and register the request signing filter
Client client = ClientBuilder.newBuilder().build().register(requestSigningFilter);
// 3) Target an endpoint. You must ensure that path arguments and query
// params are escaped correctly yourself
WebTarget target =
client.target("https://iaas.us-phoenix-1.oraclecloud.com")
.path("20160918")
.path("instances")
.path(UrlEscapers.urlPathSegmentEscaper().escape(instanceId));
// 4) Set the expected type and invoke the call
Invocation.Builder ib = target.request();
ib.accept(MediaType.APPLICATION_JSON);
Response response = ib.get();
// 5) Print the response headers and the body (JSON) as a string
MultivaluedMap<String, Object> responseHeaders = response.getHeaders();
System.out.println(responseHeaders);
InputStream responseBody = (InputStream) response.getEntity();
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(responseBody, StandardCharsets.UTF_8))) {
StringBuilder jsonBody = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBody.append(line);
}
System.out.println(jsonBody.toString());
}
}
}
Python
This Python sample code requires TLS 1.2, which is not included with the default Python on Mac OS X.
# coding: utf-8
# Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl
# or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
import requests
from oci.config import from_file
from oci.signer import Signer
config = from_file()
auth = Signer(
tenancy=config['tenancy'],
user=config['user'],
fingerprint=config['fingerprint'],
private_key_file_location=config['key_file'],
pass_phrase=config['pass_phrase']
)
endpoint = 'https://identity.us-phoenix-1.oraclecloud.com/20160918/users/'
body = {
'compartmentId': config['tenancy'], # root compartment
'name': 'TestUser',
'description': 'Created with a raw request'
}
response = requests.post(endpoint, json=body, auth=auth)
response.raise_for_status()
print(response.json()['id'])
TypeScript
/**
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl
* or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
*/
import { DefaultRequestSigner, HttpRequest } from "oci-common";
import { provider } from "./authentication";
import * as promise from "es6-promise";
import "isomorphic-fetch";
promise.polyfill();
const userID = "Add User OCID here";
(async () => {
// 1. Create Request Signing instance
const signer = new DefaultRequestSigner(provider);
// 2. Create HttpRequest to be signed
const httpRequest: HttpRequest = {
uri: `https://identity.us-phoenix-1.oraclecloud.com/20160918/users/${userID}`,
headers: new Headers(),
method: "GET"
};
// 3. sign request
await signer.signHttpRequest(httpRequest);
// 4. Make the call
const response = await fetch(
new Request(httpRequest.uri, {
method: httpRequest.method,
headers: httpRequest.headers,
body: httpRequest.body
})
);
// 5. Print response
console.log(await response.json());
})();
JavaScript
/**
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl
* or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
*/
const common = require("oci-common");
const promise = require("es6-promise");
require("isomorphic-fetch");
promise.polyfill();
const configurationFilePath = "~/.oci/config";
const configProfile = "DEFAULT";
const provider = new common.ConfigFileAuthenticationDetailsProvider(
configurationFilePath,
configProfile
);
const userID = "<INSERT_SAMPLE_USER_OCID_HERE>";
(async () => {
// 1. Create Request Signing instance
const signer = new common.DefaultRequestSigner(provider);
// 2. Create HttpRequest to be signed
const httpRequest = {
uri: `https://identity.us-phoenix-1.oraclecloud.com/20160918/users/${userID}`,
headers: new Headers(),
method: "GET"
};
// 3. sign request
await signer.signHttpRequest(httpRequest);
// 4. Make the call
const response = await fetch(
new Request(httpRequest.uri, {
method: httpRequest.method,
headers: httpRequest.headers,
body: httpRequest.body
})
);
// 5. Print response
console.log(await response.json());
})();
Ruby
# Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or
# Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
require 'oci'
require 'net/http'
config = OCI::ConfigFileLoader.load_config(config_file_location:my_config_file_location)
endpoint = OCI::Regions.get_service_endpoint(config.region, :IdentityClient)
uri = URI(endpoint + '/20160918/users/' + config.user)
request = Net::HTTP::Get.new(uri)
signer = OCI::Signer.new(config.user, config.fingerprint, config.tenancy, config.key_file, pass_phrase:my_private_key_pass_phrase)
signer.sign(:get, uri.to_s, request, nil)
result = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) {|http|
http.request(request)
}
puts result.body
Go
The following example shows how to create a default signer.
The SDK for Go exposes a stand-alone signer that you can use to sign custom requests. You can find related code at http_signer.go.
// Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or
// Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
// Example code for sending raw request to Service API
package example
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/oracle/oci-go-sdk/common"
"github.com/oracle/oci-go-sdk/example/helpers"
)
// ExampleRawRequest compose a request, sign it and send to server
func ExampleListUsers_RawRequest() {
// build the url
url := "https://identity.us-phoenix-1.oraclecloud.com/20160918/users/?compartmentId=" + *helpers.RootCompartmentID()
// create request
request, err := http.NewRequest("GET", url, nil)
helpers.FatalIfError(err)
// 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.DefaultRequestSigner(provider)
// Sign the request
signer.Sign(request)
client := http.Client{}
fmt.Println("send request")
// Execute the request
resp, err := client.Do(request)
helpers.FatalIfError(err)
defer resp.Body.Close()
log.Println("response Status:", resp.Status)
log.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
log.Println("response Body:", string(body))
fmt.Println("receive response")
// Output:
// send request
// receive response
}
Bash
View the Bash sample in full screen for easier reading.
#!/bin/bash
# Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
set -e
if [[ -z "$COMPARTMENT_ID" ]];then
echo "COMPARTMENT_ID must be defined in the environment. "
exit 1
fi
USER_NAME="TestUser"
USER_DESCRIPTION="User created by raw request"
TARGET_URI='https://identity.us-phoenix-1.oraclecloud.com/20160918/users/'
HTTP_METHOD='POST'
PROFILE='ADMIN'
REQUEST_BODY="{\"compartmentId\": \"$COMPARTMENT_ID\", \"name\": \"$USER_NAME\", \"description\": \"$USER_DESCRIPTION\"}"
echo "oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id'"
USER_OCID=$(oci raw-request --profile ${PROFILE} --target-uri ${TARGET_URI} --http-method ${HTTP_METHOD} --request-body "${REQUEST_BODY}" | jq -r '.data.id')
echo "Created user OCID: $USER_OCID"
C#
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or
* Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
*/
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Oci.Common.Http.Signing;
namespace Oci.Examples
{
public class RawRestCallExample
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public static async Task MainRaw()
{
var namespaceName = Environment.GetEnvironmentVariable("NAMESPACE_NAME");
var compartmentId = Environment.GetEnvironmentVariable("COMPARTMENT_ID");
var httpClientHandler = OciHttpClientHandler.FromConfigFile("~/.oci/config", "DEFAULT");
var GET_BUCKETS_URL = $"https://objectstorage.us-phoenix-1.oraclecloud.com/n/{namespaceName}/b/?compartmentId={compartmentId}";
var client = new HttpClient(httpClientHandler);
var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(GET_BUCKETS_URL));
var response = await client.SendAsync(requestMessage);
logger.Info($"Is rest call successful: {response.IsSuccessStatusCode}");
var responseJson = await response.Content.ReadAsStringAsync();
logger.Info($"Parsed Response: {responseJson}");
}
}
}