on xenial help sign in
Add a touch of security to fingerprinting
e0cd1ccMichael Terry
plugins/AccountsService/AccountsService.cpp+12
@@ -39,6 +39,7 @@
39#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
39#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
40#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
40#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
41#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
41#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
42+#define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
42#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
43#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
43#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
44#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
44#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
45#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
@@ -101,6 +102,7 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
101 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
102 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
102 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
103 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
103 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
104 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
105+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
104 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
106 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
105
107
106 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
108 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -282,6 +284,16 @@ QStringList AccountsService::keymaps() const
282 return {QStringLiteral("us")};
284 return {QStringLiteral("us")};
283}
285}
284
286
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+
285uint AccountsService::failedLogins() const
297uint AccountsService::failedLogins() const
286{
298{
287 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
299 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+7
@@ -63,6 +63,10 @@ class AccountsService: public QObject
63 READ failedLogins
63 READ failedLogins
64 WRITE setFailedLogins
64 WRITE setFailedLogins
65 NOTIFY failedLoginsChanged)
65 NOTIFY failedLoginsChanged)
66+ Q_PROPERTY (uint failedFingerprintLogins
67+ READ failedFingerprintLogins
68+ WRITE setFailedFingerprintLogins
69+ NOTIFY failedFingerprintLoginsChanged)
66 Q_PROPERTY(bool hereEnabled
70 Q_PROPERTY(bool hereEnabled
67 READ hereEnabled
71 READ hereEnabled
68 WRITE setHereEnabled
72 WRITE setHereEnabled
@@ -102,6 +106,8 @@ public:
102 PasswordDisplayHint passwordDisplayHint() const;
106 PasswordDisplayHint passwordDisplayHint() const;
103 uint failedLogins() const;
107 uint failedLogins() const;
104 void setFailedLogins(uint failedLogins);
108 void setFailedLogins(uint failedLogins);
109+ uint failedFingerprintLogins() const;
110+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
105 bool hereEnabled() const;
111 bool hereEnabled() const;
106 void setHereEnabled(bool enabled);
112 void setHereEnabled(bool enabled);
107 QString hereLicensePath() const;
113 QString hereLicensePath() const;
@@ -123,6 +129,7 @@ Q_SIGNALS:
123 void statsWelcomeScreenChanged();
129 void statsWelcomeScreenChanged();
124 void passwordDisplayHintChanged();
130 void passwordDisplayHintChanged();
125 void failedLoginsChanged();
131 void failedLoginsChanged();
132+ void failedFingerprintLoginsChanged();
126 void hereEnabledChanged();
133 void hereEnabledChanged();
127 void hereLicensePathChanged();
134 void hereLicensePathChanged();
128 void realNameChanged();
135 void realNameChanged();
plugins/AccountsService/com.canonical.unity.AccountsService.xml+4
@@ -47,5 +47,9 @@
47 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
47 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
48 </property>
48 </property>
49
49
50+ <property name="FailedFingerprintLogins" type="u" access="readwrite">
51+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
52+ </property>
53+
50 </interface>
54 </interface>
51</node>
55</node>
qml/Greeter/Greeter.qml+39−16
@@ -54,6 +54,7 @@ Showable {
54 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
54 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
55 property int failedLoginsDelayAttempts: 7 // number of failed logins
55 property int failedLoginsDelayAttempts: 7 // number of failed logins
56 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
56 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
57+ property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
57
58
58 readonly property bool animating: loader.item ? loader.item.animating : false
59 readonly property bool animating: loader.item ? loader.item.animating : false
59
60
@@ -62,6 +63,9 @@ Showable {
62 signal emergencyCall()
63 signal emergencyCall()
63
64
64 function forceShow() {
65 function forceShow() {
66+ if (!active) {
67+ d.isLockscreen = true;
68+ }
65 forcedUnlock = false;
69 forcedUnlock = false;
66 if (!required) {
70 if (!required) {
67 showNow(); // loader.onLoaded will select a user
71 showNow(); // loader.onLoaded will select a user
@@ -121,7 +125,11 @@ Showable {
121 readonly property bool multiUser: lightDM.users.count > 1
125 readonly property bool multiUser: lightDM.users.count > 1
122 property int currentIndex
126 property int currentIndex
123 property bool waiting
127 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
125
133
126 // We want 'launcherOffset' to animate down to zero. But not to animate
134 // We want 'launcherOffset' to animate down to zero. But not to animate
127 // while being dragged. So ideally we change this only when the user
135 // while being dragged. So ideally we change this only when the user
@@ -137,16 +145,8 @@ Showable {
137 UbuntuNumberAnimation {}
145 UbuntuNumberAnimation {}
138 }
146 }
139
147
140− onFingerprintFailureCountChanged: {
141− if (d.fingerprintFailureCount >= 3) {
142− d.startUnlock(false /* toTheRight */);
143− d.fingerprintFailureCount = 0;
144− }
145− }
146−
147 function selectUser(uid, reset) {
148 function selectUser(uid, reset) {
148 d.waiting = true;
149 d.waiting = true;
149− d.fingerprintFailureCount = 0;
150 if (reset) {
150 if (reset) {
151 loader.item.reset();
151 loader.item.reset();
152 }
152 }
@@ -199,6 +199,13 @@ Showable {
199 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
199 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
200 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
200 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
201
201
202+ onLockedChanged: {
203+ if (!locked) {
204+ AccountsService.failedLogins = 0;
205+ AccountsService.failedFingerprintLogins = 0;
206+ }
207+ }
208+
202 onRequiredChanged: {
209 onRequiredChanged: {
203 if (required) {
210 if (required) {
204 d.waiting = true;
211 d.waiting = true;
@@ -360,7 +367,7 @@ Showable {
360 Binding {
367 Binding {
361 target: loader.item
368 target: loader.item
362 property: "alphanumeric"
369 property: "alphanumeric"
363− value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
370+ value: d.alphanumeric
364 }
371 }
365
372
366 Binding {
373 Binding {
@@ -419,7 +426,6 @@ Showable {
419 d.waiting = false;
426 d.waiting = false;
420
427
421 if (lightDM.greeter.authenticated) {
428 if (lightDM.greeter.authenticated) {
422− AccountsService.failedLogins = 0;
423 d.login();
429 d.login();
424 if (!lightDM.greeter.promptless) {
430 if (!lightDM.greeter.promptless) {
425 loader.item.hide();
431 loader.item.hide();
@@ -515,10 +521,16 @@ Showable {
515
521
516 function failOperation(reason) {
522 function failOperation(reason) {
517 console.log("Failed to identify user by fingerprint:", reason);
523 console.log("Failed to identify user by fingerprint:", reason);
518− d.fingerprintFailureCount++;
519 restartOperation();
524 restartOperation();
525+ if (!allowFingerprint) {
526+ d.startUnlock(false /* toTheRight */);
527+ }
520− if (loader.item)
528+ if (loader.item) {
521− loader.item.showErrorMessage(i18n.tr("Try again"));
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+ }
522 }
534 }
523
535
524 Component.onCompleted: restartOperation()
536 Component.onCompleted: restartOperation()
@@ -526,17 +538,28 @@ Showable {
526 onIdEnabledChanged: restartOperation()
538 onIdEnabledChanged: restartOperation()
527
539
528 onSucceeded: {
540 onSucceeded: {
541+ if (!d.allowFingerprint) {
542+ failOperation("too many failures");
543+ return;
544+ }
529 if (result !== lightDM.users.data(d.currentIndex, lightDM.userRoles.UidRole)) {
545 if (result !== lightDM.users.data(d.currentIndex, lightDM.userRoles.UidRole)) {
546+ AccountsService.failedFingerprintLogins++;
530 failOperation("not the selected user");
547 failOperation("not the selected user");
531 return;
548 return;
532 }
549 }
533 console.log("Identified user by fingerprint:", result);
550 console.log("Identified user by fingerprint:", result);
534− d.fingerprintFailureCount = 0;
535 if (loader.item)
551 if (loader.item)
536 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
552 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
537 if (root.active)
553 if (root.active)
538 root.forcedUnlock = true;
554 root.forcedUnlock = true;
539 }
555 }
556+ onFailed: {
557+ if (!d.allowFingerprint) {
558+ failOperation("too many failures");
559+ } else {
560+ AccountsService.failedFingerprintLogins++;
540− onFailed: failOperation(reason)
561+ failOperation(reason);
562+ }
563+ }
541 }
564 }
542}
565}
tests/mocks/AccountsService/AccountsService.cpp+12
@@ -26,6 +26,7 @@ AccountsService::AccountsService(QObject* parent)
26 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
26 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
27 m_statsWelcomeScreen(true),
27 m_statsWelcomeScreen(true),
28 m_failedLogins(0),
28 m_failedLogins(0),
29+ m_failedFingerprintLogins(0),
29 m_demoEdges(false),
30 m_demoEdges(false),
30 m_demoEdgesCompleted(),
31 m_demoEdgesCompleted(),
31 m_hereEnabled(false),
32 m_hereEnabled(false),
@@ -149,6 +150,17 @@ void AccountsService::setFailedLogins(uint failedLogins)
149 failedLoginsChanged();
150 failedLoginsChanged();
150}
151}
151
152
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+
152bool AccountsService::hereEnabled() const
164bool AccountsService::hereEnabled() const
153{
165{
154 return m_hereEnabled;
166 return m_hereEnabled;
tests/mocks/AccountsService/AccountsService.h+8
@@ -65,6 +65,10 @@ class AccountsService: public QObject
65 READ failedLogins
65 READ failedLogins
66 WRITE setFailedLogins
66 WRITE setFailedLogins
67 NOTIFY failedLoginsChanged)
67 NOTIFY failedLoginsChanged)
68+ Q_PROPERTY (uint failedFingerprintLogins
69+ READ failedFingerprintLogins
70+ WRITE setFailedFingerprintLogins
71+ NOTIFY failedFingerprintLoginsChanged)
68 Q_PROPERTY(bool hereEnabled
72 Q_PROPERTY(bool hereEnabled
69 READ hereEnabled
73 READ hereEnabled
70 WRITE setHereEnabled
74 WRITE setHereEnabled
@@ -111,6 +115,8 @@ public:
111 PasswordDisplayHint passwordDisplayHint() const;
115 PasswordDisplayHint passwordDisplayHint() const;
112 uint failedLogins() const;
116 uint failedLogins() const;
113 void setFailedLogins(uint failedLogins);
117 void setFailedLogins(uint failedLogins);
118+ uint failedFingerprintLogins() const;
119+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
114 bool hereEnabled() const;
120 bool hereEnabled() const;
115 void setHereEnabled(bool enabled);
121 void setHereEnabled(bool enabled);
116 QString hereLicensePath() const;
122 QString hereLicensePath() const;
@@ -134,6 +140,7 @@ Q_SIGNALS:
134 void statsWelcomeScreenChanged();
140 void statsWelcomeScreenChanged();
135 void passwordDisplayHintChanged();
141 void passwordDisplayHintChanged();
136 void failedLoginsChanged();
142 void failedLoginsChanged();
143+ void failedFingerprintLoginsChanged();
137 void hereEnabledChanged();
144 void hereEnabledChanged();
138 void hereLicensePathChanged();
145 void hereLicensePathChanged();
139 void realNameChanged();
146 void realNameChanged();
@@ -148,6 +155,7 @@ private:
148 QString m_user;
155 QString m_user;
149 bool m_statsWelcomeScreen;
156 bool m_statsWelcomeScreen;
150 uint m_failedLogins;
157 uint m_failedLogins;
158+ uint m_failedFingerprintLogins;
151 bool m_demoEdges;
159 bool m_demoEdges;
152 QStringList m_demoEdgesCompleted;
160 QStringList m_demoEdgesCompleted;
153 bool m_hereEnabled;
161 bool m_hereEnabled;