399 lines
14 KiB
C
399 lines
14 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* 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;
|
|
}
|