OSDN Git Service

Enhanced self-test routine + added macro to "safely" free SlunkCrypt instance.
[slunkcrypt/SlunkCrypt.git] / frontend / src / crypt.c
1 /******************************************************************************/
2 /* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
3 /* This work has been released under the CC0 1.0 Universal license!           */
4 /******************************************************************************/
5
6 #ifdef _WIN32
7 #  define _CRT_SECURE_NO_WARNINGS 1
8 #else
9 #  define _GNU_SOURCE 1
10 #endif
11
12 /* Internal */
13 #include "crypt.h"
14 #include <slunkcrypt.h>
15 #include "utils.h"
16 #include "blake2.h"
17
18 /* CRT */
19 #include <time.h>
20 #include <inttypes.h>
21 #include <ctype.h>
22 #include <assert.h>
23 #include <errno.h>
24
25 // ==========================================================================
26 // Constants
27 // ==========================================================================
28
29 static const uint64_t MAGIC_NUMBER = 0x243F6A8885A308D3ull;
30
31 #define BUFFER_SIZE 65536U
32
33 // ==========================================================================
34 // Auxiliary functions
35 // ==========================================================================
36
37 static int open_files(FILE **const file_in, FILE **const file_out, const CHR *const input_path, const CHR *const output_path)
38 {
39         if (!(*file_in = FOPEN(input_path, T("rb"))))
40         {
41                 FPRINTF(stderr, T("Error: Failed to open input file \"%") T(PRISTR) T("\" for reading!\n\n%") T(PRISTR) T("\n\n"), input_path, STRERROR(errno));
42                 *file_out = NULL;
43                 return EXIT_FAILURE;
44         }
45
46         if (!(*file_out = FOPEN(output_path, T("wb"))))
47         {
48                 FPRINTF(stderr, T("Error: Failed to open output file \"%") T(PRISTR) T("\" for writing!\n\n%") T(PRISTR) T("\n\n"), output_path, STRERROR(errno));
49                 return EXIT_FAILURE;
50         }
51
52         setvbuf(*file_in,  NULL, _IOFBF, (((8U * BUFFER_SIZE) + (BUFSIZ - 1U)) / BUFSIZ) * BUFSIZ);
53         setvbuf(*file_out, NULL, _IOFBF, (((8U * BUFFER_SIZE) + (BUFSIZ - 1U)) / BUFSIZ) * BUFSIZ);
54
55         return EXIT_SUCCESS;
56 }
57
58 static void init_slunk_param(slunkparam_t *const param, const crypt_options_t *const options)
59 {
60         slunkcrypt_bzero(param, sizeof(slunkparam_t));
61         param->version = SLUNKCRYPT_PARAM_VERSION;
62         param->thread_count = options->thread_count;
63 }
64
65 #define UPDATE_PROGRESS_INDICATOR(CLK_UPDATE, CURRENT, TOTAL) do \
66 { \
67         const uint64_t clk_now = clock_read(); \
68         if ((clk_now < (CLK_UPDATE)) || (clk_now - (CLK_UPDATE) > update_interval)) \
69         { \
70                 FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%% "), ((CURRENT) / ((double)(TOTAL))) * 100.0); \
71                 fflush(stderr); \
72                 CLK_UPDATE = clk_now; \
73         } \
74 } \
75 while(0)
76
77 // ==========================================================================
78 // Encrypt
79 // ==========================================================================
80
81 int encrypt(const char *const passphrase, const CHR *const input_path, const CHR *const output_path, const crypt_options_t *const options)
82 {
83         slunkcrypt_t ctx = SLUNKCRYPT_NULL;
84         slunkparam_t param;
85         FILE* file_in = NULL, * file_out = NULL;
86         int result = EXIT_FAILURE, status;
87
88         uint8_t *buffer = malloc(BUFFER_SIZE * sizeof(uint8_t));
89         if (!buffer)
90         {
91                 FPUTS(T("Error: Failed to allocate the I/O buffer!\n\n"), stderr);
92                 goto clean_up;
93         }
94
95         if (open_files(&file_in, &file_out, input_path, output_path) != EXIT_SUCCESS)
96         {
97                 goto clean_up;
98         }
99
100         const uint64_t file_size = get_size(file_in);
101         if (file_size == UINT64_MAX)
102         {
103                 FPUTS(T("I/O error: Failed to determine size of input file!\n\n"), stderr);
104                 goto clean_up;
105         }
106         else if (file_size < 1U)
107         {
108                 FPUTS(T("Error: Input file is empty or an unsupported type!\n\n"), stderr);
109                 goto clean_up;
110         }
111
112         FPUTS(T("Encrypting file contents, please be patient... "), stderr);
113         fflush(stderr);
114
115         uint64_t nonce;
116         if (slunkcrypt_generate_nonce(&nonce) != SLUNKCRYPT_SUCCESS)
117         {
118                 FPUTS(T("\n\nSlunkCrypt error: Failed to generate nonce!\n\n"), stderr);
119                 goto  clean_up;
120         }
121
122         init_slunk_param(&param, options);
123         ctx = slunkcrypt_alloc_ext(nonce, (const uint8_t*)passphrase, strlen(passphrase), SLUNKCRYPT_ENCRYPT, &param);
124         if (!ctx)
125         {
126                 FPUTS(g_slunkcrypt_abort_flag ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to initialize encryption!\n\n"), stderr);
127                 goto clean_up;
128         }
129
130         if (fwrite_ui64(nonce ^ MAGIC_NUMBER, file_out) < 1U)
131         {
132                 FPUTS(T("\n\nI/O error: Failed to write nonce value!\n\n"), stderr);
133                 goto clean_up;
134         }
135
136         uint64_t bytes_read = 0U, clk_update = clock_read();
137         const uint64_t update_interval = (uint64_t)(clock_freq() * 1.414);
138
139         blake2s_t blake2s_state;
140         blake2s_init(&blake2s_state);
141
142         FPRINTF(stderr, T("%5.1f%% "), 0.0);
143         fflush(stderr);
144
145         while (bytes_read < file_size)
146         {
147                 const uint64_t bytes_remaining = file_size - bytes_read;
148                 const size_t request_len = (bytes_remaining < BUFFER_SIZE) ? ((size_t)bytes_remaining) : BUFFER_SIZE;
149                 const size_t count = fread(buffer, sizeof(uint8_t), request_len, file_in);
150                 if (count > 0U)
151                 {
152                         blake2s_update(&blake2s_state, buffer, count);
153                         bytes_read += count;
154                         if ((status = slunkcrypt_inplace(ctx, buffer, count)) != SLUNKCRYPT_SUCCESS)
155                         {
156                                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt data!\n\n"), stderr);
157                                 goto clean_up;
158                         }
159                         if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
160                         {
161                                 FPUTS(T("\n\nI/O error: Failed to write encrypted data!\n\n"), stderr);
162                                 goto clean_up;
163                         }
164                 }
165                 if (count < request_len)
166                 {
167                         break; /*EOF*/
168                 }
169                 UPDATE_PROGRESS_INDICATOR(clk_update, bytes_read, file_size);
170         }
171
172         if (ferror(file_in))
173         {
174                 FPUTS(T("\n\nI/O error: Failed to read input data!\n\n"), stderr);
175                 goto clean_up;
176         }
177
178         if (bytes_read != file_size)
179         {
180                 FPUTS(T("\n\nI/O error: Input file could not be fully read!\n\n"), stderr);
181                 goto clean_up;
182         }
183
184         const size_t padding = sizeof(uint64_t) - (file_size % sizeof(uint64_t));
185         assert(padding && (padding <= sizeof(uint64_t)));
186         if (slunkcrypt_random_bytes(buffer, padding) < padding)
187         {
188                 FPUTS(T("\n\nSlunkCrypt error: Failed to generate random data!\n\n"), stderr);
189                 goto clean_up;
190         }
191
192         SET_LOWBITS(buffer[padding - 1U], padding - 1U);
193         if ((status = slunkcrypt_inplace(ctx, buffer, padding)) != SLUNKCRYPT_SUCCESS)
194         {
195                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt data!\n\n"), stderr);
196                 goto clean_up;
197         }
198
199         if (fwrite(buffer, sizeof(uint8_t), padding, file_out) < padding)
200         {
201                 FPUTS(T("\n\nI/O error: Failed to write padding data!\n\n"), stderr);
202                 goto clean_up;
203         }
204
205         uint8_t checksum_buffer[sizeof(uint64_t)];
206         store_ui64(checksum_buffer, blake2s_final(&blake2s_state));
207
208         if ((status = slunkcrypt_inplace(ctx, checksum_buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
209         {
210                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt checksum!\n\n"), stderr);
211                 goto  clean_up;
212         }
213
214         if (fwrite(checksum_buffer, sizeof(uint8_t), sizeof(uint64_t), file_out) < sizeof(uint64_t))
215         {
216                 FPUTS(T("\n\nI/O error: Failed to write the checksum!\n\n"), stderr);
217                 goto clean_up;
218         }
219
220         FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%%\n\n"), 100.0);
221
222         result = EXIT_SUCCESS;
223
224         FPUTS(T("All is done.\n\n"), stderr);
225         fflush(stderr);
226
227 clean_up:
228
229         SLUNKCRYPT_SAFE_FREE(ctx);
230
231         if (file_out)
232         {
233                 fclose(file_out);
234                 if ((result != EXIT_SUCCESS) && (!options->keep_incomplete))
235                 {
236                         if (REMOVE(output_path))
237                         {
238                                 FPUTS(T("Warning: Failed to remove incomplete output file!\n\n"), stderr);
239                         }
240                 }
241         }
242
243         if (file_in)
244         {
245                 fclose(file_in);
246         }
247
248         if (buffer)
249         {
250                 slunkcrypt_bzero(buffer, BUFFER_SIZE * sizeof(uint8_t));
251                 free(buffer);
252         }
253
254         slunkcrypt_bzero(checksum_buffer, sizeof(uint64_t));
255         slunkcrypt_bzero(&blake2s_state, sizeof(blake2s_t));
256         slunkcrypt_bzero(&nonce, sizeof(uint64_t));
257
258         return result;
259 }
260
261 // ==========================================================================
262 // Decrypt
263 // ==========================================================================
264
265 int decrypt(const char *const passphrase, const CHR *const input_path, const CHR *const output_path, const crypt_options_t *const options)
266 {
267         slunkcrypt_t ctx = SLUNKCRYPT_NULL;
268         slunkparam_t param;
269         FILE *file_in = NULL, *file_out = NULL;
270         int result = EXIT_FAILURE, status;
271
272         uint8_t *buffer = malloc(BUFFER_SIZE * sizeof(uint8_t));
273         if (!buffer)
274         {
275                 FPUTS(T("Error: Failed to allocate the I/O buffer!\n\n"), stderr);
276                 goto clean_up;
277         }
278
279         if (open_files(&file_in, &file_out, input_path, output_path) != EXIT_SUCCESS)
280         {
281                 goto clean_up;
282         }
283
284         const uint64_t file_size = get_size(file_in);
285         if (file_size == UINT64_MAX)
286         {
287                 FPUTS(T("I/O error: Failed to determine size of input file!\n\n"), stderr);
288                 goto clean_up;
289         }
290         else if (file_size < (3U * sizeof(uint64_t)))
291         {
292                 FPUTS(T("Error: Input file is too small! Truncated?\n\n"), stderr);
293                 goto clean_up;
294         }
295         else if ((file_size % sizeof(uint64_t)) != 0)
296         {
297                 FPRINTF(stderr, T("Warning: File size is *not* an integer multiple of %u, ignoring excess bytes!\n\n"), (unsigned)sizeof(uint64_t));
298         }
299
300         FPUTS(T("Decrypting file contents, please be patient... "), stderr);
301         fflush(stderr);
302
303         uint64_t nonce;
304         if (fread_ui64(&nonce, file_in) < 1U)
305         {
306                 FPUTS(T("\n\nI/O error: Failed to read nonce value!\n\n"), stderr);
307                 goto clean_up;
308         }
309
310         init_slunk_param(&param, options);
311         ctx = slunkcrypt_alloc_ext(nonce ^ MAGIC_NUMBER, (const uint8_t*)passphrase, strlen(passphrase), SLUNKCRYPT_DECRYPT, &param);
312         if (!ctx)
313         {
314                 FPUTS(g_slunkcrypt_abort_flag ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to initialize decryption!\n\n"), stderr);
315                 goto clean_up;
316         }
317
318         uint64_t bytes_read = sizeof(uint64_t), clk_update = clock_read();
319         const uint64_t update_interval = (uint64_t)(clock_freq() * 1.414);
320         const uint64_t read_limit = round_down(file_size, sizeof(uint64_t)) - (2U * sizeof(uint64_t));
321
322         blake2s_t blake2s_state;
323         blake2s_init(&blake2s_state);
324
325         FPRINTF(stderr, T("%5.1f%% "), 0.0);
326         fflush(stderr);
327
328         while (bytes_read < read_limit)
329         {
330                 const uint64_t bytes_remaining = read_limit - bytes_read;
331                 const size_t request_len = (bytes_remaining < BUFFER_SIZE) ? ((size_t)bytes_remaining) : BUFFER_SIZE;
332                 const size_t count = fread(buffer, sizeof(uint8_t), request_len, file_in);
333                 if (count > 0U)
334                 {
335                         bytes_read += count;
336                         if ((status = slunkcrypt_inplace(ctx, buffer, count)) != SLUNKCRYPT_SUCCESS)
337                         {
338                                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt data!\n\n"), stderr);
339                                 goto clean_up;
340                         }
341                         blake2s_update(&blake2s_state, buffer, count);
342                         if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
343                         {
344                                 FPUTS(T("failed!\n\nI/O error: Failed to write decrypted data!\n\n"), stderr);
345                                 goto clean_up;
346                         }
347                 }
348                 if (count < request_len)
349                 {
350                         break; /*EOF*/
351                 }
352                 UPDATE_PROGRESS_INDICATOR(clk_update, bytes_read, read_limit);
353         }
354
355         if (ferror(file_in))
356         {
357                 FPUTS(T("\n\nI/O error: Failed to read input data!\n\n"), stderr);
358                 goto clean_up;
359         }
360
361         if (bytes_read != read_limit)
362         {
363                 FPUTS(T("\n\nI/O error: Input file could not be fully read!\n\n"), stderr);
364                 goto clean_up;
365         }
366
367         if (fread(buffer, sizeof(uint8_t), sizeof(uint64_t), file_in) < sizeof(uint64_t))
368         {
369                 FPUTS(T("\n\nI/O error: Failed to read final block!\n\n"), stderr);
370                 goto clean_up;
371         }
372
373         if ((status = slunkcrypt_inplace(ctx, buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
374         {
375                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt data!\n\n"), stderr);
376                 goto  clean_up;
377         }
378
379         const size_t padding = GET_LOWBITS(buffer[sizeof(uint64_t) - 1U]) + 1U;
380         assert(padding && (padding <= sizeof(uint64_t)));
381         if (padding != sizeof(uint64_t))
382         {
383                 const size_t count = sizeof(uint64_t) - padding;
384                 if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
385                 {
386                         FPUTS(T("failed!\n\nI/O error: Failed to write decrypted data!\n\n"), stderr);
387                         goto clean_up;
388                 }
389                 blake2s_update(&blake2s_state, buffer, count);
390         }
391
392         const uint64_t checksum_actual = blake2s_final(&blake2s_state);
393
394         uint8_t checksum_buffer[sizeof(uint64_t)];
395         if (fread(checksum_buffer, sizeof(uint8_t), sizeof(uint64_t), file_in) < sizeof(uint64_t))
396         {
397                 FPUTS(T("\n\nI/O error: Failed to read the checksum!\n\n"), stderr);
398                 goto clean_up;
399         }
400
401         if ((status = slunkcrypt_inplace(ctx, checksum_buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
402         {
403                 FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt checksum!\n\n"), stderr);
404                 goto clean_up;
405         }
406
407         FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%%\n\n"), 100.0);
408
409         const uint64_t checksum_stored = load_ui64(checksum_buffer);
410         if (checksum_actual != checksum_stored)
411         {
412                 FPRINTF(stderr, T("Error: Checksum mismatch detected! [expected: 0x%016") T(PRIX64) T(", actual: 0x%016") T(PRIX64) T("]\n\n"), checksum_stored, checksum_actual);
413                 FPUTS(T("Wrong passphrase or corrupted file?\n\n"), stderr);
414                 goto clean_up;
415         }
416
417         result = EXIT_SUCCESS;
418
419         FPUTS(T("Checksum is correct.\n\n"), stderr);
420         fflush(stderr);
421
422 clean_up:
423
424         SLUNKCRYPT_SAFE_FREE(ctx);
425
426         if (file_out)
427         {
428                 fclose(file_out);
429                 if ((result != EXIT_SUCCESS) && (!options->keep_incomplete))
430                 {
431                         if (REMOVE(output_path))
432                         {
433                                 FPUTS(T("Warning: Failed to remove incomplete output file!\n\n"), stderr);
434                         }
435                 }
436         }
437
438         if (file_in)
439         {
440                 fclose(file_in);
441         }
442
443         if (buffer)
444         {
445                 slunkcrypt_bzero(buffer, BUFFER_SIZE * sizeof(uint8_t));
446                 free(buffer);
447         }
448
449         slunkcrypt_bzero(checksum_buffer, sizeof(uint64_t));
450         slunkcrypt_bzero(&blake2s_state, sizeof(blake2s_t));
451         slunkcrypt_bzero(&nonce, sizeof(uint64_t));
452         slunkcrypt_bzero((void*)&checksum_stored, sizeof(uint64_t));
453         slunkcrypt_bzero((void*)&checksum_actual, sizeof(uint64_t));
454
455         return result;
456 }