Files
n_signer/libotppad/libotppad.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;
}