Mercurial
view connectors/core.c @ 281:c57149ad216e default tip
Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 22:18:15 -0700 |
| parents | b3b547563ec7 |
| children |
line wrap: on
line source
#include "connectors/connector.h" #include <openssl/evp.h> #include <openssl/rand.h> #include <stdio.h> #include <string.h> static const char BASE64URL[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; boolean Connector_Base64Url_Encode( const uint8 *input, size_t input_length, char *output, size_t output_size) { size_t required = (input_length * 4 + 2) / 3; if (!input || !output || output_size <= required) return FALSE; size_t i = 0, j = 0; while (i + 3 <= input_length) { uint32 value = ((uint32)input[i] << 16) | ((uint32)input[i + 1] << 8) | input[i + 2]; output[j++] = BASE64URL[(value >> 18) & 63]; output[j++] = BASE64URL[(value >> 12) & 63]; output[j++] = BASE64URL[(value >> 6) & 63]; output[j++] = BASE64URL[value & 63]; i += 3; } if (i < input_length) { uint32 value = (uint32)input[i] << 16; output[j++] = BASE64URL[(value >> 18) & 63]; if (i + 1 < input_length) { value |= (uint32)input[i + 1] << 8; output[j++] = BASE64URL[(value >> 12) & 63]; output[j++] = BASE64URL[(value >> 6) & 63]; } else { output[j++] = BASE64URL[(value >> 12) & 63]; } } output[j] = '\0'; return TRUE; } static int32 base64url_value(char c) { const char *position = strchr(BASE64URL, c); return position ? (int32)(position - BASE64URL) : -1; } boolean Connector_Base64Url_Decode( const char *input, uint8 *output, size_t output_size, size_t *output_length) { if (!input || !output || !output_length) return FALSE; size_t length = strlen(input); if ((length % 4) == 1 || output_size < (length * 3) / 4) return FALSE; uint32 accumulator = 0; int32 bits = 0; size_t written = 0; for (size_t i = 0; i < length; ++i) { int32 value = base64url_value(input[i]); if (value < 0) return FALSE; accumulator = (accumulator << 6) | (uint32)value; bits += 6; if (bits >= 8) { bits -= 8; if (written >= output_size) return FALSE; output[written++] = (uint8)((accumulator >> bits) & 255); } } *output_length = written; return TRUE; } boolean Connector_Encrypt( const Connector_Master_Key *key, const char *plaintext, char *encoded, size_t encoded_size) { if (!key || !plaintext || !encoded) return FALSE; size_t plaintext_length = strlen(plaintext); if (plaintext_length > 4096) return FALSE; uint8 nonce[12], tag[16], ciphertext[4096]; if (RAND_bytes(nonce, sizeof(nonce)) != 1) return FALSE; EVP_CIPHER_CTX *context = EVP_CIPHER_CTX_new(); int32 length = 0, total = 0; boolean ok = context && EVP_EncryptInit_ex(context, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1 && EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, sizeof(nonce), NULL) == 1 && EVP_EncryptInit_ex(context, NULL, NULL, key->key, nonce) == 1 && EVP_EncryptUpdate(context, ciphertext, &length, (const uint8 *)plaintext, (int32)plaintext_length) == 1; total = length; ok = ok && EVP_EncryptFinal_ex(context, ciphertext + total, &length) == 1; total += length; ok = ok && EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag) == 1; EVP_CIPHER_CTX_free(context); if (!ok) return FALSE; uint8 envelope[4 + 12 + 16 + 4096]; envelope[0] = (uint8)(key->version >> 24); envelope[1] = (uint8)(key->version >> 16); envelope[2] = (uint8)(key->version >> 8); envelope[3] = (uint8)key->version; memcpy(envelope + 4, nonce, sizeof(nonce)); memcpy(envelope + 16, tag, sizeof(tag)); memcpy(envelope + 32, ciphertext, (size_t)total); return Connector_Base64Url_Encode( envelope, 32 + (size_t)total, encoded, encoded_size); } boolean Connector_Decrypt( const Connector_Master_Key *key, const char *encoded, char *plaintext, size_t plaintext_size) { uint8 envelope[4 + 12 + 16 + 4096]; size_t envelope_length = 0; if (!key || !encoded || !plaintext || !Connector_Base64Url_Decode( encoded, envelope, sizeof(envelope), &envelope_length) || envelope_length < 32) return FALSE; uint32 version = ((uint32)envelope[0] << 24) | ((uint32)envelope[1] << 16) | ((uint32)envelope[2] << 8) | envelope[3]; size_t ciphertext_length = envelope_length - 32; if (version != key->version || plaintext_size <= ciphertext_length) return FALSE; EVP_CIPHER_CTX *context = EVP_CIPHER_CTX_new(); int32 length = 0, total = 0; boolean ok = context && EVP_DecryptInit_ex(context, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1 && EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, 12, NULL) == 1 && EVP_DecryptInit_ex(context, NULL, NULL, key->key, envelope + 4) == 1 && EVP_DecryptUpdate(context, (uint8 *)plaintext, &length, envelope + 32, (int32)ciphertext_length) == 1; total = length; ok = ok && EVP_CIPHER_CTX_ctrl( context, EVP_CTRL_GCM_SET_TAG, 16, envelope + 16) == 1 && EVP_DecryptFinal_ex(context, (uint8 *)plaintext + total, &length) == 1; total += length; EVP_CIPHER_CTX_free(context); if (!ok) { memset(plaintext, 0, plaintext_size); return FALSE; } plaintext[total] = '\0'; return TRUE; } boolean Connector_Form_Encode( const char *input, char *output, size_t output_size) { static const char hex[] = "0123456789ABCDEF"; if (!input || !output) return FALSE; size_t j = 0; for (size_t i = 0; input[i]; ++i) { uint8 c = (uint8)input[i]; boolean safe = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~'; size_t needed = safe ? 1 : 3; if (j + needed >= output_size) return FALSE; if (safe) output[j++] = (char)c; else { output[j++] = '%'; output[j++] = hex[c >> 4]; output[j++] = hex[c & 15]; } } output[j] = '\0'; return TRUE; } const char *Connector_Operation_Name(Connector_Operation operation) { static const char *names[] = { "drive.list", "drive.get", "drive.download", "drive.changes", "drive.create", "drive.upload", "drive.update", "gmail.list", "gmail.get", "gmail.attachment", "gmail.history", "gmail.draft.create", "gmail.send" }; return operation >= CONNECTOR_OP_DRIVE_LIST && operation <= CONNECTOR_OP_GMAIL_SEND ? names[operation] : "unknown"; } boolean Connector_Operation_Is_Mutation(Connector_Operation operation) { return operation == CONNECTOR_OP_DRIVE_CREATE || operation == CONNECTOR_OP_DRIVE_UPLOAD || operation == CONNECTOR_OP_DRIVE_UPDATE || operation == CONNECTOR_OP_GMAIL_DRAFT_CREATE || operation == CONNECTOR_OP_GMAIL_SEND; } boolean Connector_Operation_Is_Allowed(Connector_Operation operation) { return operation >= CONNECTOR_OP_DRIVE_LIST && operation <= CONNECTOR_OP_GMAIL_SEND; } boolean Connector_Request_Digest( const char *user_id, const char *account_id, Connector_Operation operation, const Connector_Provider_Request *request, char output[65]) { if (!user_id || !account_id || !request || !output || request->body_length > CONNECTOR_MAX_JSON_BYTES) return FALSE; EVP_MD_CTX *context = EVP_MD_CTX_new(); uint8 digest[32]; uint32 digest_length = 0; const char separator = '\0'; #define HASH_FIELD(value, length) do { \ EVP_DigestUpdate(context, (value) ? (value) : "", (value) ? (length) : 0); \ EVP_DigestUpdate(context, &separator, 1); \ } while (0) boolean ok = context && EVP_DigestInit_ex(context, EVP_sha256(), NULL) == 1; if (!ok) { EVP_MD_CTX_free(context); return FALSE; } const char *name = Connector_Operation_Name(operation); HASH_FIELD(user_id, strlen(user_id)); HASH_FIELD(account_id, strlen(account_id)); HASH_FIELD(name, strlen(name)); HASH_FIELD(request->method, request->method ? strlen(request->method) : 0); HASH_FIELD(request->path, request->path ? strlen(request->path) : 0); HASH_FIELD(request->query, request->query ? strlen(request->query) : 0); HASH_FIELD(request->content_type, request->content_type ? strlen(request->content_type) : 0); HASH_FIELD(request->body, request->body_length); uint8 overwrite = request->overwrite ? 1 : 0; EVP_DigestUpdate(context, &overwrite, 1); ok = EVP_DigestFinal_ex(context, digest, &digest_length) == 1; EVP_MD_CTX_free(context); #undef HASH_FIELD if (!ok || digest_length != 32) return FALSE; for (size_t i = 0; i < sizeof(digest); ++i) snprintf(output + i * 2, 3, "%02x", digest[i]); output[64] = '\0'; return TRUE; } boolean Connector_OAuth_PKCE_Start(Connector_OAuth_Start *start) { uint8 state[32], verifier[48], digest[32]; uint32 digest_length = 0; if (!start || RAND_bytes(state, sizeof(state)) != 1 || RAND_bytes(verifier, sizeof(verifier)) != 1 || !Connector_Base64Url_Encode( state, sizeof(state), start->state, sizeof(start->state)) || !Connector_Base64Url_Encode( verifier, sizeof(verifier), start->code_verifier, sizeof(start->code_verifier))) return FALSE; if (EVP_Digest( start->code_verifier, strlen(start->code_verifier), digest, &digest_length, EVP_sha256(), NULL) != 1 || digest_length != 32) return FALSE; return Connector_Base64Url_Encode( digest, sizeof(digest), start->code_challenge, sizeof(start->code_challenge)); }