From 059b75cdbbfb2ce5ce928d71a7cea1f53873ef87 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 27 Jun 2013 08:54:19 -0400 Subject: [PATCH] authPrompt: support smartcard authentication This commit detects when a user inserts a smartcard, and then initiates user verification using the gdm-smartcard PAM service. Likewise, if a user removes their smartcard, password verification (or the user list depending on auth mode and configuration) are initiated https://bugzilla.gnome.org/show_bug.cgi?id=683437 --- js/gdm/authPrompt.js | 39 ++++++++++++++++++++++++++++++++++- js/gdm/util.js | 48 ++++++++++++++++++++++++++++++++++++++++++- js/ui/screenShield.js | 8 ++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 640395afe..e3d0ab9d7 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -60,6 +60,8 @@ const AuthPrompt = new Lang.Class({ this._userVerifier.connect('reset', Lang.bind(this, this._onReset)); this._userVerifier.connect('show-login-hint', Lang.bind(this, this._onShowLoginHint)); this._userVerifier.connect('hide-login-hint', Lang.bind(this, this._onHideLoginHint)); + this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged)); + this.smartcardDetected = this._userVerifier.smartcardDetected; this.connect('next', Lang.bind(this, function() { this.updateSensitivity(false); @@ -221,6 +223,25 @@ const AuthPrompt = new Lang.Class({ this.emit('prompted'); }, + _onSmartcardStatusChanged: function() { + this.smartcardDetected = this._userVerifier.smartcardDetected; + + // Most of the time we want to reset if the user inserts or removes + // a smartcard. Smartcard insertion "preempts" what the user was + // doing, and smartcard removal aborts the preemption. + // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying + // with a smartcard + // 2) Don't reset if we've already succeeded at verification and + // the user is getting logged in. + if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) && + this.verificationStatus == AuthPromptStatus.VERIFYING && + this.smartcardDetected) + return; + + if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED) + this.reset(); + }, + _onShowMessage: function(userVerifier, message, styleClass) { this.setMessage(message, styleClass); this.emit('prompted'); @@ -437,7 +458,23 @@ const AuthPrompt = new Lang.Class({ if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED) this.emit('failed'); - this.emit('reset', BeginRequestType.PROVIDE_USERNAME); + let beginRequestType; + + if (this._mode == AuthPromptMode.UNLOCK_ONLY) { + // The user is constant at the unlock screen, so it will immediately + // respond to the request with the username + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } else if (this.smartcardDetected && + this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) { + // We don't need to know the username if the user preempted the login screen + // with a smartcard. + beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME; + } else { + // In all other cases, we should get the username up front. + beginRequestType = BeginRequestType.PROVIDE_USERNAME; + } + + this.emit('reset', beginRequestType); }, addCharacter: function(unichar) { diff --git a/js/gdm/util.js b/js/gdm/util.js index a7b8b626d..7ed50979b 100644 --- a/js/gdm/util.js +++ b/js/gdm/util.js @@ -13,16 +13,19 @@ const Fprint = imports.gdm.fingerprint; const Main = imports.ui.main; const Params = imports.misc.params; const ShellEntry = imports.ui.shellEntry; +const SmartcardManager = imports.misc.smartcardManager; const Tweener = imports.ui.tweener; const PASSWORD_SERVICE_NAME = 'gdm-password'; const FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint'; +const SMARTCARD_SERVICE_NAME = 'gdm-smartcard'; const FADE_ANIMATION_TIME = 0.16; const CLONE_FADE_ANIMATION_TIME = 0.25; const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; const PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication'; const FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication'; +const SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication'; const BANNER_MESSAGE_KEY = 'banner-message-enable'; const BANNER_MESSAGE_TEXT_KEY = 'banner-message-text'; const ALLOWED_FAILURES_KEY = 'allowed-failures'; @@ -122,6 +125,19 @@ const ShellUserVerifier = new Lang.Class({ this._updateDefaultService(); this._fprintManager = new Fprint.FprintManager(); + this._smartcardManager = SmartcardManager.getSmartcardManager(); + + // We check for smartcards right away, since an inserted smartcard + // at startup should result in immediately initiating authentication. + // This is different than fingeprint readers, where we only check them + // after a user has been picked. + this._checkForSmartcard(); + + this._smartcardManager.connect('smartcard-inserted', + Lang.bind(this, this._checkForSmartcard)); + this._smartcardManager.connect('smartcard-removed', + Lang.bind(this, this._checkForSmartcard)); + this._messageQueue = []; this._messageQueueTimeoutId = 0; this.hasPendingMessages = false; @@ -253,6 +269,28 @@ const ShellUserVerifier = new Lang.Class({ })); }, + _checkForSmartcard: function() { + let smartcardDetected; + + if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY)) + smartcardDetected = false; + else if (this.reauthenticating) + smartcardDetected = this._smartcardManager.hasInsertedLoginToken(); + else + smartcardDetected = this._smartcardManager.hasInsertedTokens(); + + if (smartcardDetected != this.smartcardDetected) { + this.smartcardDetected = smartcardDetected; + + if (this.smartcardDetected) + this._preemptingService = SMARTCARD_SERVICE_NAME; + else if (this._preemptingService == SMARTCARD_SERVICE_NAME) + this._preemptingService = null; + + this.emit('smartcard-status-changed'); + } + }, + _reportInitError: function(where, error) { logError(error, where); this._hold.release(); @@ -310,7 +348,9 @@ const ShellUserVerifier = new Lang.Class({ }, _getForegroundService: function() { - // For now, the foreground service is always the default service + if (this._preemptingService) + return this._preemptingService; + return this._defaultService; }, @@ -318,9 +358,15 @@ const ShellUserVerifier = new Lang.Class({ return serviceName == this._getForegroundService(); }, + serviceIsDefault: function(serviceName) { + return serviceName == this._defaultService; + }, + _updateDefaultService: function() { if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY)) this._defaultService = PASSWORD_SERVICE_NAME; + else if (this.smartcardDetected) + this._defaultService = SMARTCARD_SERVICE_NAME; else if (this._haveFingerprintReader) this._defaultService = FINGERPRINT_SERVICE_NAME; }, diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index 910462a1f..f375735dc 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -25,6 +25,7 @@ const Main = imports.ui.main; const Overview = imports.ui.overview; const MessageTray = imports.ui.messageTray; const ShellDBus = imports.ui.shellDBus; +const SmartcardManager = imports.misc.smartcardManager; const Tweener = imports.ui.tweener; const Util = imports.misc.util; @@ -546,6 +547,13 @@ const ScreenShield = new Lang.Class({ this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); + this._smartcardManager = SmartcardManager.getSmartcardManager(); + this._smartcardManager.connect('smartcard-inserted', + Lang.bind(this, function(token) { + if (this._isLocked && token.UsedToLogin) + this._liftShield(true, 0); + })); + this._inhibitor = null; this._aboutToSuspend = false; this._loginManager = LoginManager.getLoginManager();