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':
as opposed to
Permission denied (publickey).
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':
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);
}
Top comments (0)