Squashed merge of many commits, including (but not limited to) :
authorDan Breslau <dbreslau@painless-security.com>
Wed, 28 Sep 2016 19:13:57 +0000 (15:13 -0400)
committerDan Breslau <dbreslau@painless-security.com>
Wed, 19 Oct 2016 20:22:35 +0000 (16:22 -0400)
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.

25 files changed:
.gitignore
Makefile.am
libmoonshot/libmoonshot-dbus.c
libmoonshot/libmoonshot.def
libmoonshot/libmoonshot.h
libmoonshot/libmoonshot.vapi
src/moonshot-custom-vbox.vala
src/moonshot-id.vala
src/moonshot-idcard-widget.vala
src/moonshot-identities-manager.vala
src/moonshot-identity-dialog.vala
src/moonshot-identity-management-view.vala
src/moonshot-identity-manager-app.vala
src/moonshot-keyring-store.vala
src/moonshot-local-flat-file-store.vala
src/moonshot-password-dialog.vala
src/moonshot-provisioning-common.vala
src/moonshot-server-linux.vala [moved from src/moonshot-server.vala with 59% similarity]
src/moonshot-server-msrpc.vala [new file with mode: 0644]
src/moonshot-trust-anchor-dialog.vala
src/moonshot-utils.vala
src/moonshot-warning-dialog.vala
webprovisioning/blank-test.msht [new file with mode: 0644]
webprovisioning/cert-test.msht [new file with mode: 0644]
webprovisioning/complex-test.msht

index b2b436a..fbc5605 100755 (executable)
@@ -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
index a578f76..0335016 100644 (file)
@@ -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 \
index e7c4b51..e5ac3f1 100644 (file)
@@ -34,6 +34,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <dbus/dbus-glib.h>
@@ -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;
+}
index 0819d1b..98f4393 100644 (file)
@@ -4,3 +4,4 @@ EXPORTS
     moonshot_error_new
     moonshot_get_default_identity
     moonshot_get_identity
+    moonshot_confirm_ca_certificate
index d7830c2..5b2524d 100644 (file)
@@ -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
index 8662596..840e19f 100644 (file)
@@ -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);
 }
index 088f2a0..428c053 100644 (file)
@@ -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;
             }
         }
index b8e92a6..3a0f960 100644 (file)
@@ -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);
index 03f4d61..25976b3 100644 (file)
@@ -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("<span rise='8000'><big>%s</big></span>", 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();
     }
index 01eea71..5cb4279 100644 (file)
@@ -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<ArrayList<IdCard>> duplicates)
+    {
+        var nais = new HashMap<string, ArrayList<IdCard>>();
+
+        duplicates = new ArrayList<ArrayList<IdCard>>();
+        LinkedList<IdCard> 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<IdCard> list = nais.get(id_card.nai);
+                list.add(id_card);
+            }
+            else {
+                ArrayList<IdCard> list = new ArrayList<IdCard>();
+                list.add(id_card);
+                nais.set(id_card.nai, list);
+            }
+        }
+
+        duplicates = new ArrayList<ArrayList<IdCard>>();
+        foreach (Map.Entry<string, ArrayList<IdCard>> 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();
index 3eb8c2b..315f586 100644 (file)
@@ -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);
 
index a7a297a..1a042b9 100644 (file)
@@ -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<ArrayList<IdCard>> duplicates;
+        identities_manager.find_duplicate_nai_sets(out duplicates);
+        foreach (ArrayList<IdCard> 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();
     }
index 0170139..5d0fbd5 100644 (file)
@@ -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
 
index 8ca5e31..7ae0d22 100644 (file)
@@ -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");
 
index 8cc905c..742ca74 100644 (file)
@@ -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)'");
         }
 
index 77c4ba4..a68301a 100644 (file)
@@ -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);
index 29160dd..2311cab 100644 (file)
@@ -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);
                 }
similarity index 59%
rename from src/moonshot-server.vala
rename to src/moonshot-server-linux.vala
index 4fbcd2a..4aecef2 100644 (file)
@@ -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<IdCard>? 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 (file)
index 0000000..b396ac9
--- /dev/null
@@ -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<IdCard>? 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;
+    }
+}
index 0537494..28169d2 100644 (file)
 */
 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);
index 4eaed1f..7846745 100644 (file)
@@ -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
+}
index 72233a4..43f7aa3 100644 (file)
@@ -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 (file)
index 0000000..5287550
--- /dev/null
@@ -0,0 +1,24 @@
+<!-- See https://bugs.launchpad.net/moonshot-ui/+bug/1430980, including comments -->
+
+<identities>
+
+  <identity>
+    <display-name>Not No Identity</display-name>
+    <user>someone</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+      <service>something/painless-security.com</service>
+      <service>somethingelse/painless-security.com</service>
+    </services>
+  </identity>
+
+  <identity>
+    <display-name>No Identity</display-name>
+    <services>
+      <service>a_service/painless-security.com</service>
+      <service>another_service/painless-security.com</service>
+    </services>
+  </identity>
+
+</identities> 
diff --git a/webprovisioning/cert-test.msht b/webprovisioning/cert-test.msht
new file mode 100644 (file)
index 0000000..ab56e5f
--- /dev/null
@@ -0,0 +1,120 @@
+<identities>
+    <identity>
+    <display-name>No Trust Anchor</display-name>
+    <user>user5</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+  </identity>
+
+  <identity>
+    <display-name>Bad CA Certificate</display-name>
+    <user>user1</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <selection-rules>
+    </selection-rules>
+    <trust-anchor>
+     <!-- PEM encoded, minus header and footer -->
+      <ca-cert>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=</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
+      <!-- Or alternatively -->
+      <server-cert></server-cert>
+    </trust-anchor>
+  </identity>
+  <identity>
+    <display-name>Good CA Certificate</display-name>
+    <user>user2</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <selection-rules>
+    </selection-rules>
+    <trust-anchor>
+      <ca-cert>
+      <!-- DER format, base64-encoded -->
+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=
+</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
+    </trust-anchor>
+  </identity>
+    <identity>
+    <display-name>Good Fingerprint</display-name>
+    <user>user3</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <trust-anchor>
+      <server-cert>F2FCC5FAD4CCB7A7236A8AEEF5E94E0C0FB27BEC29DE0AE03C5B455D08D4DE77</server-cert>
+    </trust-anchor>
+  </identity>
+    <identity>
+    <display-name>Bad Fingerprint</display-name>
+    <user>user4</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <trust-anchor>
+      <server-cert>4242424242424242424242424242424242424242424242424242424242424242</server-cert>
+    </trust-anchor>
+  </identity>
+
+</identities> 
index bf6319a..e5279b9 100644 (file)
@@ -78,29 +78,34 @@ musuCxXeWkqDtw0clWg6vkf5Tb9v/JQ2PW0=</ca-cert>
     </selection-rules>
     <trust-anchor>
       <ca-cert>
-      <!-- DER format, base64-encoded -->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=
+      <!-- DER format, base64-encoded -->
+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=
 </ca-cert>
       <subject>Painless Security Server Certificate</subject>
     </trust-anchor>
@@ -128,7 +133,7 @@ WkqDtw0clWg6vkf5Tb9v/JQ2PW0=
       <service>email/painless-security.com</service>
     </services>
     <trust-anchor>
-      <server-cert>3838E17EC9A2A06D7B6030E3C5727E3466EAB4BB4159DCE7CF6297ADAFC8A56F</server-cert>
+      <server-cert>4242424242424242424242424242424242424242424242424242424242424242</server-cert>
     </trust-anchor>
   </identity>