cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

cf-secret.c
Go to the documentation of this file.
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 #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 {
74 
75 /* see README.md for details about the format */
76 typedef enum {
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 
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  */
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 
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 
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 
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 
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 != 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 (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 
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  */
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 
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 
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 
548 static bool ParseHeaders(FILE *input_file, RSA *privkey, size_t *enc_key_pos, size_t *n_enc_keys)
549 {
550  /* Actually works for a private RSA key too because it contains the public
551  * key. */
552  char *key_digest = GetPubkeyDigest(privkey);
553  bool version_specified = false;
554  bool found_matching_digest = false;
555  size_t n_enc_for_headers = 0;
556 
557  char key[MAX_HEADER_KEY_LEN + 1];
558  char value[MAX_HEADER_VAL_LEN + 1];
559 
560  while (ParseHeader(input_file, key, value))
561  {
562  Log(LOG_LEVEL_DEBUG, "Parsed header '%s: %s'", key, value);
563  if (!CheckHeader(key, value))
564  {
565  free(key_digest);
566  return false;
567  }
568 
569  if (StringEqual(key, "Version"))
570  {
571  version_specified = true;
572  }
573  else if (StringEqual(key, "Encrypted-for"))
574  {
575  Log(LOG_LEVEL_DEBUG, "Encrypted for '%s'", value);
576  if (StringEqual(value, key_digest))
577  {
578  found_matching_digest = true;
579  *enc_key_pos = n_enc_for_headers;
580  }
581  n_enc_for_headers++;
582  }
583 
584  /* headers are supposed to be terminated by a blank line */
585  int next = fgetc(input_file);
586  if (next == '\n')
587  {
588  if (!version_specified)
589  {
590  Log(LOG_LEVEL_ERR, "File format version not specified");
591  }
592  if (!found_matching_digest)
593  {
594  Log(LOG_LEVEL_ERR, "File not encrypted for host '%s'", key_digest);
595  }
596  *n_enc_keys = n_enc_for_headers;
597  free(key_digest);
598  return (version_specified && found_matching_digest);
599  }
600  else if (next == EOF)
601  {
602  Log(LOG_LEVEL_ERR, "Failed to parse headers from");
603  free(key_digest);
604  return false;
605  }
606  else
607  {
608  /* keep trying */
609  ungetc(next, input_file);
610  }
611  }
612  Log(LOG_LEVEL_ERR, "Failed to parse headers");
613  free(key_digest);
614  return false;
615 }
616 
617 static bool RSADecrypt(RSA *privkey, const char *input_path, const char *output_path)
618 {
619  FILE *input_file = OpenInputOutput(input_path, "r");
620  if (input_file == NULL)
621  {
622  Log(LOG_LEVEL_ERR, "Cannot open input file '%s'", input_path);
623  return false;
624  }
625 
626  FILE *output_file = OpenInputOutput(output_path, "w");
627  if (output_file == NULL)
628  {
629  Log(LOG_LEVEL_ERR, "Cannot open output file '%s'", output_path);
630  fclose(input_file);
631  return false;
632  }
633 
634  EVP_PKEY *evp_key = EVP_PKEY_new();
635  if (EVP_PKEY_set1_RSA(evp_key, privkey) == 0)
636  {
637  Log(LOG_LEVEL_ERR, "Failed to initialize decryption context");
638  fclose(input_file);
639  fclose(output_file);
640  return false;
641  }
642 
643  bool success = true;
644 
645  Log(LOG_LEVEL_VERBOSE, "Parsing headers");
646  size_t our_key_pos;
647  size_t n_enc_keys;
648  if (!ParseHeaders(input_file, privkey, &our_key_pos, &n_enc_keys))
649  {
650  fclose(input_file);
651  fclose(output_file);
652  return false;
653  }
654  Log(LOG_LEVEL_DEBUG, "Parsed %zu keys", n_enc_keys);
655 
656  const EVP_CIPHER *cipher = EVP_aes_256_cbc();
657  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
658 
659  const int iv_size = EVP_CIPHER_iv_length(cipher);
660  unsigned char iv[iv_size];
661 
662  const int key_size = EVP_PKEY_size(evp_key);
663  unsigned char ek[key_size];
664  unsigned char dev_null[key_size];
665 
666  const int block_size = EVP_CIPHER_block_size(cipher);
667 
668  char plaintext[block_size], ciphertext[2 * block_size];
669  int pt_len;
670 
671  ssize_t n_read = ReadFileStreamToBuffer(input_file, iv_size, iv);
672  if (n_read != iv_size)
673  {
674  Log(LOG_LEVEL_ERR, "Failed to read the IV from '%s'", input_path);
675  goto cleanup;
676  }
677 
678  /* just skip the keys that are not ours */
679  size_t nth_key = 0;
680  for (; nth_key < our_key_pos; nth_key++)
681  {
682  n_read = ReadFileStreamToBuffer(input_file, key_size, dev_null);
683  if (n_read != key_size)
684  {
685  Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
686  goto cleanup;
687  }
688  Log(LOG_LEVEL_DEBUG, "Skipping key");
689  }
690  /* read our key */
691  Log(LOG_LEVEL_DEBUG, "Reading key");
692  n_read = ReadFileStreamToBuffer(input_file, key_size, ek);
693  if (n_read != key_size)
694  {
695  Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
696  goto cleanup;
697  }
698  nth_key++;
699  /* skip the remaining keys */
700  for (; nth_key < n_enc_keys; nth_key++)
701  {
702  n_read = ReadFileStreamToBuffer(input_file, key_size, dev_null);
703  if (n_read != key_size)
704  {
705  Log(LOG_LEVEL_ERR, "Failed to read the key from '%s'", input_path);
706  goto cleanup;
707  }
708  Log(LOG_LEVEL_DEBUG, "Skipping key");
709  }
710 
711  int ret = EVP_OpenInit(ctx, cipher, ek, key_size, iv, evp_key);
712  if (ret == 0)
713  {
714  char *key_digest = GetPubkeyDigest(privkey);
715  Log(LOG_LEVEL_ERR, "Failed to decrypt contents using key '%s'", key_digest);
716  free(key_digest);
717  success = false;
718  goto cleanup;
719  }
720 
721  Log(LOG_LEVEL_VERBOSE, "Decrypting data");
722  size_t processed = 0;
723  ssize_t n_written;
724  while (success && !feof(input_file))
725  {
726  n_read = ReadFileStreamToBuffer(input_file, block_size, ciphertext);
727  if (n_read == FILE_ERROR_READ)
728  {
729  Log(LOG_LEVEL_ERR, "Could not read file '%s'", input_path);
730  success = false;
731  break;
732  }
733  ret = EVP_OpenUpdate(ctx, plaintext, &pt_len, ciphertext, n_read);
734  if (ret == 0)
735  {
736  Log(LOG_LEVEL_ERR, "Failed to decrypt data: %s",
737  ERR_error_string(ERR_get_error(), NULL));
738  success = false;
739  break;
740  }
741  n_written = FullWrite(fileno(output_file), plaintext, pt_len);
742  if (n_written < 0)
743  {
744  Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
745  success = false;
746  break;
747  }
748  processed += n_read;
749  Log(LOG_LEVEL_VERBOSE, "%zu bytes processed", processed);
750  }
751  Log(LOG_LEVEL_VERBOSE, "Finalizing");
752  ret = EVP_OpenFinal(ctx, plaintext, &pt_len);
753  if (ret == 0)
754  {
755  Log(LOG_LEVEL_ERR, "Failed to decrypt data: %s",
756  ERR_error_string(ERR_get_error(), NULL));
757  success = false;
758  }
759  if (pt_len > 0)
760  {
761  n_written = FullWrite(fileno(output_file), plaintext, pt_len);
762  if (n_written < 0)
763  {
764  Log(LOG_LEVEL_ERR, "Could not write file '%s'", output_path);
765  success = false;
766  }
767  }
768  OPENSSL_cleanse(plaintext, block_size);
769 
770  cleanup:
771  fclose(input_file);
772  fclose(output_file);
773  EVP_PKEY_free(evp_key);
774  EVP_CIPHER_CTX_free(ctx);
775  return success;
776 }
777 
778 static Seq *LoadPublicKeys(Seq *key_paths)
779 {
780  const size_t n_keys = SeqLength(key_paths);
781  Seq *pub_keys = SeqNew(n_keys, RSA_free);
782  for (size_t i = 0; i < n_keys; i++)
783  {
784  const char *key_path = SeqAt(key_paths, i);
785  Log(LOG_LEVEL_VERBOSE, "Reading key '%s'", key_path);
786  RSA *pubkey = ReadPublicKey(key_path);
787  if (pubkey == NULL)
788  {
789  SeqDestroy(pub_keys);
790  return NULL;
791  }
792  SeqAppend(pub_keys, pubkey);
793  }
794  return pub_keys;
795 }
796 
797 static void CFKeyCryptHelp()
798 {
799  Writer *w = FileWriter(stdout);
800  WriterWriteHelp(w, "cf-secret", OPTIONS, HINTS, COMMANDS, true, true);
801  FileWriterDetach(w);
802 }
803 
805 {
806  Writer *out = FileWriter(stdout);
807  ManPageWrite(out, "cf-secret", time(NULL),
810  OPTIONS, HINTS, COMMANDS, true, true);
811  FileWriterDetach(out);
812 }
813 
814 int main(int argc, char *argv[])
815 {
816  if (argc < 2)
817  {
818  CFKeyCryptHelp();
819  DoCleanupAndExit(EXIT_FAILURE);
820  }
821 
822  opterr = 0;
823  char *key_path_arg = NULL;
824  char *input_path = NULL;
825  char *output_path = NULL;
826  char *host_arg = NULL;
827  bool encrypt = false;
828  bool decrypt = false;
829  bool print_headers = false;
830 
831  size_t offset = 0;
832  if (StringEqual(argv[1], "encrypt"))
833  {
834  encrypt = true;
835  offset++;
836  }
837  else if (StringEqual(argv[1], "decrypt"))
838  {
839  offset++;
840  decrypt = true;
841  }
842  else if (StringEqual(argv[1], "print-headers"))
843  {
844  print_headers = true;
845  offset++;
846  }
847 
848  int c = 0;
849  while ((c = getopt_long(argc - offset, argv + offset, "hMedk:o:H:", OPTIONS, NULL)) != -1)
850  {
851  switch (c)
852  {
853  case 'h':
854  CFKeyCryptHelp();
855  DoCleanupAndExit(EXIT_SUCCESS);
856  break;
857  case 'M':
858  CFKeyCryptMan();
859  DoCleanupAndExit(EXIT_SUCCESS);
860  break;
861  case 'd':
863  Log(LOG_LEVEL_DEBUG, "Debug log level enabled");
864  break;
865  case 'v':
867  Log(LOG_LEVEL_VERBOSE, "Verbose log level enabled");
868  break;
869  case 'I':
871  Log(LOG_LEVEL_INFO, "Inform log level enabled");
872  break;
873  case 'g':
875  break;
876  case 'k':
877  key_path_arg = optarg;
878  break;
879  case 'o':
880  output_path = optarg;
881  break;
882  case 'H':
883  host_arg = optarg;
884  break;
885  default:
886  Log(LOG_LEVEL_ERR, "Unknown option '-%c'", optopt);
887  CFKeyCryptHelp();
888  DoCleanupAndExit(EXIT_FAILURE);
889  }
890  }
891 
892  if (!(decrypt || encrypt || print_headers))
893  {
894  printf("Command required. Specify either 'encrypt', 'decrypt' or 'print-headers'\n");
895  CFKeyCryptHelp();
896  DoCleanupAndExit(EXIT_FAILURE);
897  }
898 
899  /* Increment 'optind' because of command being argv[0]. */
900  optind++;
901  input_path = argv[optind];
902 
903  /* Some more unexpected arguments? */
904  if (argc > (optind + offset))
905  {
906  Log(LOG_LEVEL_ERR, "Unexpected non-option argument: '%s'", argv[optind + 1]);
907  DoCleanupAndExit(EXIT_FAILURE);
908  }
909 
910  if (print_headers)
911  {
912  FILE *input_file = OpenInputOutput(input_path, "r");
913  char key[MAX_HEADER_KEY_LEN + 1];
914  char value[MAX_HEADER_VAL_LEN + 1];
915 
916  bool done = false;
917  while (!done && ParseHeader(input_file, key, value))
918  {
919  Log(LOG_LEVEL_DEBUG, "Parsed header '%s: %s'", key, value);
920  if (!CheckHeader(key, value))
921  {
922  fclose(input_file);
923  DoCleanupAndExit(EXIT_FAILURE);
924  }
925  printf("%s: %s\n", key, value);
926 
927  /* headers are supposed to be terminated by a blank line */
928  int next = fgetc(input_file);
929  if (next == '\n')
930  {
931  done = true;
932  }
933  else
934  {
935  ungetc(next, input_file);
936  }
937  }
938  fclose(input_file);
939  DoCleanupAndExit(EXIT_SUCCESS);
940  }
941 
942  if (decrypt && (host_arg == NULL) && (key_path_arg == NULL))
943  {
944  /* Decryption requires a private key which is usually only available for
945  * the local host. Let's just default to localhost if no other specific
946  * host/key is given for decryption. */
947  Log(LOG_LEVEL_VERBOSE, "Using the localhost private key for decryption");
948  host_arg = "localhost";
949  }
950 
951  if ((host_arg != NULL) && (key_path_arg != NULL))
952  {
954  "--host/-H is used to specify a public key and cannot be used with --key/-k");
955  DoCleanupAndExit(EXIT_FAILURE);
956  }
957 
958  if (input_path == NULL)
959  {
960  Log(LOG_LEVEL_ERR, "No input file specified (Use -h for help)");
961  DoCleanupAndExit(EXIT_FAILURE);
962  }
963  if (output_path == NULL)
964  {
965  Log(LOG_LEVEL_ERR, "No output file specified (Use -h for help)");
966  DoCleanupAndExit(EXIT_FAILURE);
967  }
968 
971 
972  Seq *key_paths;
973  if (key_path_arg != NULL)
974  {
975  Log(LOG_LEVEL_DEBUG, "-k/--key given: '%s'", key_path_arg);
976  key_paths = SeqStringFromString(key_path_arg, ',');
977  }
978  else
979  {
980  key_paths = SeqNew(16, free);
981  }
982 
983  if (host_arg != NULL)
984  {
985  Log(LOG_LEVEL_DEBUG, "-H/--host given: '%s'", host_arg);
986  Seq *hosts = SeqStringFromString(host_arg, ',');
987  const size_t n_hosts = SeqLength(hosts);
988  for (size_t i = 0; i < n_hosts; i++)
989  {
991  char *host = SeqAt(hosts, i);
992  char *host_key_path = GetHostRSAKey(host, key_type);
993  if (!host_key_path)
994  {
995  Log(LOG_LEVEL_ERR, "Unable to locate key for host '%s'", host);
996  SeqDestroy(hosts);
997  SeqDestroy(key_paths);
998  DoCleanupAndExit(EXIT_FAILURE);
999  }
1000  SeqAppend(key_paths, host_key_path);
1001  }
1002  SeqDestroy(hosts);
1003  }
1004  assert ((key_paths != NULL) && (SeqLength(key_paths) > 0));
1005 
1006  // Encrypt or decrypt
1007  bool success;
1008  if (encrypt)
1009  {
1010  Log(LOG_LEVEL_DEBUG, "Encrypting");
1011  Seq *pub_keys = LoadPublicKeys(key_paths);
1012  SeqDestroy(key_paths);
1013  if (pub_keys == NULL)
1014  {
1015  Log(LOG_LEVEL_ERR, "Failed to load public key(s)");
1016  DoCleanupAndExit(EXIT_FAILURE);
1017  }
1018 
1019  success = RSAEncrypt(pub_keys, input_path, output_path);
1020  SeqDestroy(pub_keys);
1021  if (!success)
1022  {
1023  Log(LOG_LEVEL_ERR, "Encryption failed");
1024  DoCleanupAndExit(EXIT_FAILURE);
1025  }
1026  }
1027  else if (decrypt)
1028  {
1029  Log(LOG_LEVEL_DEBUG, "Decrypting");
1030  const size_t n_keys = SeqLength(key_paths);
1031  if (n_keys > 1)
1032  {
1033  Log(LOG_LEVEL_ERR, "--decrypt requires only one key/host to be specified");
1034  SeqDestroy(key_paths);
1035  DoCleanupAndExit(EXIT_FAILURE);
1036  }
1037  RSA *private_key = ReadPrivateKey((char *) SeqAt(key_paths, 0));
1038  SeqDestroy(key_paths);
1039  success = RSADecrypt(private_key, input_path, output_path);
1040  RSA_free(private_key);
1041  if (!success)
1042  {
1043  Log(LOG_LEVEL_ERR, "Decryption failed");
1044  DoCleanupAndExit(EXIT_FAILURE);
1045  }
1046  }
1047  else
1048  {
1049  ProgrammingError("Unexpected error in cf-secret");
1050  DoCleanupAndExit(EXIT_FAILURE);
1051  }
1052 
1054  return 0;
1055 }
void * xmalloc(size_t size)
Definition: alloc-mini.c:46
#define MAX_HEADER_KEY_LEN
Definition: cf-secret.c:67
static const char passphrase[]
Definition: cf-secret.c:80
int main(int argc, char *argv[])
Definition: cf-secret.c:814
static const char *const CF_SECRET_SHORT_DESCRIPTION
Definition: cf-secret.c:86
void CFKeyCryptMan()
Definition: cf-secret.c:804
static RSA * ReadPublicKey(const char *pubkey_path)
Definition: cf-secret.c:232
#define MAX_HEADER_VAL_LEN
Definition: cf-secret.c:68
static char * GetHostRSAKey(const char *host, HostRSAKeyType type)
Definition: cf-secret.c:146
static bool ParseHeader(FILE *input_file, char *key, char *value)
Definition: cf-secret.c:534
static RSA * ReadPrivateKey(const char *privkey_path)
Definition: cf-secret.c:212
static bool RSAEncrypt(Seq *rsa_keys, const char *input_path, const char *output_path)
Definition: cf-secret.c:271
HostRSAKeyType
Definition: cf-secret.c:70
@ HOST_RSA_KEY_PUBLIC
Definition: cf-secret.c:72
@ HOST_RSA_KEY_PRIVATE
Definition: cf-secret.c:71
static const char *const CF_SECRET_MANPAGE_LONG_DESCRIPTION
Definition: cf-secret.c:89
static const char *const HINTS[]
Definition: cf-secret.c:111
#define MAX_HEADER_LEN
Definition: cf-secret.c:66
static bool RSADecrypt(RSA *privkey, const char *input_path, const char *output_path)
Definition: cf-secret.c:617
static bool ParseHeaders(FILE *input_file, RSA *privkey, size_t *enc_key_pos, size_t *n_enc_keys)
Definition: cf-secret.c:548
static void CFKeyCryptHelp()
Definition: cf-secret.c:797
CFKeyCryptFormatVersion
Definition: cf-secret.c:76
@ CF_SECRET_FORMAT_V_1_0
Definition: cf-secret.c:77
static const Description COMMANDS[]
Definition: cf-secret.c:125
static void SkipSpaces(FILE *input_file)
Definition: cf-secret.c:521
static void * GetIPAddress(struct sockaddr *sa)
Definition: cf-secret.c:133
static FILE * OpenInputOutput(const char *path, const char *mode)
Definition: cf-secret.c:253
static Seq * LoadPublicKeys(Seq *key_paths)
Definition: cf-secret.c:778
static const struct option OPTIONS[]
Definition: cf-secret.c:97
static bool CheckHeader(const char *key, const char *value)
Definition: cf-secret.c:450
#define BUFSIZE
Definition: cf-secret.c:64
static bool ReadUntilDelim(FILE *input_file, char *buffer, size_t max_chars, char delimiter, bool stop_on_space)
Definition: cf-secret.c:484
HashMethod CF_DEFAULT_DIGEST
Definition: cf3globals.c:88
int CF_DEFAULT_DIGEST_LEN
Definition: cf3globals.c:89
#define malloc
Definition: cf3lex.c:804
void free(void *)
void CallCleanupFunctions(void)
Definition: cleanup.c:39
void DoCleanupAndExit(int ret)
Definition: cleanup.c:57
char * GetPubkeyDigest(RSA *pubkey)
Definition: crypto.c:542
void CryptoInitialize()
Definition: crypto.c:71
static void cleanup(void *generic_data)
Definition: fchmodat.c:56
ssize_t ReadFileStreamToBuffer(FILE *file, size_t max_bytes, char *buf)
Definition: file_lib.c:76
ssize_t FullWrite(int desc, const char *ptr, size_t len)
Definition: file_lib.c:253
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
#define FILE_ERROR_READ
Definition: file_lib.h:40
void GenericAgentSetDefaultDigest(HashMethod *digest, int *digest_len)
void freeaddrinfo(struct addrinfo *res)
Definition: getaddrinfo.c:323
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hintp, struct addrinfo **res)
Definition: getaddrinfo.c:262
const char * gai_strerror(int errcode)
Definition: getaddrinfo.c:340
#define NULL
Definition: getopt1.c:56
int optopt
Definition: getopt.c:122
int optind
Definition: getopt.c:102
char * optarg
Definition: getopt.c:87
int opterr
Definition: getopt.c:116
#define no_argument
Definition: getopt.h:98
#define required_argument
Definition: getopt.h:99
int getopt_long()
#define CF_HOSTKEY_STRING_SIZE
Definition: hash.h:151
const char * inet_ntop(int af, const void *src, char *dst, socklen_t size)
Definition: inet_ntop.c:50
bool Address2Hostkey(char *dst, size_t dst_size, const char *address)
Definition: lastseen.c:196
void LogSetGlobalLevelArgOrExit(const char *const arg)
Definition: logging.c:567
void LogSetGlobalLevel(LogLevel level)
Definition: logging.c:561
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
@ LOG_LEVEL_INFO
Definition: logging.h:45
void ManPageWrite(Writer *out, const char *program, time_t last_modified, const char *short_description, const char *long_description, const struct option options[], const char *const option_hints[], const Description *commands, bool command_first, bool accepts_file_argument)
Definition: man.c:226
#define ProgrammingError(...)
Definition: misc_lib.h:33
size_t SeqLength(const Seq *seq)
Length of the sequence.
Definition: sequence.c:354
Seq * SeqNew(size_t initialCapacity, void(ItemDestroy)(void *item))
Definition: sequence.c:31
void *const * SeqGetData(const Seq *seq)
Get the data segment of the sequence.
Definition: sequence.c:403
void SeqDestroy(Seq *seq)
Destroy an existing Sequence.
Definition: sequence.c:60
void SeqAppend(Seq *seq, void *item)
Append a new item to the Sequence.
Definition: sequence.c:104
static void * SeqAt(const Seq *seq, int i)
Definition: sequence.h:57
bool StringStartsWith(const char *str, const char *prefix)
Check if a string starts with the given prefix.
Definition: string_lib.c:1335
bool StringEqual(const char *const a, const char *const b)
Definition: string_lib.c:256
Seq * SeqStringFromString(const char *str, char delimiter)
Create a new Sequence from splitting a string on a fixed delimiter.
Sequence data-structure.
Definition: sequence.h:50
Definition: writer.c:45
Definition: getopt.h:83
void WriterWriteHelp(Writer *w, const char *component, const struct option options[], const char *const hints[], const Description *commands, bool command_first, bool accepts_file_argument)
Definition: writer.c:331
Writer * FileWriter(FILE *file)
Definition: writer.c:56
FILE * FileWriterDetach(Writer *writer)
Definition: writer.c:277