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