[geary/wip/20-cert-pinning] Re-enable GCR support for cert pinning



commit 6b9ae903fb275c07ad840eb304b4922a1a337f52
Author: Michael Gratton <mike vee net>
Date:   Thu Jan 10 17:33:37 2019 +1100

    Re-enable GCR support for cert pinning
    
    This re-adds support for using GCR for pinning certs, but only if GCR
    is in a known good state. If so, pinned certs will be stored using GCR,
    if not, they will be stored in Geary's XDG data dirs as a fallback.

 meson.build                                        |   1 +
 .../application-certificate-manager.vala           | 128 ++++++++++++++++++---
 src/client/application/geary-controller.vala       |   5 +-
 src/client/meson.build                             |   1 +
 src/meson.build                                    |   5 +-
 5 files changed, 118 insertions(+), 22 deletions(-)
---
diff --git a/meson.build b/meson.build
index d18d3983..ce612543 100644
--- a/meson.build
+++ b/meson.build
@@ -52,6 +52,7 @@ webkit2gtk = dependency('webkit2gtk-4.0', version: '>=' + target_webkit)
 
 # Secondary deps - keep sorted alphabetically
 enchant = dependency('enchant', version: '>= 1.6')
+gck = dependency('gck-1')
 gcr = dependency('gcr-3', version: '>= 3.10.1')
 gdk = dependency('gdk-3.0', version: '>=' + target_gtk)
 gee = dependency('gee-0.8', version: '>= 0.8.5')
diff --git a/src/client/application/application-certificate-manager.vala 
b/src/client/application/application-certificate-manager.vala
index 1fb62eb8..4881d73c 100644
--- a/src/client/application/application-certificate-manager.vala
+++ b/src/client/application/application-certificate-manager.vala
@@ -6,6 +6,21 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
+// Required because GCR's VAPI is behind-the-times. See:
+// https://gitlab.gnome.org/GNOME/gcr/merge_requests/7
+extern async bool gcr_trust_add_pinned_certificate_async(
+    Gcr.Certificate cert,
+    string purpose,
+    string peer,
+    Cancellable? cancellable
+) throws Error;
+extern bool gcr_trust_is_certificate_pinned(
+    Gcr.Certificate cert,
+    string purpose,
+    string peer,
+    Cancellable? cancellable
+) throws Error;
+
 
 // All of the below basically exists since cert pinning using GCR
 // stopped working (GNOME/gcr#10) after gnome-keyring stopped
@@ -30,16 +45,56 @@ public errordomain Application.CertificateManagerError {
 public class Application.CertificateManager : GLib.Object {
 
 
+    // PCKS11 flag value lifted from pkcs11.h
+    private const ulong CKF_WRITE_PROTECTED = 1UL << 1;
+
+
+    private static async bool is_gcr_enabled(GLib.Cancellable? cancellable) {
+        // Use GCR if it looks like it should be able to be
+        // used. Specifically, if we can initialise the trust store
+        // must have both lookup and store PKCS11 slot URIs or else it
+        // won't be able to lookup or store pinned certs, secondly,
+        // there must be at least a read-write store slot available.
+        bool init_okay = false;
+        try {
+            init_okay = yield Gcr.pkcs11_initialize_async(cancellable);
+        } catch (GLib.Error err) {
+            warning("Failed to initialise GCR PCKS#11 modules: %s", err.message);
+        }
+
+        bool has_uris = false;
+        if (init_okay) {
+            has_uris = (
+                !Geary.String.is_empty(Gcr.pkcs11_get_trust_store_uri()) &&
+                Gcr.pkcs11_get_trust_lookup_uris().length > 0
+            );
+            debug("GCR slot URIs found: %s", has_uris.to_string());
+        }
+
+        bool has_rw_store = false;
+        if (has_uris) {
+            Gck.Slot? store = Gcr.pkcs11_get_trust_store_slot();
+            has_rw_store = !store.has_flags(CKF_WRITE_PROTECTED);
+            debug("GCR store is R/W: %s", has_rw_store.to_string());
+        }
+
+        return has_rw_store;
+    }
+
+
     private TlsDatabase? pinning_database;
 
 
     /**
      * Constructs a new instance, globally installing the pinning database.
      */
-    public CertificateManager(GLib.File store_dir) {
+    public async CertificateManager(GLib.File store_dir,
+                                    GLib.Cancellable? cancellable) {
+        bool use_gcr = yield is_gcr_enabled(cancellable);
         this.pinning_database = new TlsDatabase(
             GLib.TlsBackend.get_default().get_default_database(),
-            store_dir
+            store_dir,
+            use_gcr
         );
         Geary.Endpoint.default_tls_database = this.pinning_database;
     }
@@ -193,16 +248,20 @@ private class Application.TlsDatabase : GLib.TlsDatabase {
     }
 
 
-    public GLib.TlsDatabase parent { get; private set; }
-
+    private GLib.TlsDatabase parent { get; private set; }
     private GLib.File store_dir;
+    private bool use_gcr;
+
     private Gee.Map<string,TrustContext> pinned_certs =
         new Gee.HashMap<string,TrustContext>();
 
 
-    public TlsDatabase(GLib.TlsDatabase parent, GLib.File store_dir) {
+        public TlsDatabase(GLib.TlsDatabase parent,
+                           GLib.File store_dir,
+                           bool use_gcr) {
         this.parent = parent;
         this.store_dir = store_dir;
+        this.use_gcr = use_gcr;
     }
 
     public async void pin_certificate(GLib.TlsCertificate certificate,
@@ -216,7 +275,18 @@ private class Application.TlsDatabase : GLib.TlsDatabase {
             this.pinned_certs.set(id, context);
         }
         if (save) {
-            yield context.save(this.store_dir, to_name(identity), cancellable);
+            if (this.use_gcr) {
+                yield gcr_trust_add_pinned_certificate_async(
+                    new Gcr.SimpleCertificate(certificate.certificate.data),
+                    GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER,
+                    id,
+                    cancellable
+                );
+            } else {
+                yield context.save(
+                    this.store_dir, to_name(identity), cancellable
+                );
+            }
         }
     }
 
@@ -354,26 +424,48 @@ private class Application.TlsDatabase : GLib.TlsDatabase {
                         GLib.SocketConnectable identity,
                         GLib.Cancellable? cancellable)
         throws GLib.Error {
+        bool is_verified = false;
         string id = to_name(identity);
         TrustContext? context = null;
         lock (this.pinned_certs) {
             context = this.pinned_certs.get(id);
-            if (context == null) {
-                try {
-                    context = new TrustContext.lookup(
-                        this.store_dir, id, cancellable
+            if (context != null) {
+                is_verified = true;
+            } else {
+                // Cert not found in memory, check with GCR if
+                // enabled.
+                if (this.use_gcr) {
+                    is_verified = gcr_trust_is_certificate_pinned(
+                        new Gcr.SimpleCertificate(chain.certificate.data),
+                        GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER,
+                        id,
+                        cancellable
                     );
-                    this.pinned_certs.set(id, context);
-                } catch (GLib.IOError.NOT_FOUND err) {
-                    // Cert was not found saved, so it not pinned
-                } catch (GLib.Error err) {
-                    Geary.ErrorContext err_context = new Geary.ErrorContext(err);
-                    debug("Error loading pinned certificate: %s",
-                          err_context.format_full_error());
+                }
+
+                if (!is_verified) {
+                    // Cert is not pinned in memory or in GCR, so look
+                    // for it on disk. Do this even if GCR support is
+                    // enabled, since if the cert was previously saved
+                    // to disk, it should still be able to be used
+                    try {
+                        context = new TrustContext.lookup(
+                            this.store_dir, id, cancellable
+                        );
+                        this.pinned_certs.set(id, context);
+                        is_verified = true;
+                    } catch (GLib.IOError.NOT_FOUND err) {
+                        // Cert was not found saved, so it not pinned
+                    } catch (GLib.Error err) {
+                        Geary.ErrorContext err_context =
+                            new Geary.ErrorContext(err);
+                        debug("Error loading pinned certificate: %s",
+                              err_context.format_full_error());
+                    }
                 }
             }
         }
-        return (context != null);
+        return is_verified;
     }
 
     private async bool verify_async(GLib.TlsCertificate chain,
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 1fc61df7..2a956691 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -327,8 +327,9 @@ public class GearyController : Geary.BaseObject {
 
         // Hook up cert, accounts and credentials machinery
 
-        this.certificate_manager = new Application.CertificateManager(
-            this.application.get_user_data_directory().get_child("pinned-certs")
+        this.certificate_manager = yield new Application.CertificateManager(
+            this.application.get_user_data_directory().get_child("pinned-certs"),
+            cancellable
         );
 
         SecretMediator? libsecret = null;
diff --git a/src/client/meson.build b/src/client/meson.build
index 8f6fd54d..a037366e 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -109,6 +109,7 @@ geary_client_sources = [
 geary_client_dependencies = [
   libmath,
   enchant,
+  gck,
   gcr,
   gee,
   gio,
diff --git a/src/meson.build b/src/meson.build
index 2eef6927..c569a6a9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -38,8 +38,9 @@ geary_c_options = [
   # Select libunwind's optimised, local-only backtrace unwiding. See
   # libunwind(3).
   '-DUNW_LOCAL_ONLY',
-  # Yes yes, GOA's API is unstable. :(
-  '-DGOA_API_IS_SUBJECT_TO_CHANGE'
+  # Neither GOA nor GCK want to hang out unless you are cool enough
+  '-DGOA_API_IS_SUBJECT_TO_CHANGE',
+  '-DGCK_API_SUBJECT_TO_CHANGE',
 ]
 
 subdir('sqlite3-unicodesn')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]