/* * libotppad.h — format-critical helpers for one-time-pad encryption. * * Bit-compatible with the `otp` project (https://git.laantungir.net/laantungir/otp): * - XOR transform * - ASCII armored message format ("-----BEGIN OTP MESSAGE-----") * - Binary .otp file format (magic "OTP\0") * - ISO/IEC 9797-1 Method 2 (Padmé) padding with exponential bucketing * - Per-pad .state file ("offset=\n") * - 256-bit XOR pad checksum (position-dependent, XORed with first 32 pad bytes) * * This library is self-contained: it does not depend on the `otp` project's * main.h, global state, or UI code. Both `otp` and `n_signer` link against it. * * License: same as the otp project. */ #ifndef LIBOTPPAD_H #define LIBOTPPAD_H #include #include #include #ifdef __cplusplus extern "C" { #endif /* ------------------------------------------------------------------ */ /* Constants */ /* ------------------------------------------------------------------ */ #define OTPPAD_CHKSUM_HEX_LEN 64 /* 32 bytes -> 64 hex chars */ #define OTPPAD_CHKSUM_BIN_LEN 32 #define OTPPAD_HEADER_RESERVED 32 /* bytes reserved at start of pad */ #define OTPPAD_MAGIC "OTP\0" /* 4-byte binary file magic */ #define OTPPAD_MAGIC_LEN 4 #define OTPPAD_FORMAT_VERSION 1 /* binary .otp format version */ #define OTPPAD_ARMOR_BEGIN "-----BEGIN OTP MESSAGE-----" #define OTPPAD_ARMOR_END "-----END OTP MESSAGE-----" /* ------------------------------------------------------------------ */ /* XOR transform */ /* ------------------------------------------------------------------ */ /* * XOR `data_len` bytes of `data` with `pad_data` into `result`. * `data`, `pad_data`, `result` must each be at least `data_len` bytes. * `result` may alias `data` or `pad_data`. * Returns 0 on success, non-zero on null pointer. */ int otppad_xor(const unsigned char *data, size_t data_len, const unsigned char *pad_data, unsigned char *result); /* ------------------------------------------------------------------ */ /* Base64 */ /* ------------------------------------------------------------------ */ /* Encode `length` bytes of `input` as a NUL-terminated base64 string. * Caller frees the returned string. Returns NULL on allocation failure. */ char *otppad_base64_encode(const unsigned char *input, int length); /* Decode NUL-terminated base64 `input` into bytes. * Caller frees the returned buffer. *output_length receives the byte count. * Returns NULL on invalid input or allocation failure. */ unsigned char *otppad_base64_decode(const char *input, int *output_length); /* ------------------------------------------------------------------ */ /* Padmé padding (ISO/IEC 9797-1 Method 2) + exponential bucketing */ /* ------------------------------------------------------------------ */ /* Calculate the bucket size for a message of `msg_len` bytes. * Starts at 256 bytes and doubles until `chunk >= msg_len + 1`. */ size_t otppad_chunk_size(size_t msg_len); /* Apply Padmé padding to `buffer` (must hold `chunk_size` bytes). * Writes 0x80 at `buffer[msg_len]` then zeroes to `chunk_size`. * Returns 0 on success, non-zero on error. */ int otppad_pad_apply(unsigned char *buffer, size_t msg_len, size_t chunk_size); /* Remove Padmé padding: scan backwards for 0x80, set *msg_len to its index. * Returns 0 on success, non-zero on invalid padding. */ int otppad_pad_remove(const unsigned char *buffer, size_t chunk_size, size_t *msg_len); /* Human-readable chunk size string (e.g. "256 bytes", "1.0 KB"). */ void otppad_chunk_format(size_t chunk_size, char *buffer, size_t buffer_size); /* ------------------------------------------------------------------ */ /* ASCII armored message format */ /* ------------------------------------------------------------------ */ /* * Parse an ASCII-armored OTP message. * * `chksum` must be at least OTPPAD_CHKSUM_HEX_LEN+1 bytes. * `base64_data` must be at least `base64_buf_size` bytes; the decoded * ciphertext is NOT returned here — only the raw base64 text. Use * otppad_base64_decode() to get the bytes. * * On success returns 0 and sets `chksum`, `*offset`, and `base64_data`. * Returns non-zero on malformed input. */ int otppad_armor_parse(const char *message, char *chksum, uint64_t *offset, char *base64_data, size_t base64_buf_size); /* * Build an ASCII-armored OTP message. * * `version` is the version string for the "Version:" header (e.g. "v0.3.53"). * `chksum` is the 64-char hex pad checksum. * `offset` is the pad offset where the slice begins. * `encrypted_data` / `data_length` is the ciphertext to base64-encode. * * On success returns 0 and sets `*ascii_output` to a malloc'd NUL-terminated * string. Caller frees `*ascii_output`. */ 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); /* ------------------------------------------------------------------ */ /* Binary .otp file format */ /* ------------------------------------------------------------------ */ /* * Binary .otp header (58 bytes). All fields are little-endian on disk * (written via fwrite of host-endian integers — matches the otp project, * which is x86/ARM little-endian in practice). * * Offset Size Field * 0 4 Magic "OTP\0" * 4 2 Version (uint16, currently 1) * 6 32 Pad checksum (binary, 32 bytes) * 38 8 Pad offset (uint64) * 46 4 Original file mode (uint32) * 50 8 Original file size (uint64, NOT padded size) * 58 var Encrypted (padded) data */ typedef struct { char magic[OTPPAD_MAGIC_LEN]; uint16_t version; unsigned char pad_chksum[OTPPAD_CHKSUM_BIN_LEN]; uint64_t pad_offset; uint32_t file_mode; uint64_t file_size; /* original (unpadded) size */ } otppad_bin_header_t; /* Write a binary .otp header to `fp`. Returns 0 on success. */ int otppad_bin_header_write(FILE *fp, const otppad_bin_header_t *hdr); /* Read a binary .otp header from `fp`. Returns 0 on success, non-zero on * malformed input. Does not validate the magic — use otppad_bin_is_magic() * first if needed. */ int otppad_bin_header_read(FILE *fp, otppad_bin_header_t *hdr); /* Return 1 if the first 4 bytes of `buf` match the OTP magic. */ int otppad_bin_is_magic(const unsigned char *buf, size_t len); /* ------------------------------------------------------------------ */ /* Per-pad .state file */ /* ------------------------------------------------------------------ */ /* * Read the offset from `/.state`. * Returns 0 on success and sets *offset. Non-zero on error. */ int otppad_state_read(const char *pads_dir, const char *chksum, uint64_t *offset); /* * Atomically write the offset to `/.state`. * Writes to a temp file then renames, so a crash cannot corrupt the state. * Returns 0 on success, non-zero on error. */ int otppad_state_write(const char *pads_dir, const char *chksum, uint64_t offset); /* ------------------------------------------------------------------ */ /* Pad checksum */ /* ------------------------------------------------------------------ */ /* * Compute the 256-bit XOR checksum of the pad file at `pad_path`. * `checksum_hex` must be at least OTPPAD_CHKSUM_HEX_LEN+1 bytes. * * Algorithm (matches otp/src/crypto.c:calculate_checksum): * - XOR every byte into one of 32 buckets, selected by (position % 32), * also XORing in bytes (pos>>8),(pos>>16),(pos>>24) of the position. * - XOR the resulting 32-byte checksum with the first 32 bytes of the pad * (the "pad key"). * - Hex-encode the 32-byte result. * * Returns 0 on success, non-zero on error. */ int otppad_checksum(const char *pad_path, char *checksum_hex); #ifdef __cplusplus } #endif #endif /* LIBOTPPAD_H */