mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-06-30 19:12:48 -05:00
Initial support for host certificates.
Now we offer the OpenSSH certificate key types in our KEXINIT host key algorithm list, so that if the server has a certificate, they can send it to us. There's a new storage.h abstraction for representing a list of trusted host CAs, and which ones are trusted to certify hosts for what domains. This is stored outside the normal saved session data, because the whole point of host certificates is to avoid per-host faffing. Configuring this set of trusted CAs is done via a new GUI dialog box, separate from the main PuTTY config box (because it modifies a single set of settings across all saved sessions), which you can launch by clicking a button in the 'Host keys' pane. The GUI is pretty crude for the moment, and very much at a 'just about usable' stage right now. It will want some polishing. If we have no CA configured that matches the hostname, we don't offer to receive certified host keys in the first place. So for existing users who haven't set any of this up yet, nothing will immediately change. Currently, if we do offer to receive certified host keys and the server presents one signed by a CA we don't trust, PuTTY will bomb out unconditionally with an error, instead of offering a confirmation box. That's an unfinished part which I plan to fix before this goes into a release.
This commit is contained in:
@ -4219,3 +4219,84 @@ int gtkdlg_askappend(Seat *seat, Filename *filename,
|
||||
|
||||
return -1; /* dialog still in progress */
|
||||
}
|
||||
|
||||
struct ca_config_box {
|
||||
GtkWidget *window;
|
||||
struct controlbox *cb;
|
||||
struct Shortcuts scs;
|
||||
dlgparam dp;
|
||||
};
|
||||
static struct ca_config_box *cacfg; /* one of these, cross-instance */
|
||||
|
||||
static void cacfg_destroy(GtkWidget *widget, gpointer data)
|
||||
{
|
||||
cacfg->window = NULL;
|
||||
dlg_cleanup(&cacfg->dp);
|
||||
ctrl_free_box(cacfg->cb);
|
||||
cacfg->cb = NULL;
|
||||
}
|
||||
void show_ca_config_box(dlgparam *dp)
|
||||
{
|
||||
if (!cacfg) {
|
||||
cacfg = snew(struct ca_config_box);
|
||||
memset(cacfg, 0, sizeof(*cacfg));
|
||||
}
|
||||
|
||||
if (cacfg->window) {
|
||||
/* This dialog box is already displayed; re-focus it */
|
||||
gtk_widget_grab_focus(cacfg->window);
|
||||
return;
|
||||
}
|
||||
|
||||
dlg_init(&cacfg->dp);
|
||||
for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) {
|
||||
cacfg->scs.sc[i].action = SHORTCUT_EMPTY;
|
||||
}
|
||||
|
||||
cacfg->cb = ctrl_new_box();
|
||||
setup_ca_config_box(cacfg->cb);
|
||||
|
||||
cacfg->window = our_dialog_new();
|
||||
gtk_window_set_title(GTK_WINDOW(cacfg->window),
|
||||
"PuTTY trusted host certification authorities");
|
||||
gtk_widget_set_size_request(
|
||||
cacfg->window, string_width(
|
||||
"ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5"
|
||||
"dLV2vl7bTnCPjw64x3kLkz6BY8"), -1);
|
||||
|
||||
/* Set up everything else */
|
||||
for (int i = 0; i < cacfg->cb->nctrlsets; i++) {
|
||||
struct controlset *s = cacfg->cb->ctrlsets[i];
|
||||
GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s,
|
||||
GTK_WINDOW(cacfg->window));
|
||||
gtk_container_set_border_width(GTK_CONTAINER(w), 10);
|
||||
gtk_widget_show(w);
|
||||
|
||||
if (!*s->pathname) {
|
||||
our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w);
|
||||
} else {
|
||||
our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w,
|
||||
true, true, 0);
|
||||
}
|
||||
}
|
||||
|
||||
cacfg->dp.data = cacfg;
|
||||
cacfg->dp.shortcuts = &cacfg->scs;
|
||||
cacfg->dp.lastfocus = NULL;
|
||||
cacfg->dp.retval = 0;
|
||||
cacfg->dp.window = cacfg->window;
|
||||
|
||||
dlg_refresh(NULL, &cacfg->dp);
|
||||
|
||||
if (dp) {
|
||||
set_transient_window_pos(dp->window, cacfg->window);
|
||||
} else {
|
||||
gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER);
|
||||
}
|
||||
gtk_widget_show(cacfg->window);
|
||||
|
||||
g_signal_connect(G_OBJECT(cacfg->window), "destroy",
|
||||
G_CALLBACK(cacfg_destroy), NULL);
|
||||
g_signal_connect(G_OBJECT(cacfg->window), "key_press_event",
|
||||
G_CALLBACK(win_key_press), &cacfg->dp);
|
||||
}
|
||||
|
145
unix/storage.c
145
unix/storage.c
@ -28,7 +28,7 @@
|
||||
|
||||
enum {
|
||||
INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
|
||||
INDEX_SESSIONDIR, INDEX_SESSION,
|
||||
INDEX_SESSIONDIR, INDEX_SESSION, INDEX_HOSTCADIR, INDEX_HOSTCA
|
||||
};
|
||||
|
||||
static const char hex[16] = "0123456789ABCDEF";
|
||||
@ -202,6 +202,23 @@ static char *make_filename(int index, const char *subname)
|
||||
sfree(tmp);
|
||||
return ret;
|
||||
}
|
||||
if (index == INDEX_HOSTCADIR) {
|
||||
env = getenv("PUTTYSSHHOSTCAS");
|
||||
if (env)
|
||||
return dupstr(env);
|
||||
tmp = make_filename(INDEX_DIR, NULL);
|
||||
ret = dupprintf("%s/sshhostcas", tmp);
|
||||
sfree(tmp);
|
||||
return ret;
|
||||
}
|
||||
if (index == INDEX_HOSTCA) {
|
||||
strbuf *sb = strbuf_new();
|
||||
tmp = make_filename(INDEX_HOSTCADIR, NULL);
|
||||
put_fmt(sb, "%s/", tmp);
|
||||
sfree(tmp);
|
||||
make_session_filename(subname, sb);
|
||||
return strbuf_to_str(sb);
|
||||
}
|
||||
tmp = make_filename(INDEX_DIR, NULL);
|
||||
ret = dupprintf("%s/ERROR", tmp);
|
||||
sfree(tmp);
|
||||
@ -545,25 +562,25 @@ settings_e *enum_settings_start(void)
|
||||
return toret;
|
||||
}
|
||||
|
||||
bool enum_settings_next(settings_e *handle, strbuf *out)
|
||||
static bool enum_dir_next(DIR *dp, int index, strbuf *out)
|
||||
{
|
||||
struct dirent *de;
|
||||
struct stat st;
|
||||
strbuf *fullpath;
|
||||
|
||||
if (!handle->dp)
|
||||
return NULL;
|
||||
if (!dp)
|
||||
return false;
|
||||
|
||||
fullpath = strbuf_new();
|
||||
|
||||
char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL);
|
||||
char *sessiondir = make_filename(index, NULL);
|
||||
put_dataz(fullpath, sessiondir);
|
||||
sfree(sessiondir);
|
||||
put_byte(fullpath, '/');
|
||||
|
||||
size_t baselen = fullpath->len;
|
||||
|
||||
while ( (de = readdir(handle->dp)) != NULL ) {
|
||||
while ( (de = readdir(dp)) != NULL ) {
|
||||
strbuf_shrink_to(fullpath, baselen);
|
||||
put_dataz(fullpath, de->d_name);
|
||||
|
||||
@ -579,6 +596,11 @@ bool enum_settings_next(settings_e *handle, strbuf *out)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enum_settings_next(settings_e *handle, strbuf *out)
|
||||
{
|
||||
return enum_dir_next(handle->dp, INDEX_SESSIONDIR, out);
|
||||
}
|
||||
|
||||
void enum_settings_finish(settings_e *handle)
|
||||
{
|
||||
if (handle->dp)
|
||||
@ -586,6 +608,117 @@ void enum_settings_finish(settings_e *handle)
|
||||
sfree(handle);
|
||||
}
|
||||
|
||||
struct host_ca_enum {
|
||||
DIR *dp;
|
||||
};
|
||||
|
||||
host_ca_enum *enum_host_ca_start(void)
|
||||
{
|
||||
host_ca_enum *handle = snew(host_ca_enum);
|
||||
|
||||
char *filename = make_filename(INDEX_HOSTCADIR, NULL);
|
||||
handle->dp = opendir(filename);
|
||||
sfree(filename);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool enum_host_ca_next(host_ca_enum *handle, strbuf *out)
|
||||
{
|
||||
return enum_dir_next(handle->dp, INDEX_HOSTCADIR, out);
|
||||
}
|
||||
|
||||
void enum_host_ca_finish(host_ca_enum *handle)
|
||||
{
|
||||
if (handle->dp)
|
||||
closedir(handle->dp);
|
||||
sfree(handle);
|
||||
}
|
||||
|
||||
host_ca *host_ca_load(const char *name)
|
||||
{
|
||||
char *filename = make_filename(INDEX_HOSTCA, name);
|
||||
FILE *fp = fopen(filename, "r");
|
||||
sfree(filename);
|
||||
if (!fp)
|
||||
return NULL;
|
||||
|
||||
host_ca *hca = snew(host_ca);
|
||||
memset(hca, 0, sizeof(*hca));
|
||||
hca->name = dupstr(name);
|
||||
|
||||
size_t wcsize = 0;
|
||||
char *line;
|
||||
|
||||
while ( (line = fgetline(fp)) ) {
|
||||
char *value = strchr(line, '=');
|
||||
|
||||
if (!value) {
|
||||
sfree(line);
|
||||
continue;
|
||||
}
|
||||
*value++ = '\0';
|
||||
value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
|
||||
|
||||
if (!strcmp(line, "PublicKey")) {
|
||||
hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value));
|
||||
} else if (!strcmp(line, "MatchHosts")) {
|
||||
sgrowarray(hca->hostname_wildcards, wcsize,
|
||||
hca->n_hostname_wildcards);
|
||||
hca->hostname_wildcards[hca->n_hostname_wildcards++] =
|
||||
dupstr(value);
|
||||
}
|
||||
|
||||
sfree(line);
|
||||
}
|
||||
|
||||
return hca;
|
||||
}
|
||||
|
||||
char *host_ca_save(host_ca *hca)
|
||||
{
|
||||
if (!*hca->name)
|
||||
return dupstr("CA record must have a name");
|
||||
|
||||
char *filename = make_filename(INDEX_HOSTCA, hca->name);
|
||||
FILE *fp = fopen(filename, "w");
|
||||
if (!fp)
|
||||
return dupprintf("Unable to open file '%s'", filename);
|
||||
|
||||
fprintf(fp, "PublicKey=");
|
||||
base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0);
|
||||
fprintf(fp, "\n");
|
||||
|
||||
for (size_t i = 0; i < hca->n_hostname_wildcards; i++)
|
||||
fprintf(fp, "MatchHosts=%s\n", hca->hostname_wildcards[i]);
|
||||
|
||||
bool bad = ferror(fp);
|
||||
if (fclose(fp) < 0)
|
||||
bad = true;
|
||||
|
||||
char *err = NULL;
|
||||
if (bad)
|
||||
err = dupprintf("Unable to write file '%s'", filename);
|
||||
|
||||
sfree(filename);
|
||||
return err;
|
||||
}
|
||||
|
||||
char *host_ca_delete(const char *name)
|
||||
{
|
||||
if (!*name)
|
||||
return dupstr("CA record must have a name");
|
||||
char *filename = make_filename(INDEX_HOSTCA, name);
|
||||
bool bad = remove(filename) < 0;
|
||||
|
||||
char *err = NULL;
|
||||
if (bad)
|
||||
err = dupprintf("Unable to delete file '%s'", filename);
|
||||
|
||||
sfree(filename);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lines in the host keys file are of the form
|
||||
*
|
||||
|
Reference in New Issue
Block a user