/* * libotppad.c — implementation of libotppad.h. * * Extracted from the otp project (src/crypto.c, src/padding.c, src/pads.c) * and made self-contained: no main.h, no global state, no UI. */ #define _POSIX_C_SOURCE 200809L #include "libotppad.h" #include #include #include #include /* ------------------------------------------------------------------ */ /* XOR transform */ /* ------------------------------------------------------------------ */ int otppad_xor(const unsigned char *data, size_t data_len, const unsigned char *pad_data, unsigned char *result) { if (!data || !pad_data || !result) { return 1; } for (size_t i = 0; i < data_len; i++) { result[i] = data[i] ^ pad_data[i]; } return 0; } /* ------------------------------------------------------------------ */ /* Base64 */ /* ------------------------------------------------------------------ */ static const char b64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const int b64_decode_table[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, -1, 0, 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,-1,-1,-1,-1,-1, -1,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,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }; char *otppad_base64_encode(const unsigned char *input, int length) { if (!input || length < 0) return NULL; int output_length = 4 * ((length + 2) / 3); char *encoded = (char *)malloc((size_t)output_length + 1); if (!encoded) return NULL; int i, j; for (i = 0, j = 0; i < length;) { uint32_t octet_a = i < length ? input[i++] : 0; uint32_t octet_b = i < length ? input[i++] : 0; uint32_t octet_c = i < length ? input[i++] : 0; uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; encoded[j++] = b64_chars[(triple >> 18) & 63]; encoded[j++] = b64_chars[(triple >> 12) & 63]; encoded[j++] = b64_chars[(triple >> 6) & 63]; encoded[j++] = b64_chars[triple & 63]; } for (int pad = 0; pad < (3 - length % 3) % 3; pad++) { encoded[output_length - 1 - pad] = '='; } encoded[output_length] = '\0'; return encoded; } unsigned char *otppad_base64_decode(const char *input, int *output_length) { if (!input || !output_length) return NULL; int input_length = (int)strlen(input); if (input_length % 4 != 0) return NULL; *output_length = input_length / 4 * 3; if (input[input_length - 1] == '=') (*output_length)--; if (input[input_length - 2] == '=') (*output_length)--; unsigned char *decoded = (unsigned char *)malloc((size_t)*output_length); if (!decoded) return NULL; int i, j; for (i = 0, j = 0; i < input_length;) { int sa = input[i] == '=' ? 0 & i++ : b64_decode_table[(unsigned char)input[i++]]; int sb = input[i] == '=' ? 0 & i++ : b64_decode_table[(unsigned char)input[i++]]; int sc = input[i] == '=' ? 0 & i++ : b64_decode_table[(unsigned char)input[i++]]; int sd = input[i] == '=' ? 0 & i++ : b64_decode_table[(unsigned char)input[i++]]; if (sa == -1 || sb == -1 || sc == -1 || sd == -1) { free(decoded); return NULL; } uint32_t triple = ((uint32_t)sa << 18) + ((uint32_t)sb << 12) + ((uint32_t)sc << 6) + (uint32_t)sd; if (j < *output_length) decoded[j++] = (triple >> 16) & 255; if (j < *output_length) decoded[j++] = (triple >> 8) & 255; if (j < *output_length) decoded[j++] = triple & 255; } return decoded; } /* ------------------------------------------------------------------ */ /* Padmé padding */ /* ------------------------------------------------------------------ */ size_t otppad_chunk_size(size_t msg_len) { size_t chunk = 256; while (chunk < msg_len + 1) { chunk *= 2; } return chunk; } int otppad_pad_apply(unsigned char *buffer, size_t msg_len, size_t chunk_size) { if (!buffer) return 1; if (chunk_size < msg_len + 1) return 2; buffer[msg_len] = 0x80; if (chunk_size > msg_len + 1) { memset(buffer + msg_len + 1, 0x00, chunk_size - msg_len - 1); } return 0; } int otppad_pad_remove(const unsigned char *buffer, size_t chunk_size, size_t *msg_len) { if (!buffer || !msg_len) return 1; if (chunk_size == 0) return 2; for (int i = (int)chunk_size - 1; i >= 0; i--) { if (buffer[i] == 0x80) { *msg_len = (size_t)i; return 0; } else if (buffer[i] != 0x00) { return 3; } } return 4; } void otppad_chunk_format(size_t chunk_size, char *buffer, size_t buffer_size) { if (!buffer || buffer_size == 0) return; if (chunk_size < 1024) { snprintf(buffer, buffer_size, "%zu bytes", chunk_size); } else if (chunk_size < 1024 * 1024) { snprintf(buffer, buffer_size, "%.1f KB", chunk_size / 1024.0); } else if (chunk_size < 1024 * 1024 * 1024) { snprintf(buffer, buffer_size, "%.1f MB", chunk_size / (1024.0 * 1024.0)); } else { snprintf(buffer, buffer_size, "%.1f GB", chunk_size / (1024.0 * 1024.0 * 1024.0)); } } /* ------------------------------------------------------------------ */ /* ASCII armored message format */ /* ------------------------------------------------------------------ */ int otppad_armor_parse(const char *message, char *chksum, uint64_t *offset, char *base64_data, size_t base64_buf_size) { if (!message || !chksum || !offset || !base64_data || base64_buf_size == 0) { return 1; } size_t msg_len = strlen(message); char *copy = (char *)malloc(msg_len + 1); if (!copy) return 1; strcpy(copy, message); char *line = strtok(copy, "\n"); int found_begin = 0, in_data = 0, found_chksum = 0, found_offset = 0; chksum[0] = '\0'; *offset = 0; base64_data[0] = '\0'; while (line != NULL) { if (strcmp(line, OTPPAD_ARMOR_BEGIN) == 0) { found_begin = 1; } else if (strcmp(line, OTPPAD_ARMOR_END) == 0) { break; } else if (found_begin) { if (strncmp(line, "Pad-ChkSum: ", 12) == 0) { strncpy(chksum, line + 12, OTPPAD_CHKSUM_HEX_LEN); chksum[OTPPAD_CHKSUM_HEX_LEN] = '\0'; found_chksum = 1; } else if (strncmp(line, "Pad-Offset: ", 12) == 0) { *offset = strtoull(line + 12, NULL, 10); found_offset = 1; } else if (strlen(line) == 0) { in_data = 1; } else if (in_data) { strncat(base64_data, line, base64_buf_size - strlen(base64_data) - 1); } else if (strncmp(line, "Version:", 8) != 0 && strncmp(line, "Pad-", 4) != 0) { strncat(base64_data, line, base64_buf_size - strlen(base64_data) - 1); } } line = strtok(NULL, "\n"); } free(copy); if (!found_begin || !found_chksum || !found_offset) { return 2; } return 0; } int otppad_armor_generate(const char *version, const char *chksum, uint64_t offset, const unsigned char *encrypted_data, size_t data_length, char **ascii_output) { if (!chksum || !encrypted_data || !ascii_output) return 1; char *b64 = otppad_base64_encode(encrypted_data, (int)data_length); if (!b64) return 2; size_t b64_len = strlen(b64); size_t total = 256 + b64_len + (b64_len / 64) + 64; *ascii_output = (char *)malloc(total); if (!*ascii_output) { free(b64); return 3; } char line[256]; strcpy(*ascii_output, OTPPAD_ARMOR_BEGIN); strcat(*ascii_output, "\n"); snprintf(line, sizeof(line), "Version: %s\n", version ? version : "v0"); strcat(*ascii_output, line); snprintf(line, sizeof(line), "Pad-ChkSum: %s\n", chksum); strcat(*ascii_output, line); snprintf(line, sizeof(line), "Pad-Offset: %llu\n", (unsigned long long)offset); strcat(*ascii_output, line); strcat(*ascii_output, "\n"); int b64_len_int = (int)b64_len; for (int i = 0; i < b64_len_int; i += 64) { snprintf(line, sizeof(line), "%.64s\n", b64 + i); strcat(*ascii_output, line); } strcat(*ascii_output, OTPPAD_ARMOR_END); strcat(*ascii_output, "\n"); free(b64); return 0; } /* ------------------------------------------------------------------ */ /* Binary .otp file format */ /* ------------------------------------------------------------------ */ int otppad_bin_header_write(FILE *fp, const otppad_bin_header_t *hdr) { if (!fp || !hdr) return 1; if (fwrite(OTPPAD_MAGIC, 1, OTPPAD_MAGIC_LEN, fp) != OTPPAD_MAGIC_LEN) return 2; if (fwrite(&hdr->version, sizeof(uint16_t), 1, fp) != 1) return 3; if (fwrite(hdr->pad_chksum, 1, OTPPAD_CHKSUM_BIN_LEN, fp) != OTPPAD_CHKSUM_BIN_LEN) return 4; if (fwrite(&hdr->pad_offset, sizeof(uint64_t), 1, fp) != 1) return 5; if (fwrite(&hdr->file_mode, sizeof(uint32_t), 1, fp) != 1) return 6; if (fwrite(&hdr->file_size, sizeof(uint64_t), 1, fp) != 1) return 7; return 0; } int otppad_bin_header_read(FILE *fp, otppad_bin_header_t *hdr) { if (!fp || !hdr) return 1; memset(hdr, 0, sizeof(*hdr)); if (fread(hdr->magic, 1, OTPPAD_MAGIC_LEN, fp) != OTPPAD_MAGIC_LEN) return 2; if (fread(&hdr->version, sizeof(uint16_t), 1, fp) != 1) return 3; if (fread(hdr->pad_chksum, 1, OTPPAD_CHKSUM_BIN_LEN, fp) != OTPPAD_CHKSUM_BIN_LEN) return 4; if (fread(&hdr->pad_offset, sizeof(uint64_t), 1, fp) != 1) return 5; if (fread(&hdr->file_mode, sizeof(uint32_t), 1, fp) != 1) return 6; if (fread(&hdr->file_size, sizeof(uint64_t), 1, fp) != 1) return 7; return 0; } int otppad_bin_is_magic(const unsigned char *buf, size_t len) { if (!buf || len < OTPPAD_MAGIC_LEN) return 0; return memcmp(buf, OTPPAD_MAGIC, OTPPAD_MAGIC_LEN) == 0; } /* ------------------------------------------------------------------ */ /* Per-pad .state file */ /* ------------------------------------------------------------------ */ int otppad_state_read(const char *pads_dir, const char *chksum, uint64_t *offset) { if (!pads_dir || !chksum || !offset) return 1; char path[1024]; snprintf(path, sizeof(path), "%s/%s.state", pads_dir, chksum); FILE *f = fopen(path, "r"); if (!f) return 2; char line[128]; if (!fgets(line, sizeof(line), f)) { fclose(f); return 3; } fclose(f); if (strncmp(line, "offset=", 7) != 0) { return 4; } *offset = strtoull(line + 7, NULL, 10); return 0; } int otppad_state_write(const char *pads_dir, const char *chksum, uint64_t offset) { if (!pads_dir || !chksum) return 1; char path[1024]; char tmp[1100]; snprintf(path, sizeof(path), "%s/%s.state", pads_dir, chksum); snprintf(tmp, sizeof(tmp), "%s/%s.state.tmp.XXXXXX", pads_dir, chksum); int tfd = mkstemp(tmp); if (tfd < 0) return 2; FILE *f = fdopen(tfd, "w"); if (!f) { close(tfd); unlink(tmp); return 3; } if (fprintf(f, "offset=%llu\n", (unsigned long long)offset) < 0) { fclose(f); unlink(tmp); return 4; } if (fclose(f) != 0) { unlink(tmp); return 5; } if (rename(tmp, path) != 0) { unlink(tmp); return 6; } return 0; } /* ------------------------------------------------------------------ */ /* Pad checksum */ /* ------------------------------------------------------------------ */ int otppad_checksum(const char *pad_path, char *checksum_hex) { if (!pad_path || !checksum_hex) return 1; FILE *file = fopen(pad_path, "rb"); if (!file) return 2; unsigned char checksum[OTPPAD_CHKSUM_BIN_LEN]; unsigned char buffer[64 * 1024]; size_t bytes_read; size_t total_bytes = 0; memset(checksum, 0, OTPPAD_CHKSUM_BIN_LEN); while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { for (size_t i = 0; i < bytes_read; i++) { size_t pos = total_bytes + i; unsigned char bucket = (unsigned char)(pos % OTPPAD_CHKSUM_BIN_LEN); checksum[bucket] ^= (unsigned char)buffer[i] ^ (unsigned char)((pos >> 8) & 0xFF) ^ (unsigned char)((pos >> 16) & 0xFF) ^ (unsigned char)((pos >> 24) & 0xFF); } total_bytes += bytes_read; } fclose(file); file = fopen(pad_path, "rb"); if (!file) return 3; unsigned char pad_key[OTPPAD_CHKSUM_BIN_LEN]; if (fread(pad_key, 1, OTPPAD_CHKSUM_BIN_LEN, file) != OTPPAD_CHKSUM_BIN_LEN) { fclose(file); return 4; } fclose(file); unsigned char enc[OTPPAD_CHKSUM_BIN_LEN]; for (int i = 0; i < OTPPAD_CHKSUM_BIN_LEN; i++) { enc[i] = checksum[i] ^ pad_key[i]; } for (int i = 0; i < OTPPAD_CHKSUM_BIN_LEN; i++) { sprintf(checksum_hex + (i * 2), "%02x", enc[i]); } checksum_hex[OTPPAD_CHKSUM_HEX_LEN] = '\0'; return 0; }