1 /*
2    Copyright 2020 Northern.tech AS
3 
4    This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6    This program is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the
8    Free Software Foundation; version 3.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 /*
26    cf-secret.c
27 
28    Copyright (C) 2017 cfengineers.net
29 
30    Written and maintained by Jon Henrik Bjornstad <jonhenrik@cfengineers.net>
31 
32    Licensed under the Apache License, Version 2.0 (the "License");
33    you may not use this file except in compliance with the License.
34    You may obtain a copy of the License at
35 
36        http://www.apache.org/licenses/LICENSE-2.0
37 
38    Unless required by applicable law or agreed to in writing, software
39    distributed under the License is distributed on an "AS IS" BASIS,
40    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
41    See the License for the specific language governing permissions and
42    limitations under the License.
43 
44 */
45 
46 #include <platform.h>
47 #include <openssl/err.h>
48 
49 #include <lastseen.h>
50 #include <crypto.h>
51 #include <generic_agent.h> /* GenericAgentSetDefaultDigest */
52 #include <writer.h>
53 #include <man.h>
54 #include <conversion.h>
55 #include <hash.h>
56 #include <known_dirs.h>
57 #include <string_lib.h>
58 #include <file_lib.h>
59 #include <sequence.h>
60 #include <string_sequence.h>
61 #include <unistd.h>
62 
63 #define BUFSIZE 1024
64 
65 #define MAX_HEADER_LEN 256      /* "Key[126]: Value[128]" */
66 #define MAX_HEADER_KEY_LEN 126
67 #define MAX_HEADER_VAL_LEN 128
68 
69 typedef enum {
70     HOST_RSA_KEY_PRIVATE,
71     HOST_RSA_KEY_PUBLIC,
72 } HostRSAKeyType;
73 
74 /* see README.md for details about the format */
75 typedef enum {
76     CF_SECRET_FORMAT_V_1_0,
77 } CFKeyCryptFormatVersion;
78 
79 static const char passphrase[] = "Cfengine passphrase";
80 
81 //*******************************************************************
82 // DOCUMENTATION / GETOPT CONSTS:
83 //*******************************************************************
84 
85 static const char *const CF_SECRET_SHORT_DESCRIPTION =
86     "cf-secret: Use CFEngine cryptographic keys to encrypt and decrypt files";
87 
88 static const char *const CF_SECRET_MANPAGE_LONG_DESCRIPTION =
89     "cf-secret offers a simple way to encrypt or decrypt files using keys "
90     "generated by cf-key. CFEngine uses asymmetric cryptography, and "
91     "cf-secret allows you to encrypt a file using a public key file. "
92     "The encrypted file can only be decrypted on the host with the "
93     "corresponding private key. Original author: Jon Henrik Bjornstad "
94     "<jonhenrik@cfengineers.net>";
95 
96 static const struct option OPTIONS[] =
97 {
98     {"help",        no_argument,        0, 'h'},
99     {"manpage",     no_argument,        0, 'M'},
100     {"debug",       no_argument,        0, 'd'},
101     {"verbose",     no_argument,        0, 'v'},
102     {"log-level",   required_argument,  0, 'g'},
103     {"inform",      no_argument,        0, 'I'},
104     {"key",         required_argument,  0, 'k'},
105     {"host",        required_argument,  0, 'H'},
106     {"output",      required_argument,  0, 'o'},
107     {NULL,          0,                  0, '\0'}
108 };
109 
110 static const char *const HINTS[] =
111 {
112     "Print the help message",
113     "Print the man page",
114     "Enable debugging output",
115     "Enable verbose output",
116     "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'",
117     "Enable basic information output",
118     "Comma-separated list of key files to use (one of -k/-H options is required for encryption)",
119     "Comma-separated list of hosts to encrypt/decrypt for (defaults to 'localhost' for decryption)",
120     "Output file (required)",
121     NULL
122 };
123 
124 static const Description COMMANDS[] =
125 {
126     {"encrypt", "Encrypt data for one or more hosts/keys", "cf-secret encrypt -k/-H KEY/HOST -o OUTPUT INPUT"},
127     {"decrypt", "Decrypt data", "cf-secret decrypt [-k/-H KEY/HOST] -o OUTPUT INPUT"},
128     {"print-headers", "Print headers from an encrypted file", "cf-secret print-headers ENCRYPTED_FILE"},
129     {NULL, NULL, NULL}
130 };
131 
GetIPAddress(struct sockaddr * sa)132 static inline void *GetIPAddress(struct sockaddr *sa)
133 {
134     assert(sa != NULL);
135     if (sa->sa_family == AF_INET)
136     {
137         return &(((struct sockaddr_in*)sa)->sin_addr);
138     }
139     return &(((struct sockaddr_in6*)sa)->sin6_addr);
140 }
141 
142 /**
143  * Get path of the RSA key for the given host.
144  */
GetHostRSAKey(const char * host,HostRSAKeyType type)145 static char *GetHostRSAKey(const char *host, HostRSAKeyType type)
146 {
147     const char *key_ext = NULL;
148     if (type == HOST_RSA_KEY_PRIVATE)
149     {
150         key_ext = ".priv";
151     }
152     else
153     {
154         key_ext = ".pub";
155     }
156 
157     struct addrinfo *result;
158     int error = getaddrinfo(host, NULL, NULL, &result);
159     if (error != 0)
160     {
161         Log(LOG_LEVEL_ERR, "Failed to get IP from host (getaddrinfo: %s)",
162             gai_strerror(error));
163         return NULL;
164     }
165 
166     char *buffer = malloc(BUFSIZE);
167     char hash[CF_HOSTKEY_STRING_SIZE];
168     char ipaddress[64];
169     bool found = false;
170     for (struct addrinfo *res = result; !found && (res != NULL); res = res->ai_next)
171     {
172         inet_ntop(res->ai_family,
173                   GetIPAddress((struct sockaddr *) res->ai_addr),
174                   ipaddress, sizeof(ipaddress));
175         if (StringStartsWith(ipaddress, "127.") || StringEqual(ipaddress, "::1"))
176         {
177             Log(LOG_LEVEL_VERBOSE, "Using localhost%s key", key_ext);
178             found = true;
179             snprintf(buffer, BUFSIZE, "%s/ppkeys/localhost%s", WORKDIR, key_ext);
180             return buffer;
181         }
182         found = Address2Hostkey(hash, sizeof(hash), ipaddress);
183     }
184     if (found)
185     {
186         Log(LOG_LEVEL_DEBUG, "Found host '%s' for address '%s'", hash, ipaddress);
187         snprintf(buffer, BUFSIZE, "%s/ppkeys/root-%s%s", WORKDIR, hash, key_ext);
188         freeaddrinfo(result);
189         return buffer;
190     }
191     else
192     {
193         Log(LOG_LEVEL_DEBUG, "Searching key by IP");
194         for (struct addrinfo *res = result; res != NULL; res = res->ai_next)
195         {
196             inet_ntop(res->ai_family,
197                       GetIPAddress((struct sockaddr *) res->ai_addr),
198                       ipaddress, sizeof(ipaddress));
199             snprintf(buffer, BUFSIZE, "%s/ppkeys/root-%s%s", WORKDIR, ipaddress, key_ext);
200             if (access(buffer, F_OK) == 0)
201             {
202                 Log(LOG_LEVEL_DEBUG, "Found matching key: '%s'", buffer);
203                 freeaddrinfo(result);
204                 return buffer;
205             }
206         }
207     }
208     return NULL;
209 }
210 
ReadPrivateKey(const char * privkey_path)211 static RSA *ReadPrivateKey(const char *privkey_path)
212 {
213     FILE *fp = safe_fopen(privkey_path,"r");
214 
215     if (fp == NULL)
216     {
217         Log(LOG_LEVEL_ERR, "Could not open private key '%s'", privkey_path);
218         return NULL;
219     }
220     RSA *privkey = PEM_read_RSAPrivateKey(fp, (RSA **) NULL, NULL, (void *) passphrase);
221     if (privkey == NULL)
222     {
223         unsigned long err = ERR_get_error();
224         Log(LOG_LEVEL_ERR, "Could not read private key '%s': %s",
225             privkey_path, ERR_reason_error_string(err));
226     }
227     fclose(fp);
228     return privkey;
229 }
230 
ReadPublicKey(const char * pubkey_path)231 static RSA *ReadPublicKey(const char *pubkey_path)
232 {
233     FILE *fp = safe_fopen(pubkey_path, "r");
234 
235     if (fp == NULL)
236     {
237         Log(LOG_LEVEL_ERR, "Could not open public key '%s'", pubkey_path);
238         return NULL;
239     }
240 
241     RSA *pubkey = PEM_read_RSAPublicKey(fp, NULL, NULL, (void *) passphrase);
242     if (pubkey == NULL)
243     {
244         unsigned long err = ERR_get_error();
245         Log(LOG_LEVEL_ERR, "Could not read public key '%s': %s",
246             pubkey_path, ERR_reason_error_string(err));
247     }
248     fclose(fp);
249     return pubkey;
250 }
251 
OpenInputOutput(const char * path,const char * mode)252 static FILE *OpenInputOutput(const char *path, const char *mode)
253 {
254     assert(path != NULL);
255     assert(mode != NULL);
256     if (StringEqual(path, "-"))
257     {
258         if (*mode == 'r')
259         {
260             return stdin;
261         }
262         else
263         {
264             return stdout;
265         }
266     }
267     return safe_fopen(path, mode);
268 }
269 
RSAEncrypt(Seq * rsa_keys,const char * input_path,const char * output_path)270 static bool RSAEncrypt(Seq *rsa_keys, const char *input_path, const char *output_path)
271 {
272     assert((rsa_keys != NULL) && (SeqLength(rsa_keys) > 0));
273 
274     FILE *input_file = OpenInputOutput(input_path, "r");
275     if (input_file == NULL)
276     {
277         Log(LOG_LEVEL_ERR, "Could not open input file '%s'", input_path);
278         return false;
279     }
280 
281     FILE *output_file = OpenInputOutput(output_path, "w");
282     if (output_file == NULL)
283     {
284         Log(LOG_LEVEL_ERR, "Could not create or open output file '%s'", output_path);
285         fclose(input_file);
286         return false;
287     }
288 
289     const size_t n_keys = SeqLength(rsa_keys);
290     Seq *evp_keys = SeqNew(n_keys, EVP_PKEY_free);
291     for (size_t i = 0; i < n_keys; i++)
292     {
293         RSA *rsa_key = SeqAt(rsa_keys, i);
294         EVP_PKEY *evp_key = EVP_PKEY_new();
295         if (EVP_PKEY_set1_RSA(evp_key, rsa_key) == 0)
296         {
297             Log(LOG_LEVEL_ERR, "Failed to initialize encryption context");
298             SeqDestroy(evp_keys);
299             fclose(input_file);
300             fclose(output_file);
301             return false;
302         }
303         SeqAppend(evp_keys, evp_key);
304     }
305 
306     bool success = true;
307 
308     const EVP_CIPHER *cipher = EVP_aes_256_cbc();
309     EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
310     const int key_size = EVP_PKEY_size((EVP_PKEY*) SeqAt(evp_keys, 0));
311 
312     /* This sequence and the 'enc_key_sizes' array are both populated by the
313      * EVP_SealInit() call below. */
314     Seq *enc_keys = SeqNew(n_keys, free);
315     for (size_t i = 0; i < n_keys; i++)
316     {
317         SeqAppend(enc_keys, xmalloc(key_size));
318     }
319     int enc_key_sizes[n_keys];
320 
321     const int iv_size = EVP_CIPHER_iv_length(cipher);
322     unsigned char iv[iv_size];
323 
324     const int block_size = EVP_CIPHER_block_size(cipher);
325     char plaintext[block_size], ciphertext[2 * block_size];
326     int ct_len;
327 
328     int ret = EVP_SealInit(ctx, cipher,
329                            (unsigned char**) SeqGetData(enc_keys), enc_key_sizes, iv,
330                            (EVP_PKEY**) SeqGetData(evp_keys), n_keys);
331     if (ret == 0)
332     {
333         Log(LOG_LEVEL_ERR, "Failed to initialize encryption context");
334         success = false;
335         goto cleanup;
336     }
337 
338     /* newline and NUL-byte => +2 */
339     Log(LOG_LEVEL_VERBOSE, "Writing headers");
340     char header[MAX_HEADER_LEN + 2] = "Version: 1.0\n";
341     size_t header_len = strlen(header);
342     ssize_t n_written = FullWrite(fileno(output_file), header, header_len);
343     if (n_written != header_len)
344     {
345         Log(LOG_LEVEL_ERR, "Failed to write header to the output file '%s'", output_path);
346         success = false;
347         goto cleanup;
348     }
349     Log(LOG_LEVEL_VERBOSE, "Writing Encrypted-for headers");
350     for (size_t i = 0; i < n_keys; i++)
351     {
352         char *key_digest = GetPubkeyDigest(SeqAt(rsa_keys, i));
353         header_len = snprintf(header, MAX_HEADER_LEN + 2, "Encrypted-for: %s\n", key_digest);
354         free(key_digest);
355         assert(header_len <= (MAX_HEADER_LEN + 2));
356         n_written = FullWrite(fileno(output_file), header, header_len);
357         if (n_written != header_len)
358         {
359             Log(LOG_LEVEL_ERR, "Failed to write header to the output file '%s'", output_path);
360             success = false;
361             goto cleanup;
362         }
363     }
364     n_written = FullWrite(fileno(output_file), "\n", 1);
365     if (n_written != 1)
366     {
367         Log(LOG_LEVEL_ERR, "Failed to write header to the output file '%s'", output_path);
368         success = false;
369         goto cleanup;
370     }
371     Log(LOG_LEVEL_VERBOSE, "Writing IV");
372     n_written = FullWrite(fileno(output_file), iv, iv_size);
373     if (n_written != iv_size)
374     {
375         Log(LOG_LEVEL_ERR, "Failed to write IV to the output file '%s'", output_path);
376         success = false;
377         goto cleanup;
378     }
379 
380     Log(LOG_LEVEL_VERBOSE, "Writing keys");
381     for (size_t i = 0; i < n_keys; i++)
382     {
383         const char *enc_key = SeqAt(enc_keys, i);
384         n_written = FullWrite(fileno(output_file), enc_key, enc_key_sizes[i]);
385         if (n_written != enc_key_sizes[i])
386         {
387             Log(LOG_LEVEL_ERR, "Failed to write key to the output file '%s'", output_path);
388             success = false;
389             goto cleanup;
390         }
391     }
392 
393     size_t processed = 0;
394     while (success && !feof(input_file))
395     {
396         ssize_t n_read = ReadFileStreamToBuffer(input_file, block_size, plaintext);
397         if (n_read == FILE_ERROR_READ)
398         {
399             Log(LOG_LEVEL_ERR, "Could not read file '%s'", input_path);
400             success = false;
401             break;
402         }
403         ret = EVP_SealUpdate(ctx, ciphertext, &ct_len, plaintext, n_read);
404         if (ret == 0)
405         {
406             Log(LOG_LEVEL_ERR, "Failed to encrypt data: %s",
407                 ERR_error_string(ERR_get_error(), NULL));
408             success = false;
409             break;
410         }
411         n_written = FullWrite(fileno(output_file), ciphertext, ct_len);
412         if (n_written < 0)
413         {
414             Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
415             success = false;
416             break;
417         }
418         processed += n_read;
419         Log(LOG_LEVEL_VERBOSE, "%zu bytes processed", processed);
420     }
421     Log(LOG_LEVEL_VERBOSE, "Finalizing");
422     ret = EVP_SealFinal(ctx, ciphertext, &ct_len);
423     if (ret == 0)
424     {
425         Log(LOG_LEVEL_ERR, "Failed to encrypt data: %s",
426             ERR_error_string(ERR_get_error(), NULL));
427         success = false;
428     }
429     if (ct_len > 0)
430     {
431         n_written = FullWrite(fileno(output_file), ciphertext, ct_len);
432         if (n_written < 0)
433         {
434             Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
435             success = false;
436         }
437     }
438     OPENSSL_cleanse(plaintext, block_size);
439 
440   cleanup:
441     fclose(input_file);
442     fclose(output_file);
443     SeqDestroy(evp_keys);
444     SeqDestroy(enc_keys);
445     EVP_CIPHER_CTX_free(ctx);
446     return success;
447 }
448 
CheckHeader(const char * key,const char * value)449 static inline bool CheckHeader(const char *key, const char *value)
450 {
451     if (StringEqual(key, "Version"))
452     {
453         if (!StringEqual(value, "1.0"))
454         {
455             Log(LOG_LEVEL_ERR, "Unsupported file format version: '%s'", value);
456             return false;
457         }
458         else
459         {
460             return true;
461         }
462     }
463     else if (StringEqual(key, "Encrypted-for"))
464     {
465         /* TODO: do some verification that 'value' is valid hash digest? */
466         return true;
467     }
468     else
469     {
470         Log(LOG_LEVEL_ERR, "Unsupported header: '%s'", key);
471         return false;
472     }
473 }
474 
475 /**
476  * Read from #input_file into #buffer at most #max_chars or until #delimiter is
477  * encountered. If #stop_on_space, also stop when ' ' is read. #buffer is
478  * NUL-terminated when this function returns.
479  *
480  * @warning #buffer has to be at least #max_chars+1 big to accommodate for the
481  *          terminating NUL byte.
482  */
ReadUntilDelim(FILE * input_file,char * buffer,size_t max_chars,char delimiter,bool stop_on_space)483 static inline bool ReadUntilDelim(FILE *input_file, char *buffer, size_t max_chars, char delimiter, bool stop_on_space)
484 {
485     bool done = false;
486     size_t i = 0;
487     int c = fgetc(input_file);
488     while (!done && (i < max_chars))
489     {
490         if (c == EOF)
491         {
492             done = true;
493         }
494         else if (c == delimiter)
495         {
496             done = true;
497             ungetc(c, input_file);
498         }
499         else if (stop_on_space && (c == ' '))
500         {
501             done = true;
502             ungetc(c, input_file);
503         }
504         else
505         {
506             buffer[i] = (char) c;
507             i++;
508             if (i < max_chars)
509             {
510                 c = fgetc(input_file);
511             }
512         }
513     }
514     buffer[i] = '\0';
515 
516     bool ran_out = ((i > max_chars) || (c == EOF));
517     return !ran_out;
518 }
519 
SkipSpaces(FILE * input_file)520 static inline void SkipSpaces(FILE *input_file)
521 {
522     int c = ' ';
523     while (!feof(input_file) && (c == ' '))
524     {
525         c = fgetc(input_file);
526     }
527     if (c != ' ')
528     {
529         ungetc(c, input_file);
530     }
531 }
532 
ParseHeader(FILE * input_file,char * key,char * value)533 static inline bool ParseHeader(FILE *input_file, char *key, char *value)
534 {
535     bool ok = ReadUntilDelim(input_file, key, MAX_HEADER_KEY_LEN, ':', true);
536     if (ok)
537     {
538         SkipSpaces(input_file);
539         ok = (fgetc(input_file) == ':');
540         SkipSpaces(input_file);
541         ok = ReadUntilDelim(input_file, value, MAX_HEADER_VAL_LEN, '\n', false);
542     }
543     ok = (fgetc(input_file) == '\n');
544     return ok;
545 }
546 
ParseHeaders(FILE * input_file,RSA * privkey,size_t * enc_key_pos,size_t * n_enc_keys)547 static bool ParseHeaders(FILE *input_file, RSA *privkey, size_t *enc_key_pos, size_t *n_enc_keys)
548 {
549     assert(enc_key_pos != NULL);
550     assert(n_enc_keys != NULL);
551 
552     /* Make sure these are always set by this function. */
553     *enc_key_pos = 0;
554     *n_enc_keys = 0;
555 
556     /* Actually works for a private RSA key too because it contains the public
557      * key. */
558     char *key_digest = GetPubkeyDigest(privkey);
559     bool version_specified = false;
560     bool found_matching_digest = false;
561     size_t n_enc_for_headers = 0;
562 
563     char key[MAX_HEADER_KEY_LEN + 1];
564     char value[MAX_HEADER_VAL_LEN + 1];
565 
566     while (ParseHeader(input_file, key, value))
567     {
568         Log(LOG_LEVEL_DEBUG, "Parsed header '%s: %s'", key, value);
569         if (!CheckHeader(key, value))
570         {
571             free(key_digest);
572             return false;
573         }
574 
575         if (StringEqual(key, "Version"))
576         {
577             version_specified = true;
578         }
579         else if (StringEqual(key, "Encrypted-for"))
580         {
581             Log(LOG_LEVEL_DEBUG, "Encrypted for '%s'", value);
582             if (StringEqual(value, key_digest))
583             {
584                 found_matching_digest = true;
585                 *enc_key_pos = n_enc_for_headers;
586             }
587             n_enc_for_headers++;
588         }
589 
590         /* headers are supposed to be terminated by a blank line */
591         int next = fgetc(input_file);
592         if (next == '\n')
593         {
594             if (!version_specified)
595             {
596                 Log(LOG_LEVEL_ERR, "File format version not specified");
597             }
598             if (!found_matching_digest)
599             {
600                 Log(LOG_LEVEL_ERR, "File not encrypted for host '%s'", key_digest);
601             }
602             *n_enc_keys = n_enc_for_headers;
603             free(key_digest);
604             return (version_specified && found_matching_digest);
605         }
606         else if (next == EOF)
607         {
608             Log(LOG_LEVEL_ERR, "Failed to parse headers from");
609             free(key_digest);
610             return false;
611         }
612         else
613         {
614             /* keep trying */
615             ungetc(next, input_file);
616         }
617     }
618     Log(LOG_LEVEL_ERR, "Failed to parse headers");
619     free(key_digest);
620     return false;
621 }
622 
RSADecrypt(RSA * privkey,const char * input_path,const char * output_path)623 static bool RSADecrypt(RSA *privkey, const char *input_path, const char *output_path)
624 {
625     FILE *input_file = OpenInputOutput(input_path, "r");
626     if (input_file == NULL)
627     {
628         Log(LOG_LEVEL_ERR, "Cannot open input file '%s'", input_path);
629         return false;
630     }
631 
632     FILE *output_file = OpenInputOutput(output_path, "w");
633     if (output_file == NULL)
634     {
635         Log(LOG_LEVEL_ERR, "Cannot open output file '%s'", output_path);
636         fclose(input_file);
637         return false;
638     }
639 
640     EVP_PKEY *evp_key = EVP_PKEY_new();
641     if (EVP_PKEY_set1_RSA(evp_key, privkey) == 0)
642     {
643         Log(LOG_LEVEL_ERR, "Failed to initialize decryption context");
644         fclose(input_file);
645         fclose(output_file);
646         return false;
647     }
648 
649     bool success = true;
650 
651     Log(LOG_LEVEL_VERBOSE, "Parsing headers");
652     size_t our_key_pos;
653     size_t n_enc_keys;
654     if (!ParseHeaders(input_file, privkey, &our_key_pos, &n_enc_keys))
655     {
656         fclose(input_file);
657         fclose(output_file);
658         return false;
659     }
660     Log(LOG_LEVEL_DEBUG, "Parsed %zu keys", n_enc_keys);
661 
662     const EVP_CIPHER *cipher = EVP_aes_256_cbc();
663     EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
664 
665     const int iv_size = EVP_CIPHER_iv_length(cipher);
666     unsigned char iv[iv_size];
667 
668     const int key_size = EVP_PKEY_size(evp_key);
669     unsigned char ek[key_size];
670     unsigned char dev_null[key_size];
671 
672     const int block_size = EVP_CIPHER_block_size(cipher);
673 
674     char plaintext[block_size], ciphertext[2 * block_size];
675     int pt_len;
676 
677     ssize_t n_read = ReadFileStreamToBuffer(input_file, iv_size, iv);
678     if (n_read != iv_size)
679     {
680         Log(LOG_LEVEL_ERR, "Failed to read the IV from '%s'", input_path);
681         goto cleanup;
682     }
683 
684     /* just skip the keys that are not ours */
685     size_t nth_key = 0;
686     for (; nth_key < our_key_pos; nth_key++)
687     {
688         n_read = ReadFileStreamToBuffer(input_file, key_size, dev_null);
689         if (n_read != key_size)
690         {
691             Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
692             goto cleanup;
693         }
694         Log(LOG_LEVEL_DEBUG, "Skipping key");
695     }
696     /* read our key */
697     Log(LOG_LEVEL_DEBUG, "Reading key");
698     n_read = ReadFileStreamToBuffer(input_file, key_size, ek);
699     if (n_read != key_size)
700     {
701         Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
702         goto cleanup;
703     }
704     nth_key++;
705     /* skip the remaining keys */
706     for (; nth_key < n_enc_keys; nth_key++)
707     {
708         n_read = ReadFileStreamToBuffer(input_file, key_size, dev_null);
709         if (n_read != key_size)
710         {
711             Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
712             goto cleanup;
713         }
714         Log(LOG_LEVEL_DEBUG, "Skipping key");
715     }
716 
717     int ret = EVP_OpenInit(ctx, cipher, ek, key_size, iv, evp_key);
718     if (ret == 0)
719     {
720         char *key_digest = GetPubkeyDigest(privkey);
721         Log(LOG_LEVEL_ERR, "Failed to decrypt contents using key '%s'", key_digest);
722         free(key_digest);
723         success = false;
724         goto cleanup;
725     }
726 
727     Log(LOG_LEVEL_VERBOSE, "Decrypting data");
728     size_t processed = 0;
729     ssize_t n_written;
730     while (success && !feof(input_file))
731     {
732         n_read = ReadFileStreamToBuffer(input_file, block_size, ciphertext);
733         if (n_read == FILE_ERROR_READ)
734         {
735             Log(LOG_LEVEL_ERR, "Could not read file '%s'", input_path);
736             success = false;
737             break;
738         }
739         ret = EVP_OpenUpdate(ctx, plaintext, &pt_len, ciphertext, n_read);
740         if (ret == 0)
741         {
742             Log(LOG_LEVEL_ERR, "Failed to decrypt data: %s",
743                 ERR_error_string(ERR_get_error(), NULL));
744             success = false;
745             break;
746         }
747         n_written = FullWrite(fileno(output_file), plaintext, pt_len);
748         if (n_written < 0)
749         {
750             Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
751             success = false;
752             break;
753         }
754         processed += n_read;
755         Log(LOG_LEVEL_VERBOSE, "%zu bytes processed", processed);
756     }
757     Log(LOG_LEVEL_VERBOSE, "Finalizing");
758     ret = EVP_OpenFinal(ctx, plaintext, &pt_len);
759     if (ret == 0)
760     {
761         Log(LOG_LEVEL_ERR, "Failed to decrypt data: %s",
762             ERR_error_string(ERR_get_error(), NULL));
763         success = false;
764     }
765     if (pt_len > 0)
766     {
767         n_written = FullWrite(fileno(output_file), plaintext, pt_len);
768         if (n_written < 0)
769         {
770             Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
771             success = false;
772         }
773     }
774     OPENSSL_cleanse(plaintext, block_size);
775 
776   cleanup:
777     fclose(input_file);
778     fclose(output_file);
779     EVP_PKEY_free(evp_key);
780     EVP_CIPHER_CTX_free(ctx);
781     return success;
782 }
783 
LoadPublicKeys(Seq * key_paths)784 static Seq *LoadPublicKeys(Seq *key_paths)
785 {
786     const size_t n_keys = SeqLength(key_paths);
787     Seq *pub_keys = SeqNew(n_keys, RSA_free);
788     for (size_t i = 0; i < n_keys; i++)
789     {
790         const char *key_path = SeqAt(key_paths, i);
791         Log(LOG_LEVEL_VERBOSE, "Reading key '%s'", key_path);
792         RSA *pubkey = ReadPublicKey(key_path);
793         if (pubkey == NULL)
794         {
795             SeqDestroy(pub_keys);
796             return NULL;
797         }
798         SeqAppend(pub_keys, pubkey);
799     }
800     return pub_keys;
801 }
802 
CFKeyCryptHelp()803 static void CFKeyCryptHelp()
804 {
805     Writer *w = FileWriter(stdout);
806     WriterWriteHelp(w, "cf-secret", OPTIONS, HINTS, COMMANDS, true, true);
807     FileWriterDetach(w);
808 }
809 
CFKeyCryptMan()810 void CFKeyCryptMan()
811 {
812     Writer *out = FileWriter(stdout);
813     ManPageWrite(out, "cf-secret", time(NULL),
814                  CF_SECRET_SHORT_DESCRIPTION,
815                  CF_SECRET_MANPAGE_LONG_DESCRIPTION,
816                  OPTIONS, HINTS, COMMANDS, true, true);
817     FileWriterDetach(out);
818 }
819 
main(int argc,char * argv[])820 int main(int argc, char *argv[])
821 {
822     if (argc < 2)
823     {
824         CFKeyCryptHelp();
825         exit(EXIT_FAILURE);
826     }
827 
828     opterr = 0;
829     char *key_path_arg = NULL;
830     char *input_path = NULL;
831     char *output_path = NULL;
832     char *host_arg = NULL;
833     bool encrypt = false;
834     bool decrypt = false;
835     bool print_headers = false;
836 
837     size_t offset = 0;
838     if (StringEqual(argv[1], "encrypt"))
839     {
840         encrypt = true;
841         offset++;
842     }
843     else if (StringEqual(argv[1], "decrypt"))
844     {
845         offset++;
846         decrypt = true;
847     }
848     else if (StringEqual(argv[1], "print-headers"))
849     {
850         print_headers = true;
851         offset++;
852     }
853 
854     int c = 0;
855     while ((c = getopt_long(argc - offset, argv + offset, "hMedk:o:H:", OPTIONS, NULL)) != -1)
856     {
857         switch (c)
858         {
859             case 'h':
860                 CFKeyCryptHelp();
861                 exit(EXIT_SUCCESS);
862                 break;
863             case 'M':
864                 CFKeyCryptMan();
865                 exit(EXIT_SUCCESS);
866                 break;
867             case 'd':
868                 LogSetGlobalLevel(LOG_LEVEL_DEBUG);
869                 Log(LOG_LEVEL_DEBUG, "Debug log level enabled");
870                 break;
871             case 'v':
872                 LogSetGlobalLevel(LOG_LEVEL_VERBOSE);
873                 Log(LOG_LEVEL_VERBOSE, "Verbose log level enabled");
874                 break;
875             case 'I':
876                 LogSetGlobalLevel(LOG_LEVEL_INFO);
877                 Log(LOG_LEVEL_INFO, "Inform log level enabled");
878                 break;
879             case 'g':
880                 LogSetGlobalLevelArgOrExit(optarg);
881                 break;
882             case 'k':
883                 key_path_arg = optarg;
884                 break;
885             case 'o':
886                 output_path = optarg;
887                 break;
888             case 'H':
889                 host_arg = optarg;
890                 break;
891             default:
892                 Log(LOG_LEVEL_ERR, "Unknown option '-%c'", optopt);
893                 CFKeyCryptHelp();
894                 exit(EXIT_FAILURE);
895         }
896     }
897 
898     if (!(decrypt || encrypt || print_headers))
899     {
900         printf("Command required. Specify either 'encrypt', 'decrypt' or 'print-headers'\n");
901         CFKeyCryptHelp();
902         exit(EXIT_FAILURE);
903     }
904 
905     /* Increment 'optind' because of command being argv[0]. */
906     optind++;
907     input_path = argv[optind];
908 
909     /* Some more unexpected arguments? */
910     if (argc > (optind + offset))
911     {
912         Log(LOG_LEVEL_ERR, "Unexpected non-option argument: '%s'", argv[optind + 1]);
913         exit(EXIT_FAILURE);
914     }
915 
916     if (print_headers)
917     {
918         FILE *input_file = OpenInputOutput(input_path, "r");
919         char key[MAX_HEADER_KEY_LEN + 1];
920         char value[MAX_HEADER_VAL_LEN + 1];
921 
922         bool done = false;
923         while (!done && ParseHeader(input_file, key, value))
924         {
925             Log(LOG_LEVEL_DEBUG, "Parsed header '%s: %s'", key, value);
926             if (!CheckHeader(key, value))
927             {
928                 fclose(input_file);
929                 exit(EXIT_FAILURE);
930             }
931             printf("%s: %s\n", key, value);
932 
933             /* headers are supposed to be terminated by a blank line */
934             int next = fgetc(input_file);
935             if (next == '\n')
936             {
937                 done = true;
938             }
939             else
940             {
941                 ungetc(next, input_file);
942             }
943         }
944         fclose(input_file);
945         exit(EXIT_SUCCESS);
946     }
947 
948     if (decrypt && (host_arg == NULL) && (key_path_arg == NULL))
949     {
950         /* Decryption requires a private key which is usually only available for
951          * the local host. Let's just default to localhost if no other specific
952          * host/key is given for decryption. */
953         Log(LOG_LEVEL_VERBOSE, "Using the localhost private key for decryption");
954         host_arg = "localhost";
955     }
956 
957     if ((host_arg != NULL) && (key_path_arg != NULL))
958     {
959         Log(LOG_LEVEL_ERR,
960             "--host/-H is used to specify a public key and cannot be used with --key/-k");
961         exit(EXIT_FAILURE);
962     }
963 
964     if (input_path == NULL)
965     {
966         Log(LOG_LEVEL_ERR, "No input file specified (Use -h for help)");
967         exit(EXIT_FAILURE);
968     }
969     if (output_path == NULL)
970     {
971         Log(LOG_LEVEL_ERR, "No output file specified (Use -h for help)");
972         exit(EXIT_FAILURE);
973     }
974 
975     CryptoInitialize();
976     GenericAgentSetDefaultDigest(&CF_DEFAULT_DIGEST, &CF_DEFAULT_DIGEST_LEN);
977 
978     Seq *key_paths;
979     if (key_path_arg != NULL)
980     {
981         Log(LOG_LEVEL_DEBUG, "-k/--key given: '%s'", key_path_arg);
982         key_paths = SeqStringFromString(key_path_arg, ',');
983     }
984     else
985     {
986         key_paths = SeqNew(16, free);
987     }
988 
989     if (host_arg != NULL)
990     {
991         Log(LOG_LEVEL_DEBUG, "-H/--host given: '%s'", host_arg);
992         Seq *hosts = SeqStringFromString(host_arg, ',');
993         const size_t n_hosts = SeqLength(hosts);
994         for (size_t i = 0; i < n_hosts; i++)
995         {
996             HostRSAKeyType key_type = encrypt ? HOST_RSA_KEY_PUBLIC : HOST_RSA_KEY_PRIVATE;
997             char *host = SeqAt(hosts, i);
998             char *host_key_path = GetHostRSAKey(host, key_type);
999             if (!host_key_path)
1000             {
1001                 Log(LOG_LEVEL_ERR, "Unable to locate key for host '%s'", host);
1002                 SeqDestroy(hosts);
1003                 SeqDestroy(key_paths);
1004                 exit(EXIT_FAILURE);
1005             }
1006             SeqAppend(key_paths, host_key_path);
1007         }
1008         SeqDestroy(hosts);
1009     }
1010     assert ((key_paths != NULL) && (SeqLength(key_paths) > 0));
1011 
1012     // Encrypt or decrypt
1013     bool success;
1014     if (encrypt)
1015     {
1016         Log(LOG_LEVEL_DEBUG, "Encrypting");
1017         Seq *pub_keys = LoadPublicKeys(key_paths);
1018         SeqDestroy(key_paths);
1019         if (pub_keys == NULL)
1020         {
1021             Log(LOG_LEVEL_ERR, "Failed to load public key(s)");
1022             exit(EXIT_FAILURE);
1023         }
1024 
1025         success = RSAEncrypt(pub_keys, input_path, output_path);
1026         SeqDestroy(pub_keys);
1027         if (!success)
1028         {
1029             Log(LOG_LEVEL_ERR, "Encryption failed");
1030             exit(EXIT_FAILURE);
1031         }
1032     }
1033     else if (decrypt)
1034     {
1035         Log(LOG_LEVEL_DEBUG, "Decrypting");
1036         const size_t n_keys = SeqLength(key_paths);
1037         if (n_keys > 1)
1038         {
1039             Log(LOG_LEVEL_ERR, "--decrypt requires only one key/host to be specified");
1040             SeqDestroy(key_paths);
1041             exit(EXIT_FAILURE);
1042         }
1043         RSA *private_key = ReadPrivateKey((char *) SeqAt(key_paths, 0));
1044         SeqDestroy(key_paths);
1045         success = RSADecrypt(private_key, input_path, output_path);
1046         RSA_free(private_key);
1047         if (!success)
1048         {
1049             Log(LOG_LEVEL_ERR, "Decryption failed");
1050             exit(EXIT_FAILURE);
1051         }
1052     }
1053     else
1054     {
1055         ProgrammingError("Unexpected error in cf-secret");
1056         exit(EXIT_FAILURE);
1057     }
1058 
1059     return 0;
1060 }
1061