forked from Keyfactor/Keyfactor-CAgent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttpclient.c
More file actions
497 lines (463 loc) · 23.8 KB
/
httpclient.c
File metadata and controls
497 lines (463 loc) · 23.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/******************************************************************************/
/* Copyright 2021 Keyfactor */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain a */
/* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless */
/* required by applicable law or agreed to in writing, software distributed */
/* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES */
/* OR CONDITIONS OF ANY KIND, either express or implied. See the License for */
/* thespecific language governing permissions and limitations under the */
/* License. */
/******************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include <time.h>
#include "logging.h"
#include "httpclient.h"
#include "global.h"
#include "utils.h"
#include "config.h"
#if defined(__TPM__)
#include "agent.h"
#include <tss2/tss2_mu.h>
#include <tss2/tss2_esys.h>
#include <tpm2-tss-engine.h>
#endif
/******************************************************************************/
/***************************** GLOBAL VARIABLES *******************************/
/******************************************************************************/
bool add_client_cert_to_header = false;
/******************************************************************************/
/***************************** LOCAL DEFINES *********************************/
/******************************************************************************/
static const size_t MAX_CERT_SIZE = 4096;
/******************************************************************************/
/************************ LOCAL GLOBAL STRUCTURES *****************************/
/******************************************************************************/
/** */
/* The memory structure used by curl in its callback function */
/* memory holds the curl response data (callback NULL terminates this data) */
/* size holds the size of the data */
/* */
struct MemoryStruct {
char *memory;
size_t size;
};
/******************************************************************************/
/************************** LOCAL GLOBAL VARIABLES ****************************/
/******************************************************************************/
/******************************************************************************/
/************************ LOCAL FUNCTION DEFINITIONS **************************/
/******************************************************************************/
/** */
/* The memory callback function curl uses -- the default is fwrite, so we */
/* want to change that behaviour. */
/* */
/* to send data to this function, execute: */
/* curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback) */
/* */
/* to pass our 'chunk' structure we need this code: */
/* struct MemoryStruct chunk; */
/* curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); */
/* */
/* This function gets called by libcurl as soon as there is data received */
/* that needs to be saved. For most transfers, this callback gets called many*/
/* times and each invoke delivers another chunk of data. */
/* */
/* @param - [Output] contents = The delivered data (NOT NULL TERMINATED) */
/* @param - [N/A] size = 1. This is always one (refers to a byte) */
/* @param - [Input] nmemb = The size of the delivered contents */
/* @param - [Output] userp = */
/* @return - success = the number of bytes taken care of */
/* failure = the number of bytes taken care of */
/* */
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = realloc( mem->memory, (mem->size + realsize + 1) );
if( NULL == mem->memory ) {
log_error("%s::%s(%d): out of memory", LOG_INF);
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = '\0';
return realsize;
} /* WriteMemoryCallback */
/** */
/* Check if a file exists */
/* @param fileName = the filename to look on the filesystem for */
/* @retval true = file exists */
/* @retval false = file isn't there or other error */
/* */
static bool check_file_exists( const char *fileName )
{
FILE *fp;
if ( (fp = fopen( fileName, "r")) )
{
(void)fclose(fp);
return true;
}
else
{
return false;
}
} /* file_exists */
static void stripCR(char string[])
{
static const char CR = '\n';
size_t x, y;
x = 0;
y = 0;
while (('\0' != string[x]) && (MAX_CERT_SIZE > x)) {
if (CR != string[x]) {
string[y] = string[x];
x++;
y++;
} else {
x++;
}
} /* while */
string[y] = '\0';
return;
}
/******************************************************************************/
/*********************** GLOBAL FUNCTION DEFINITIONS **************************/
/******************************************************************************/
/** */
/* Issue an HTTP POST command stating that the content is JSON and that a */
/* JSON response is accepted. */
/* */
/* @param url = a string with the URL address to contact */
/* @param username = a string with the username to log into the URL address */
/* @param password = a string with password to log into the URL address */
/* @param trustStore = a string with a filename containing additional */
/* trusted certificates */
/* @param clientCert = a string with a filename containing a CA signed */
/* cert for this platform (for TLS communication) */
/* @param clientKey = a string with a filename containing the private key */
/* associated with the clientCert */
/* @param clientKeyPass = a string with the password associated with the */
/* clientKey */
/* @param postData = a JSON string */
/* @param pRespData = a pointer to a string where the HTTP response */
/* data gets set. NOTE: This memory gets DYNAMICALLY */
/* allocated here! You need to properly dispose of it in*/
/* the calling function. */
/* @param retryCount = The number of times to try the http session */
/* @param retryInterval = The time (in seconds) between retries */
/* @return 0 on successfull completion */
/* 1-99 corresponding to the failed cURL response code */
/* 255 if the dynamic memory allocation for pRespData fails */
/* 300-511 The HTTP response error (e.g. 404 Not Found) */
/* */
int http_post_json(const char* url, const char* username,
const char* password, const char* trustStore,
const char* clientCert, const char* clientKey,
const char* clientKeyPass, char* postData,
char** pRespData, int retryCount, int retryInterval)
{
log_info("%s::%s(%d) : Preparing to POST to Platform at %s", LOG_INF, url);
bool client_cert_present = false;
unsigned char* client_cert_compressed = NULL;
size_t dummySize = 0;
int toReturn = -1;
log_trace("%s::%s(%d) : Initializing cURL", LOG_INF);
CURL* curl = curl_easy_init();
char errBuff[CURL_ERROR_SIZE];
if(curl) {
log_trace("%s::%s(%d) : Curl initialized ok", LOG_INF);
struct MemoryStruct chunk;
chunk.size = 0;
chunk.memory = calloc(1,sizeof(*chunk.memory));
if ( !chunk.memory ) {
log_error("%s::%s(%d): Out of memory when allocating chunk", LOG_INF);
curl_easy_cleanup(curl);
return CURLE_FAILED_INIT;
}
#if defined(__TPM__)
if (ConfigData->EnrollOnStartup)
{
log_info("%s::%s(%d) : Skipping TPM - enroll on startup is turned on.",
LOG_INF);
goto skipTPM;
}
/**************************************************************************/
/* When a TPM is used, the clientKey is an encrypted BLOB. The BLOB can */
/* only be decoded inside the TPM. Tell cURL that this is the case & */
/* use the engine_id global variable. This is usually the tpm2tss engine*/
/**************************************************************************/
log_verbose("%s::%s(%d) : Setting cURL to use TPM as SSL Engine %s",
LOG_INF, engine_id);
int errNum = curl_easy_setopt(curl, CURLOPT_SSLENGINE, engine_id);
if ( CURLE_OK != errNum )
{
/* When tracing, dump the error buffer to stderr */
if ( is_log_trace() )
{
size_t len = strlen( errBuff );
log_error( "%s::%s-libcurl: (%d) ", LOG_INF, errNum );
if ( len )
{
log_error( "%s::%s(%d) : %s%s", LOG_INF, errBuff,
((errBuff[len-1] != '\n') ? "\n" : ""));
}
else
{
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum) );
}
}
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum));
curl_easy_cleanup(curl);
return errNum;
}
log_verbose("%s::%s(%d) : Setting cURL to use TPM as the default "
"SSL Engine %s", LOG_INF, engine_id);
errNum = curl_easy_setopt(curl, CURLOPT_SSLENGINE_DEFAULT, 1L);
if ( CURLE_OK != errNum )
{
/* When tracing, dump the error buffer to stderr */
if ( is_log_trace() )
{
size_t len = strlen( errBuff );
log_error( "%s::%s-libcurl: (%d) ", LOG_INF, errNum );
if ( len )
{
log_error( "%s::%s(%d) : %s%s", LOG_INF, errBuff,
((errBuff[len-1] != '\n') ? "\n" : ""));
}
else
{
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum) );
}
}
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum));
curl_easy_cleanup(curl);
return errNum;
}
log_verbose("%s::%s(%d) : Setting cURL to have keytype as engine", LOG_INF);
errNum = curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "ENG");
if ( CURLE_OK != errNum ) {
/* When tracing, dump the error buffer to stderr */
if ( is_log_trace() )
{
size_t len = strlen( errBuff );
log_error( "%s::%s-libcurl: (%d) ", LOG_INF, errNum );
if ( len )
{
log_error( "%s::%s(%d) : %s%s", LOG_INF, errBuff,
((errBuff[len-1] != '\n') ? "\n" : ""));
}
else
{
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum) );
}
}
log_error("%s::%s(%d) : %s", LOG_INF, curl_easy_strerror(errNum));
curl_easy_cleanup(curl);
return errNum;
}
skipTPM:
#endif
/**************************************************************************/
/* Set up curl to POST to a url using the username and Password */
/* passed to the function */
/**************************************************************************/
log_trace("%s::%s(%d) : Configuring cURL options", LOG_INF);
(void)curl_easy_setopt(curl, CURLOPT_URL, url);
(void)curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (username && password) {
log_trace("%s::%s(%d) Configuring username and password", LOG_INF);
(void)curl_easy_setopt(curl, CURLOPT_USERNAME, username);
(void)curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
} else {
log_trace("%s::%s(%d) : Username and password not supplied - skipping",
LOG_INF);
}
(void)curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECTION_TIMEOUT);
#ifdef __HTTP_1_1__
/* Some versions of openSSL default to v2.0. If the platform is set for */
/* v1.1, curl will not failover to v1.1. So, force V1.1 */
(void)curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
#endif
/**************************************************************************/
/* If the passed files exist in the system, then use them for additional */
/* trusted certificates, and certs to create the TLS connection. */
/**************************************************************************/
if( 1 == file_exists(trustStore) ) {
log_trace("%s::%s(%d) : Setting trustStore to %s", LOG_INF, trustStore);
(void) curl_easy_setopt(curl, CURLOPT_CAINFO, trustStore);
} else {
log_trace("%s::%s(%d) : Trust store does not exist", LOG_INF);
}
/* Set the cert based on enroll on startup and UseBootStrapCert */
if( ConfigData->EnrollOnStartup ) {
if ( ConfigData->UseBootstrapCert ) {
log_trace("%s::%s(%d) : Attempting to use the BOOTSTRAP cert and key", LOG_INF);
if( 1 == file_exists(ConfigData->BootstrapCert) ) {
log_trace("%s::%s(%d) : Setting clientCert to %s", LOG_INF, ConfigData->BootstrapCert);
(void)curl_easy_setopt(curl, CURLOPT_SSLCERT, ConfigData->BootstrapCert);
read_file_bytes(ConfigData->BootstrapCert, &client_cert_compressed, &dummySize);
if (NULL == client_cert_compressed) {
log_error("%s::%s(%d) : Out of memory copying client certificate", LOG_INF);
goto exit;
}
} else {
log_warn("%s::%s(%d) : The BOOTSTRAP cert was not found at %s", LOG_INF,
ConfigData->BootstrapCert);
}
if( 1 == file_exists(ConfigData->BootstrapKey) ) {
log_trace("%s::%s(%d) : Setting clientKey to %s", LOG_INF, ConfigData->BootstrapKey);
(void)curl_easy_setopt(curl, CURLOPT_SSLKEY, ConfigData->BootstrapKey);
} else {
log_warn("%s::%s(%d) : The BOOTSTRAP key was not found at %s", LOG_INF,
ConfigData->BootstrapKey);
}
if( (1 == file_exists(ConfigData->BootstrapKey)) && ConfigData->BootstrapKeyPassword ) {
log_trace("%s::%s(%d) : Setting clientPassword to %s", LOG_INF,
ConfigData->BootstrapKeyPassword);
(void) curl_easy_setopt(curl, CURLOPT_KEYPASSWD, ConfigData->BootstrapKeyPassword);
}
} else {
log_info("%s::%s(%d) : Bypassing client certificates on initial startup", LOG_INF);
}
} else {
log_trace("%s::%s(%d) : Use the Agent cert and key", LOG_INF);
if( 1 == file_exists(clientCert) ) {
log_trace("%s::%s(%d) : Setting clientCert to %s", LOG_INF, clientCert);
(void)curl_easy_setopt(curl, CURLOPT_SSLCERT, clientCert);
read_file_bytes(clientCert, &client_cert_compressed, &dummySize);
if (NULL == client_cert_compressed) {
log_error("%s::%s(%d) : Out of memory copying client certificate", LOG_INF);
goto exit;
}
} else {
log_warn("%s::%s(%d) : The clientCert does not exist at %s", LOG_INF, clientCert);
}
if( 1 == file_exists(clientKey) ) {
log_trace("%s::%s(%d) : Setting clientKey to %s", LOG_INF, clientKey);
(void)curl_easy_setopt(curl, CURLOPT_SSLKEY, clientKey);
} else {
log_warn("%s::%s(%d) : The clientKey does not exist at %s", LOG_INF, clientKey);
}
if( (1 == file_exists(clientKey)) && clientKeyPass ) {
log_trace("%s::%s(%d) : Setting clientPassword to %s", LOG_INF,clientKeyPass);
(void)curl_easy_setopt(curl, CURLOPT_KEYPASSWD, clientKeyPass);
}
}
/* Turn on verbose output for tracing */
if ( is_log_trace() ) {
log_trace("%s::%s(%d) : Turning on cURL verbose output", LOG_INF);
(void)curl_easy_setopt( curl, CURLOPT_VERBOSE, 1 );
(void)curl_easy_setopt( curl, CURLOPT_ERRORBUFFER, errBuff );
errBuff[0] = 0; /* empty the error buffer */
}
/* send all data to this function */
(void)curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
/* we pass our 'chunk' struct to the callback function */
(void)curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
log_trace("%s::%s(%d) : cURL options set correctly", LOG_INF);
struct curl_slist* list = NULL;
/**************************************************************************/
/* Set up the HTTP header to tell the API this is standard JSON. */
/* NOTE: Some versions of Internet Explorer have a problem using */
/* these headers. */
/* Also, set the content length header option to the data size. */
/* TODO: Error checking, as this is a dynamic memory allocation and any*/
/* on-demand memory allocation needs a verification step. */
/**************************************************************************/
list = curl_slist_append(NULL, "Content-Type: application/json");
list = curl_slist_append(list, "Accept: application/json");
char clBuf[30];
(void)snprintf(clBuf, 30, "Content-Length: %d", (int)strlen(postData));
list = curl_slist_append(list, clBuf);
if ((add_client_cert_to_header) && (NULL != client_cert_compressed)) {
log_debug("%s::%s(%d) : Adding client certificate to %s header", LOG_INF, CLIENT_CERT_HEADER);
char certBuf[MAX_CERT_SIZE];
stripCR(client_cert_compressed);
(void)snprintf(certBuf, MAX_CERT_SIZE, "%s: %s",CLIENT_CERT_HEADER, client_cert_compressed);
list = curl_slist_append(list, certBuf);
} else {
log_debug("%s::%s(%d) : Skipping adding header = %s", LOG_INF, CLIENT_CERT_HEADER);
}
/**************************************************************************/
/* Now add the header & data to the HTTP POST request. */
/**************************************************************************/
(void)curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
(void)curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData);
(void)curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (int)strlen(postData));
log_trace("%s::%s(%d): postData = %s", LOG_INF, postData);
/**************************************************************************/
/* Make sure the cURL operation succeeded and the HTTP response code */
/* indicates success. If we are successfull, place the response message */
/* If the cURL operation fails, return the cURL error code. */
/* If the HTTP response is an error, return the HTTP failure code. */
/**************************************************************************/
long httpCode = 0;
int res = CURLE_FAILED_INIT;
int tries = retryCount;
while (0 < tries) {
res = curl_easy_perform(curl);
(void)curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &httpCode);
/* if there was an error & we still have tries left to do */
tries--;
log_verbose("%s::%s(%d): curl resp = %d, httpCode = %ld, tries left = %d",
LOG_INF,res, httpCode, tries);
if(((CURLE_OK != res) || (httpCode >= 300)) && (0 < tries)) {
log_verbose("%s::%s(%d): Failed curl post. Sleeping %d seconds before retry", LOG_INF,retryInterval);
if ( 0 < retryInterval ) {
(void)sleep((unsigned int)retryInterval);
}
} else {
tries = 0; /* exit the loop */
}
} /* while */
if(res != CURLE_OK) {
/* When tracing, dump the error buffer to stderr */
if ( is_log_trace() ) {
size_t len = strlen( errBuff );
log_error( "%s::%s(%d): libcurl: (%d) ", LOG_INF, res );
if ( 0 != len ) {
log_error( "%s::%s(%d): %s%s", LOG_INF, errBuff,((errBuff[len-1] != '\n') ? "\n" : ""));
} else {
log_error("%s::%s(%d): %s\n", LOG_INF, curl_easy_strerror(res) );
}
}
log_error("%s::%s(%d): %s", LOG_INF, curl_easy_strerror(res));
toReturn = res;
} else if(httpCode >= 300) {
log_error("%s::%s(%d): HTTP Response: %ld", LOG_INF, httpCode);
toReturn = (int)httpCode;
} else {
log_verbose("%s::%s(%d): %lu bytes retrieved -- allocating memory for response",
LOG_INF, (unsigned long)chunk.size);
*pRespData = strdup(chunk.memory);
log_trace("%s::%s(%d): Response is:\n%s", LOG_INF, *pRespData);
if ( NULL == *pRespData ) {
log_error("%s::%s(%d): Out of memory", LOG_INF);
toReturn = 255;
} else {
toReturn = 0;
}
}
exit:
/* Cleanup, de-allocate, etc. */
if (list) {
curl_slist_free_all(list);
}
if (curl) {
curl_easy_cleanup(curl);
}
if (chunk.memory) {
free(chunk.memory);
}
} /* if curl */
return toReturn;
} /* http_post_json */
/******************************************************************************/
/******************************* END OF FILE **********************************/
/******************************************************************************/