on xenial help sign in
Add a touch of security to fingerprinting
commite0cd1cc851179d845c417b2a7490602507b228b6 authorMichael Terry <michael.terry@canonical.com> dateMon 20 Jun 2016, 11:24 (10 years ago) parent1bbcd37
6 files changed+82−16
plugins/AccountsService/AccountsService.cpp+12 plugins/AccountsService/AccountsService.h+7 plugins/AccountsService/com.canonical.unity.AccountsService.xml+4 qml/Greeter/Greeter.qml+39−16 tests/mocks/AccountsService/AccountsService.cpp+12 tests/mocks/AccountsService/AccountsService.h+8
plugins/AccountsService/AccountsService.cpp+12
@@ -39,6 +39,7 @@
3939#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
4040#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
4141#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
42+#define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
4243#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
4344#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
4445#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
@@ -101,6 +102,7 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
101102 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
102103 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
103104 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
105+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
104106 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
105107
106108 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -282,6 +284,16 @@ QStringList AccountsService::keymaps() const
282284 return {QStringLiteral("us")};
283285}
284286
287+uint AccountsService::failedFingerprintLogins() const
288+{
289+ return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
290+}
291+
292+void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
293+{
294+ setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
295+}
296+
285297uint AccountsService::failedLogins() const
286298{
287299 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+7
@@ -63,6 +63,10 @@ class AccountsService: public QObject
6363 READ failedLogins
6464 WRITE setFailedLogins
6565 NOTIFY failedLoginsChanged)
66+ Q_PROPERTY (uint failedFingerprintLogins
67+ READ failedFingerprintLogins
68+ WRITE setFailedFingerprintLogins
69+ NOTIFY failedFingerprintLoginsChanged)
6670 Q_PROPERTY(bool hereEnabled
6771 READ hereEnabled
6872 WRITE setHereEnabled
@@ -102,6 +106,8 @@ public:
102106 PasswordDisplayHint passwordDisplayHint() const;
103107 uint failedLogins() const;
104108 void setFailedLogins(uint failedLogins);
109+ uint failedFingerprintLogins() const;
110+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
105111 bool hereEnabled() const;
106112 void setHereEnabled(bool enabled);
107113 QString hereLicensePath() const;
@@ -123,6 +129,7 @@ Q_SIGNALS:
123129 void statsWelcomeScreenChanged();
124130 void passwordDisplayHintChanged();
125131 void failedLoginsChanged();
132+ void failedFingerprintLoginsChanged();
126133 void hereEnabledChanged();
127134 void hereLicensePathChanged();
128135 void realNameChanged();
plugins/AccountsService/com.canonical.unity.AccountsService.xml+4
@@ -47,5 +47,9 @@
4747 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
4848 </property>
4949
50+ <property name="FailedFingerprintLogins" type="u" access="readwrite">
51+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
52+ </property>
53+
5054 </interface>
5155</node>
qml/Greeter/Greeter.qml+39−16
@@ -54,6 +54,7 @@ Showable {
5454 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
5555 property int failedLoginsDelayAttempts: 7 // number of failed logins
5656 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
57+ property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
5758
5859 readonly property bool animating: loader.item ? loader.item.animating : false
5960
@@ -62,6 +63,9 @@ Showable {
6263 signal emergencyCall()
6364
6465 function forceShow() {
66+ if (!active) {
67+ d.isLockscreen = true;
68+ }
6569 forcedUnlock = false;
6670 if (!required) {
6771 showNow(); // loader.onLoaded will select a user
@@ -121,7 +125,11 @@ Showable {
121125 readonly property bool multiUser: lightDM.users.count > 1
122126 property int currentIndex
123127 property bool waiting
124− property int fingerprintFailureCount
128+ property bool isLockscreen // true when we are locking an active session, rather than first user login
129+ readonly property bool allowFingerprint: isLockscreen &&
130+ AccountsService.failedFingerprintLogins <
131+ root.failedFingerprintLoginsDisableAttempts
132+ readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
125133
126134 // We want 'launcherOffset' to animate down to zero. But not to animate
127135 // while being dragged. So ideally we change this only when the user
@@ -137,16 +145,8 @@ Showable {
137145 UbuntuNumberAnimation {}
138146 }
139147
140− onFingerprintFailureCountChanged: {
141− if (d.fingerprintFailureCount >= 3) {
142− d.startUnlock(false /* toTheRight */);
143− d.fingerprintFailureCount = 0;
144− }
145− }
146−
147148 function selectUser(uid, reset) {
148149 d.waiting = true;
149− d.fingerprintFailureCount = 0;
150150 if (reset) {
151151 loader.item.reset();
152152 }
@@ -199,6 +199,13 @@ Showable {
199199 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
200200 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
201201
202+ onLockedChanged: {
203+ if (!locked) {
204+ AccountsService.failedLogins = 0;
205+ AccountsService.failedFingerprintLogins = 0;
206+ }
207+ }
208+
202209 onRequiredChanged: {
203210 if (required) {
204211 d.waiting = true;
@@ -360,7 +367,7 @@ Showable {
360367 Binding {
361368 target: loader.item
362369 property: "alphanumeric"
363− value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
370+ value: d.alphanumeric
364371 }
365372
366373 Binding {
@@ -419,7 +426,6 @@ Showable {
419426 d.waiting = false;
420427
421428 if (lightDM.greeter.authenticated) {
422− AccountsService.failedLogins = 0;
423429 d.login();
424430 if (!lightDM.greeter.promptless) {
425431 loader.item.hide();
@@ -515,10 +521,16 @@ Showable {
515521
516522 function failOperation(reason) {
517523 console.log("Failed to identify user by fingerprint:", reason);
518− d.fingerprintFailureCount++;
519524 restartOperation();
520− if (loader.item)
521− loader.item.showErrorMessage(i18n.tr("Try again"));
525+ if (!allowFingerprint) {
526+ d.startUnlock(false /* toTheRight */);
527+ }
528+ if (loader.item) {
529+ var msg = d.allowFingerprint ? i18n.tr("Try again")
530+ d.alphanumeric ? i18n.tr("Use passphrase")
531+ : i18n.tr("Use passcode");
532+ loader.item.showErrorMessage(msg);
533+ }
522534 }
523535
524536 Component.onCompleted: restartOperation()
@@ -526,17 +538,28 @@ Showable {
526538 onIdEnabledChanged: restartOperation()
527539
528540 onSucceeded: {
541+ if (!d.allowFingerprint) {
542+ failOperation("too many failures");
543+ return;
544+ }
529545 if (result !== lightDM.users.data(d.currentIndex, lightDM.userRoles.UidRole)) {
546+ AccountsService.failedFingerprintLogins++;
530547 failOperation("not the selected user");
531548 return;
532549 }
533550 console.log("Identified user by fingerprint:", result);
534− d.fingerprintFailureCount = 0;
535551 if (loader.item)
536552 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
537553 if (root.active)
538554 root.forcedUnlock = true;
539555 }
540− onFailed: failOperation(reason)
556+ onFailed: {
557+ if (!d.allowFingerprint) {
558+ failOperation("too many failures");
559+ } else {
560+ AccountsService.failedFingerprintLogins++;
561+ failOperation(reason);
562+ }
563+ }
541564 }
542565}
tests/mocks/AccountsService/AccountsService.cpp+12
@@ -26,6 +26,7 @@ AccountsService::AccountsService(QObject* parent)
2626 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
2727 m_statsWelcomeScreen(true),
2828 m_failedLogins(0),
29+ m_failedFingerprintLogins(0),
2930 m_demoEdges(false),
3031 m_demoEdgesCompleted(),
3132 m_hereEnabled(false),
@@ -149,6 +150,17 @@ void AccountsService::setFailedLogins(uint failedLogins)
149150 failedLoginsChanged();
150151}
151152
153+uint AccountsService::failedFingerprintLogins() const
154+{
155+ return m_failedFingerprintLogins;
156+}
157+
158+void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
159+{
160+ m_failedFingerprintLogins = failedFingerprintLogins;
161+ failedFingerprintLoginsChanged();
162+}
163+
152164bool AccountsService::hereEnabled() const
153165{
154166 return m_hereEnabled;
tests/mocks/AccountsService/AccountsService.h+8
@@ -65,6 +65,10 @@ class AccountsService: public QObject
6565 READ failedLogins
6666 WRITE setFailedLogins
6767 NOTIFY failedLoginsChanged)
68+ Q_PROPERTY (uint failedFingerprintLogins
69+ READ failedFingerprintLogins
70+ WRITE setFailedFingerprintLogins
71+ NOTIFY failedFingerprintLoginsChanged)
6872 Q_PROPERTY(bool hereEnabled
6973 READ hereEnabled
7074 WRITE setHereEnabled
@@ -111,6 +115,8 @@ public:
111115 PasswordDisplayHint passwordDisplayHint() const;
112116 uint failedLogins() const;
113117 void setFailedLogins(uint failedLogins);
118+ uint failedFingerprintLogins() const;
119+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
114120 bool hereEnabled() const;
115121 void setHereEnabled(bool enabled);
116122 QString hereLicensePath() const;
@@ -134,6 +140,7 @@ Q_SIGNALS:
134140 void statsWelcomeScreenChanged();
135141 void passwordDisplayHintChanged();
136142 void failedLoginsChanged();
143+ void failedFingerprintLoginsChanged();
137144 void hereEnabledChanged();
138145 void hereLicensePathChanged();
139146 void realNameChanged();
@@ -148,6 +155,7 @@ private:
148155 QString m_user;
149156 bool m_statsWelcomeScreen;
150157 uint m_failedLogins;
158+ uint m_failedFingerprintLogins;
151159 bool m_demoEdges;
152160 QStringList m_demoEdgesCompleted;
153161 bool m_hereEnabled;