Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
committerDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
commit302fb2e8ddea1c993552c9a30c02f41d01ca54a9 (patch)
treed6cf1b32664296ef2cecda33caeafbe39e6695c1 /ssh/ca-config.c
parent59105d9b26363e47f00676bd365b2ac8d4cb536a (diff)
parent4ff82ab29a22936b78510c68f544a99e677efed3 (diff)
Merge tag 'tags/0.78'HEADmaster
Diffstat (limited to 'ssh/ca-config.c')
-rw-r--r--ssh/ca-config.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/ssh/ca-config.c b/ssh/ca-config.c
new file mode 100644
index 00000000..8c180b36
--- /dev/null
+++ b/ssh/ca-config.c
@@ -0,0 +1,497 @@
+/*
+ * Define and handle the configuration dialog box for SSH host CAs,
+ * using the same portable dialog specification API as config.c.
+ */
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+#include "tree234.h"
+#include "ssh.h"
+
+const bool has_ca_config_box = true;
+
+#define NRSATYPES 3
+
+struct ca_state {
+ dlgcontrol *ca_name_edit;
+ dlgcontrol *ca_reclist;
+ dlgcontrol *ca_pubkey_edit;
+ dlgcontrol *ca_pubkey_info;
+ dlgcontrol *ca_validity_edit;
+ dlgcontrol *rsa_type_checkboxes[NRSATYPES];
+ char *name, *pubkey, *validity;
+ tree234 *ca_names; /* stores plain 'char *' */
+ ca_options opts;
+ strbuf *ca_pubkey_blob;
+};
+
+static int ca_name_compare(void *av, void *bv)
+{
+ return strcmp((const char *)av, (const char *)bv);
+}
+
+static inline void clear_string_tree(tree234 *t)
+{
+ char *p;
+ while ((p = delpos234(t, 0)) != NULL)
+ sfree(p);
+}
+
+static void ca_state_free(void *vctx)
+{
+ struct ca_state *st = (struct ca_state *)vctx;
+ clear_string_tree(st->ca_names);
+ freetree234(st->ca_names);
+ sfree(st->name);
+ sfree(st->validity);
+ sfree(st);
+}
+
+static void ca_refresh_name_list(struct ca_state *st)
+{
+ clear_string_tree(st->ca_names);
+
+ host_ca_enum *hce = enum_host_ca_start();
+ if (hce) {
+ strbuf *namebuf = strbuf_new();
+
+ while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) {
+ char *name = dupstr(namebuf->s);
+ char *added = add234(st->ca_names, name);
+ /* Just imaginable that concurrent filesystem access might
+ * cause a repetition; avoid leaking memory if so */
+ if (added != name)
+ sfree(name);
+ }
+
+ strbuf_free(namebuf);
+ enum_host_ca_finish(hce);
+ }
+}
+
+static void set_from_hca(struct ca_state *st, host_ca *hca)
+{
+ sfree(st->name);
+ st->name = dupstr(hca->name ? hca->name : "");
+
+ sfree(st->pubkey);
+ if (hca->ca_public_key)
+ st->pubkey = strbuf_to_str(
+ base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0));
+ else
+ st->pubkey = dupstr("");
+
+ st->validity = dupstr(hca->validity_expression ?
+ hca->validity_expression : "");
+
+ st->opts = hca->opts; /* structure copy */
+}
+
+static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp)
+{
+ char *text = NULL;
+ ssh_key *key = NULL;
+ strbuf *blob = strbuf_new();
+
+ ptrlen data = ptrlen_from_asciz(st->pubkey);
+
+ if (st->ca_pubkey_blob)
+ strbuf_free(st->ca_pubkey_blob);
+ st->ca_pubkey_blob = NULL;
+
+ if (!data.len) {
+ text = dupstr(" ");
+ goto out;
+ }
+
+ /*
+ * See if we have a plain base64-encoded public key blob.
+ */
+ if (base64_valid(data)) {
+ base64_decode_bs(BinarySink_UPCAST(blob), data);
+ } else {
+ /*
+ * Otherwise, try to decode as if it was a public key _file_.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, data);
+ const char *error;
+ if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) {
+ text = dupprintf("Cannot decode key: %s", error);
+ goto out;
+ }
+ }
+
+ ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob));
+ if (!alg_name.len) {
+ text = dupstr("Invalid key (no key type)");
+ goto out;
+ }
+
+ const ssh_keyalg *alg = find_pubkey_alg_len(alg_name);
+ if (!alg) {
+ text = dupprintf("Unrecognised key type '%.*s'",
+ PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+ if (alg->is_certificate) {
+ text = dupprintf("CA key may not be a certificate (type is '%.*s')",
+ PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+
+ key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob));
+ if (!key) {
+ text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+
+ text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT);
+ st->ca_pubkey_blob = blob;
+ blob = NULL; /* prevent free */
+
+ out:
+ dlg_text_set(st->ca_pubkey_info, dp, text);
+ if (key)
+ ssh_key_free(key);
+ sfree(text);
+ if (blob)
+ strbuf_free(blob);
+}
+
+static void ca_load_selected_record(struct ca_state *st, dlgparam *dp)
+{
+ int i = dlg_listbox_index(st->ca_reclist, dp);
+ if (i < 0) {
+ dlg_beep(dp);
+ return;
+ }
+ const char *name = index234(st->ca_names, i);
+ if (!name) { /* in case the list box and the tree got out of sync */
+ dlg_beep(dp);
+ return;
+ }
+ host_ca *hca = host_ca_load(name);
+ if (!hca) {
+ char *msg = dupprintf("Unable to load host CA record '%s'", name);
+ dlg_error_msg(dp, msg);
+ sfree(msg);
+ return;
+ }
+
+ set_from_hca(st, hca);
+ host_ca_free(hca);
+
+ dlg_refresh(st->ca_name_edit, dp);
+ dlg_refresh(st->ca_pubkey_edit, dp);
+ dlg_refresh(st->ca_validity_edit, dp);
+ for (size_t i = 0; i < NRSATYPES; i++)
+ dlg_refresh(st->rsa_type_checkboxes[i], dp);
+ ca_refresh_pubkey_info(st, dp);
+}
+
+static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dp, 0);
+}
+
+static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->name);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->name);
+ st->name = dlg_editbox_get(ctrl, dp);
+
+ /*
+ * Try to auto-select the typed name in the list.
+ */
+ int index;
+ if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index))
+ index = count234(st->ca_names) - 1;
+ if (index >= 0)
+ dlg_listbox_select(st->ca_reclist, dp, index);
+ }
+}
+
+static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_update_start(ctrl, dp);
+ dlg_listbox_clear(ctrl, dp);
+ const char *name;
+ for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++)
+ dlg_listbox_add(ctrl, dp, name);
+ dlg_update_done(ctrl, dp);
+ } else if (event == EVENT_ACTION) {
+ /* Double-clicking a session loads it */
+ ca_load_selected_record(st, dp);
+ }
+}
+
+static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ ca_load_selected_record(st, dp);
+ }
+}
+
+static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ if (!*st->validity) {
+ dlg_error_msg(dp, "No validity expression configured "
+ "for this key");
+ return;
+ }
+
+ char *error_msg;
+ ptrlen error_loc;
+ if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) {
+ char *error_full = dupprintf("Error in expression: %s", error_msg);
+ dlg_error_msg(dp, error_full);
+ dlg_set_focus(st->ca_validity_edit, dp);
+ dlg_editbox_select_range(
+ st->ca_validity_edit, dp,
+ (const char *)error_loc.ptr - st->validity, error_loc.len);
+ sfree(error_msg);
+ sfree(error_full);
+ return;
+ }
+
+ if (!st->ca_pubkey_blob) {
+ dlg_error_msg(dp, "No valid CA public key entered");
+ return;
+ }
+
+ host_ca *hca = snew(host_ca);
+ memset(hca, 0, sizeof(*hca));
+ hca->name = dupstr(st->name);
+ hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf(
+ st->ca_pubkey_blob));
+ hca->validity_expression = dupstr(st->validity);
+ hca->opts = st->opts; /* structure copy */
+
+ char *error = host_ca_save(hca);
+ host_ca_free(hca);
+
+ if (error) {
+ dlg_error_msg(dp, error);
+ sfree(error);
+ } else {
+ ca_refresh_name_list(st);
+ dlg_refresh(st->ca_reclist, dp);
+ }
+ }
+}
+
+static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ int i = dlg_listbox_index(st->ca_reclist, dp);
+ if (i < 0) {
+ dlg_beep(dp);
+ return;
+ }
+ const char *name = index234(st->ca_names, i);
+ if (!name) { /* in case the list box and the tree got out of sync */
+ dlg_beep(dp);
+ return;
+ }
+
+ char *error = host_ca_delete(name);
+ if (error) {
+ dlg_error_msg(dp, error);
+ sfree(error);
+ } else {
+ ca_refresh_name_list(st);
+ dlg_refresh(st->ca_reclist, dp);
+ }
+ }
+}
+
+static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->pubkey);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->pubkey);
+ st->pubkey = dlg_editbox_get(ctrl, dp);
+ ca_refresh_pubkey_info(st, dp);
+ }
+}
+
+static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ Filename *filename = dlg_filesel_get(ctrl, dp);
+ strbuf *keyblob = strbuf_new();
+ const char *load_error;
+ bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob),
+ NULL, &load_error);
+ if (!ok) {
+ char *message = dupprintf(
+ "Unable to load public key from '%s': %s",
+ filename_to_str(filename), load_error);
+ dlg_error_msg(dp, message);
+ sfree(message);
+ } else {
+ sfree(st->pubkey);
+ st->pubkey = strbuf_to_str(
+ base64_encode_sb(ptrlen_from_strbuf(keyblob), 0));
+ dlg_refresh(st->ca_pubkey_edit, dp);
+ }
+ filename_free(filename);
+ strbuf_free(keyblob);
+ }
+}
+
+static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->validity);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->validity);
+ st->validity = dlg_editbox_get(ctrl, dp);
+ }
+}
+
+static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ size_t offset = ctrl->context2.i;
+ bool *option = (bool *)((char *)&st->opts + offset);
+
+ if (event == EVENT_REFRESH) {
+ dlg_checkbox_set(ctrl, dp, *option);
+ } else if (event == EVENT_VALCHANGE) {
+ *option = dlg_checkbox_get(ctrl, dp);
+ }
+}
+
+void setup_ca_config_box(struct controlbox *b)
+{
+ struct controlset *s;
+ dlgcontrol *c;
+
+ /* Internal state for manipulating the host CA system */
+ struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free(
+ b, sizeof(struct ca_state), ca_state_free);
+ memset(st, 0, sizeof(*st));
+ st->ca_names = newtree234(ca_name_compare);
+ st->validity = dupstr("");
+ ca_refresh_name_list(st);
+
+ /* Initialise the settings to a default blank host_ca */
+ {
+ host_ca *hca = host_ca_new();
+ set_from_hca(st, hca);
+ host_ca_free(hca);
+ }
+
+ /* Action area, with the Done button in it */
+ s = ctrl_getset(b, "", "", "");
+ ctrl_columns(s, 5, 20, 20, 20, 20, 20);
+ c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert),
+ ca_ok_handler, P(st));
+ c->button.iscancel = true;
+ c->column = 4;
+
+ /* Load/save box, as similar as possible to the main saved sessions one */
+ s = ctrl_getset(b, "Main", "loadsave",
+ "Load, save or delete a host CA record");
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_editbox(s, "Name for this CA (shown in log messages)",
+ 'n', 100, HELPCTX(ssh_kex_cert),
+ ca_name_handler, P(st), P(NULL));
+ c->column = 0;
+ st->ca_name_edit = c;
+ /* Reset columns so that the buttons are alongside the list, rather
+ * than alongside that edit box. */
+ ctrl_columns(s, 1, 100);
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert),
+ ca_reclist_handler, P(st));
+ c->column = 0;
+ c->listbox.height = 6;
+ st->ca_reclist = c;
+ c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert),
+ ca_load_handler, P(st));
+ c->column = 1;
+ c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert),
+ ca_save_handler, P(st));
+ c->column = 1;
+ c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert),
+ ca_delete_handler, P(st));
+ c->column = 1;
+
+ s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record");
+
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_editbox(s, "Public key of certification authority", 'k', 100,
+ HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler,
+ P(st), P(NULL));
+ c->column = 0;
+ st->ca_pubkey_edit = c;
+ c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false,
+ "Select public key file of certification authority",
+ HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st));
+ c->fileselect.just_button = true;
+ c->align_next_to = st->ca_pubkey_edit;
+ c->column = 1;
+ ctrl_columns(s, 1, 100);
+ st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert));
+ c->text.wrap = false;
+
+ s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do");
+
+ c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100,
+ HELPCTX(ssh_cert_valid_expr), ca_validity_handler,
+ P(st), P(NULL));
+ st->ca_validity_edit = c;
+
+ ctrl_columns(s, 4, 44, 18, 18, 18);
+ c = ctrl_text(s, "Signature types (RSA keys only):",
+ HELPCTX(ssh_cert_rsa_hash));
+ c->column = 0;
+ dlgcontrol *sigtypelabel = c;
+ c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 1;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha1));
+ st->rsa_type_checkboxes[0] = c;
+ c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 2;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha256));
+ st->rsa_type_checkboxes[1] = c;
+ c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 3;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha512));
+ st->rsa_type_checkboxes[2] = c;
+ ctrl_columns(s, 1, 100);
+}