First pass at supporting date/time added for Trust Anchors
authorDan Breslau <dbreslau@painless-security.com>
Fri, 19 Aug 2016 17:16:24 +0000 (13:16 -0400)
committerDan Breslau <dbreslau@painless-security.com>
Fri, 19 Aug 2016 17:16:24 +0000 (13:16 -0400)
src/moonshot-id.vala
src/moonshot-idcard-store.vala
src/moonshot-identities-manager.vala
src/moonshot-identity-dialog.vala
src/moonshot-identity-management-view.vala
src/moonshot-keyring-store.vala
src/moonshot-local-flat-file-store.vala
src/moonshot-provisioning-common.vala
src/moonshot-server.vala
src/moonshot-settings.vala

index b1f0860..dcf4bc9 100644 (file)
@@ -51,23 +51,25 @@ public class TrustAnchor : Object
     private string _subject = "";
     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) {
-        _ca_cert = ca_cert;
-        _server_cert = server_cert;
-        _subject = subject;
-        _subject_alt = 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 = "";
     }
 
     public TrustAnchor.empty() {
-        _ca_cert = "";
-        _server_cert = "";
-        _subject = "";
-        _subject_alt = "";
-        this.user_verified = false;
     }
 
 
@@ -96,6 +98,12 @@ public class TrustAnchor : Object
         }
     }
 
+    public string datetime_added {
+        get {
+            return _datetime_added;
+        }
+    }
+
     public bool is_empty() {
         return ca_cert == "" && subject == "" && subject_alt == "" && server_cert == "";
     }
@@ -104,18 +112,36 @@ public class TrustAnchor : Object
         return server_cert == "" ? TrustAnchorType.CA_CERT : TrustAnchorType.SERVER_CERT;
     }
 
+    internal void set_datetime_added(string datetime) {
+        _datetime_added = fixup(datetime);
+    }
+
+    internal static string format_datetime_now() {
+        DateTime now = new DateTime.now_utc();
+        string dt = now.format("%b %d %T %Y %Z");
+        return dt;
+    }
+
     public int Compare(TrustAnchor other)
     {
-        if (this.ca_cert != other.ca_cert)
+        if (this.ca_cert != other.ca_cert) {
             return 1;
-        if (this.subject != other.subject)
+        }
+        if (this.subject != other.subject) {
             return 1;
-        if (this.subject_alt != other.subject_alt)
+        }
+        if (this.subject_alt != other.subject_alt) {
             return 1;
-        if (this.server_cert != other.server_cert)
+        }
+        if (this.server_cert != other.server_cert) {
             return 1;
-        if (this.user_verified != other.user_verified)
+        }
+        if (this.user_verified != other.user_verified) {
             return 1;
+        }
+        // if (!is_empty() && this.datetime_added != other.datetime_added) {
+        //     return 1;
+        // }
         return 0;
     }
 
index 3d6c863..b9c9368 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -42,5 +42,8 @@ public interface IIdentityCardStore : Object {
     public abstract IdCard? update_card(IdCard card);
     public abstract StoreType get_store_type();
     public abstract LinkedList<IdCard> get_card_list(); 
+
+    // Note that (at least right now) store_id_cards() will re-load the cards after saving them.
+    internal abstract void store_id_cards(); 
 }
 
index 8154052..a1cac6b 100644 (file)
@@ -244,6 +244,8 @@ public class IdentityManagerModel : Object {
         return false;
     }
 
+    // The name is misleading: This not only sets the store type,
+    // it also creates a new store instance, which loads the card data.
     public void set_store_type(IIdentityCardStore.StoreType type) {
         if ((store != null) && (store.get_store_type() == type))
             return;
@@ -258,6 +260,21 @@ public class IdentityManagerModel : Object {
             store = new LocalFlatFileStore();
             break;
         }
+
+        // Loop through the loaded IDs. If any trust anchors are old enough that we didn't record
+        // the datetime_added, add it now.
+        string before_now = _("Before ") + TrustAnchor.format_datetime_now();
+        bool save_needed = false;
+        foreach (IdCard id in this.store.get_card_list()) {
+            if (!id.trust_anchor.is_empty() && id.trust_anchor.datetime_added == "") {
+                logger.trace("set_store_type : Set ta_datetime_added for old trust anchor on '%s' to '%s'".printf(id.display_name, before_now));
+                id.trust_anchor.set_datetime_added(before_now);
+                save_needed = true;
+            }
+        }
+        if (save_needed) {
+            this.store.store_id_cards();
+        }
     }
 
     public IIdentityCardStore.StoreType get_store_type() {
index 40f03cf..ccec92c 100644 (file)
@@ -234,8 +234,7 @@ class IdentityDialog : Dialog
         ta_table.attach(ta_clear_button, 1, 2, row, row + 1, fill, fill, 0, 0);
         row++;
 
-        //!!TODO
-        Label added_label = new Label(_("Added on: N/A"));
+        Label added_label = new Label(_("Added : " + id.trust_anchor.datetime_added));
         added_label.set_alignment(0, 0.5f);
         ta_table.attach(added_label, 0, 1, row, row + 1, opts, opts, 20, 5);
         row++;
index 2b0a4ba..fae10f0 100644 (file)
@@ -334,7 +334,7 @@ public class IdentityManagerView : Window {
         Gtk.MessageDialog dialog;
         IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
         logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
-                     .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? "non-null" : "null")));
+                     .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? prev_id.display_name : "null")));
         if (prev_id!=null) {
             int flags = prev_id.Compare(id_card);
             logger.trace("add_identity: compare returned " + flags.to_string());
index e24a6fe..d32fc71 100644 (file)
@@ -60,6 +60,8 @@ public class KeyringStore : Object, IIdentityCardStore {
                 return idcard;
             }
         }
+
+        logger.error(@"update_card: card '$(card.display_name)' was not found after re-loading!");
         return null;
     }
 
@@ -110,6 +112,7 @@ public class KeyringStore : Object, IIdentityCardStore {
             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];
                 string value = "";
@@ -141,10 +144,15 @@ public class KeyringStore : Object, IIdentityCardStore {
                     store_password = value;
                 } else if (attribute.name == "CACert_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);
+            if (ta_datetime_added != "") {
+                ta.set_datetime_added(ta_datetime_added);
+            }
             id_card.set_trust_anchor_from_store(ta);
 
             if ((rules_always_confirm_index != -1) && (rules_patterns_index != -1)) {
@@ -175,7 +183,7 @@ public class KeyringStore : Object, IIdentityCardStore {
         }
     }
 
-    public void store_id_cards() {
+    internal void store_id_cards() {
         logger.trace("store_id_cards");
         clear_keyring();
         foreach (IdCard id_card in this.id_card_list) {
@@ -205,6 +213,7 @@ public class KeyringStore : Object, IIdentityCardStore {
             attributes.append_string("Subject", id_card.trust_anchor.subject);
             attributes.append_string("Subject-Alt", id_card.trust_anchor.subject_alt);
             attributes.append_string("CACert_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");
 
             GnomeKeyring.Result result = GnomeKeyring.item_create_sync(null,
index 55c535b..b3784f2 100644 (file)
@@ -32,6 +32,8 @@
 using Gee; 
 
 public class LocalFlatFileStore : Object, IIdentityCardStore {
+    static MoonshotLogger logger = get_logger("LocalFlatFileStore");
+
     private LinkedList<IdCard> id_card_list;
     private const string FILE_NAME = "identities.txt";
 
@@ -44,9 +46,12 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
         id_card_list.remove(card);
         id_card_list.add(card);
         store_id_cards();
-        foreach(IdCard idcard in id_card_list)
-        if (idcard.display_name == card.display_name)
-            return idcard;
+        foreach(IdCard idcard in id_card_list) {
+            if (idcard.display_name == card.display_name) {
+                return idcard;
+            }
+        }
+        logger.error(@"update_card: card '$(card.display_name)' was not found after re-loading!");
         return null;
     }
 
@@ -71,6 +76,7 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
         var key_file = new KeyFile();
         var path = get_data_dir();
         var filename = Path.build_filename(path, FILE_NAME);
+        logger.trace("load_id_cards: attempting to load from " + filename);
         
         try {
             key_file.load_from_file(filename, KeyFileFlags.NONE);
@@ -115,8 +121,12 @@ 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 = key_file.get_boolean(identity, "CACert_User_Verified");
+                bool  user_verified = get_bool_setting(identity, "TA_DateTime_Added", false, key_file);
                 var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt, user_verified);
+                string ta_datetime_added = get_string_setting(identity, "TA_DateTime_Added", "", key_file);
+                if (ta_datetime_added != "") {
+                    ta.set_datetime_added(ta_datetime_added);
+                }
                 id_card.set_trust_anchor_from_store(ta);
                 id_card_list.add(id_card);
             }
@@ -137,9 +147,11 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
         return path;
     }
     
-    public void store_id_cards() {
+    internal void store_id_cards() {
         var key_file = new KeyFile();
         foreach (IdCard id_card in this.id_card_list) {
+            logger.trace(@"store_id_cards: Storing '$(id_card.display_name)'");
+
             /* workaround for Centos vala array property bug: use temp arrays */
             var rules = id_card.rules;
             string[] empty = {};
@@ -175,11 +187,13 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
             key_file.set_string(id_card.display_name, "StorePassword", id_card.store_password ? "yes" : "no");
             
             // Trust anchor 
-            key_file.set_string(id_card.display_name, "CA-Cert", id_card.trust_anchor.ca_cert ?? "");
-            key_file.set_string(id_card.display_name, "Subject", id_card.trust_anchor.subject ?? "");
-            key_file.set_string(id_card.display_name, "SubjectAlt", id_card.trust_anchor.subject_alt ?? "");
-            key_file.set_string(id_card.display_name, "ServerCert", id_card.trust_anchor.server_cert ?? "");
+            key_file.set_string(id_card.display_name, "CA-Cert", id_card.trust_anchor.ca_cert);
+            key_file.set_string(id_card.display_name, "Subject", id_card.trust_anchor.subject);
+            key_file.set_string(id_card.display_name, "SubjectAlt", id_card.trust_anchor.subject_alt);
+            key_file.set_string(id_card.display_name, "ServerCert", id_card.trust_anchor.server_cert);
+            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, "CACert_User_Verified", id_card.trust_anchor.user_verified);
+            logger.trace(@"store_id_cards: Stored '$(id_card.display_name)'");
         }
 
         var text = key_file.to_data(null);
@@ -187,6 +201,7 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
         try {
             var path = get_data_dir();
             var filename = Path.build_filename(path, FILE_NAME);
+            logger.trace("store_id_cards: attempting to store to " + filename);
             var file  = File.new_for_path(filename);
             var stream = file.replace(null, false, FileCreateFlags.PRIVATE);
             #if GIO_VAPI_USES_ARRAYS
@@ -197,6 +212,7 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
             #endif
                 }
         catch (Error e) {
+            logger.error("store_id_cards: Error while saving keyfile: %s\n".printf(e.message));
             stdout.printf("Error:  %s\n", e.message);
         }
 
index cd85f9a..c0b35c4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -130,9 +130,13 @@ namespace WebProvisioning
             {
                 if (element_name == "identity")
                 {
-                    logger.trace("start_element_func (%p): Adding an identity".printf(this));
                     card = new IdCard();
                     _cards += card;
+
+                    ta_ca_cert = "";
+                    ta_server_cert = "";
+                    ta_subject = "";
+                    ta_subject_alt = "";
                 }
                 else if (element_name == "rule")
                 {
@@ -140,6 +144,22 @@ namespace WebProvisioning
                 }
             }
 
+            private void end_element_func(MarkupParseContext context,
+                                          string element_name) throws MarkupError
+            {
+                if (element_name == "identity")
+                {
+                    if (ta_ca_cert != "" || ta_server_cert != "") {
+                        var ta = new TrustAnchor(ta_ca_cert,
+                                                 ta_server_cert,
+                                                 ta_subject,
+                                                 ta_subject_alt,
+                                                 false);
+                        card.set_trust_anchor_from_store(ta);
+                    }
+                }
+            }
+
             private void
             text_element_func(MarkupParseContext context,
                               string             text,
@@ -149,8 +169,6 @@ namespace WebProvisioning
                 if (text_len < 1)
                     return;
 
-                logger.trace("text_element_func (%p): text='%s'".printf(this, stack.nth_data(0)));
-
                 if (stack.nth_data(0) == "display-name" && display_name_handler(stack))
                 {
                     card.display_name = text;
@@ -187,56 +205,26 @@ namespace WebProvisioning
                         card.rules[temp.length - 1].always_confirm = text;
                     }
                 }
-                // This is ugly, but... we use the TrustAnchor field in the IdCard as a placeholder,
-                // replacing it with a new one every time we read a new element.
-                // "user_verified" is always false, since we're reading the TrustAnchor from XML.
                 else if (stack.nth_data(0) == "ca-cert" && ca_cert_handler(stack))
                 {
-                    string ca_cert = text;
-                    var ta = new TrustAnchor(ca_cert,
-                                             card.trust_anchor.server_cert,
-                                             card.trust_anchor.subject,
-                                             card.trust_anchor.subject_alt,
-                                             false);
-                    card.set_trust_anchor_from_store(ta);
+                    ta_ca_cert = text ?? "";
                 }
                 else if (stack.nth_data(0) == "server-cert" && server_cert_handler(stack))
                 {
-                    string server_cert = text;
-                    var ta = new TrustAnchor(card.trust_anchor.ca_cert,
-                                             server_cert,
-                                             card.trust_anchor.subject,
-                                             card.trust_anchor.subject_alt,
-                                             false);
-                    card.set_trust_anchor_from_store(ta);
-
+                    ta_server_cert = text ?? "";
                 }
                 else if (stack.nth_data(0) == "subject" && subject_handler(stack))
                 {
-                    string subject = text;
-                    var ta = new TrustAnchor(card.trust_anchor.ca_cert,
-                                             card.trust_anchor.server_cert,
-                                             subject,
-                                             card.trust_anchor.subject_alt,
-                                             false);
-                    card.set_trust_anchor_from_store(ta);
+                    ta_subject = text;
                 }
                 else if (stack.nth_data(0) == "subject-alt" && subject_alt_handler(stack))
                 {
-                    string subject_alt = text;
-                    var ta = new TrustAnchor(card.trust_anchor.ca_cert,
-                                             card.trust_anchor.server_cert,
-                                             card.trust_anchor.subject,
-                                             subject_alt,
-                                             false);
-                    card.set_trust_anchor_from_store(ta);
+                    ta_subject_alt = text;
                 }
             }
 
-
-
         private const MarkupParser parser = {
-            start_element_func, null, text_element_func, null, null
+            start_element_func, end_element_func, text_element_func, null, null
         };
 
         private MarkupParseContext ctx;
@@ -244,6 +232,11 @@ namespace WebProvisioning
         private string       text;
         private string       path;
 
+        private string ta_ca_cert;
+        private string ta_server_cert;
+        private string ta_subject;
+        private string ta_subject_alt;
+
         private IdCard card;
         private IdCard[] _cards = {};
 
@@ -268,8 +261,7 @@ namespace WebProvisioning
                 while ((line = dis.read_line(null)) != null) {
                     text += line;
 
-                    // Preserve newlines -- important for certificate import.
-                    // (X509 certs can't be parsed without the newlines.)
+                    // Preserve newlines.
                     //
                     // This may add an extra newline at EOF. Maybe use
                     // dis.read_upto("\n", ...) followed by dis.read_byte() instead?
@@ -280,8 +272,6 @@ namespace WebProvisioning
             {
                 error("Could not retreive file size");
             }
-
-            logger.trace(@"Parser(): read text to parse; length=$(text.length)");
         }
 
         public void parse() {
index 5f0fa2f..7c95ff7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -199,7 +199,12 @@ public class MoonshotServer : Object {
         idcard.issuer = realm;
         idcard.update_services(services);
         var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt, false);
-        idcard.set_trust_anchor_from_store(ta);
+        if (!ta.is_empty()) {
+            string ta_datetime_added = TrustAnchor.format_datetime_now();
+            ta.set_datetime_added(ta_datetime_added);
+            logger.trace("install_id_card : Set ta_datetime_added for '%s' to '%s'".printf(idcard.display_name, ta_datetime_added));
+            idcard.set_trust_anchor_from_store(ta);
+        }
 
         logger.trace("install_id_card: Card '%s' has services: '%s'"
                      .printf(idcard.display_name, idcard.get_services_string("; ")));
index 8107c05..4dea622 100644 (file)
@@ -100,17 +100,31 @@ private void save_keyfile(KeyFile key_file)
     // streams close automatically
 }
 
-internal void set_bool_setting(string group_name, string key_name, bool value)
+internal void set_bool_setting(string group_name, string key_name, bool value, KeyFile? key_file=null)
 {
-    KeyFile key_file = get_keyfile();
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
 
     key_file.set_boolean(group_name, key_name, value);
-    save_keyfile(key_file);
+
+    if (tmp_key_file != null) {
+        // This is a "one-shot" settings update; save it now.
+        save_keyfile(key_file);
+    }
 }
 
-internal bool get_bool_setting(string group_name, string key_name, bool default=false)
+internal bool get_bool_setting(string group_name, string key_name, bool default=false, KeyFile? key_file=null)
 {
-    KeyFile key_file = get_keyfile();
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
 
     if (key_file == null)
         return default;
@@ -137,17 +151,30 @@ internal bool get_bool_setting(string group_name, string key_name, bool default=
 }
 
 
-internal void set_string_setting(string group_name, string key_name, string value)
+internal void set_string_setting(string group_name, string key_name, string value, KeyFile? key_file=null)
 {
-    KeyFile key_file = get_keyfile();
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
 
     key_file.set_string(group_name, key_name, value);
-    save_keyfile(key_file);
+    if (tmp_key_file != null) {
+        // This is a "one-shot" settings update; save it now.
+        save_keyfile(key_file);
+    }
 }
 
-internal string get_string_setting(string group_name, string key_name, string default="")
+internal string get_string_setting(string group_name, string key_name, string default="", KeyFile? key_file=null)
 {
-    KeyFile key_file = get_keyfile();
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
 
     if (key_file == null)
         return default;