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"