1 /** Practical One-time Pad Library
17 /** Show a list of all loaded pads. */
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
)
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
) {
44 snprintf(offset_filename
, filename_length
, "%s" OFFSET_FILE_EXTENSION
, p
->local_filename
);
46 /* Read offset from file. */
47 ofp
= fopen(offset_filename
, mode
);
49 fprintf(stderr
, "opening offset file %s failed\n", offset_filename
);
51 free(offset_filename
);
54 free(offset_filename
);
59 /** Read the pad offset of a given pad. */
60 unsigned long read_offset(PAD
*p
)
63 char buffer
[OFFSET_SIZE
];
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
);
74 if (fclose(ofp
) != 0) {
75 fprintf(stderr
, "error closing offset file for reading for %s\n", p
->local_filename
);
80 /* We finally got it! */
81 offset
= strtoul(buffer
, NULL
, 10);
86 /** Write the pad offset to the given pad. */
87 void write_offset(PAD
*p
, unsigned long offset
)
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
);
103 if (fclose(ofp
) != 0) {
104 fprintf(stderr
, "error closing offset file for writing for %s\n", p
->local_filename
);
110 /** Load a pad file from disk, adding to 'pads' global. */
111 void load_pad(char *local_filename
, char *pad_name
)
116 fp
= fopen("/Volumes/Not Backed Up/otp/otp-dazzlement", "rb");
122 new_pad
= malloc(sizeof(PAD
));
125 exit(EX_UNAVAILABLE
);
128 new_pad
->local_filename
= strdup(local_filename
);
129 new_pad
->name
= strdup(pad_name
);
131 new_pad
->next
= NULL
;
133 /* Add to linked list. */
140 for (p
= pads
; p
; p
= p
->next
)
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
)
160 while ((c
= fgetc(fp
)) != EOF
) {
168 if (c
== EOF
&& i
== 0)
175 void load_config(char *config_filename
)
178 char line
[MAX_CONFIG_LINE
];
180 cfg
= fopen(config_filename
, "rt");
182 fprintf(stderr
, "failed to open configuration file %s\n", config_filename
);
183 perror("fopen config file");
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
, "=");
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
)
208 /* Null or blank pad = default (first) pad. */
209 if (!pad_name
|| !strcmp(pad_name
, ""))
212 for (p
= pads
; p
; p
= p
->next
) {
213 if (!strcmp(p
->name
, pad_name
))
220 /** Close all pads and free allocated memory. */
225 for (p
= pads
; p
; p
= next
) {
227 free(p
->local_filename
);
234 void free_message(MESSAGE
*msg
)
236 free(msg
->cipher_text
);
240 /** Unpackage a message packaged for transport.
242 * Caller must free_message(). */
243 MESSAGE
*unpackage(char *input
)
247 char *s
, *end
, *b64_ct
;
248 char pad_name
[PAD_NAME_LENGTH
];
250 msg
= malloc(sizeof(MESSAGE
));
253 exit(EX_UNAVAILABLE
);
256 /** Format <v0.7 (pad name is unspecified; use default):
257 * MARKER_BEGIN + offset + comma
262 * MARKER_BEGIN + offset + comma + pad_name + comma
269 /* Locate where the message begins. */
270 s
= strstr(input
, MARKER_BEGIN
);
272 fprintf(stderr
, "unpackage: input |%s| lacks beginning marker %s\n",
273 input
, MARKER_BEGIN
);
278 end
= strstr(input
, MARKER_END
);
280 fprintf(stderr
, "unpackage: input |%s| lacks ending marker %s\n",
285 s
+= strlen(MARKER_BEGIN
);
286 msg
->offset
= strtoul(s
, &s
, 10);
288 printf("offset=%ld\n", msg
->offset
);
290 /* Move after mandatory comma. */
292 fprintf(stderr
, "unpackage: missing comma after offset, at |%s|, input |%s|\n",
298 memset(pad_name
, 0, PAD_NAME_LENGTH
);
300 /* Includes pad name? */
301 if (s
[0] != '\n' && s
[0] != '\r') {
305 /* v0.7+ message, includes pad name */
306 while(s
[0] != '\n' && s
[0] != '\r' && s
[0] != ',' && s
< end
) {
310 if (i
> PAD_NAME_LENGTH
) {
311 fprintf(stderr
, "unpackage: pad name length > maximum %d, in input |%s|\n",
312 PAD_NAME_LENGTH
, input
);
318 printf("Pad name: |%s|\n", pad_name
);
320 msg
->pad
= find_pad(pad_name
);
322 fprintf(stderr
, "No such pad by name '%s'\n", pad_name
);
326 /* Go to next line */
327 while((s
[0] == '\n' || s
[0] == '\r' || s
[0] == ',') && (s
< end
))
332 /* Extract base64 data from end of message. */
336 printf("b64_data=<%s>\n", b64_ct
);
339 msg
->cipher_text
= malloc(strlen(b64_ct
));
340 msg
->length
= base64_decode(b64_ct
, msg
->cipher_text
);
343 printf("decoded to %ld bytes\n", msg
->length
);
348 /** Decrypt a packaged message and return plaintext.
350 * @param out Pointer to pointer which is set to the output. Caller must free.
351 * There is an extra null at the end of this buffer, not part of the output,
352 * but there may be embedded nulls if binary data is encoded. If this is expected,
353 * use the return value:
355 * @return Length of message, in bytes.
357 unsigned int otp_decrypt(char *input
, char **out
)
362 msg
= unpackage(input
);
363 length
= msg
->length
;
364 *out
= otp_decrypt_msg(msg
);
370 /** Deccrypt an encrypted message.
372 * @return Decrypted message, of length msg->length. Caller frees.
374 char *otp_decrypt_msg(MESSAGE
*msg
)
376 char *pad_data
, *out
;
379 /* Seek to area of pad we're using. */
380 if (fseek(msg
->pad
->fp
, msg
->offset
, SEEK_SET
) < 0) {
381 fprintf(stderr
, "failed to seek in pad %s to %ld\n",
382 msg
->pad
->name
, msg
->offset
);
388 pad_data
= malloc(msg
->length
);
390 perror("malloc pad data");
391 exit(EX_UNAVAILABLE
);
393 if (fread(pad_data
, msg
->length
, 1, msg
->pad
->fp
) < 1) {
394 fprintf(stderr
, "read pad %s, offset %ld, length %ld failed",
395 msg
->pad
->name
, msg
->offset
, msg
->length
);
399 /* Apply XOR to give output. */
400 out
= malloc(msg
->length
+ 1);
402 perror("malloc output buffer");
403 exit(EX_UNAVAILABLE
);
406 for (i
= 0; i
< msg
->length
; ++i
) {
407 out
[i
] = pad_data
[i
] ^ msg
->cipher_text
[i
];
410 /* Null-terminate output for convenience when using text-only messages.
411 * The output is still of length msg->length; use msg->length when decrypting
412 * binary messages. */
413 out
[msg
->length
+ 1] = 0;