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:
parent
812bce2a2f
commit
6b9ae903fb
5 changed files with 118 additions and 22 deletions
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ geary_client_sources = [
|
||||||
geary_client_dependencies = [
|
geary_client_dependencies = [
|
||||||
libmath,
|
libmath,
|
||||||
enchant,
|
enchant,
|
||||||
|
gck,
|
||||||
gcr,
|
gcr,
|
||||||
gee,
|
gee,
|
||||||
gio,
|
gio,
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue