OCI REST for ESP32
oci.h
1 #ifndef Oci_h
2 #define Oci_h
3 
4 #include "Arduino.h"
5 #include <esp32-hal-log.h>
6 #include <HTTPClient.h>
7 #include "WiFiClientSecure.h"
8 #include "time.h"
9 #include "mbedtls/timing.h"
10 #include "mbedtls/sha256.h"
11 #include "mbedtls/bignum.h"
12 #include "mbedtls/ctr_drbg.h"
13 #include "mbedtls/rsa.h"
14 #include "mbedtls/pk.h"
15 #include "mbedtls/base64.h"
16 
21 struct header {
22  char* headerName;
23  char* headerValue;
28  header(char* hN) {
29  this->headerName = hN;
30  };
36  header(char* hN, char* hV) {
37  this->headerName = hN;
38  this->headerValue = hV;
39  };
40 };
42 typedef struct header Header;
43 
44 
49 struct ociApiRequest {
50  char* host;
51  char* path;
52  const char* requestMethod;
53  char* endpointCert=NULL;
54  char* content = "";
55  char* contentType="application/json";
58 
70  char* host,
71  char* path,
72  const char* requestMethod,
74  int requestHeaderCount=0,
75  char* endpointCert=NULL,
76  char* content="",
77  char* contentType="application/json"
78  ){
79  this->host = host;
80  this->path = path;
81  this->requestMethod = requestMethod;
82  this->endpointCert = endpointCert;
83  this->content = content;
84  this->contentType = contentType;
85  this->requestHeaders = requestHeaders;
86  this->requestHeaderCount = requestHeaderCount;
87  }
88 };
89 typedef struct ociApiRequest OciApiRequest;
90 
96  String response;
97  int statusCode;
98  String opcRequestId;
99  String errorMsg;
102 
108  this->responseHeaders = responseHeaders;
109  this->responseHeaderCount = responseHeaderCount;
110  };
111 
121  this->response = response;
122  this->statusCode = statusCode;
123  this->opcRequestId = opcRequestId;
124  this->errorMsg = errorMsg;
125  this->responseHeaders = responseHeaders;
126  this->responseHeaderCount = responseHeaderCount;
127  }
128 };
129 typedef struct ociApiResponse OciApiResponse;
130 
135 struct ociProfile {
136  char* tenancyOcid;
137  char* userOcid;
139  char* privateKey;
141 
145 
154  this->tenancyOcid = tenancyOcid;
155  this->userOcid = userOcid;
156  this->keyFingerprint = keyFingerprint;
157  this->privateKey = privateKey;
158  this->privateKeyPassphrase = privateKeyPassphrase;
159  }
160 };
161 typedef struct ociProfile OciProfile;
162 
166 class Oci {
167  public:
168  WiFiClientSecure client;
169 
170  const char* HTTP_METHOD_GET = "GET";
171  const char* HTTP_METHOD_POST = "POST";
172  const char* HTTP_METHOD_PUT = "PUT";
173  const char* HTTP_METHOD_PATCH = "PATCH";
174  const char* HTTP_METHOD_DELETE = "DELETE";
175 
177  char* ntpServer = "pool.ntp.org";
178  long gmtOffset = 0;
179  int daylightOffset = 0;
180 
188  OciProfile profile,
189  char* timeServer="pool.ntp.org",
190  long gmtOffsetSeconds=0,
191  int daylightOffsetSeconds=0
192  ){
193  ociProfile = profile;
194  ntpServer = timeServer;
195  gmtOffset = gmtOffsetSeconds;
196  daylightOffset = daylightOffsetSeconds;
197  configTime((const long) gmtOffset, (const int) daylightOffset, (const char*) ntpServer);
198  }
199 
204  const unsigned char* toEncrypt,
205  unsigned char (&encoded)[500]
206  ) {
207  mbedtls_pk_context pk;
208  mbedtls_pk_init(&pk);
209  char* pwd = NULL;
210  int pwdLen = 0;
213  pwdLen = strlen(pwd);
214  }
215  mbedtls_pk_parse_key(&pk, (const unsigned char*) ociProfile.privateKey, strlen(ociProfile.privateKey)+1, (const unsigned char*) pwd, pwdLen);
216  mbedtls_rsa_context *rsa = mbedtls_pk_rsa(pk);
217 
218  int keyValid = mbedtls_rsa_check_privkey(rsa);
219  unsigned char encryptBuffer[512];
220  if( keyValid == 0 ) {
221  byte hashResult[32];
222  mbedtls_sha256(toEncrypt, strlen((char*) toEncrypt), hashResult, 0);
223  int success = mbedtls_rsa_rsassa_pkcs1_v15_sign( rsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, MBEDTLS_MD_SHA256, strlen((char*) hashResult), hashResult, encryptBuffer );
224  size_t encodedOutLen;
225  mbedtls_base64_encode( encoded, sizeof(encoded), &encodedOutLen, (const unsigned char*) encryptBuffer, sizeof(encryptBuffer) / 2 );
226  }
227  }
228 
232  void apiCall(
233  OciApiRequest request,
234  OciApiResponse &response
235  ) {
236  unsigned char encoded[500];
237  char timestamp[35];
238  boolean putPost = request.requestMethod == HTTP_METHOD_POST || request.requestMethod == HTTP_METHOD_PUT;
239  size_t contentLen = 0;
240  if( request.content != NULL ) contentLen = strlen(request.content);
241  unsigned char contentEncoded[64] = "";
242  char contentLenStr[10] = "0";
243  sprintf(contentLenStr, "%d", contentLen);
244 
245  {
246  char signingString[400] = "";
247  strcat(signingString, "(request-target): ");
248  char meth[10];
249  strcpy(meth, request.requestMethod);
250  strlwr(meth);
251  strcat(signingString, meth);
252  strcat(signingString, " ");
253  strcat(signingString, request.path);
254  strcat(signingString, "\n");
255  struct tm timeinfo;
256  while(!getLocalTime(&timeinfo)){
257  Serial.println("Failed to obtain time");
258  delay(100);
259  }
260  strftime(timestamp, 35, "%a, %d %b %Y %H:%M:%S GMT" , &timeinfo);
261  strcat(signingString, "date: ");
262  strcat(signingString, timestamp);
263  strcat(signingString, "\n");
264 
265  strcat(signingString, "host: ");
266  strcat(signingString, request.host);
267  if( putPost ) {
268  strcat(signingString, "\n");
269  {
270  byte contentHash[32];
271  mbedtls_sha256((const unsigned char*) request.content, strlen((char*) request.content), contentHash, 0);
272  size_t contentEncodedOutLen;
273  mbedtls_base64_encode(contentEncoded, 64, &contentEncodedOutLen, (const unsigned char*) contentHash, sizeof(contentHash));
274  strcat(signingString, "x-content-sha256: ");
275  strcat(signingString, (char*) contentEncoded);
276  strcat(signingString, "\n");
277  }
278  // add content-length
279  strcat(signingString, "content-length: ");
280  strcat(signingString, contentLenStr);
281  strcat(signingString, "\n");
282 
283  // add content-type
284  strcat(signingString, "content-type: ");
285  strcat(signingString, request.contentType);
286  }
287  encryptAndEncode((const unsigned char*) signingString, encoded);
288  }
289 
290  char authHeader[800] = "Signature version=\"1\",headers=\"(request-target) date host";
291  if(putPost) strcat(authHeader, " x-content-sha256 content-length content-type");
292  strcat(authHeader, "\",keyId=\"");
293  strcat(authHeader, ociProfile.tenancyOcid);
294  strcat(authHeader, "/");
295  strcat(authHeader, ociProfile.userOcid);
296  strcat(authHeader, "/");
297  strcat(authHeader, ociProfile.keyFingerprint);
298  strcat(authHeader, "\",algorithm=\"rsa-sha256\",signature=\"");
299  strcat(authHeader, ((char*) encoded));
300  strcat(authHeader, "\"");
301 
302  String url = "https://" + String(request.host) + String(request.path);
303  /*
304  Serial.println("URL");
305  Serial.println(url);
306  Serial.println("Time:");
307  Serial.println(timestamp);
308  Serial.println("Auth Header:");
309  Serial.println(String( (char*) authHeader));
310  Serial.println("Content:");
311  Serial.println(request.content);
312  Serial.println("Content Length:");
313  Serial.println(contentLenStr);
314  Serial.println("Content Encoded:");
315  Serial.println((char*) contentEncoded);
316  Serial.println("Cert:");
317  Serial.println(request.endpointCert);
318  */
319 
320  if(request.endpointCert) {
321  client.setCACert(request.endpointCert);
322  }
323  else {
324  client.setInsecure();
325  }
326  {
327  log_v("Connecting to %s on 443", request.host);
328  if( !client.connect(request.host, 443) ) {
329  Serial.println("Connection failed!");
330  }
331  else {
332  client.println(String(request.requestMethod) + " " + url + " HTTP/1.1");
333  log_v("%s %s HTTP/1.1", request.requestMethod, url.c_str());
334 
335  client.println("date: " + String(timestamp));
336  log_v("date: %s", (char*) timestamp);
337  client.println("Authorization: " + String( (char*) authHeader));
338  log_v("Authorization: %s", (char*) authHeader);
339  client.println("host: " + String(request.host));
340  log_v("Host: %s", (char*) request.host);
341  if(putPost) {
342  client.println("x-content-sha256: " + String((char*) contentEncoded));
343  log_v("x-content-sha256: %s", (char*) contentEncoded);
344  }
345  // user defined headers - add them to the request
346  for (int i=0; i<request.requestHeaderCount; i++) {
347  char* hN = request.requestHeaders[i].headerName;
348  char* hV = request.requestHeaders[i].headerValue;
349  client.println(String(hN) + ": " + String(hV));
350  log_v("%s : %s", hN, hV);
351  }
352  client.println("content-type: " + String(request.contentType));
353  log_v("content-type: %s", request.contentType);
354  client.print("content-length: ");
355  client.println(contentLenStr);
356  log_v("content-length: %s", contentLenStr);
357  client.println("Connection: close");
358  log_v("Connection: close");
359  client.println();
360  if(contentLen > 0) {
361  client.println(request.content);
362  }
363  bool firstLine = true;
364  while (client.connected()) {
365  size_t len = client.available();
366  if(len > 0) {
367  String line = client.readStringUntil('\n');
368 
369  if(firstLine) {
370  firstLine = false;
371  int codePos = line.indexOf(' ') + 1;
372  response.statusCode = line.substring(codePos, line.indexOf(' ', codePos)).toInt();
373  log_v("Set status code to: %d", response.statusCode);
374  }
375  else {
376  char headerStr[250];
377  line.toCharArray(headerStr, sizeof(headerStr));
378 
379  if( response.responseHeaders != NULL ) {
380  char* separator = strchr(headerStr, ':');
381  if (separator != 0){
382  // Actually split the string in 2: replace ':' with 0
383  *separator = 0;
384  char* hN = headerStr;
385  ++separator;
386  char* hV = &separator[1]; // trim the leading space
387  // not ideal... need to refactor this
388  for (int i=0; i<response.responseHeaderCount; i++) {
389  if( strcmp(response.responseHeaders[i].headerName, hN) == 0 ) {
390  log_v("Setting requested response header: %s to value %s", hN, hV);
391  response.responseHeaders[i].headerValue = hV;
392  }
393  }
394  }
395  }
396  log_v("Response Header: %s", line.c_str());
397  if (line == "\r") {
398  log_v("Headers Received");
399  break;
400  }
401  }
402  }
403  }
404  // if there are incoming bytes available
405  // from the server, read them and print them:
406  String clientResponse = "";
407  while (client.available()) {
408  clientResponse.concat(client.readString());
409  }
410  response.response = clientResponse;
411 
412  client.stop();
413  }
414  }
415  }
416 };
417 #endif
The OCI API.
Definition: oci.h:166
Oci(OciProfile profile, char *timeServer="pool.ntp.org", long gmtOffsetSeconds=0, int daylightOffsetSeconds=0)
Construct a new instance of the OCI API.
Definition: oci.h:187
const char * HTTP_METHOD_DELETE
HTTP DELETE.
Definition: oci.h:174
char * ntpServer
The ntpServer.
Definition: oci.h:177
const char * HTTP_METHOD_POST
HTTP POST.
Definition: oci.h:171
const char * HTTP_METHOD_PUT
HTTP PUT.
Definition: oci.h:172
void encryptAndEncode(const unsigned char *toEncrypt, unsigned char(&encoded)[500])
Used internally to hash, sign and base64 encode the auth header to sign the request.
Definition: oci.h:203
OciProfile ociProfile
The OciProfile to use.
Definition: oci.h:176
void apiCall(OciApiRequest request, OciApiResponse &response)
Make a call to the OCI REST API.
Definition: oci.h:232
long gmtOffset
The offset from GMT to use for retrieving time. You shouldn't change this - requests should be signed...
Definition: oci.h:178
const char * HTTP_METHOD_GET
HTTP GET.
Definition: oci.h:170
const char * HTTP_METHOD_PATCH
HTTP PATCH.
Definition: oci.h:173
int daylightOffset
The daylight savings offset. Should not change this either.
Definition: oci.h:179
A Header object is a simple key/value pair.
Definition: oci.h:21
char * headerName
The header name.
Definition: oci.h:22
header(char *hN, char *hV)
Construct a Header with a name and value.
Definition: oci.h:36
header(char *hN)
Construct a Header with just a name.
Definition: oci.h:28
char * headerValue
The header value.
Definition: oci.h:23
A request contains all of the information required to make a call to the OCI API.
Definition: oci.h:49
ociApiRequest(char *host, char *path, const char *requestMethod, Header *requestHeaders={}, int requestHeaderCount=0, char *endpointCert=NULL, char *content="", char *contentType="application/json")
An OCI API request.
Definition: oci.h:69
const char * requestMethod
The HTTP method. See Oci for const values.
Definition: oci.h:52
Header * requestHeaders
An array of Header objects (set both key and value)
Definition: oci.h:56
char * contentType
The content type.
Definition: oci.h:55
char * host
The REST API endpoint host. Ex: objectstorage.us-phoenix-1.oraclecloud.com.
Definition: oci.h:50
char * content
The body to be passed to the API call.
Definition: oci.h:54
int requestHeaderCount
The count of headers in the requestHeaders array.
Definition: oci.h:57
char * endpointCert
The text value of the Root CA cert for the API endpoint. Used to make secure connections....
Definition: oci.h:53
char * path
The path. Ex: "/n/".
Definition: oci.h:51
Contains the response from an API call.
Definition: oci.h:95
ociApiResponse(String response, int statusCode, String opcRequestId, String errorMsg, Header *responseHeaders={}, int responseHeaderCount=0)
Construct a full response object.
Definition: oci.h:120
ociApiResponse(Header *responseHeaders={}, int responseHeaderCount=0)
Construct a response with an array of headers and the count.
Definition: oci.h:107
int responseHeaderCount
The count of headers to collect.
Definition: oci.h:101
String response
The string returned from the API call. Usually JSON.
Definition: oci.h:96
String opcRequestId
The opc-request-id
Definition: oci.h:98
int statusCode
The HTTP status code returned.
Definition: oci.h:97
String errorMsg
Any error message returned from the API call.
Definition: oci.h:99
Header * responseHeaders
An array of headers to collect from the API call.
Definition: oci.h:100
Contains the OCIDs and key & key related data necessary to sign the request.
Definition: oci.h:135
char * privateKeyPassphrase
Optional - the private key password (for protected keys)
Definition: oci.h:140
ociProfile()
Construct an instance of OciProfile.
Definition: oci.h:144
char * privateKey
The private key text. must be null terminated! (have a newline at the end of the text)
Definition: oci.h:139
char * tenancyOcid
The tenancy OCID.
Definition: oci.h:136
char * keyFingerprint
The key fingerprint.
Definition: oci.h:138
ociProfile(char *tenancyOcid, char *userOcid, char *keyFingerprint, char *privateKey, char *privateKeyPassphrase=NULL)
Construct an instance of OciProfile.
Definition: oci.h:153
char * userOcid
The user OCID.
Definition: oci.h:137