From 6b9c02ac9b2331291f26a3612cf35a44d5f55aff Mon Sep 17 00:00:00 2001 From: Dan Breslau Date: Wed, 28 Sep 2016 15:13:57 -0400 Subject: [PATCH] Squashed merge of many commits, including (but not limited to) : Split moonshot-server.vala into linux and win32 pieces Split moonshot-server.vala into moonshot-server-linix.vala and moonshot-server-msrpc.vala Implement DBUS call for ConfirmCACertificate Save server fingerprint in keyring after user approves it; check keyring for fingerprint before asking user to approve it. Use containers to align the Services: label and list of services Also, align the expansion/contraction arrow with the top of the idcard widget, not the middle. Set background color to white only on Windows Bug #1632163 No check for duplicate NAIs when modifying an identity Check for duplicate NAIs after an entity is modified, as well as checking at startup. If duplicates are found, warn the user. --- .gitignore | 3 +- Makefile.am | 2 +- libmoonshot/libmoonshot-dbus.c | 56 +++++ libmoonshot/libmoonshot.def | 1 + libmoonshot/libmoonshot.h | 15 ++ libmoonshot/libmoonshot.vapi | 7 + src/moonshot-custom-vbox.vala | 7 +- src/moonshot-id.vala | 30 ++- src/moonshot-idcard-widget.vala | 77 +++--- src/moonshot-identities-manager.vala | 44 +++- src/moonshot-identity-dialog.vala | 12 +- src/moonshot-identity-management-view.vala | 118 +++++---- src/moonshot-identity-manager-app.vala | 7 +- src/moonshot-keyring-store.vala | 7 +- src/moonshot-local-flat-file-store.vala | 4 +- src/moonshot-password-dialog.vala | 4 +- src/moonshot-provisioning-common.vala | 3 +- ...shot-server.vala => moonshot-server-linux.vala} | 251 +------------------ src/moonshot-server-msrpc.vala | 267 +++++++++++++++++++++ src/moonshot-trust-anchor-dialog.vala | 111 ++++++++- src/moonshot-utils.vala | 20 +- src/moonshot-warning-dialog.vala | 4 +- webprovisioning/blank-test.msht | 24 ++ webprovisioning/cert-test.msht | 120 +++++++++ webprovisioning/complex-test.msht | 53 ++-- 25 files changed, 849 insertions(+), 398 deletions(-) rename src/{moonshot-server.vala => moonshot-server-linux.vala} (59%) create mode 100644 src/moonshot-server-msrpc.vala create mode 100644 webprovisioning/blank-test.msht create mode 100644 webprovisioning/cert-test.msht diff --git a/.gitignore b/.gitignore index b2b436a..fbc5605 100755 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,8 @@ src/moonshot-logger.c src/moonshot-password-dialog.c src/moonshot-provisioning-common-new.vala src/moonshot-provisioning-common.c -src/moonshot-server.c +src/moonshot-server-linux.c +src/moonshot-server-msrpc.c src/moonshot-settings.c src/moonshot-trust-anchor-dialog.c src/moonshot-utils.c diff --git a/Makefile.am b/Makefile.am index a578f76..0335016 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,7 +64,7 @@ src_moonshot_SOURCES = \ src/moonshot-custom-vbox.vala \ src/moonshot-identities-manager.vala \ src/moonshot-identity-request.vala \ - src/moonshot-server.vala \ + src/moonshot-server-linux.vala \ src/moonshot-settings.vala \ src/moonshot-password-dialog.vala \ src/moonshot-provisioning-common.vala \ diff --git a/libmoonshot/libmoonshot-dbus.c b/libmoonshot/libmoonshot-dbus.c index e7c4b51..e5ac3f1 100644 --- a/libmoonshot/libmoonshot-dbus.c +++ b/libmoonshot/libmoonshot-dbus.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -447,3 +448,58 @@ int moonshot_install_id_card (const char *display_name, return success; } + +int moonshot_confirm_ca_certificate (const char *identity_name, + const char *realm, + const unsigned char *ca_hash, + int hash_len, + MoonshotError **error) +{ + GError *g_error = NULL; + int success = 99; + int confirmed = 99; + char hash_str[65]; + DBusGProxy *dbus_proxy = get_dbus_proxy (error); + int out = 0; + int i; + + if (*error != NULL) { + return FALSE; + } + + g_return_val_if_fail (DBUS_IS_G_PROXY (dbus_proxy), FALSE); + + /* Convert hash byte array to string */ + out = 0; + for (i = 0; i < hash_len; i++) { + sprintf(&(hash_str[out]), "%02X", ca_hash[i]); + out += 2; + } + + printf("moonshot_confirm_ca_certificate: calling ConfirmCaCertificate; hash='%s'\n", hash_str); + + int call_ok = dbus_g_proxy_call_with_timeout (dbus_proxy, + "ConfirmCaCertificate", + INFINITE_TIMEOUT, + &g_error, + G_TYPE_STRING, identity_name, + G_TYPE_STRING, realm, + G_TYPE_STRING, hash_str, + G_TYPE_INVALID, + G_TYPE_INT, &confirmed, + G_TYPE_BOOLEAN, &success, + G_TYPE_INVALID); + + printf("moonshot_confirm_ca_certificate: back from ConfirmCaCertificate. call_ok=%d; confirmed=%d; success=%d\n", + (call_ok? 1 : 0), confirmed, success); + + g_object_unref (dbus_proxy); + + if (g_error != NULL) { + *error = moonshot_error_new (MOONSHOT_ERROR_IPC_ERROR, + g_error->message); + return FALSE; + } + + return (int) confirmed; +} diff --git a/libmoonshot/libmoonshot.def b/libmoonshot/libmoonshot.def index 0819d1b..98f4393 100644 --- a/libmoonshot/libmoonshot.def +++ b/libmoonshot/libmoonshot.def @@ -4,3 +4,4 @@ EXPORTS moonshot_error_new moonshot_get_default_identity moonshot_get_identity + moonshot_confirm_ca_certificate diff --git a/libmoonshot/libmoonshot.h b/libmoonshot/libmoonshot.h index d7830c2..5b2524d 100644 --- a/libmoonshot/libmoonshot.h +++ b/libmoonshot/libmoonshot.h @@ -187,4 +187,19 @@ int moonshot_install_id_card (const char *display_name, int force_flat_file_store, MoonshotError **error); + + +/** + * moonshot_confirm_ca_certificate + * @ + * Return value: %TRUE if the certificate is approved; %FALSE otherwise + */ + +int moonshot_confirm_ca_certificate (const char *identity_name, + const char *realm, + const unsigned char *sha256, + int sha256_length, + MoonshotError **error); + + #endif diff --git a/libmoonshot/libmoonshot.vapi b/libmoonshot/libmoonshot.vapi index 8662596..840e19f 100644 --- a/libmoonshot/libmoonshot.vapi +++ b/libmoonshot/libmoonshot.vapi @@ -51,4 +51,11 @@ namespace Moonshot { string? server_cert, int force_flat_file_store, out Moonshot.Error error); + + [CCode (cname = "moonshot_confirm_ca_certificate")] + public bool moonshot_confirm_ca_certificate (string identity_name, + string realm, + string ca_hash, + out uint32 confirmed, + out Moonshot.Error error); } diff --git a/src/moonshot-custom-vbox.vala b/src/moonshot-custom-vbox.vala index 088f2a0..428c053 100644 --- a/src/moonshot-custom-vbox.vala +++ b/src/moonshot-custom-vbox.vala @@ -67,10 +67,13 @@ class CustomVBox : VBox id_card_widget.position = next_pos++; } - public IdCardWidget? find_idcard_widget(IdCard id_card) { + public IdCardWidget? find_idcard_widget(IdCard card) { + if (card == null) { + return null; + } foreach (var w in get_children()) { IdCardWidget widget = (IdCardWidget) w; - if (widget.id_card.nai == id_card.nai) { + if (widget.id_card == card) { return widget; } } diff --git a/src/moonshot-id.vala b/src/moonshot-id.vala index b8e92a6..3a0f960 100644 --- a/src/moonshot-id.vala +++ b/src/moonshot-id.vala @@ -43,6 +43,7 @@ public class TrustAnchor : Object private static const string CERT_FOOTER = "-----END CERTIFICATE-----"; public enum TrustAnchorType { + EMPTY, CA_CERT, SERVER_CERT } @@ -52,18 +53,16 @@ public class TrustAnchor : Object private string _subject_alt = ""; private string _server_cert = ""; private string _datetime_added = ""; - public bool user_verified = false; private static string fixup (string s) { return (s == null ? "" : s.strip()); } - public TrustAnchor(string ca_cert, string server_cert, string subject, string subject_alt, bool user_verified) { + public TrustAnchor(string ca_cert, string server_cert, string subject, string subject_alt) { _ca_cert = fixup(ca_cert); _server_cert = fixup(server_cert); _subject = fixup(subject); _subject_alt = fixup(subject_alt); - this.user_verified = user_verified; // If we're reading from store, this will be overridden (see set_datetime_added) _datetime_added = ""; @@ -105,11 +104,12 @@ public class TrustAnchor : Object } public bool is_empty() { - return ca_cert == "" && subject == "" && subject_alt == "" && server_cert == ""; + return ca_cert == "" && server_cert == ""; } public TrustAnchorType get_anchor_type() { - return server_cert == "" ? TrustAnchorType.CA_CERT : TrustAnchorType.SERVER_CERT; + return (server_cert != "" ? TrustAnchorType.SERVER_CERT + : (ca_cert != "" ? TrustAnchorType.CA_CERT : TrustAnchorType.EMPTY)); } internal void set_datetime_added(string datetime) { @@ -122,6 +122,12 @@ public class TrustAnchor : Object return dt; } + internal void update_server_fingerprint(string fingerprint) { + this._server_cert = fingerprint; + string ta_datetime_added = TrustAnchor.format_datetime_now(); + this.set_datetime_added(ta_datetime_added); + } + public int Compare(TrustAnchor other) { if (this.ca_cert != other.ca_cert) { @@ -141,7 +147,7 @@ public class TrustAnchor : Object return 1; } - // Do not compare the user_verified and datetime_added fields; they are not essential. + // Do not compare the datetime_added fields; it's not essential. return 0; } @@ -332,6 +338,18 @@ public class IdCard : Object public bool store_password { get; set; default = false; } + // uuid is currently used only for debugging. Must be unique, even between cards with same nai and display name. + public string uuid { + public get {return _uuid;} + } + private string _uuid = generate_uuid(); + + internal static string generate_uuid() { + uint32 rand1 = Random.next_int(); + uint32 rand2 = Random.next_int(); + return "%08X.%08X::%s".printf(rand1, rand2, TrustAnchor.format_datetime_now()); + } + public bool is_no_identity() { return (display_name == NO_IDENTITY); diff --git a/src/moonshot-idcard-widget.vala b/src/moonshot-idcard-widget.vala index 03f4d61..25976b3 100644 --- a/src/moonshot-idcard-widget.vala +++ b/src/moonshot-idcard-widget.vala @@ -41,12 +41,12 @@ class IdCardWidget : Box public IdCard id_card { get; set; default = null; } private VBox main_vbox; - private HBox table; + private HBox hbox; private EventBox event_box; private bool is_selected = false; private Arrow arrow; - private Label label; + private VBox details; internal int _position = 0; internal int position { @@ -72,7 +72,7 @@ class IdCardWidget : Box public void expand() { is_selected = true; - update_id_card_label(); + details.show_all(); set_idcard_color(); arrow.set(ArrowType.DOWN, ARROW_SHADOW); @@ -81,7 +81,7 @@ class IdCardWidget : Box public void collapse() { is_selected = false; - update_id_card_label(); + details.hide(); set_idcard_color(); arrow.set(ArrowType.RIGHT, ARROW_SHADOW); @@ -125,29 +125,12 @@ class IdCardWidget : Box } private void - update_id_card_label() + make_id_card_label(Label label) { - // !!TODO: Use a table to format the labels and values - string service_spacer = "\n "; - var display_name = (manager_view.selection_in_progress() && this.id_card.is_no_identity() ? _("Do not use a Moonshot identity for this service") : this.id_card.display_name); var label_text = Markup.printf_escaped("%s", display_name); - if (is_selected) - { - if (!this.id_card.is_no_identity()) { - label_text += "\n" + _("Username") + ": " + id_card.username; - label_text += "\n" + _("Realm:") + " " + id_card.issuer; - if (!id_card.trust_anchor.is_empty()) { - label_text += "\n" + _("Trust anchor: Enterprise provisioned"); - } - } - - string services_text = _("Services: ") + this.id_card.get_services_string(service_spacer); - label_text += "\n" + services_text; - } - label.set_markup(label_text); } @@ -156,25 +139,54 @@ class IdCardWidget : Box this.id_card = id_card; this.manager_view = manager_view; - label = new Label(null); - label.set_alignment((float) 0, (float) 0.5); - label.set_ellipsize(Pango.EllipsizeMode.END); - update_id_card_label(); - - table = new Gtk.HBox(false, 6); + var display_name_label = new Label(null); + display_name_label.set_alignment((float) 0, (float) 0.5); + display_name_label.set_ellipsize(Pango.EllipsizeMode.END); + make_id_card_label(display_name_label); + + var details_wrapper = new VBox(false, 0); + details_wrapper.pack_start(display_name_label, false, false, 0); + this.details = new VBox(false, 0); + details_wrapper.pack_start(details, false, false, 0); + + if (!this.id_card.is_no_identity()) { + var upper_details_text = _("Username") + ": " + id_card.username; + upper_details_text += "\n" + _("Realm:") + " " + id_card.issuer; + if (!id_card.trust_anchor.is_empty()) { + upper_details_text += "\n" + _("Trust anchor: Enterprise provisioned"); + } + Label upper_details = new Label(upper_details_text); + upper_details.set_alignment(0, 0); + details.pack_start(upper_details); + } + var services_hbox = new HBox(false, 6); + Label services_label = new Label(_("Services: ")); + services_label.set_alignment(0, 0); + + string services_text = this.id_card.get_services_string("\n"); + Label service_list = new Label(services_text); + service_list.set_alignment(0, 0); + service_list.set_ellipsize(Pango.EllipsizeMode.END); + service_list.set_max_width_chars(50); + services_hbox.pack_start(services_label, false, false, 0); + services_hbox.pack_start(service_list, false, false, 0); + details.pack_start(services_hbox); + + hbox = new Gtk.HBox(false, 6); var image = new Image.from_pixbuf(get_pixbuf(id_card)); if (this.id_card.is_no_identity()) { image.clear(); // Use padding to make the image size = 48x48 (size = 2x padding) image.set_padding(24, 24); } - table.pack_start(image, false, false, 0); - table.pack_start(label, true, true, 0); + hbox.pack_start(image, false, false, 0); + hbox.pack_start(details_wrapper, true, true, 0); this.arrow = new Arrow(ArrowType.RIGHT, ARROW_SHADOW); - table.pack_start(arrow, false, false); + this.arrow.set_alignment((float) 0.5, (float) 0); + hbox.pack_start(arrow, false, false); this.main_vbox = new VBox(false, 12); - main_vbox.pack_start(table, true, true, 0); + main_vbox.pack_start(hbox, true, true, 0); main_vbox.set_border_width(12); event_box = new EventBox(); @@ -184,6 +196,7 @@ class IdCardWidget : Box this.pack_start(event_box, true, true); this.show_all(); + details.hide(); set_idcard_color(); } diff --git a/src/moonshot-identities-manager.vala b/src/moonshot-identities-manager.vala index 01eea71..5cb4279 100644 --- a/src/moonshot-identities-manager.vala +++ b/src/moonshot-identities-manager.vala @@ -157,9 +157,8 @@ public class IdentityManagerModel : Object { remove_card_internal(id_card); if (new_card.trust_anchor.Compare(id_card.trust_anchor) == 0) { - logger.trace("Old and new cards have same trust anchor. Re-using the datetime_added and user_verified fields from the old card."); + logger.trace("Old and new cards have same trust anchor. Re-using the datetime_added field from the old card."); new_card.trust_anchor.set_datetime_added(id_card.trust_anchor.datetime_added); - new_card.trust_anchor.user_verified = id_card.trust_anchor.user_verified; } } @@ -170,6 +169,47 @@ public class IdentityManagerModel : Object { return (dups.size > 0); } + + public bool find_duplicate_nai_sets(out ArrayList> duplicates) + { + var nais = new HashMap>(); + + duplicates = new ArrayList>(); + LinkedList card_list = get_card_list() ; + if (card_list == null) { + return false; + } + + bool found = false; + foreach (IdCard id_card in card_list) { + logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'"); + + //!!TODO: This uniqueness check really belongs somewhere else -- like where we add + // IDs, and/or read them from storage. However, we should never hit this. + + if (nais.has_key(id_card.nai)) { + ArrayList list = nais.get(id_card.nai); + list.add(id_card); + } + else { + ArrayList list = new ArrayList(); + list.add(id_card); + nais.set(id_card.nai, list); + } + } + + duplicates = new ArrayList>(); + foreach (Map.Entry> entry in nais.entries) { + var list = entry.value; + if (list.size > 1) { + duplicates.add(list); + found = true; + } + } + return found; + } + + public IdCard? find_id_card(string nai, bool force_flat_file_store) { IdCard? retval = null; IIdentityCardStore.StoreType saved_store_type = get_store_type(); diff --git a/src/moonshot-identity-dialog.vala b/src/moonshot-identity-dialog.vala index 3eb8c2b..315f586 100644 --- a/src/moonshot-identity-dialog.vala +++ b/src/moonshot-identity-dialog.vala @@ -196,7 +196,7 @@ class IdentityDialog : Dialog this.set_border_width(6); this.set_resizable(false); - this.modify_bg(StateType.NORMAL, white); + set_bg_color(this); this.show_all(); } @@ -235,11 +235,11 @@ class IdentityDialog : Dialog row++; if (id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) { - Widget fingerprint = make_ta_fingerprint_widget(id.trust_anchor); - ta_table.attach(fingerprint, 0, 1, row, row + 2, fill_and_expand, fill_and_expand, 5, 5); + Widget fingerprint = make_ta_fingerprint_widget(id.trust_anchor.server_cert); + // ta_table.attach(fingerprint, 0, 1, row, row + 2, fill_and_expand, fill_and_expand, 5, 5); // To make the fingerprint box wider, try: - // ta_table.attach(fingerprint, 0, 2, row, row + 2, fill_and_expand, fill_and_expand, 20, 5); + ta_table.attach(fingerprint, 0, 2, row, row + 2, fill_and_expand, fill_and_expand, 20, 5); } else { @@ -370,7 +370,7 @@ class IdentityDialog : Dialog var services_table = new Table(card.services.size, 1, false); services_table.set_row_spacings(1); services_table.set_col_spacings(0); - services_table.modify_bg(StateType.NORMAL, white); + set_bg_color(services_table); var table_button_hbox = new HBox(false, 6); table_button_hbox.pack_start(services_vscroll, true, true, 4); @@ -383,7 +383,7 @@ class IdentityDialog : Dialog // A table doesn't have a background color, so put it in an EventBox, and // set the EventBox's background color instead. EventBox table_bg = new EventBox(); - table_bg.modify_bg(StateType.NORMAL, white); + set_bg_color(table_bg); table_bg.add(services_table); services_vbox_alignment.add(table_bg); diff --git a/src/moonshot-identity-management-view.vala b/src/moonshot-identity-management-view.vala index a7a297a..1a042b9 100644 --- a/src/moonshot-identity-management-view.vala +++ b/src/moonshot-identity-management-view.vala @@ -68,7 +68,7 @@ public class IdentityManagerView : Window { internal CheckButton remember_identity_binding = null; - private IdCard selected_idcard = null; + private IdCard selected_card = null; private string import_directory = null; @@ -103,10 +103,32 @@ public class IdentityManagerView : Window { set_default_size(WINDOW_WIDTH, WINDOW_HEIGHT); build_ui(); setup_list_model(); - load_id_cards(); + load_id_cards(); connect_signals(); + report_duplicate_nais(); } + private void report_duplicate_nais() { + ArrayList> duplicates; + identities_manager.find_duplicate_nai_sets(out duplicates); + foreach (ArrayList list in duplicates) { + string message = _("The following identities use the same Network Access Identifier (NAI),\n'%s'.").printf(list.get(0).nai) + + _("\n\nDuplicate NAIs are not allowed. Please remove identities you don't need, or modify") + + _(" user ID or issuer fields so that they are no longer the same NAI."); + + foreach (var card in list) { + message += "\n\nDisplay Name: '%s'\nServices:\n %s".printf(card.display_name, card.get_services_string(",\n ")); + } + var msg_dialog = new Gtk.MessageDialog(this, + Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.INFO, + Gtk.ButtonsType.OK, + message); + msg_dialog.run(); + msg_dialog.destroy(); + } + } + private void on_card_list_changed() { logger.trace("on_card_list_changed"); load_id_cards(); @@ -258,7 +280,7 @@ public class IdentityManagerView : Window { { logger.trace("add_id_card_widget: id_card.nai='%s'; selected nai='%s'" .printf(id_card.nai, - this.selected_idcard == null ? "[null selection]" : this.selected_idcard.nai)); + this.selected_card == null ? "[null selection]" : this.selected_card.nai)); var id_card_widget = new IdCardWidget(id_card, this); @@ -266,9 +288,19 @@ public class IdentityManagerView : Window { id_card_widget.expanded.connect(this.widget_selected_cb); id_card_widget.collapsed.connect(this.widget_unselected_cb); - if (this.selected_idcard != null && this.selected_idcard.nai == id_card.nai) { + if (this.selected_card != null && this.selected_card.nai == id_card.nai) { logger.trace(@"add_id_card_widget: Expanding selected idcard widget"); id_card_widget.expand(); + + // After a card is added, modified, or deleted, we reload all the cards. + // (I'm not sure why, or if it's necessary to do this.) This means that the + // selected_card may now point to a card instance that's not in the current list. + // Hence the only way to carry the selection across reloads is to identify + // the selected card by its NAI. And hence we need to reset what our idea of the + // "selected card" is. + // There should be a better way to do this, especially since we're not great + // at preventing duplicate NAIs. + this.selected_card = id_card; } return id_card_widget; } @@ -277,7 +309,7 @@ public class IdentityManagerView : Window { { logger.trace(@"widget_selected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'"); - this.selected_idcard = id_card_widget.id_card; + this.selected_card = id_card_widget.id_card; bool allow_removes = !id_card_widget.id_card.is_no_identity(); this.remove_button.set_sensitive(allow_removes); this.edit_button.set_sensitive(true); @@ -291,7 +323,7 @@ public class IdentityManagerView : Window { { logger.trace(@"widget_unselected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'"); - this.selected_idcard = null; + this.selected_card = null; this.remove_button.set_sensitive(false); this.edit_button.set_sensitive(false); this.custom_vbox.receive_collapsed_event(id_card_widget); @@ -390,6 +422,9 @@ public class IdentityManagerView : Window { switch (result) { case ResponseType.OK: this.identities_manager.update_card(update_id_card_data(dialog, card)); + + // Make sure we haven't created a duplicate NAI via this update. + report_duplicate_nais(); break; default: break; @@ -400,11 +435,8 @@ public class IdentityManagerView : Window { private void remove_identity(IdCard id_card) { logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'"); - if (id_card != this.selected_idcard) { - logger.error("remove_identity: id_card != this.selected_idcard!"); - } - this.selected_idcard = null; + this.selected_card = null; this.identities_manager.remove_card(id_card); // Nothing is selected, so disable buttons @@ -486,8 +518,7 @@ public class IdentityManagerView : Window { set_prompting_service(request.service); remember_identity_binding.show(); - if (this.selected_idcard != null - && this.custom_vbox.find_idcard_widget(this.selected_idcard) != null) { + if (this.custom_vbox.find_idcard_widget(this.selected_card) != null) { // A widget is already selected, and has not been filtered out of the display via search send_button.set_sensitive(true); } @@ -547,11 +578,6 @@ public class IdentityManagerView : Window { { return_if_fail(this.selection_in_progress()); - if (!check_and_confirm_trust_anchor(id)) { - // Allow user to pick again - return; - } - var request = this.request_queue.pop_head(); var identity = check_add_password(id, request, identities_manager); send_button.set_sensitive(false); @@ -586,33 +612,6 @@ public class IdentityManagerView : Window { remember_identity_binding.hide(); } - private bool check_and_confirm_trust_anchor(IdCard id) - { - if (!id.trust_anchor.is_empty() && id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) { - if (!id.trust_anchor.user_verified) { - - bool ret = false; - int result = ResponseType.CANCEL; - var dialog = new TrustAnchorDialog(id, this); - while (!dialog.complete) - result = dialog.run(); - - switch (result) { - case ResponseType.OK: - id.trust_anchor.user_verified = true; - ret = true; - break; - default: - break; - } - - dialog.destroy(); - return ret; - } - } - return true; - } - private void on_about_action() { string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR); @@ -663,7 +662,7 @@ SUCH DAMAGE. about.set_modal(true); about.set_transient_for(this); about.response.connect((a, b) => {about.destroy();}); - about.modify_bg(StateType.NORMAL, white); + set_bg_color(about); about.run(); } @@ -717,8 +716,7 @@ SUCH DAMAGE. private void build_ui() { - // Note: On Debian7/Gtk+2, the menu bar remains gray. This doesn't happen on Debian8/Gtk+3. - this.modify_bg(StateType.NORMAL, white); + set_bg_color(this); create_ui_manager(); @@ -798,13 +796,13 @@ SUCH DAMAGE. row++; this.edit_button = new Button.with_label(_("Edit")); - edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_idcard);}); + edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_card);}); edit_button.set_sensitive(false); top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0); row++; this.remove_button = new Button.with_label(_("Remove")); - remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_idcard);}); + remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_card);}); remove_button.set_sensitive(false); top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0); row++; @@ -812,7 +810,7 @@ SUCH DAMAGE. // push the send button down another row. row++; this.send_button = new Button.with_label(_("Send")); - send_button.clicked.connect((w) => {send_identity_cb(this.selected_idcard);}); + send_button.clicked.connect((w) => {send_identity_cb(this.selected_card);}); // send_button.set_visible(false); send_button.set_sensitive(false); top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0); @@ -826,7 +824,6 @@ SUCH DAMAGE. // quit_item.hide(); Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell; - menushell.modify_bg(StateType.NORMAL, white); osxApp.set_menu_bar(menushell); osxApp.set_use_quartz_accelerators(true); @@ -835,7 +832,7 @@ SUCH DAMAGE. #else var menubar = this.ui_manager.get_widget("/MenuBar"); main_vbox.pack_start(menubar, false, false, 0); - menubar.modify_bg(StateType.NORMAL, white); + set_bg_color(menubar); #endif main_vbox.pack_start(top_table, true, true, 6); @@ -947,14 +944,15 @@ SUCH DAMAGE. logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'"); } } - var msg_dialog = new Gtk.MessageDialog(this, - Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.INFO, - Gtk.ButtonsType.OK, - _("Import completed. %d Identities were added or updated."), - import_count); - msg_dialog.run(); - msg_dialog.destroy(); + if (import_count == 0) { + var msg_dialog = new Gtk.MessageDialog(this, + Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.INFO, + Gtk.ButtonsType.OK, + _("Import completed. No identities were added or updated.")); + msg_dialog.run(); + msg_dialog.destroy(); + } } dialog.destroy(); } diff --git a/src/moonshot-identity-manager-app.vala b/src/moonshot-identity-manager-app.vala index 0170139..5d0fbd5 100644 --- a/src/moonshot-identity-manager-app.vala +++ b/src/moonshot-identity-manager-app.vala @@ -54,6 +54,7 @@ public class IdentityManagerApp { private MoonshotServer ipc_server; private bool name_is_owned; private bool show_requested; + public bool use_flat_file_store {public get; private set;} #if OS_MACOS public OSXApplication osxApp; @@ -81,13 +82,14 @@ public class IdentityManagerApp { } } -#if LOG4VALA +#if USE_LOG4VALA // Call this from main() to ensure that the logger is initialized internal IdentityManagerApp.dummy() {} #endif public IdentityManagerApp(bool headless, bool use_flat_file_store) { use_flat_file_store |= UserForcesFlatFileStore(); + this.use_flat_file_store = use_flat_file_store; #if GNOME_KEYRING bool keyring_available = (!use_flat_file_store) && GnomeKeyring.is_available(); @@ -413,7 +415,8 @@ const GLib.OptionEntry[] options = { public static int main(string[] args) { -#if LOG4VALA +#if USE_LOG4VALA + // Initialize the logger. new IdentityManagerApp.dummy(); #endif diff --git a/src/moonshot-keyring-store.vala b/src/moonshot-keyring-store.vala index 8ca5e31..7ae0d22 100644 --- a/src/moonshot-keyring-store.vala +++ b/src/moonshot-keyring-store.vala @@ -111,7 +111,6 @@ public class KeyringStore : Object, IIdentityCardStore { string server_cert = ""; string subject = ""; string subject_alt = ""; - bool user_verified = false; string ta_datetime_added = ""; for (i = 0; i < entry.attributes.len; i++) { var attribute = ((GnomeKeyring.Attribute *) entry.attributes.data)[i]; @@ -142,14 +141,12 @@ public class KeyringStore : Object, IIdentityCardStore { subject_alt = value; } else if (attribute.name == "StorePassword") { store_password = value; - } else if (attribute.name == "TA_User_Verified") { - user_verified = (value == "true"); } else if (attribute.name == "TA_DateTime_Added") { ta_datetime_added = value; } } - var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt, user_verified); + var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt); if (ta_datetime_added != "") { ta.set_datetime_added(ta_datetime_added); } @@ -179,6 +176,7 @@ public class KeyringStore : Object, IIdentityCardStore { id_card.password = entry.secret; else id_card.password = null; + id_card_list.add(id_card); } } @@ -212,7 +210,6 @@ public class KeyringStore : Object, IIdentityCardStore { attributes.append_string("Server-Cert", id_card.trust_anchor.server_cert); attributes.append_string("Subject", id_card.trust_anchor.subject); attributes.append_string("Subject-Alt", id_card.trust_anchor.subject_alt); - attributes.append_string("TA_User_Verified", id_card.trust_anchor.user_verified ? "true" : "false"); attributes.append_string("TA_DateTime_Added", id_card.trust_anchor.datetime_added); attributes.append_string("StorePassword", id_card.store_password ? "yes" : "no"); diff --git a/src/moonshot-local-flat-file-store.vala b/src/moonshot-local-flat-file-store.vala index 8cc905c..742ca74 100644 --- a/src/moonshot-local-flat-file-store.vala +++ b/src/moonshot-local-flat-file-store.vala @@ -121,8 +121,7 @@ public class LocalFlatFileStore : Object, IIdentityCardStore { string server_cert = key_file.get_string(identity, "ServerCert"); string subject = key_file.get_string(identity, "Subject"); string subject_alt = key_file.get_string(identity, "SubjectAlt"); - bool user_verified = get_bool_setting(identity, "TA_User_Verified", false, key_file); - var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt, user_verified); + var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt); string ta_datetime_added = get_string_setting(identity, "TA_DateTime_Added", "", key_file); if (ta_datetime_added != "") { ta.set_datetime_added(ta_datetime_added); @@ -194,7 +193,6 @@ public class LocalFlatFileStore : Object, IIdentityCardStore { if (id_card.trust_anchor.datetime_added != "") { key_file.set_string(id_card.display_name, "TA_DateTime_Added", id_card.trust_anchor.datetime_added); } - key_file.set_boolean(id_card.display_name, "TA_User_Verified", id_card.trust_anchor.user_verified); logger.trace(@"store_id_cards: Stored '$(id_card.display_name)'"); } diff --git a/src/moonshot-password-dialog.vala b/src/moonshot-password-dialog.vala index 77c4ba4..a68301a 100644 --- a/src/moonshot-password-dialog.vala +++ b/src/moonshot-password-dialog.vala @@ -50,7 +50,7 @@ class AddPasswordDialog : Dialog { this.set_title(_("Moonshot - Password")); this.set_modal(true); - this.modify_bg(StateType.NORMAL, white); + set_bg_color(this); this.add_buttons(_("Cancel"), ResponseType.CANCEL, _("Connect"), ResponseType.OK); @@ -59,7 +59,7 @@ class AddPasswordDialog : Dialog var content_area = this.get_content_area(); ((Box) content_area).set_spacing(12); - content_area.modify_bg(StateType.NORMAL, white); + set_bg_color(content_area); Label dialog_label = new Label(_("Enter the password for ") + id_card.display_name); dialog_label.set_alignment(0, 0); diff --git a/src/moonshot-provisioning-common.vala b/src/moonshot-provisioning-common.vala index 29160dd..2311cab 100644 --- a/src/moonshot-provisioning-common.vala +++ b/src/moonshot-provisioning-common.vala @@ -153,8 +153,7 @@ namespace WebProvisioning var ta = new TrustAnchor(ta_ca_cert, ta_server_cert, ta_subject, - ta_subject_alt, - false); + ta_subject_alt); // Set the datetime_added in moonshot-server.vala, since it doesn't get sent via IPC card.set_trust_anchor_from_store(ta); } diff --git a/src/moonshot-server.vala b/src/moonshot-server-linux.vala similarity index 59% rename from src/moonshot-server.vala rename to src/moonshot-server-linux.vala index 4fbcd2a..4aecef2 100644 --- a/src/moonshot-server.vala +++ b/src/moonshot-server-linux.vala @@ -32,8 +32,6 @@ using Gee; -#if IPC_DBUS - [DBus (name = "org.janet.Moonshot")] public class MoonshotServer : Object { @@ -201,7 +199,7 @@ public class MoonshotServer : Object { idcard.store_password = true; idcard.issuer = realm; idcard.update_services(services); - var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt, false); + var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt); if (!ta.is_empty()) { // We have to set the datetime_added here, because it isn't delivered via IPC. @@ -299,244 +297,21 @@ public class MoonshotServer : Object { } return installed_cards; } -} - - -#elif IPC_MSRPC - -using Rpc; -using MoonshotRpcInterface; - -/* This class must be a singleton, because we use a global RPC - * binding handle. I cannot picture a situation where more than - * one instance of the same interface would be needed so this - * shouldn't be a problem. - * - * Shutdown is automatically done by the RPC runtime when the - * process ends - */ -public class MoonshotServer : Object { - private static IdentityManagerApp parent_app; - - private static MoonshotServer instance = null; - - public static void start(IdentityManagerApp app) - { - parent_app = app; - Rpc.server_start(MoonshotRpcInterface.spec, "/org/janet/Moonshot", Rpc.Flags.PER_USER); - } - - public static MoonshotServer get_instance() - { - if (instance == null) - instance = new MoonshotServer(); - return instance; - } - [CCode (cname = "moonshot_get_identity_rpc")] - public static void get_identity(Rpc.AsyncCall call, - string nai, - string password, - string service, - ref string nai_out, - ref string password_out, - ref string server_certificate_hash, - ref string ca_certificate, - ref string subject_name_constraint, - ref string subject_alt_name_constraint) + public async bool confirm_ca_certificate(string nai, + string realm, + string ca_hash, + out int confirmed) { - logger.trace("(static) get_identity"); - - bool result = false; - - var request = new IdentityRequest(parent_app, - nai, - password, - service); - - // Pass execution to the main loop and block the RPC thread - request.mutex = new Mutex(); - request.cond = new Cond(); - request.set_callback(return_identity_cb); - - request.mutex.lock(); - Idle.add(request.execute); - - while (request.complete == false) - request.cond.wait(request.mutex); - - nai_out = ""; - password_out = ""; - server_certificate_hash = ""; - ca_certificate = ""; - subject_name_constraint = ""; - subject_alt_name_constraint = ""; - - var id_card = request.id_card; - - if (id_card != null) { - // The strings are freed by the RPC runtime - nai_out = id_card.nai; - password_out = id_card.password; - server_certificate_hash = id_card.trust_anchor.server_cert; - ca_certificate = id_card.trust_anchor.ca_cert; - subject_name_constraint = id_card.trust_anchor.subject; - subject_alt_name_constraint = id_card.trust_anchor.subject_alt; - - return_if_fail(nai_out != null); - return_if_fail(password_out != null); - return_if_fail(server_certificate_hash != null); - return_if_fail(ca_certificate != null); - return_if_fail(subject_name_constraint != null); - return_if_fail(subject_alt_name_constraint != null); - - result = true; - } - - // The outputs must be set before this function is called. For this - // reason they are 'ref' not 'out' parameters - Vala assigns to the - // 'out' parameters only at the end of the function, which is too - // late. - call.return(&result); - - request.cond.signal(); - request.mutex.unlock(); - } - - [CCode (cname = "moonshot_get_default_identity_rpc")] - public static void get_default_identity(Rpc.AsyncCall call, - ref string nai_out, - ref string password_out, - ref string server_certificate_hash, - ref string ca_certificate, - ref string subject_name_constraint, - ref string subject_alt_name_constraint) - { - logger.trace("(static) get_default_identity"); - - bool result; - - var request = new IdentityRequest.default(parent_app); - request.mutex = new Mutex(); - request.cond = new Cond(); - request.set_callback(return_identity_cb); + logger.trace(@"MoonshotServer.confirm_ca_certificate: nai='$nai'; realm='$realm'; ca_hash='$ca_hash'"); - request.mutex.lock(); - Idle.add(request.execute); - - while (request.complete == false) - request.cond.wait(request.mutex); - - nai_out = ""; - password_out = ""; - server_certificate_hash = ""; - ca_certificate = ""; - subject_name_constraint = ""; - subject_alt_name_constraint = ""; - - if (request.id_card != null) - { - nai_out = request.id_card.nai; - password_out = request.id_card.password; - server_certificate_hash = "certificate"; - - return_if_fail(nai_out != null); - return_if_fail(password_out != null); - return_if_fail(server_certificate_hash != null); - return_if_fail(ca_certificate != null); - return_if_fail(subject_name_constraint != null); - return_if_fail(subject_alt_name_constraint != null); - - result = true; - } - else - { - result = false; - } - - call.return(&result); - - request.cond.signal(); - request.mutex.unlock(); - } - - // Called from the main loop thread when an identity has - // been selected - static void return_identity_cb(IdentityRequest request) { - // Notify the RPC thread that the request is complete - request.mutex.lock(); - request.cond.signal(); - - // Block the main loop until the RPC call has returned - // to avoid any races - request.cond.wait(request.mutex); - request.mutex.unlock(); - } - - [CCode (cname = "moonshot_install_id_card_rpc")] - public static bool install_id_card(string display_name, - string user_name, - string password, - string realm, - string[] rules_patterns, - string[] rules_always_confirm, - string[] services, - string ca_cert, - string subject, - string subject_alt, - string server_cert, - bool force_flat_file_store) - { - logger.trace("(static) install_id_card"); - IdCard idcard = new IdCard(); - - bool success = false; - Mutex mutex = new Mutex(); - Cond cond = new Cond(); - - idcard.display_name = display_name; - idcard.username = user_name; - idcard.password = password; - idcard.issuer = realm; - idcard.services = services; - idcard.trust_anchor.ca_cert = ca_cert; - idcard.trust_anchor.subject = subject; - idcard.trust_anchor.subject_alt = subject_alt; - idcard.trust_anchor.server_cert = server_cert; - - if (rules_patterns.length == rules_always_confirm.length) - { - idcard.rules = new Rule[rules_patterns.length]; - - for (int i = 0; i < idcard.rules.length; i++) - { - idcard.rules[i].pattern = rules_patterns[i]; - idcard.rules[i].always_confirm = rules_always_confirm[i]; - } - } - - mutex.lock(); - - ArrayList? old_duplicates = null; - // Defer addition to the main loop thread. - Idle.add(() => { - mutex.lock(); - success = parent_app.add_identity(idcard, force_flat_file_store, out old_duplicates); - foreach (IdCard id_card in old_duplicates) { - stdout.printf("removing duplicate id for '%s'\n", new_card.nai); - } - cond.signal(); - mutex.unlock(); - return false; - }); - - cond.wait(mutex); - mutex.unlock(); + var request = new TrustAnchorConfirmationRequest(parent_app, nai, realm, ca_hash); + request.set_callback((TrustAnchorConfirmationRequest) => confirm_ca_certificate.callback()); + request.execute(); + yield; - return success; + confirmed = (request.confirmed ? 1 : 0); + logger.trace(@"MoonshotServer.confirm_ca_certificate: confirmed=$confirmed"); + return true; } - } - - -#endif diff --git a/src/moonshot-server-msrpc.vala b/src/moonshot-server-msrpc.vala new file mode 100644 index 0000000..b396ac9 --- /dev/null +++ b/src/moonshot-server-msrpc.vala @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2011-2016, JANET(UK) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of JANET(UK) nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. +*/ + +using Gee; + + +using Rpc; +using MoonshotRpcInterface; + +/* This class must be a singleton, because we use a global RPC + * binding handle. I cannot picture a situation where more than + * one instance of the same interface would be needed so this + * shouldn't be a problem. + * + * Shutdown is automatically done by the RPC runtime when the + * process ends + */ +public class MoonshotServer : Object { + private static IdentityManagerApp parent_app; + + private static MoonshotServer instance = null; + + public static void start(IdentityManagerApp app) + { + parent_app = app; + Rpc.server_start(MoonshotRpcInterface.spec, "/org/janet/Moonshot", Rpc.Flags.PER_USER); + } + + public static MoonshotServer get_instance() + { + if (instance == null) + instance = new MoonshotServer(); + return instance; + } + + [CCode (cname = "moonshot_get_identity_rpc")] + public static void get_identity(Rpc.AsyncCall call, + string nai, + string password, + string service, + ref string nai_out, + ref string password_out, + ref string server_certificate_hash, + ref string ca_certificate, + ref string subject_name_constraint, + ref string subject_alt_name_constraint) + { + logger.trace("(static) get_identity"); + + bool result = false; + + var request = new IdentityRequest(parent_app, + nai, + password, + service); + + // Pass execution to the main loop and block the RPC thread + request.mutex = new Mutex(); + request.cond = new Cond(); + request.set_callback(return_identity_cb); + + request.mutex.lock(); + Idle.add(request.execute); + + while (request.complete == false) + request.cond.wait(request.mutex); + + nai_out = ""; + password_out = ""; + server_certificate_hash = ""; + ca_certificate = ""; + subject_name_constraint = ""; + subject_alt_name_constraint = ""; + + var id_card = request.id_card; + + if (id_card != null) { + // The strings are freed by the RPC runtime + nai_out = id_card.nai; + password_out = id_card.password; + server_certificate_hash = id_card.trust_anchor.server_cert; + ca_certificate = id_card.trust_anchor.ca_cert; + subject_name_constraint = id_card.trust_anchor.subject; + subject_alt_name_constraint = id_card.trust_anchor.subject_alt; + + return_if_fail(nai_out != null); + return_if_fail(password_out != null); + return_if_fail(server_certificate_hash != null); + return_if_fail(ca_certificate != null); + return_if_fail(subject_name_constraint != null); + return_if_fail(subject_alt_name_constraint != null); + + result = true; + } + + // The outputs must be set before this function is called. For this + // reason they are 'ref' not 'out' parameters - Vala assigns to the + // 'out' parameters only at the end of the function, which is too + // late. + call.return(&result); + + request.cond.signal(); + request.mutex.unlock(); + } + + [CCode (cname = "moonshot_get_default_identity_rpc")] + public static void get_default_identity(Rpc.AsyncCall call, + ref string nai_out, + ref string password_out, + ref string server_certificate_hash, + ref string ca_certificate, + ref string subject_name_constraint, + ref string subject_alt_name_constraint) + { + logger.trace("(static) get_default_identity"); + + bool result; + + var request = new IdentityRequest.default(parent_app); + request.mutex = new Mutex(); + request.cond = new Cond(); + request.set_callback(return_identity_cb); + + request.mutex.lock(); + Idle.add(request.execute); + + while (request.complete == false) + request.cond.wait(request.mutex); + + nai_out = ""; + password_out = ""; + server_certificate_hash = ""; + ca_certificate = ""; + subject_name_constraint = ""; + subject_alt_name_constraint = ""; + + if (request.id_card != null) + { + nai_out = request.id_card.nai; + password_out = request.id_card.password; + server_certificate_hash = "certificate"; + + return_if_fail(nai_out != null); + return_if_fail(password_out != null); + return_if_fail(server_certificate_hash != null); + return_if_fail(ca_certificate != null); + return_if_fail(subject_name_constraint != null); + return_if_fail(subject_alt_name_constraint != null); + + result = true; + } + else + { + result = false; + } + + call.return(&result); + + request.cond.signal(); + request.mutex.unlock(); + } + + // Called from the main loop thread when an identity has + // been selected + static void return_identity_cb(IdentityRequest request) { + // Notify the RPC thread that the request is complete + request.mutex.lock(); + request.cond.signal(); + + // Block the main loop until the RPC call has returned + // to avoid any races + request.cond.wait(request.mutex); + request.mutex.unlock(); + } + + [CCode (cname = "moonshot_install_id_card_rpc")] + public static bool install_id_card(string display_name, + string user_name, + string password, + string realm, + string[] rules_patterns, + string[] rules_always_confirm, + string[] services, + string ca_cert, + string subject, + string subject_alt, + string server_cert, + bool force_flat_file_store) + { + logger.trace("(static) install_id_card"); + IdCard idcard = new IdCard(); + + bool success = false; + Mutex mutex = new Mutex(); + Cond cond = new Cond(); + + idcard.display_name = display_name; + idcard.username = user_name; + idcard.password = password; + idcard.issuer = realm; + idcard.services = services; + idcard.trust_anchor.ca_cert = ca_cert; + idcard.trust_anchor.subject = subject; + idcard.trust_anchor.subject_alt = subject_alt; + idcard.trust_anchor.server_cert = server_cert; + + if (rules_patterns.length == rules_always_confirm.length) + { + idcard.rules = new Rule[rules_patterns.length]; + + for (int i = 0; i < idcard.rules.length; i++) + { + idcard.rules[i].pattern = rules_patterns[i]; + idcard.rules[i].always_confirm = rules_always_confirm[i]; + } + } + + mutex.lock(); + + ArrayList? old_duplicates = null; + // Defer addition to the main loop thread. + Idle.add(() => { + mutex.lock(); + success = parent_app.add_identity(idcard, force_flat_file_store, out old_duplicates); + foreach (IdCard id_card in old_duplicates) { + stdout.printf("removing duplicate id for '%s'\n", new_card.nai); + } + cond.signal(); + mutex.unlock(); + return false; + }); + + cond.wait(mutex); + mutex.unlock(); + + return success; + } +} diff --git a/src/moonshot-trust-anchor-dialog.vala b/src/moonshot-trust-anchor-dialog.vala index 0537494..28169d2 100644 --- a/src/moonshot-trust-anchor-dialog.vala +++ b/src/moonshot-trust-anchor-dialog.vala @@ -31,18 +31,115 @@ */ using Gtk; +public delegate void TrustAnchorConfirmationCallback(TrustAnchorConfirmationRequest request); + +public class TrustAnchorConfirmationRequest : GLib.Object { + static MoonshotLogger logger = get_logger("TrustAnchorConfirmationRequest"); + + IdentityManagerApp parent_app; + string userid; + string realm; + string ca_hash; + public bool confirmed = false; + + TrustAnchorConfirmationCallback callback = null; + + public TrustAnchorConfirmationRequest(IdentityManagerApp parent_app, + string userid, + string realm, + string ca_hash) + { + this.parent_app = parent_app; + this.userid = userid; + this.realm = realm; + this.ca_hash = ca_hash; + } + + public void set_callback(owned TrustAnchorConfirmationCallback cb) + { +// #if VALA_0_12 + this.callback = ((owned) cb); +// #else +// this.callback = ((IdCard) => cb(IdCard)); +// #endif + } + + public bool execute() { + + string nai = userid + "@" + realm; + IdCard? card = parent_app.model.find_id_card(nai, parent_app.use_flat_file_store); + if (card == null) { + logger.warn(@"execute: Could not find ID card for NAI $nai; returning false."); + return_confirmation(false); + return false; + } + + if (!(card.trust_anchor.is_empty() || card.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT)) { + logger.warn(@"execute: Trust anchor type for NAI $nai is not empty or SERVER_CERT; returning true."); + return_confirmation(true); + return false; + } + + if (card.trust_anchor.server_cert == ca_hash) { + logger.trace(@"execute: Fingerprint for $nai matches stored value; returning true."); + return_confirmation(true); + return false; + } + + var dialog = new TrustAnchorDialog(userid, realm, ca_hash); + var response = dialog.run(); + dialog.destroy(); + bool is_confirmed = (response == ResponseType.OK); + + if (is_confirmed) { + logger.trace(@"execute: Fingerprint confirmed; updating stored value."); + + card.trust_anchor.update_server_fingerprint(ca_hash); + parent_app.model.update_card(card); + } + + return_confirmation(is_confirmed); + + /* This function works as a GSourceFunc, so it can be passed to + * the main loop from other threads + */ + return false; + } + + private void return_confirmation(bool confirmed) { + return_if_fail(callback != null); + + this.confirmed = confirmed; + logger.trace(@"return_confirmation: confirmed=$confirmed"); + + // Send back the confirmation (we can't directly run the + // callback because we may be being called from a 'yield') + GLib.Idle.add( + () => { + logger.trace("return_confirmation[Idle handler]: invoking callback"); + callback(this); + return false; + } + ); + } +} + + + class TrustAnchorDialog : Dialog { private static Gdk.Color white = make_color(65535, 65535, 65535); public bool complete = false; - public TrustAnchorDialog(IdCard idcard, Window parent) + public TrustAnchorDialog(string userid, + string realm, + string ca_hash) { this.set_title(_("Trust Anchor")); this.set_modal(true); - this.set_transient_for(parent); - this.modify_bg(StateType.NORMAL, white); +// this.set_transient_for(parent); + set_bg_color(this); this.add_buttons(_("Cancel"), ResponseType.CANCEL, _("Confirm"), ResponseType.OK); @@ -51,7 +148,7 @@ class TrustAnchorDialog : Dialog var content_area = this.get_content_area(); ((Box) content_area).set_spacing(12); - content_area.modify_bg(StateType.NORMAL, white); + set_bg_color(content_area); Label dialog_label = new Label(""); dialog_label.set_alignment(0, 0); @@ -62,16 +159,16 @@ class TrustAnchorDialog : Dialog dialog_label.set_line_wrap(true); dialog_label.set_width_chars(60); - var user_label = new Label(_("Username: ") + idcard.username); + var user_label = new Label(_("Username: ") + userid); user_label.set_alignment(0, 0.5f); - var realm_label = new Label(_("Realm: ") + idcard.issuer); + var realm_label = new Label(_("Realm: ") + realm); realm_label.set_alignment(0, 0.5f); Label confirm_label = new Label(_("Please confirm that this is the correct trust anchor.")); confirm_label.set_alignment(0, 0.5f); - var trust_anchor_display = make_ta_fingerprint_widget(idcard.trust_anchor); + var trust_anchor_display = make_ta_fingerprint_widget(ca_hash); var vbox = new VBox(false, 0); vbox.set_border_width(6); diff --git a/src/moonshot-utils.vala b/src/moonshot-utils.vala index 4eaed1f..7846745 100644 --- a/src/moonshot-utils.vala +++ b/src/moonshot-utils.vala @@ -124,7 +124,7 @@ internal void set_atk_relation(Widget widget, Widget target_widget, Atk.Relation } -internal Widget make_ta_fingerprint_widget(TrustAnchor trust_anchor) +internal Widget make_ta_fingerprint_widget(string server_cert) { var fingerprint_label = new Label(_("SHA-256 fingerprint:")); fingerprint_label.set_alignment(0, 0.5f); @@ -135,7 +135,7 @@ internal Widget make_ta_fingerprint_widget(TrustAnchor trust_anchor) fingerprint.set_editable(false); fingerprint.set_left_margin(3); var buffer = fingerprint.get_buffer(); - buffer.set_text(colonize(trust_anchor.server_cert, 16), -1); + buffer.set_text(colonize(server_cert, 16), -1); fingerprint.wrap_mode = Gtk.WrapMode.WORD_CHAR; set_atk_relation(fingerprint_label, fingerprint, Atk.RelationType.LABEL_FOR); @@ -143,7 +143,7 @@ internal Widget make_ta_fingerprint_widget(TrustAnchor trust_anchor) var fingerprint_width_constraint = new ScrolledWindow(null, null); fingerprint_width_constraint.set_policy(PolicyType.NEVER, PolicyType.NEVER); fingerprint_width_constraint.set_shadow_type(ShadowType.IN); - fingerprint_width_constraint.set_size_request(300, 60); + fingerprint_width_constraint.set_size_request(360, 60); fingerprint_width_constraint.add_with_viewport(fingerprint); var vbox = new VBox(false, 0); @@ -173,3 +173,17 @@ internal static string colonize(string input, int bytes_per_line) { } return result; } + +static Gdk.Color white; +static void set_bg_color(Widget w) +{ +#if OS_WIN32 + + if (white == null) { + white = make_color(65535, 65535, 65535); + } + + w.modify_bg(StateType.NORMAL, white); + +#endif +} diff --git a/src/moonshot-warning-dialog.vala b/src/moonshot-warning-dialog.vala index 72233a4..43f7aa3 100644 --- a/src/moonshot-warning-dialog.vala +++ b/src/moonshot-warning-dialog.vala @@ -98,10 +98,10 @@ class WarningDialog // dialog.set_modal(true); dialog.set_title(_("Warning")); - dialog.modify_bg(StateType.NORMAL, white); + set_bg_color(dialog); // ((Box) content_area).set_spacing(12); - content_area.modify_bg(StateType.NORMAL, white); + set_bg_color(content_area); content_area.show_all(); diff --git a/webprovisioning/blank-test.msht b/webprovisioning/blank-test.msht new file mode 100644 index 0000000..5287550 --- /dev/null +++ b/webprovisioning/blank-test.msht @@ -0,0 +1,24 @@ + + + + + + Not No Identity + someone + + painless-security.com + + something/painless-security.com + somethingelse/painless-security.com + + + + + No Identity + + a_service/painless-security.com + another_service/painless-security.com + + + + diff --git a/webprovisioning/cert-test.msht b/webprovisioning/cert-test.msht new file mode 100644 index 0000000..ab56e5f --- /dev/null +++ b/webprovisioning/cert-test.msht @@ -0,0 +1,120 @@ + + + No Trust Anchor + user5 + + painless-security.com + + + + + + Bad CA Certificate + user1 + + painless-security.com + + + + + + + MIIE5DCCA8ygAwIBAgIJAOz4PYh7hLBrMA0GCSqGSIb3DQEBBQUAMIGTMQswCQYD +VQQGEwJGUjEPMA0GA1UECBMGUmFkaXVzMRIwEAYDVQQHEwlTb21ld2hlcmUxFTAT +BgNVBAoTDEV4YW1wbGUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBs +ZS5vcmcxJjAkBgNVBAMTHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4X +DTE2MTAwNDE3MDYwMFoXDTE2MTIwMzE3MDYwMFowgZMxCzAJBgNVBAYTAkZSMQ8w +DQYDVQQIEwZSYWRpdXMxEjAQBgNVBAcTCVNvbWV3aGVyZTEVMBMGA1UEChMMRXhh +bXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9yZzEmMCQG +A1UEAxMdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC3wTX5gGxa/Ge1RN4ZDx67WIeFxmKv6ZkS0c1p +H2BS7RDGmBrWMG+RMX/kUdSNL1tarnbHknPYzKSdoTsM7bWLQpj1fV8nI4ZAF4Tp +QG8jQHFmpTfHPjDv+E6wEfilyfXRDpym8ITQfLXTn85zvK82F+153Aqh+BrCyOvJ +gANAoulArphg0UH6eyBuD+dezqXsinIMXfXZheTXi/TL3oSGjYkwF//WZUpmK5Kx +w1NupFMjjCJzWf0MtDgG4Gl83JJMzoy/pSKvzWeglvaI8tt64iUkbKNstJT9C0G2 +y+fVM9aVeRAQ+1O1LRaKgqhQMTrs/LPQNUapRKGvkwXlCR6lAgMBAAGjggE3MIIB +MzAdBgNVHQ4EFgQUMin4EfnT51ecs4f1KjpamdjQUDcwgcgGA1UdIwSBwDCBvYAU +Min4EfnT51ecs4f1KjpamdjQUDehgZmkgZYwgZMxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZSYWRpdXMxEjAQBgNVBAcTCVNvbWV3aGVyZTEVMBMGA1UEChMMRXhhbXBs +ZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9yZzEmMCQGA1UE +AxMdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDs+D2Ie4SwazAPBgNV +HRMBAf8EBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93d3cuZXhhbXBs +ZS5vcmcvZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQEFBQADggEBAHzr/erMvZ76 +7FHpf5H3G/TL57k5POnDlTnolXmQdK2eaq0xLhaPuilvNa3txGGI0iBJAD20K5ss +2o7ULHaAeuNAZ5+zuRx2xZrtLV3FQkugQZb70K/lECf3uCX4S/SqTeOo5VfPjaTM +6MMgU9Tmvo9a1q7xHzm2yEqzhCbP7dZ4BmUPw9QIkqbirlcQ2GgxOah5m94e2ETf +4SOBwM+5Lg+CAaCoIC0gpX2R3H+n4edslmiCoyU1r/Q6RQXiyWPdI6jjln08Jdt5 +0/rSpmhObSL0L7/z53+ka7jqW1ZxizVKYJiEIH9Y9Aw/vgf5nhnTgfTPfyPESyCO +fi186fRC+Zs= + Painless Security Server Certificate + + + + + + Good CA Certificate + user2 + + painless-security.com + + + + + + + +MIIE9jCCA96gAwIBAgIJANI5K4+KXvQyMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR +UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFp +bmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwg +SW5jLjAeFw0xNjA4MzAxOTU4MjlaFw0xOTEyMTMxOTU4MjlaMIGaMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFp +bmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxl +c3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5j +LjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ6tW6x+aO4n7VJu5W55 +DeNKn7+89oaaTgSRs6jg3C+RpTmXugPonh9+YRbuWpSNeru2eWGFNYFs01OjaDUw +CxPcFDgF3xP/wAyCsI7WUgmkz8991PUGo9RxVbkDxMePHNaLGQVNz/+EJgK/Ycfy +jYvenb/BGvcBmMftk2HsHio65ZsHsGMfW2Wcg/ehvKeDRZ3WR1ujhkzIFWdgdH3E +u/yI2pHEfxQQ3PuYcQz43YZyIwhwzwnQG8qTK2jWkMF+wzRKRYfLdRD8nUUingvu +IbngXLs71JqQHmbXzw1WTJClXtfF6R2VZuZ6PT8ZK1bDFPvTgnkUcAk70H+VnDM5 +K48CAwEAAaOCATswggE3MB0GA1UdDgQWBBQoFRKLJrZvkNmqvw8DNuTLPyru/DCB +zwYDVR0jBIHHMIHEgBQoFRKLJrZvkNmqvw8DNuTLPyru/KGBoKSBnTCBmjELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQHEwZNYWxkZW4xGjAYBgNVBAoT +EVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0bWFzdGVyQHBh +aW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks +IEluYy6CCQDSOSuPil70MjAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeG +JWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcN +AQEFBQADggEBAIN38UOXvwd89+a89V+/rjeN8JfpcjafLf0c2I3nex9OxBWji5bf +cWTNfm1t9GpS4HhVT2tl5xdxyW5UrE9Q+oFadN0LxtRPbGU+Gvt4pVo8Pst6/2P8 +PA3/OA1UchIpZR6EWQQsws4esNLLwDbj48MkQdVCjpp1cVpFVmJUzYYFD9h9EMK2 +kxpGf5wfp9LI2A5/qACNQPBDfRsR+dcNBsBbmD1LulqputUPuKPXnVbHWL28VZUY +PITHl2Ndbmk6znSu7ILef3CGyeXqTTj+Jo+5AQz3sneko6oMn8PqfRj1h0uUyykT +lavp3iTNstQs/rdqdI+lPYMokDKXRSD3pK8= + + Painless Security Server Certificate + + + + Good Fingerprint + user3 + + painless-security.com + + + + F2FCC5FAD4CCB7A7236A8AEEF5E94E0C0FB27BEC29DE0AE03C5B455D08D4DE77 + + + + Bad Fingerprint + user4 + + painless-security.com + + + + 4242424242424242424242424242424242424242424242424242424242424242 + + + + diff --git a/webprovisioning/complex-test.msht b/webprovisioning/complex-test.msht index bf6319a..e5279b9 100644 --- a/webprovisioning/complex-test.msht +++ b/webprovisioning/complex-test.msht @@ -78,29 +78,34 @@ musuCxXeWkqDtw0clWg6vkf5Tb9v/JQ2PW0= - MIIE9jCCA96gAwIBAgIJAJ6SVDCP6o2nMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYDVQQGEwJVUzEL -MAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFpbmxlc3MgU2VjdXJpdHkx -LzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQD -ExdQYWlubGVzcyBTZWN1cml0eSwgSW5jLjAeFw0xNjA4MDExNjIxMDVaFw0xOTExMTQxNjIxMDVa -MIGaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR -UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxlc3Mtc2Vj -dXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5jLjCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKPiSkw1y6zMJFjnoPjd5Bh9EA1NhQcoNxJAtgYEJtpH9a2tfjnX -XncXpbIMIfMgv2VKRAxvKb+knCfSCRtUPM9i998+ZhJY9o6SSFomlMvdaClauPvBhQvQMmJmp1WI -NgMUHPpzsGlj04kkl7jwiK/oDxp1becikKc10Gr9W03aEJtOaiSqC45zeIgnz9GoQ2tJvz2DDBcd -daaT1mSVn/lk4ahPC4XaJ08Jn1L6XkVVyDGD38Rwg7r1SFI7ByBFvvQh93Fa48Z7ik0I8s48U1eu -Hak2gSJ4zfzLndvGy05qMjhRTlxQu+Rt1g7CS3CLcJqqYzWNrEJWpD8Wn7iAMIUCAwEAAaOCATsw -ggE3MB0GA1UdDgQWBBR1qlvY7r2DqhHu5s+sCUPeqBcQuzCBzwYDVR0jBIHHMIHEgBR1qlvY7r2D -qhHu5s+sCUPeqBcQu6GBoKSBnTCBmjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQH -EwZNYWxkZW4xGjAYBgNVBAoTEVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0 -bWFzdGVyQHBhaW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks -IEluYy6CCQCeklQwj+qNpzAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93 -d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQEFBQADggEBAB6J5Zxvq96S -dIsfEajqU+pANBiA2VTZCpxfIMAKz8KfyzWzFvCM8epvYDliyOjw1zR9cYxhQqOcbPHrjLXheVvC -ePd3jCUOv+tt1Nw2gS2DiMuq37DOBZOTlPJ3m2NnvJVO3NjB2I+Pk9v3YlG6mkiVc9dNWgO20SqT -2Y+KvHqA5Of8Cb/suIBftctvGpIyEnqSmU7KB0nhIWe65Bsu60hjHHfX1qhJE7qGKbqNaHujssQ/ -SBXJg7HUhtywv8z3TFoYW0MoBpKGM2Ojc9kQ8f0rYvUKTiD1UfjQoll/Io5xwKy7FXtnmusuCxXe -WkqDtw0clWg6vkf5Tb9v/JQ2PW0= + +MIIE9jCCA96gAwIBAgIJANI5K4+KXvQyMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR +UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFp +bmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwg +SW5jLjAeFw0xNjA4MzAxOTU4MjlaFw0xOTEyMTMxOTU4MjlaMIGaMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFp +bmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxl +c3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5j +LjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ6tW6x+aO4n7VJu5W55 +DeNKn7+89oaaTgSRs6jg3C+RpTmXugPonh9+YRbuWpSNeru2eWGFNYFs01OjaDUw +CxPcFDgF3xP/wAyCsI7WUgmkz8991PUGo9RxVbkDxMePHNaLGQVNz/+EJgK/Ycfy +jYvenb/BGvcBmMftk2HsHio65ZsHsGMfW2Wcg/ehvKeDRZ3WR1ujhkzIFWdgdH3E +u/yI2pHEfxQQ3PuYcQz43YZyIwhwzwnQG8qTK2jWkMF+wzRKRYfLdRD8nUUingvu +IbngXLs71JqQHmbXzw1WTJClXtfF6R2VZuZ6PT8ZK1bDFPvTgnkUcAk70H+VnDM5 +K48CAwEAAaOCATswggE3MB0GA1UdDgQWBBQoFRKLJrZvkNmqvw8DNuTLPyru/DCB +zwYDVR0jBIHHMIHEgBQoFRKLJrZvkNmqvw8DNuTLPyru/KGBoKSBnTCBmjELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQHEwZNYWxkZW4xGjAYBgNVBAoT +EVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0bWFzdGVyQHBh +aW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks +IEluYy6CCQDSOSuPil70MjAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeG +JWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcN +AQEFBQADggEBAIN38UOXvwd89+a89V+/rjeN8JfpcjafLf0c2I3nex9OxBWji5bf +cWTNfm1t9GpS4HhVT2tl5xdxyW5UrE9Q+oFadN0LxtRPbGU+Gvt4pVo8Pst6/2P8 +PA3/OA1UchIpZR6EWQQsws4esNLLwDbj48MkQdVCjpp1cVpFVmJUzYYFD9h9EMK2 +kxpGf5wfp9LI2A5/qACNQPBDfRsR+dcNBsBbmD1LulqputUPuKPXnVbHWL28VZUY +PITHl2Ndbmk6znSu7ILef3CGyeXqTTj+Jo+5AQz3sneko6oMn8PqfRj1h0uUyykT +lavp3iTNstQs/rdqdI+lPYMokDKXRSD3pK8= Painless Security Server Certificate @@ -128,7 +133,7 @@ WkqDtw0clWg6vkf5Tb9v/JQ2PW0= email/painless-security.com - 3838E17EC9A2A06D7B6030E3C5727E3466EAB4BB4159DCE7CF6297ADAFC8A56F + 4242424242424242424242424242424242424242424242424242424242424242 -- 2.1.4