@cryptotaxi247 / netdata-1 / commits / 5c5cf7ea4

Cleanup Challenge Response Code (#11730)

- Use OpenSSL for Base64 encode/decode - general cleanup of code

Timotej S committed May 27, 2022 at 11:09 UTC 5c5cf7ea4a621657cfc4bcbd302c9fda7098249d
1 file changed +187 -215
aclk/aclk_otp.c
+187 -215
@@ -9,164 +9,6 @@
9
10 #include "mqtt_websockets/c-rbuf/include/ringbuffer.h"
11
12 -struct dictionary_singleton {
13 - char *key;
14 - char *result;
15 -};
16 -
17 -static int json_extract_singleton(JSON_ENTRY *e)
18 -{
19 - struct dictionary_singleton *data = e->callback_data;
20 -
21 - switch (e->type) {
22 - case JSON_OBJECT:
23 - case JSON_ARRAY:
24 - break;
25 - case JSON_STRING:
26 - if (!strcmp(e->name, data->key)) {
27 - data->result = strdupz(e->data.string);
28 - break;
29 - }
30 - break;
31 - case JSON_NUMBER:
32 - case JSON_BOOLEAN:
33 - case JSON_NULL:
34 - break;
35 - }
36 - return 0;
37 -}
38 -
39 -// Base-64 decoder.
40 -// Note: This is non-validating, invalid input will be decoded without an error.
41 -// Challenges are packed into json strings so we don't skip newlines.
42 -// Size errors (i.e. invalid input size or insufficient output space) are caught.
43 -static size_t base64_decode(unsigned char *input, size_t input_size, unsigned char *output, size_t output_size)
44 -{
45 - static char lookup[256];
46 - static int first_time=1;
47 - if (first_time)
48 - {
49 - first_time = 0;
50 - for(int i=0; i<256; i++)
51 - lookup[i] = -1;
52 - for(int i='A'; i<='Z'; i++)
53 - lookup[i] = i-'A';
54 - for(int i='a'; i<='z'; i++)
55 - lookup[i] = i-'a' + 26;
56 - for(int i='0'; i<='9'; i++)
57 - lookup[i] = i-'0' + 52;
58 - lookup['+'] = 62;
59 - lookup['/'] = 63;
60 - }
61 - if ((input_size & 3) != 0)
62 - {
63 - error("Can't decode base-64 input length %zu", input_size);
64 - return 0;
65 - }
66 - size_t unpadded_size = (input_size/4) * 3;
67 - if ( unpadded_size > output_size )
68 - {
69 - error("Output buffer size %zu is too small to decode %zu into", output_size, input_size);
70 - return 0;
71 - }
72 - // Don't check padding within full quantums
73 - for (size_t i = 0 ; i < input_size-4 ; i+=4 )
74 - {
75 - uint32_t value = (lookup[input[0]] << 18) + (lookup[input[1]] << 12) + (lookup[input[2]] << 6) + lookup[input[3]];
76 - output[0] = value >> 16;
77 - output[1] = value >> 8;
78 - output[2] = value;
79 - //error("Decoded %c %c %c %c -> %02x %02x %02x", input[0], input[1], input[2], input[3], output[0], output[1], output[2]);
80 - output += 3;
81 - input += 4;
82 - }
83 - // Handle padding only in last quantum
84 - if (input[2] == '=') {
85 - uint32_t value = (lookup[input[0]] << 6) + lookup[input[1]];
86 - output[0] = value >> 4;
87 - //error("Decoded %c %c %c %c -> %02x", input[0], input[1], input[2], input[3], output[0]);
88 - return unpadded_size-2;
89 - }
90 - else if (input[3] == '=') {
91 - uint32_t value = (lookup[input[0]] << 12) + (lookup[input[1]] << 6) + lookup[input[2]];
92 - output[0] = value >> 10;
93 - output[1] = value >> 2;
94 - //error("Decoded %c %c %c %c -> %02x %02x", input[0], input[1], input[2], input[3], output[0], output[1]);
95 - return unpadded_size-1;
96 - }
97 - else
98 - {
99 - uint32_t value = (input[0] << 18) + (input[1] << 12) + (input[2]<<6) + input[3];
100 - output[0] = value >> 16;
101 - output[1] = value >> 8;
102 - output[2] = value;
103 - //error("Decoded %c %c %c %c -> %02x %02x %02x", input[0], input[1], input[2], input[3], output[0], output[1], output[2]);
104 - return unpadded_size;
105 - }
106 -}
107 -
108 -static size_t base64_encode(unsigned char *input, size_t input_size, char *output, size_t output_size)
109 -{
110 - uint32_t value;
111 - static char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
112 - "abcdefghijklmnopqrstuvwxyz"
113 - "0123456789+/";
114 - if ((input_size/3+1)*4 >= output_size)
115 - {
116 - error("Output buffer for encoding size=%zu is not large enough for %zu-bytes input", output_size, input_size);
117 - return 0;
118 - }
119 - size_t count = 0;
120 - while (input_size>3)
121 - {
122 - value = ((input[0] << 16) + (input[1] << 8) + input[2]) & 0xffffff;
123 - output[0] = lookup[value >> 18];
124 - output[1] = lookup[(value >> 12) & 0x3f];
125 - output[2] = lookup[(value >> 6) & 0x3f];
126 - output[3] = lookup[value & 0x3f];
127 - //error("Base-64 encode (%04x) -> %c %c %c %c\n", value, output[0], output[1], output[2], output[3]);
128 - output += 4;
129 - input += 3;
130 - input_size -= 3;
131 - count += 4;
132 - }
133 - switch (input_size)
134 - {
135 - case 2:
136 - value = (input[0] << 10) + (input[1] << 2);
137 - output[0] = lookup[(value >> 12) & 0x3f];
138 - output[1] = lookup[(value >> 6) & 0x3f];
139 - output[2] = lookup[value & 0x3f];
140 - output[3] = '=';
141 - //error("Base-64 encode (%06x) -> %c %c %c %c\n", (value>>2)&0xffff, output[0], output[1], output[2], output[3]);
142 - count += 4;
143 - break;
144 - case 1:
145 - value = input[0] << 4;
146 - output[0] = lookup[(value >> 6) & 0x3f];
147 - output[1] = lookup[value & 0x3f];
148 - output[2] = '=';
149 - output[3] = '=';
150 - //error("Base-64 encode (%06x) -> %c %c %c %c\n", value, output[0], output[1], output[2], output[3]);
151 - count += 4;
152 - break;
153 - case 0:
154 - break;
155 - }
156 - return count;
157 -}
158 -
159 -static int private_decrypt(RSA *p_key, unsigned char * enc_data, int data_len, unsigned char *decrypted)
160 -{
161 - int result = RSA_private_decrypt( data_len, enc_data, decrypted, p_key, RSA_PKCS1_OAEP_PADDING);
162 - if (result == -1) {
163 - char err[512];
164 - ERR_error_string_n(ERR_get_error(), err, sizeof(err));
165 - error("Decryption of the challenge failed: %s", err);
166 - }
167 - return result;
168 -}
169 -
12 static int aclk_https_request(https_req_t *request, https_req_response_t *response) {
13 int rc;
14 // wrapper for ACLK only which loads ACLK specific proxy settings
@@ -426,112 +268,242 @@ exit:
268 }
269 #endif
270
271 +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110
272 +static EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void)
273 +{
274 + EVP_ENCODE_CTX *ctx = OPENSSL_malloc(sizeof(*ctx));
275 +
276 + if (ctx != NULL) {
277 + memset(ctx, 0, sizeof(*ctx));
278 + }
279 + return ctx;
280 +}
281 +static void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx)
282 +{
283 + OPENSSL_free(ctx);
284 + return;
285 +}
286 +#endif
287 +
288 +#define CHALLENGE_LEN 256
289 +#define CHALLENGE_LEN_BASE64 344
290 +inline static int base64_decode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len)
291 +{
292 + unsigned char remaining_data[CHALLENGE_LEN];
293 + EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
294 + EVP_DecodeInit(ctx);
295 + EVP_DecodeUpdate(ctx, out, outl, in, in_len);
296 + int remainder = 0;
297 + EVP_DecodeFinal(ctx, remaining_data, &remainder);
298 + EVP_ENCODE_CTX_free(ctx);
299 + if (remainder) {
300 + error("Unexpected data at EVP_DecodeFinal");
301 + return 1;
302 + }
303 + return 0;
304 +}
305 +
306 +inline static int base64_encode_helper(unsigned char *out, int *outl, const unsigned char *in, int in_len)
307 +{
308 + int len;
309 + unsigned char *str = out;
310 + EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
311 + EVP_EncodeInit(ctx);
312 + EVP_EncodeUpdate(ctx, str, outl, in, in_len);
313 + str += *outl;
314 + EVP_EncodeFinal(ctx, str, &len);
315 + *outl += len;
316 + // if we ever expect longer output than what OpenSSL would pack into single line
317 + // we would have to skip the endlines, until then we can just cut the string short
318 + str = (unsigned char*)strchr((char*)out, '\n');
319 + if (str)
320 + *str = 0;
321 + EVP_ENCODE_CTX_free(ctx);
322 + return 0;
323 +}
324 +
325 #define OTP_URL_PREFIX "/api/v1/auth/node/"
430 -int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target) {
431 - // TODO this fnc will be rewritten and simplified in following PRs
432 - // still carries lot of baggage from ACLK Legacy
326 +int aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char **challenge, int *challenge_bytes)
327 +{
328 int rc = 1;
434 - BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
435 -
329 https_req_t req = HTTPS_REQ_T_INITIALIZER;
330 https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
331
439 - char *agent_id = is_agent_claimed();
440 - if (agent_id == NULL)
441 - {
442 - error("Agent was not claimed - cannot perform challenge/response");
443 - goto cleanup;
444 - }
332 + BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
333
446 - // GET Challenge
334 req.host = target->host;
335 req.port = target->port;
336 buffer_sprintf(url, "%s/node/%s/challenge", target->path, agent_id);
450 - req.url = url->buffer;
337 + req.url = (char *)buffer_tostring(url);
338
339 if (aclk_https_request(&req, &resp)) {
340 error ("ACLK_OTP Challenge failed");
454 - goto cleanup;
341 + buffer_free(url);
342 + return 1;
343 }
344 if (resp.http_code != 200) {
345 error ("ACLK_OTP Challenge HTTP code not 200 OK (got %d)", resp.http_code);
346 + buffer_free(url);
347 if (resp.payload_size)
348 aclk_parse_otp_error(resp.payload);
349 goto cleanup_resp;
350 }
462 - info ("ACLK_OTP Got Challenge from Cloud");
351 + buffer_free(url);
352
464 - struct dictionary_singleton challenge = { .key = "challenge", .result = NULL };
353 + info ("ACLK_OTP Got Challenge from Cloud");
354
466 - if (json_parse(resp.payload, &challenge, json_extract_singleton) != JSON_OK)
467 - {
468 - freez(challenge.result);
469 - error("Could not parse the the challenge");
355 + json_object *json = json_tokener_parse(resp.payload);
356 + if (!json) {
357 + error ("Couldn't parse HTTP GET challenge payload");
358 goto cleanup_resp;
359 }
472 - if (challenge.result == NULL) {
473 - error("Could not retrieve challenge JSON key from challenge response");
474 - goto cleanup_resp;
360 + json_object *challenge_json;
361 + if (!json_object_object_get_ex(json, "challenge", &challenge_json)) {
362 + error ("No key named \"challenge\" in the returned JSON");
363 + goto cleanup_json;
364 + }
365 + if (!json_object_is_type(challenge_json, json_type_string)) {
366 + error ("\"challenge\" is not a string JSON type");
367 + goto cleanup_json;
368 + }
369 + const char *challenge_base64;
370 + if (!(challenge_base64 = json_object_get_string(challenge_json))) {
371 + error("Failed to extract challenge from JSON object");
372 + goto cleanup_json;
373 + }
374 + if (strlen(challenge_base64) != CHALLENGE_LEN_BASE64) {
375 + error("Received Challenge has unexpected length of %zu (expected %d)", strlen(challenge_base64), CHALLENGE_LEN_BASE64);
376 + goto cleanup_json;
377 }
378
477 - // Decrypt the Challenge and Calculate Response
478 - size_t challenge_len = strlen(challenge.result);
479 - unsigned char decoded[512];
480 - size_t decoded_len = base64_decode((unsigned char*)challenge.result, challenge_len, decoded, sizeof(decoded));
481 - freez(challenge.result);
482 -
483 - unsigned char plaintext[4096]={};
484 - int decrypted_length = private_decrypt(p_key, decoded, decoded_len, plaintext);
485 - char encoded[512];
486 - size_t encoded_len = base64_encode(plaintext, decrypted_length, encoded, sizeof(encoded));
487 - encoded[encoded_len] = 0;
488 - debug(D_ACLK, "Encoded len=%zu Decryption len=%d: '%s'", encoded_len, decrypted_length, encoded);
489 -
490 - char response_json[4096]={};
491 - sprintf(response_json, "{\"response\":\"%s\"}", encoded);
492 - debug(D_ACLK, "Password phase: %s",response_json);
379 + *challenge = mallocz((CHALLENGE_LEN_BASE64 / 4) * 3);
380 + base64_decode_helper(*challenge, challenge_bytes, (const unsigned char*)challenge_base64, strlen(challenge_base64));
381 + if (*challenge_bytes != CHALLENGE_LEN) {
382 + error("Unexpected challenge length of %d instead of %d", *challenge_bytes, CHALLENGE_LEN);
383 + freez(challenge);
384 + *challenge = NULL;
385 + goto cleanup_json;
386 + }
387 + rc = 0;
388
389 +cleanup_json:
390 + json_object_put(json);
391 +cleanup_resp:
392 https_req_response_free(&resp);
495 - https_req_response_init(&resp);
393 + return rc;
394 +}
395 +
396 +int aclk_send_otp_response(const char *agent_id, const unsigned char *response, int response_bytes, url_t *target, struct auth_data *mqtt_auth)
397 +{
398 + int len;
399 + int rc = 1;
400 + https_req_t req = HTTPS_REQ_T_INITIALIZER;
401 + https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
402
497 - // POST password
403 + req.host = target->host;
404 + req.port = target->port;
405 req.request_type = HTTP_REQ_POST;
499 - buffer_flush(url);
406 +
407 + unsigned char base64[CHALLENGE_LEN_BASE64 + 1];
408 + memset(base64, 0, CHALLENGE_LEN_BASE64 + 1);
409 +
410 + base64_encode_helper(base64, &len, response, response_bytes);
411 +
412 + BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
413 + BUFFER *resp_json = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
414 +
415 buffer_sprintf(url, "%s/node/%s/password", target->path, agent_id);
501 - req.url = url->buffer;
502 - req.payload = response_json;
503 - req.payload_size = strlen(response_json);
416 + buffer_sprintf(resp_json, "{\"response\":\"%s\"}", base64);
417 +
418 + req.url = (char *)buffer_tostring(url);
419 + req.payload = (char *)buffer_tostring(resp_json);
420 + req.payload_size = strlen(req.payload);
421
422 if (aclk_https_request(&req, &resp)) {
423 error ("ACLK_OTP Password error trying to post result to password");
507 - goto cleanup;
424 + goto cleanup_buffers;
425 }
426 if (resp.http_code != 201) {
427 error ("ACLK_OTP Password HTTP code not 201 Created (got %d)", resp.http_code);
428 if (resp.payload_size)
429 aclk_parse_otp_error(resp.payload);
513 - goto cleanup_resp;
430 + goto cleanup_response;
431 }
432 info ("ACLK_OTP Got Password from Cloud");
433
517 - struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL };
518 -
519 - if (parse_passwd_response(resp.payload, &data)){
434 + if (parse_passwd_response(resp.payload, mqtt_auth)){
435 error("Error parsing response of password endpoint");
521 - goto cleanup_resp;
436 + goto cleanup_response;
437 + }
438 +
439 + rc = 0;
440 +
441 +cleanup_response:
442 + https_req_response_free(&resp);
443 +cleanup_buffers:
444 + buffer_free(resp_json);
445 + buffer_free(url);
446 + return rc;
447 +}
448 +
449 +static int private_decrypt(RSA *p_key, unsigned char * enc_data, int data_len, unsigned char **decrypted)
450 +{
451 + *decrypted = mallocz(RSA_size(p_key));
452 + int result = RSA_private_decrypt(data_len, enc_data, *decrypted, p_key, RSA_PKCS1_OAEP_PADDING);
453 + if (result == -1) {
454 + char err[512];
455 + ERR_error_string_n(ERR_get_error(), err, sizeof(err));
456 + error("Decryption of the challenge failed: %s", err);
457 + }
458 + return result;
459 +}
460 +
461 +int aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target)
462 +{
463 + unsigned char *challenge;
464 + int challenge_bytes;
465 +
466 + char *agent_id = is_agent_claimed();
467 + if (agent_id == NULL) {
468 + error("Agent was not claimed - cannot perform challenge/response");
469 + return 1;
470 + }
471 +
472 + // Get Challenge
473 + if (aclk_get_otp_challenge(target, agent_id, &challenge, &challenge_bytes)) {
474 + error("Error getting challenge");
475 + freez(agent_id);
476 + return 1;
477 + }
478 +
479 + // Decrypt Challenge / Get response
480 + unsigned char *response_plaintext;
481 + int response_plaintext_bytes = private_decrypt(p_key, challenge, challenge_bytes, &response_plaintext);
482 + if (response_plaintext_bytes < 0) {
483 + error ("Couldn't decrypt the challenge received");
484 + freez(response_plaintext);
485 + freez(challenge);
486 + freez(agent_id);
487 + return 1;
488 + }
489 + freez(challenge);
490 +
491 + // Encode and Send Challenge
492 + struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL };
493 + if (aclk_send_otp_response(agent_id, response_plaintext, response_plaintext_bytes, target, &data)) {
494 + error("Error getting response");
495 + freez(response_plaintext);
496 + freez(agent_id);
497 + return 1;
498 }
499
500 *mqtt_pass = data.passwd;
501 *mqtt_usr = data.username;
502 *mqtt_id = data.client_id;
503
528 - rc = 0;
529 -cleanup_resp:
530 - https_req_response_free(&resp);
531 -cleanup:
504 + freez(response_plaintext);
505 freez(agent_id);
533 - buffer_free(url);
534 - return rc;
506 + return 0;
507 }
508
509 #define JSON_KEY_ENC "encoding"