DEV Community

Mika Feiler
Mika Feiler

Posted on • Updated on

Checking remote authorized_keys without entering key passphrase, done more neatly — and against .ssh/config

You have a bunch of client machines with private keys set up to access a bunch of destination hosts. How do you check which of them has them to access which? Maybe you wish for ssh-copy-id to not ask for key password when you can first check that quicker with ssh and ^C?
How does ssh act out

Enter passphrase for key '/path/to/key': 
Enter fullscreen mode Exit fullscreen mode

as opposed to

Permission denied (publickey).
Enter fullscreen mode Exit fullscreen mode

While -vvv (debug3) shows more, let's see just debug1 (-v):

debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Will attempt key: /path/to/key ED25519 SHA256:... explicit
debug1: Offering public key: /path/to/key ED25519 SHA256:... explicit
debug1: Server accepts key: /path/to/key ED25519 SHA256:... explicit
Enter passphrase for key '/path/to/key': 
Enter fullscreen mode Exit fullscreen mode

So we can clearly just scrape that stderr, seek the debug1 accept or the passphrase prompt, regex the path to key out of the thing.

Or we can do it neater.
Cockpit Project happened to make a contribution enabling us to use libssh for that: https://gitlab.com/libssh/libssh-mirror/-/merge_requests/134
— they happened to want to be able to prompt the user for key passphrase only when needed. We've only been having that available in libssh for two years.

The ssh_userauth_publickey_auto_get_current_identity function has to be called in an auth_function callback, and the value it obtains is the key file path.

Besides the auto mode, even without that it was already possible to check a single key — with the result of the ssh_userauth_try_publickey function.

Attached is the quick-and-dirty source code for such a utility:

// LGPL (c) 2024-02-27 Mika Feiler <m@mikf.pl>
// BUILD: cc -lssh

/*
      This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

#include <libssh/libssh.h>
#include <libssh/callbacks.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

ssh_session s;

int leave = -1;

int callback(const char *_prompt, char *_buf, size_t _len,
         int _echo, int _verify, void* _) {
  int rc;
  char *value[500];
  rc = ssh_userauth_publickey_auto_get_current_identity(s, value);
  // Thanks, Cockpit Project!
  printf("%s\n", *value);
  leave = 0;
}

int usage(char* a0) {
    printf("Usage: %s hostname [port [keypath|'' [keyuser [disregardunknownhost]]]]\n\n%s\n%s\n%s\n\n%s\n",
       a0,
       "This little program uses ssh_userauth_publickey_auto_get_current_identity,",
       "thanks to Cockpit, to offer keys (per config) to host and print out path",
       "of one that gets accepted by the host, even if it has a passphrase.",
       "This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.");
    return 0;
}

void handle_host_verification(int pass_unknown) {
  enum ssh_known_hosts_e state = ssh_session_is_known_server(s);
  switch(state) {
  case SSH_KNOWN_HOSTS_UNKNOWN: if(pass_unknown) exit(5);
  case SSH_KNOWN_HOSTS_OK: break;
  default:
    exit(3);
  }
}

int main(int argc, char* argv[]) {
  if(argc < 2)
    return usage(argv[0]);

  s = ssh_new();
  if (s == NULL)
    exit(-1);

  int verbosity = SSH_LOG_WARNING;
  ssh_options_set(s, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);

  ssh_options_set(s, SSH_OPTIONS_HOST, argv[1]);
  ssh_options_set(s, SSH_OPTIONS_PORT_STR, argc > 2 ? argv[2] : "22");

  int rc;
  rc = ssh_connect(s);
  if (rc != SSH_OK)
    exit(rc);

  handle_host_verification(argc < 6); // will allow unknown host with one more arg

  struct ssh_callbacks_struct callbacks = {
    .auth_function = callback
  };
  // As per test example for ssh_userauth_publickey_auto_get_current_identity !

  ssh_set_blocking(s, 1);

  // "User SHOULD be NULL" lol
  char* user = argc > 4 && strlen(argv[4]) > 0 ? argv[4] : NULL;

  if (argc > 3 && strlen(argv[3]) > 0) { // if key path specified
    ssh_key key = ssh_key_new();
    rc = ssh_pki_import_pubkey_file(argv[3], &key);
    if (rc != SSH_OK)
      exit(rc);
    rc = ssh_userauth_try_publickey(s, user, key);
    leave = rc;
  } else { // the cool behavior doing .ssh/config :3
    ssh_callbacks_init(&callbacks);
    ssh_set_callbacks(s, &callbacks);

    rc = ssh_userauth_publickey_auto(s, user, NULL);
  }
  leave = leave == -1 ? rc : leave;

  ssh_disconnect(s); ssh_free(s);
  exit(leave);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)