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
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ geary_client_sources = [
|
|||
geary_client_dependencies = [
|
||||
libmath,
|
||||
enchant,
|
||||
gck,
|
||||
gcr,
|
||||
gee,
|
||||
gio,
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue