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.
This commit is contained in:
Michael Gratton 2019-01-10 17:33:37 +11:00
parent 812bce2a2f
commit 6b9ae903fb
5 changed files with 118 additions and 22 deletions

View file

@ -52,6 +52,7 @@ webkit2gtk = dependency('webkit2gtk-4.0', version: '>=' + target_webkit)
# Secondary deps - keep sorted alphabetically # Secondary deps - keep sorted alphabetically
enchant = dependency('enchant', version: '>= 1.6') enchant = dependency('enchant', version: '>= 1.6')
gck = dependency('gck-1')
gcr = dependency('gcr-3', version: '>= 3.10.1') gcr = dependency('gcr-3', version: '>= 3.10.1')
gdk = dependency('gdk-3.0', version: '>=' + target_gtk) gdk = dependency('gdk-3.0', version: '>=' + target_gtk)
gee = dependency('gee-0.8', version: '>= 0.8.5') gee = dependency('gee-0.8', version: '>= 0.8.5')

View file

@ -6,6 +6,21 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (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 // All of the below basically exists since cert pinning using GCR
// stopped working (GNOME/gcr#10) after gnome-keyring stopped // stopped working (GNOME/gcr#10) after gnome-keyring stopped
@ -30,16 +45,56 @@ public errordomain Application.CertificateManagerError {
public class Application.CertificateManager : GLib.Object { 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; private TlsDatabase? pinning_database;
/** /**
* Constructs a new instance, globally installing the 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( this.pinning_database = new TlsDatabase(
GLib.TlsBackend.get_default().get_default_database(), GLib.TlsBackend.get_default().get_default_database(),
store_dir store_dir,
use_gcr
); );
Geary.Endpoint.default_tls_database = this.pinning_database; 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 GLib.File store_dir;
private bool use_gcr;
private Gee.Map<string,TrustContext> pinned_certs = private Gee.Map<string,TrustContext> pinned_certs =
new Gee.HashMap<string,TrustContext>(); 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.parent = parent;
this.store_dir = store_dir; this.store_dir = store_dir;
this.use_gcr = use_gcr;
} }
public async void pin_certificate(GLib.TlsCertificate certificate, public async void pin_certificate(GLib.TlsCertificate certificate,
@ -216,7 +275,18 @@ private class Application.TlsDatabase : GLib.TlsDatabase {
this.pinned_certs.set(id, context); this.pinned_certs.set(id, context);
} }
if (save) { 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.SocketConnectable identity,
GLib.Cancellable? cancellable) GLib.Cancellable? cancellable)
throws GLib.Error { throws GLib.Error {
bool is_verified = false;
string id = to_name(identity); string id = to_name(identity);
TrustContext? context = null; TrustContext? context = null;
lock (this.pinned_certs) { lock (this.pinned_certs) {
context = this.pinned_certs.get(id); context = this.pinned_certs.get(id);
if (context == null) { if (context != null) {
try { is_verified = true;
context = new TrustContext.lookup( } else {
this.store_dir, id, cancellable // 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 if (!is_verified) {
} catch (GLib.Error err) { // Cert is not pinned in memory or in GCR, so look
Geary.ErrorContext err_context = new Geary.ErrorContext(err); // for it on disk. Do this even if GCR support is
debug("Error loading pinned certificate: %s", // enabled, since if the cert was previously saved
err_context.format_full_error()); // 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, private async bool verify_async(GLib.TlsCertificate chain,

View file

@ -327,8 +327,9 @@ public class GearyController : Geary.BaseObject {
// Hook up cert, accounts and credentials machinery // Hook up cert, accounts and credentials machinery
this.certificate_manager = new Application.CertificateManager( this.certificate_manager = yield new Application.CertificateManager(
this.application.get_user_data_directory().get_child("pinned-certs") this.application.get_user_data_directory().get_child("pinned-certs"),
cancellable
); );
SecretMediator? libsecret = null; SecretMediator? libsecret = null;

View file

@ -109,6 +109,7 @@ geary_client_sources = [
geary_client_dependencies = [ geary_client_dependencies = [
libmath, libmath,
enchant, enchant,
gck,
gcr, gcr,
gee, gee,
gio, gio,

View file

@ -38,8 +38,9 @@ geary_c_options = [
# Select libunwind's optimised, local-only backtrace unwiding. See # Select libunwind's optimised, local-only backtrace unwiding. See
# libunwind(3). # libunwind(3).
'-DUNW_LOCAL_ONLY', '-DUNW_LOCAL_ONLY',
# Yes yes, GOA's API is unstable. :( # Neither GOA nor GCK want to hang out unless you are cool enough
'-DGOA_API_IS_SUBJECT_TO_CHANGE' '-DGOA_API_IS_SUBJECT_TO_CHANGE',
'-DGCK_API_SUBJECT_TO_CHANGE',
] ]
subdir('sqlite3-unicodesn') subdir('sqlite3-unicodesn')