From 5731b631130d8470e57f69cc7d7dae4d5a5ef74d Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Wed, 28 Jul 2021 00:30:19 -0700 Subject: [PATCH] Use KDE Connect's verification codes Fixes #1091 --- ...ome.Shell.Extensions.GSConnect.gschema.xml | 3 + data/org.gnome.Shell.Extensions.GSConnect.xml | 2 + data/ui/preferences-device-panel.ui | 258 ++++++++++++++---- src/preferences/device.js | 22 +- src/service/device.js | 101 ++++++- src/utils/remote.js | 24 ++ 6 files changed, 339 insertions(+), 71 deletions(-) diff --git a/data/org.gnome.Shell.Extensions.GSConnect.gschema.xml b/data/org.gnome.Shell.Extensions.GSConnect.gschema.xml index b3693731ac..1ead96aac4 100644 --- a/data/org.gnome.Shell.Extensions.GSConnect.gschema.xml +++ b/data/org.gnome.Shell.Extensions.GSConnect.gschema.xml @@ -51,6 +51,9 @@ "" + + false + false diff --git a/data/org.gnome.Shell.Extensions.GSConnect.xml b/data/org.gnome.Shell.Extensions.GSConnect.xml index d7795399ba..1def3dbd5a 100644 --- a/data/org.gnome.Shell.Extensions.GSConnect.xml +++ b/data/org.gnome.Shell.Extensions.GSConnect.xml @@ -6,6 +6,8 @@ + + diff --git a/data/ui/preferences-device-panel.ui b/data/ui/preferences-device-panel.ui index 4e24d65a1f..6506f47a98 100644 --- a/data/ui/preferences-device-panel.ui +++ b/data/ui/preferences-device-panel.ui @@ -2508,14 +2508,14 @@ + + + Device Settings - - - 0 @@ -2538,88 +2538,228 @@ True False - + True - False - - - False - 6 - end - - - - - + False + + + True + False + + + False + 6 + end + + + + + + + + + True + False + True + + + True + True + 2 + + + + + False + False + 0 + - - - Pair - True - True - True - settings.pair + + + False + 16 + + + + True + False + + + True + False + start + 🔑 + + + 0 + 1 + + + + + True + False + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + 0 + + True True - 2 + 0 + + + - False - False - 0 + page0 + page0 - - - False - 6 - - - True - False + + + True + False + + + False + 6 + end - + + + + + + + + Request pairing True - False - start - Device is unpaired - - - + True + True + settings.pair - 0 - 0 + True + True + 2 + + + False + False + 0 + + + + + False + 6 - + + True - False - start - You may configure this device before pairing + False + + + True + False + start + Device is unpaired + + + + + + 0 + 0 + + + + + True + False + start + You may configure this device before pairing + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + - 0 - 1 + True + True + 0 - True - True + False + False 0 - False - False - 0 + 1 @@ -2629,11 +2769,17 @@ - 0 - 0 + 0 + 0 3 + + + + + + diff --git a/src/preferences/device.js b/src/preferences/device.js index e647907d9f..0c795497dc 100644 --- a/src/preferences/device.js +++ b/src/preferences/device.js @@ -255,7 +255,10 @@ var Panel = GObject.registerClass({ }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-device-panel.ui', Children: [ - 'sidebar', 'stack', 'infobar', + 'sidebar', 'stack', + + // Pairing process + 'infobar', 'infobar_stack', 'infobar_pair', 'infobar_verify', 'verifycode', // Sharing 'sharing', 'sharing-page', @@ -306,7 +309,7 @@ var Panel = GObject.registerClass({ path: `/org/gnome/shell/extensions/gsconnect/device/${device.id}/`, }); - // Infobar + // Pairing infobar this.device.bind_property( 'paired', this.infobar, @@ -314,7 +317,22 @@ var Panel = GObject.registerClass({ (GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN) ); + this.device.connect('notify::pairing', () => { + if (this.device.pairing) { + this.infobar_stack.visible_child = this.infobar_verify; + } else { + this.infobar_stack.visible_child = this.infobar_pair; + } + }); + + this.device.bind_property( + 'verify-code', + this.verifycode, + 'label', + GObject.BindingFlags.SYNC_CREATE + ); + // Action setup this._setupActions(); // Settings Pages diff --git a/src/service/device.js b/src/service/device.js index eb0e915daf..875d0b79af 100644 --- a/src/service/device.js +++ b/src/service/device.js @@ -3,6 +3,7 @@ const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; +const ByteArray = imports.byteArray; const Config = imports.config; const Components = imports.service.components; @@ -61,6 +62,13 @@ var Device = GObject.registerClass({ GObject.ParamFlags.READABLE, null ), + 'pairing': GObject.ParamSpec.boolean( + 'pairing', + 'Pairing', + 'Whether the device is in the process of being paired', + GObject.ParamFlags.READABLE, + false + ), 'paired': GObject.ParamSpec.boolean( 'paired', 'Paired', @@ -68,6 +76,13 @@ var Device = GObject.registerClass({ GObject.ParamFlags.READABLE, false ), + 'verify-code': GObject.ParamSpec.string( + 'verify-code', + 'verifyCode', + 'The verification code for device pairing', + GObject.ParamFlags.READABLE, + null + ), 'type': GObject.ParamSpec.string( 'type', 'Type', @@ -157,26 +172,23 @@ var Device = GObject.registerClass({ return this._contacts; } - // FIXME: backend should do this stuff - get encryption_info() { - let remoteFingerprint = _('Not available'); - let localFingerprint = _('Not available'); + getCerts() { + let remoteCert, localCert; - // Bluetooth connections have no certificate so we use the host address + // Bluetooth connections have no certificate if (this.connection_type === 'bluetooth') { - // TRANSLATORS: Bluetooth address for remote device - return _('Bluetooth device at %s').format('???'); + return [null, null]; // If the device is connected use the certificate from the connection } else if (this.connected) { - remoteFingerprint = this.channel.peer_certificate.sha256(); + remoteCert = this.channel.peer_certificate; // Otherwise pull it out of the settings } else if (this.paired) { - remoteFingerprint = Gio.TlsCertificate.new_from_pem( + remoteCert = Gio.TlsCertificate.new_from_pem( this.settings.get_string('certificate-pem'), -1 - ).sha256(); + ); } // FIXME: another ugly reach-around @@ -185,21 +197,65 @@ var Device = GObject.registerClass({ if (this.service !== null) lanBackend = this.service.manager.backends.get('lan'); - if (lanBackend && lanBackend.certificate) - localFingerprint = lanBackend.certificate.sha256(); + if (lanBackend && lanBackend.certificate) { + localCert = lanBackend.certificate; + } + + return [remoteCert, localCert]; + } + + // FIXME: backend should do this stuff + get encryption_info() { + let remoteFingerprint = _('Not available'); + let localFingerprint = _('Not available'); + const [remoteCert, localCert] = this.getCerts(); + + // Bluetooth connections have no certificate so we use the host address + if (this.connection_type === 'bluetooth') { + // TRANSLATORS: Bluetooth address for remote device + return _('Bluetooth device at %s').format('???'); + + // If the device is connected use the certificate from the connection + } + + if (remoteCert) remoteFingerprint = remoteCert.sha256(); + if (localCert) localFingerprint = localCert.sha256(); // TRANSLATORS: Label for TLS Certificate fingerprint // // Example: // // Google Pixel Fingerprint: - // 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 + // 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 return _('%s Fingerprint:').format(this.name) + '\n' + remoteFingerprint + '\n\n' + _('%s Fingerprint:').format('GSConnect') + '\n' + localFingerprint; } + get verify_code() { + let verifyCode = _('Not available'); + const [remoteCert, localCert] = this.getCerts(); + +// XXX MAKE THIS WORK + //let a = ByteArray.toGBytes(localCert.get_certificate()); + //let b = ByteArray.toGBytes(remoteCert.certificate); + let a = ByteArray.toGBytes(ByteArray.fromString('a')); + let b = ByteArray.toGBytes(ByteArray.fromString('b')); + // Lexographic a < b comparison; this is what the < operator on QByteArray does + if (a.compare(b) < 0) { + [a, b] = [b, a]; // swap + } + + const checksum = new GLib.Checksum(GLib.ChecksumType.SHA256); + checksum.update(ByteArray.fromGBytes(a)); + checksum.update(ByteArray.fromGBytes(b)); + verifyCode = checksum.get_string(); + checksum.free(); + + return verifyCode; + } + get id() { return this._id; } @@ -208,6 +264,10 @@ var Device = GObject.registerClass({ return this.settings.get_string('name'); } + get pairing() { + return this.settings.get_boolean('pairing'); + } + get paired() { return this.settings.get_boolean('paired'); } @@ -772,6 +832,7 @@ var Device = GObject.registerClass({ // The device is requesting pairing } else { this._notifyPairRequest(); + this._setPairing(true); } // Device is requesting unpairing/rejecting our request } else { @@ -822,6 +883,8 @@ var Device = GObject.registerClass({ _resetPairRequest() { this.hideNotification('pair-request'); + this._setPairing(false); + if (this._incomingPairRequest) { GLib.source_remove(this._incomingPairRequest); this._incomingPairRequest = 0; @@ -833,6 +896,16 @@ var Device = GObject.registerClass({ } } + /** + * Set the internal pairing state of the device and emit ::notify + * + * @param {boolean} pairing - The pairing state to set + */ + _setPairing(pairing) { + this.settings.set_boolean('pairing', pairing); + this.notify('pairing'); + } + /** * Set the internal paired state of the device and emit ::notify * @@ -897,6 +970,8 @@ var Device = GObject.registerClass({ body: {pair: true}, }); + this._setPairing(true); + this._outgoingPairRequest = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 30, diff --git a/src/utils/remote.js b/src/utils/remote.js index 7a1c27b6b9..1a61a1727a 100644 --- a/src/utils/remote.js +++ b/src/utils/remote.js @@ -16,7 +16,9 @@ const _PROPERTIES = { 'IconName': 'icon-name', 'Id': 'id', 'Name': 'name', + 'Pairing': 'pairing', 'Paired': 'paired', + 'VerifyCode': 'verify-code', 'Type': 'type', }; @@ -86,6 +88,13 @@ var Device = GObject.registerClass({ GObject.ParamFlags.READABLE, null ), + 'pairing': GObject.ParamSpec.boolean( + 'pairing', + 'Pairing', + 'Whether the device is in the process of being paired', + GObject.ParamFlags.READABLE, + null + ), 'paired': GObject.ParamSpec.boolean( 'paired', 'Paired', @@ -93,6 +102,13 @@ var Device = GObject.registerClass({ GObject.ParamFlags.READABLE, null ), + 'verify-code': GObject.ParamSpec.string( + 'verify-code', + 'verifyCode', + 'The verification code for device pairing', + GObject.ParamFlags.READABLE, + null + ), 'type': GObject.ParamSpec.string( 'type', 'deviceType', @@ -151,10 +167,18 @@ var Device = GObject.registerClass({ return this._get('Name', 'Unknown'); } + get pairing() { + return this._get('Pairing', false); + } + get paired() { return this._get('Paired', false); } + get verify_code() { + return this._get('VerifyCode', false); + } + get service() { return this._service; }