"Fossies" - the Fresh Open Source Software Archive

Member "cryptsetup-2.4.3/tokens/ssh/cryptsetup-ssh.c" (13 Jan 2022, 10999 Bytes) of package /linux/misc/cryptsetup-2.4.3.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "cryptsetup-ssh.c" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * Example of LUKS2 token storing third party metadata (EXPERIMENTAL EXAMPLE)
    3  *
    4  * Copyright (C) 2016-2021 Milan Broz <gmazyland@gmail.com>
    5  * Copyright (C) 2021 Vojtech Trefny
    6  *
    7  * Use:
    8  *  - generate ssh example token
    9  *
   10  * This program is free software; you can redistribute it and/or
   11  * modify it under the terms of the GNU General Public License
   12  * as published by the Free Software Foundation; either version 2
   13  * of the License, or (at your option) any later version.
   14  *
   15  * This program is distributed in the hope that it will be useful,
   16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18  * GNU General Public License for more details.
   19  *
   20  * You should have received a copy of the GNU General Public License
   21  * along with this program; if not, write to the Free Software
   22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   23  */
   24 
   25 #include <stdio.h>
   26 #include <stdlib.h>
   27 #include <string.h>
   28 #include <errno.h>
   29 #include <fcntl.h>
   30 #include <argp.h>
   31 #include <json-c/json.h>
   32 #include <termios.h>
   33 #include <stdbool.h>
   34 #include "libcryptsetup.h"
   35 #include "ssh-utils.h"
   36 #include "../src/cryptsetup.h"
   37 
   38 #define TOKEN_NAME "ssh"
   39 
   40 #define l_err(cd, x...) crypt_logf(cd, CRYPT_LOG_ERROR, x)
   41 #define l_dbg(cd, x...) crypt_logf(cd, CRYPT_LOG_DEBUG, x)
   42 
   43 #define OPT_SSH_SERVER  1
   44 #define OPT_SSH_USER    2
   45 #define OPT_SSH_PATH    3
   46 #define OPT_KEY_PATH    4
   47 #define OPT_DEBUG   5
   48 #define OPT_DEBUG_JSON  6
   49 #define OPT_KEY_SLOT    7
   50 
   51 void tools_cleanup(void)
   52 {
   53 }
   54 
   55 
   56 static int token_add(
   57         const char *device,
   58         const char *server,
   59         const char *user,
   60         const char *path,
   61         const char *keypath,
   62         int keyslot)
   63 
   64 {
   65     struct crypt_device *cd;
   66     json_object *jobj = NULL;
   67     json_object *jobj_keyslots = NULL;
   68     const char *string_token;
   69     int r, token;
   70 
   71     r = crypt_init(&cd, device);
   72     if (r)
   73         return r;
   74 
   75     r = crypt_load(cd, CRYPT_LUKS2, NULL);
   76     if (r)
   77         goto out;
   78 
   79     r = -EINVAL;
   80     jobj = json_object_new_object();
   81     if (!jobj)
   82         goto out;
   83 
   84     /* type is mandatory field in all tokens and must match handler name member */
   85     json_object_object_add(jobj, "type", json_object_new_string(TOKEN_NAME));
   86 
   87     jobj_keyslots = json_object_new_array();
   88 
   89     /* mandatory array field (may be empty and assigned later */
   90     json_object_object_add(jobj, "keyslots", jobj_keyslots);
   91 
   92     /* custom metadata */
   93     json_object_object_add(jobj, "ssh_server", json_object_new_string(server));
   94     json_object_object_add(jobj, "ssh_user", json_object_new_string(user));
   95     json_object_object_add(jobj, "ssh_path", json_object_new_string(path));
   96     json_object_object_add(jobj, "ssh_keypath", json_object_new_string(keypath));
   97 
   98     string_token = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN);
   99     if (!string_token) {
  100         r = -EINVAL;
  101         goto out;
  102     }
  103 
  104     l_dbg(cd, "Token JSON: %s", string_token);
  105 
  106     r = crypt_token_json_set(cd, CRYPT_ANY_TOKEN, string_token);
  107     if (r < 0) {
  108         l_err(cd, _("Failed to write ssh token json."));
  109         goto out;
  110     }
  111 
  112     token = r;
  113     r = crypt_token_assign_keyslot(cd, token, keyslot);
  114     if (r != token) {
  115         crypt_token_json_set(cd, token, NULL);
  116         r = -EINVAL;
  117     }
  118 out:
  119     json_object_put(jobj);
  120     crypt_free(cd);
  121     return r;
  122 }
  123 
  124 const char *argp_program_version = "cryptsetup-ssh " PACKAGE_VERSION;
  125 
  126 static char doc[] = N_("Experimental cryptsetup plugin for unlocking LUKS2 devices with token connected " \
  127                "to an SSH server\v" \
  128                "This plugin currently allows only adding a token to an existing key slot.\n\n" \
  129                "Specified SSH server must contain a key file on the specified path with " \
  130                "a passphrase for an existing key slot on the device.\n" \
  131                "Provided credentials will be used by cryptsetup to get the password when " \
  132                "opening the device using the token.\n\n" \
  133                "Note: The information provided when adding the token (SSH server address, user and paths) " \
  134                "will be stored in the LUKS2 header in plaintext.");
  135 
  136 static char args_doc[] = N_("<action> <device>");
  137 
  138 static struct argp_option options[] = {
  139     {0,     0,      0,    0, N_("Options for the 'add' action:")},
  140     {"ssh-server",  OPT_SSH_SERVER, "STRING", 0, N_("IP address/URL of the remote server for this token")},
  141     {"ssh-user",    OPT_SSH_USER,   "STRING", 0, N_("Username used for the remote server")},
  142     {"ssh-path",    OPT_SSH_PATH,   "STRING", 0, N_("Path to the key file on the remote server")},
  143     {"ssh-keypath", OPT_KEY_PATH,   "STRING", 0, N_("Path to the SSH key for connecting to the remote server")},
  144     {"key-slot",    OPT_KEY_SLOT,   "NUM",    0, N_("Keyslot to assign the token to. If not specified, token will "\
  145                                 "be assigned to the first keyslot matching provided passphrase.")},
  146     {0,     0,      0,    0, N_("Generic options:")},
  147     {"verbose", 'v',        0,    0, N_("Shows more detailed error messages")},
  148     {"debug",   OPT_DEBUG,  0,    0, N_("Show debug messages")},
  149     {"debug-json",  OPT_DEBUG_JSON, 0,    0, N_("Show debug messages including JSON metadata")},
  150     { NULL,     0,      0, 0, NULL }
  151 };
  152 
  153 struct arguments {
  154     char *device;
  155     char *action;
  156     char *ssh_server;
  157     char *ssh_user;
  158     char *ssh_path;
  159     char *ssh_keypath;
  160     int keyslot;
  161     int verbose;
  162     int debug;
  163     int debug_json;
  164 };
  165 
  166 static error_t
  167 parse_opt (int key, char *arg, struct argp_state *state) {
  168     struct arguments *arguments = state->input;
  169 
  170     switch (key) {
  171     case OPT_SSH_SERVER:
  172         arguments->ssh_server = arg;
  173         break;
  174     case OPT_SSH_USER:
  175         arguments->ssh_user = arg;
  176         break;
  177     case OPT_SSH_PATH:
  178         arguments->ssh_path = arg;
  179         break;
  180     case OPT_KEY_PATH:
  181         arguments->ssh_keypath = arg;
  182         break;
  183     case OPT_KEY_SLOT:
  184         arguments->keyslot = atoi(arg);
  185         break;
  186     case 'v':
  187         arguments->verbose = 1;
  188         break;
  189     case OPT_DEBUG:
  190         arguments->debug = 1;
  191         break;
  192     case OPT_DEBUG_JSON:
  193         arguments->debug = 1;
  194         arguments->debug_json = 1;
  195         break;
  196     case ARGP_KEY_NO_ARGS:
  197         argp_usage(state);
  198         break;
  199     case ARGP_KEY_ARG:
  200         arguments->action = arg;
  201         arguments->device = state->argv[state->next];
  202         state->next = state->argc;
  203         break;
  204     default:
  205         return ARGP_ERR_UNKNOWN;
  206     }
  207 
  208     return 0;
  209 }
  210 
  211 static struct argp argp = { options, parse_opt, args_doc, doc };
  212 
  213 
  214 static void _log(int level, const char *msg, void *usrptr)
  215 {
  216     struct arguments *arguments = (struct arguments *)usrptr;
  217 
  218     switch (level) {
  219     case CRYPT_LOG_NORMAL:
  220         fprintf(stdout, "%s", msg);
  221         break;
  222     case CRYPT_LOG_VERBOSE:
  223         if (arguments && arguments->verbose)
  224             fprintf(stdout, "%s", msg);
  225         break;
  226     case CRYPT_LOG_ERROR:
  227         fprintf(stderr, "%s", msg);
  228         break;
  229     case CRYPT_LOG_DEBUG_JSON:
  230         if (arguments && arguments->debug_json)
  231             fprintf(stdout, "# %s", msg);
  232         break;
  233     case CRYPT_LOG_DEBUG:
  234         if (arguments && arguments->debug)
  235             fprintf(stdout, "# %s", msg);
  236         break;
  237     }
  238 }
  239 
  240 static int get_keyslot_for_passphrase(struct arguments *arguments, const char *pin)
  241 {
  242     int r = 0;
  243     ssh_key pkey;
  244     ssh_session ssh;
  245     char *password = NULL;
  246     size_t password_len = 0;
  247     struct crypt_device *cd = NULL;
  248     char *ssh_pass = NULL;
  249     size_t key_size = 0;
  250     char *prompt = NULL;
  251 
  252     r = crypt_init(&cd, arguments->device);
  253     if (r < 0)
  254         return r;
  255     crypt_set_log_callback(cd, &_log, arguments);
  256 
  257     r = ssh_pki_import_privkey_file(arguments->ssh_keypath, pin, NULL, NULL, &pkey);
  258     if (r != SSH_OK) {
  259         if (r == SSH_EOF) {
  260             crypt_log(cd, CRYPT_LOG_ERROR, _("Failed to open and import private key:\n"));
  261             crypt_free(cd);
  262             return -EINVAL;
  263         } else {
  264             _log(CRYPT_LOG_ERROR, _("Failed to import private key (password protected?).\n"), NULL);
  265             /* TRANSLATORS: SSH credentials prompt, e.g. "user@server's password: " */
  266             r = asprintf(&prompt, _("%s@%s's password: "), arguments->ssh_user, arguments->ssh_server);
  267             if (r < 0) {
  268                 crypt_safe_free(ssh_pass);
  269                 crypt_free(cd);
  270                 return -EINVAL;
  271             }
  272 
  273             r = tools_get_key(prompt, &ssh_pass, &key_size, 0, 0, NULL, 0, 0, 0, cd);
  274             if (r < 0) {
  275                 free(prompt);
  276                 crypt_safe_free(ssh_pass);
  277                 crypt_free(cd);
  278                 return -EINVAL;
  279             }
  280 
  281             /* now try again with the password */
  282             r = get_keyslot_for_passphrase(arguments, ssh_pass);
  283 
  284             crypt_safe_free(ssh_pass);
  285             crypt_free(cd);
  286             free(prompt);
  287 
  288             return r;
  289         }
  290     }
  291 
  292     ssh = sshplugin_session_init(cd, arguments->ssh_server, arguments->ssh_user);
  293     if (!ssh) {
  294         ssh_key_free(pkey);
  295         crypt_free(cd);
  296         return -EINVAL;
  297     }
  298 
  299     r = sshplugin_public_key_auth(cd, ssh, pkey);
  300     ssh_key_free(pkey);
  301 
  302     if (r != SSH_AUTH_SUCCESS) {
  303         crypt_free(cd);
  304         return r;
  305     }
  306 
  307     r = sshplugin_download_password(cd, ssh, arguments->ssh_path, &password, &password_len);
  308     if (r < 0) {
  309         ssh_disconnect(ssh);
  310         ssh_free(ssh);
  311         crypt_free(cd);
  312         return r;
  313     }
  314 
  315     ssh_disconnect(ssh);
  316     ssh_free(ssh);
  317 
  318     r = crypt_load(cd, CRYPT_LUKS2, NULL);
  319     if (r < 0) {
  320         crypt_safe_memzero(password, password_len);
  321         free(password);
  322         crypt_free(cd);
  323         return r;
  324     }
  325 
  326     r = crypt_activate_by_passphrase(cd, NULL, CRYPT_ANY_SLOT, password, password_len, 0);
  327     if (r < 0) {
  328         crypt_safe_memzero(password, password_len);
  329         free(password);
  330         crypt_free(cd);
  331         return r;
  332     }
  333 
  334     arguments->keyslot = r;
  335 
  336     crypt_safe_memzero(password, password_len);
  337     free(password);
  338     crypt_free(cd);
  339 
  340     return 0;
  341 }
  342 
  343 int main(int argc, char *argv[])
  344 {
  345     int ret = 0;
  346     struct arguments arguments = { 0 };
  347     arguments.keyslot = CRYPT_ANY_SLOT;
  348 
  349     setlocale(LC_ALL, "");
  350     bindtextdomain(PACKAGE, LOCALEDIR);
  351     textdomain(PACKAGE);
  352 
  353     ret = argp_parse (&argp, argc, argv, 0, 0, &arguments);
  354     if (ret != 0) {
  355         printf(_("Failed to parse arguments.\n"));
  356         return EXIT_FAILURE;
  357     }
  358 
  359     crypt_set_log_callback(NULL, _log, &arguments);
  360     if (arguments.debug)
  361         crypt_set_debug_level(CRYPT_DEBUG_ALL);
  362     if (arguments.debug_json)
  363         crypt_set_debug_level(CRYPT_DEBUG_JSON);
  364 
  365     if (arguments.action == NULL) {
  366         printf(_("An action must be specified\n"));
  367         return EXIT_FAILURE;
  368     }
  369 
  370     if (strcmp("add", arguments.action) == 0) {
  371         if (!arguments.device) {
  372             printf(_("Device must be specified for '%s' action.\n"), arguments.action);
  373             return EXIT_FAILURE;
  374         }
  375 
  376         if (!arguments.ssh_server) {
  377             printf(_("SSH server must be specified for '%s' action.\n"), arguments.action);
  378             return EXIT_FAILURE;
  379         }
  380 
  381         if (!arguments.ssh_user) {
  382             printf(_("SSH user must be specified for '%s' action.\n"), arguments.action);
  383             return EXIT_FAILURE;
  384         }
  385 
  386         if (!arguments.ssh_path) {
  387             printf(_("SSH path must be specified for '%s' action.\n"), arguments.action);
  388             return EXIT_FAILURE;
  389         }
  390 
  391         if (!arguments.ssh_keypath) {
  392             printf(_("SSH key path must be specified for '%s' action.\n"), arguments.action);
  393             return EXIT_FAILURE;
  394         }
  395 
  396         if (arguments.keyslot == CRYPT_ANY_SLOT) {
  397             ret = get_keyslot_for_passphrase(&arguments, NULL);
  398             if (ret != 0) {
  399                 printf(_("Failed open %s using provided credentials.\n"), arguments.device);
  400                 return EXIT_FAILURE;
  401             }
  402         }
  403 
  404         ret = token_add(arguments.device,
  405                 arguments.ssh_server,
  406                 arguments.ssh_user,
  407                 arguments.ssh_path,
  408                 arguments.ssh_keypath,
  409                 arguments.keyslot);
  410         if (ret < 0)
  411             return EXIT_FAILURE;
  412         else
  413             return EXIT_SUCCESS;
  414     } else {
  415         printf(_("Only 'add' action is currently supported by this plugin.\n"));
  416         return EXIT_FAILURE;
  417     }
  418 }