Output packaging works.
[easyotp.git] / libotp.c
blob804b2ba989ce2c043ed040b34603627e2b747f3c
1 /** Practical One-time Pad Library
3 * Created:20080514
4 * By Jeff Connelly
5 */
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sysexits.h>
10 #include <string.h>
12 #include "libotp.h"
13 #include "base64.h"
15 PAD *pads = NULL;
17 /** Show a list of all loaded pads. */
18 void show_pads()
20 PAD *p;
22 for (p = pads; p; p = p->next) {
23 printf("Pad: %s: %s\n", p->name, p->local_filename);
27 /** Open a companion offset file for given pad. Caller must close.
29 * @param mode Either "rt" or "wt", for reading or writing.
31 FILE *open_offset_file(PAD *p, char *mode)
33 FILE *ofp;
34 char *offset_filename;
35 size_t filename_length;
37 /* Offset is stored in a separate file; pad plus OFFSET_FILE_NAME_EXTENSION. */
38 filename_length = strlen(p->local_filename) + strlen(OFFSET_FILE_EXTENSION) + 1;
39 offset_filename = malloc(filename_length);
40 if (!offset_filename) {
41 perror("malloc");
42 exit(EX_UNAVAILABLE);
44 snprintf(offset_filename, filename_length, "%s" OFFSET_FILE_EXTENSION, p->local_filename);
46 /* Read offset from file. */
47 ofp = fopen(offset_filename, mode);
48 if (!ofp) {
49 fprintf(stderr, "opening offset file %s failed\n", offset_filename);
50 perror("fopen");
51 free(offset_filename);
52 exit(EX_IOERR);
54 free(offset_filename);
56 return ofp;
59 /** Read the pad offset of a given pad. */
60 unsigned long read_offset(PAD *p)
62 FILE *ofp;
63 char buffer[OFFSET_SIZE];
64 unsigned long offset;
66 ofp = open_offset_file(p, "rt");
68 memset(buffer, 0, OFFSET_SIZE);
69 if (fread(buffer, 1, OFFSET_SIZE - 1, ofp) < 1) {
70 fprintf(stderr, "could not read offset file for %s\n", p->local_filename);
71 exit(EX_IOERR);
74 if (fclose(ofp) != 0) {
75 fprintf(stderr, "error closing offset file for reading for %s\n", p->local_filename);
76 perror("fclose");
77 exit(EX_IOERR);
80 /* We finally got it! */
81 offset = strtoul(buffer, NULL, 10);
83 return offset;
86 /** Write the pad offset to the given pad. */
87 void write_offset(PAD *p, unsigned long offset)
89 FILE *ofp;
90 char buffer[OFFSET_SIZE];
92 ofp = open_offset_file(p, "wt");
94 memset(buffer, 0, OFFSET_SIZE);
95 snprintf(buffer, OFFSET_SIZE - 1, "%ld", offset);
96 printf("buffer=%s\n", buffer);
97 if (fwrite(buffer, strlen(buffer), 1, ofp) != 1) {
98 fprintf(stderr, "write error saving offset %ld for %s\n", offset, p->local_filename);
99 exit(EX_IOERR);
103 if (fclose(ofp) != 0) {
104 fprintf(stderr, "error closing offset file for writing for %s\n", p->local_filename);
105 perror("fclose");
106 exit(EX_IOERR);
110 /** Load a pad file from disk, adding to 'pads' global. */
111 void load_pad(char *local_filename, char *pad_name)
113 FILE *fp;
114 PAD *new_pad;
116 fp = fopen("/Volumes/Not Backed Up/otp/otp-dazzlement", "rb");
117 if (!fp) {
118 perror("fopen");
119 exit(EXIT_FAILURE);
122 new_pad = malloc(sizeof(PAD));
123 if (!new_pad) {
124 perror("malloc");
125 exit(EX_UNAVAILABLE);
128 new_pad->local_filename = strdup(local_filename);
129 new_pad->name = strdup(pad_name);
130 new_pad->fp = fp;
131 new_pad->next = NULL;
133 /* Add to linked list. */
134 if (!pads) {
135 pads = new_pad;
136 } else {
137 PAD *p, *tail;
139 /* Find tail */
140 for (p = pads; p; p = p->next)
141 tail = p;
142 tail->next = new_pad;
146 /** Read a line from a file, up to max characters.
147 * Does not place \n in line string.
149 * Based on http://www.eskimo.com/~scs/cclass/notes/sx6c.html
151 * @return Line length (0 for empty), or EOF for end-of-file.
153 static int getline(FILE *fp, char *line, unsigned int max)
155 int i, c;
157 i = 0;
158 max -= 1;
160 while ((c = fgetc(fp)) != EOF) {
161 if (c == '\n')
162 break;
163 if (i < max)
164 line[i++] = c;
168 if (c == EOF && i == 0)
169 return EOF;
171 line[i] = '\0';
172 return i;
175 void load_config(char *config_filename)
177 FILE *cfg;
178 char line[MAX_CONFIG_LINE];
180 cfg = fopen(config_filename, "rt");
181 if (!cfg) {
182 fprintf(stderr, "failed to open configuration file %s\n", config_filename);
183 perror("fopen config file");
184 exit(EX_IOERR);
187 while(getline(cfg, line, MAX_CONFIG_LINE) != EOF) {
188 char *pad_name, *pad_filename;
190 /* Line is: (pad name)=(local filename) */
191 pad_name = strtok(line, "=");
192 if (!pad_name)
193 continue;
194 pad_filename = strtok(NULL, "=");
196 /* TODO: separate send and recv filename, separated with : */
197 printf("pad=|%s|, filename=|%s|\n", pad_name, pad_filename);
199 load_pad(pad_filename, pad_name);
203 /** Find pad with given name. */
204 PAD *find_pad(char *pad_name)
206 PAD *p;
208 /* Null or blank pad = default (first) pad. */
209 if (!pad_name || !strcmp(pad_name, ""))
210 return pads;
212 for (p = pads; p; p = p->next) {
213 if (!strcmp(p->name, pad_name))
214 return p;
217 return NULL;
220 /** Close all pads and free allocated memory. */
221 void free_pads()
223 PAD *p, *next;
225 for (p = pads; p; p = next) {
226 free(p->name);
227 free(p->local_filename);
228 fclose(p->fp);
229 next = p->next;
230 free(p);
234 void free_message(MESSAGE *msg)
236 free(msg->cipher_text);
237 free(msg);
240 /** Unpackage a message packaged for transport.
242 * Caller must free_message(). */
243 MESSAGE *unpackage(char *input)
245 MESSAGE *msg;
246 unsigned int at;
247 char *s, *end, *b64_ct;
248 char pad_name[PAD_NAME_LENGTH];
250 msg = malloc(sizeof(MESSAGE));
251 if (!msg) {
252 perror("malloc");
253 exit(EX_UNAVAILABLE);
256 /** Format <v0.7 (pad name is unspecified; use default):
257 * MARKER_BEGIN + offset + comma
258 * base64'd data
259 * MARKER_END
261 * Format >=0.7:
262 * MARKER_BEGIN + offset + comma + pad_name + comma
263 * base64'd data
264 * MARKER_END
267 at = 0;
269 /* Locate where the message begins. */
270 s = strstr(input, MARKER_BEGIN);
271 if (!s) {
272 fprintf(stderr, "unpackage: input |%s| lacks beginning marker %s\n",
273 input, MARKER_BEGIN);
274 exit(EX_DATAERR);
277 /* ...and ends. */
278 end = strstr(input, MARKER_END);
279 if (!end) {
280 fprintf(stderr, "unpackage: input |%s| lacks ending marker %s\n",
281 input, MARKER_END);
282 exit(EX_DATAERR);
285 s += strlen(MARKER_BEGIN);
286 msg->offset = strtoul(s, &s, 10);
288 printf("offset=%ld\n", msg->offset);
290 /* Move after mandatory comma. */
291 if (s[0] != ',') {
292 fprintf(stderr, "unpackage: missing comma after offset, at |%s|, input |%s|\n",
293 s, input);
294 exit(EX_DATAERR);
296 ++s;
298 memset(pad_name, 0, PAD_NAME_LENGTH);
300 /* Includes pad name? */
301 if (s[0] != '\n' && s[0] != '\r') {
302 unsigned int i;
304 i = 0;
305 /* v0.7+ message, includes pad name */
306 while(s[0] != '\n' && s[0] != '\r' && s[0] != ',' && s < end) {
307 pad_name[i] = s[0];
308 ++s;
309 ++i;
310 if (i > PAD_NAME_LENGTH) {
311 fprintf(stderr, "unpackage: pad name length > maximum %d, in input |%s|\n",
312 PAD_NAME_LENGTH, input);
313 exit(EX_DATAERR);
318 printf("Pad name: |%s|\n", pad_name);
320 msg->pad = find_pad(pad_name);
321 if (!msg->pad) {
322 fprintf(stderr, "No such pad by name '%s'\n", pad_name);
323 exit(EX_DATAERR);
326 /* Go to next line */
327 while((s[0] == '\n' || s[0] == '\r' || s[0] == ',') && (s < end))
328 ++s;
330 printf("s=%s\n", s);
332 /* Extract base64 data from end of message. */
333 b64_ct = strdup(s);
334 b64_ct[end - s] = 0;
336 printf("b64_data=<%s>\n", b64_ct);
338 /* Decode base64 */
339 msg->cipher_text = malloc(strlen(b64_ct));
340 msg->length = base64_decode(b64_ct, msg->cipher_text);
341 free(b64_ct);
343 printf("decoded to %ld bytes\n", msg->length);
345 return msg;
348 /** Apply Vernam cipher (XOR) with given input and text.
350 * @param input What to XOR with the pad, either ciphertext to decrypt or plaintext to encrypt.
352 * @param length Length of input, in bytes.
354 * @param pad Pad to XOR 'input' with offset.
356 * @param offset Offset within pad to start XOR at.
358 * @return Pad XOR'd with input. Caller must free.
359 * The return value is the same length as 'length'. However, it is null-terminated
360 * in an extra byte for convenience. There may be embedded nulls.
362 static char *xor_with_pad(char *input, unsigned long length, PAD *pad, unsigned long offset)
364 char *pad_data, *out;
365 unsigned int i;
367 /* Seek to area of pad we're using. */
368 if (fseek(pad->fp, offset, SEEK_SET) < 0) {
369 fprintf(stderr, "failed to seek in pad %s to %ld\n",
370 pad->name, offset);
371 perror("fseek");
372 exit(EXIT_FAILURE);
375 /* Read pad. */
376 pad_data = malloc(length);
377 if (!pad_data) {
378 perror("malloc pad data");
379 exit(EX_UNAVAILABLE);
381 if (fread(pad_data, length, 1, pad->fp) < 1) {
382 fprintf(stderr, "read pad %s, offset %ld, length %ld failed",
383 pad->name, offset, length);
384 exit(EXIT_FAILURE);
387 /* Apply XOR to give output. */
388 out = malloc(length + 1);
389 if (!out) {
390 perror("malloc output buffer");
391 exit(EX_UNAVAILABLE);
394 for (i = 0; i < length; ++i) {
395 out[i] = pad_data[i] ^ input[i];
397 free(pad_data);
399 /* Null-terminate output for convenience when using text-only messages.
400 * The output is still of length msg->length; use msg->length when decrypting
401 * binary messages. */
402 out[length] = 0;
404 return out;
408 /** Decrypt a packaged message and return plaintext.
410 * @param out Pointer to pointer which is set to the output. Caller must free.
411 * There is an extra null at the end of this buffer, not part of the output,
412 * but there may be embedded nulls if binary data is encoded. If this is expected,
413 * use the return value:
415 * @return Length of message, in bytes.
417 unsigned int otp_decrypt(char *input, char **out)
419 MESSAGE *msg;
420 unsigned int length;
422 msg = unpackage(input);
424 if (msg->offset < read_offset(msg->pad)) {
425 fprintf(stderr, "** warning: this is an old message! possible replay attack: %ld < %ld\n",
426 msg->offset, read_offset(msg->pad));
429 length = msg->length;
430 *out = xor_with_pad(msg->cipher_text, msg->length, msg->pad, msg->offset);
431 free_message(msg);
433 return length;
436 /** Encrypt and package a message.
438 * @return Encrypted, packaged message. Caller must free.
440 char *otp_encrypt(char *input, unsigned int length, char *to)
442 PAD *p;
443 char *out;
444 unsigned int out_length;
445 unsigned long offset;
446 char *b64_data;
448 p = find_pad(to);
449 if (!p) {
450 fprintf(stderr, "otp_encrypt: no such pad %s\n", to);
451 exit(EX_DATAERR);
454 /* Packaged messages are base64'd encoded so they take
455 * 4/3 as much space, plus a fixed amount for markers, plus
456 * up to OFFSET_SIZE bytes for the offset. To avoid possible
457 * rounding errors in calculating the size, just multiply by 2.
458 * This overestimates and should be more than enough space.
460 out_length = strlen(MARKER_BEGIN) + 1 + OFFSET_SIZE + 1 +
461 1 + length * 2 + 1 + strlen(MARKER_END) + 1;
462 out = malloc(out_length);
463 if (!out) {
464 perror("malloc otp_decrypt output");
465 exit(EX_DATAERR);
468 /* Use current offset. */
469 offset = read_offset(p);
471 b64_data = "(todo)";
473 snprintf(out, out_length, "%s%d,%s,\n"
474 "%s\n"
475 "%s\n",
476 MARKER_BEGIN,
477 offset,
479 b64_data,
480 MARKER_END);
482 return out;