Android10.0 锁屏分析——KeyguardPatter

作者: 孤街酒客0911 | 来源:发表于2022-09-05 10:39 被阅读0次

    学习笔记:

    首先一起看看下面两张图:


    图案锁 图案锁对应布局
    通过前面锁屏加载流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根据不同的securityMode inflate出来,并添加到界面上的。
    我们知道,Pattern锁所使用的layout是 R.layout.keyguard_pattern_view;
    <com.android.keyguard.KeyguardPatternView ...>
    
    ...
                <com.android.internal.widget.LockPatternView
                    android:id="@+id/lockPatternView"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:layout_marginEnd="8dip"
                    android:layout_marginBottom="4dip"
                    android:layout_marginStart="8dip"
                    android:layout_gravity="center_horizontal"
                    android:gravity="center"
                    android:clipChildren="false"
                    android:clipToPadding="false" />
    
    ...
        </FrameLayout>
    
    </com.android.keyguard.KeyguardPatternView>
    

    那么图案解锁的滑动事件处理,就是在LockPatternView,是一个系统公共控件,下面我们就分析一下这个view是如何处理触摸输入的:

    // frameworks/base/core/java/com/android/internal/widget/LockPatternView.java
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!mInputEnabled || !isEnabled()) {
                return false;
            }
    
            switch(event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    handleActionDown(event);
                    return true;
                case MotionEvent.ACTION_UP:
                    handleActionUp();
                    return true;
                case MotionEvent.ACTION_MOVE:
                    handleActionMove(event);
                    return true;
                case MotionEvent.ACTION_CANCEL:
                    if (mPatternInProgress) {
                        setPatternInProgress(false);
                        resetPattern();
                        notifyPatternCleared();
                    }
                    if (PROFILE_DRAWING) {
                        if (mDrawingProfilingStarted) {
                            Debug.stopMethodTracing();
                            mDrawingProfilingStarted = false;
                        }
                    }
                    return true;
            }
            return false;
        }
    
    
    几种事件类型: 事件类型.png

    不同的MotionEvent对应几个不同的handle方法处理,代码行数太多,我们这里大致总结如下:

    • ACTION_DOWN(handleActionDown):根据触摸事件的坐标,使用算法detectAndAddHit(x, y)获取是否有命中的点,如果有,会调用addCellToPattern将命中的Cell添加到mPattern中,后即回调mOnPatternListener.onPatternStart()通知监听器,KeyguardPatternView实现并监听了OnPatternListener,做了清除安全提示内容的动作。另外计算需要重绘区域,并调用invalidate进行局部重绘。
    • ACTION_MOVE(handleActionMove):在这里 LockPatternView会对所有的历史坐标加当前事件坐标遍历for (int i = 0; i < historySize + 1; i++),获取命中点,另外如果ACTION_DOWN时没有获取到命中点,流程同上面的ACTION_UP,然后也会回调mOnPatternListener.onPatternStart()。最后会把所有motionevent对应的重绘区域进行union,并调用invalidate进行局部重绘。

    关于历史坐标
      为了效率,Android系统在处理ACTION_MOVE事件时会将连续的几个多触点移动事件打包到一个MotionEvent对象中。我们可以通过getX(int)和getY(int)来获得最近发生的一个触摸点事件的坐标,然后使用getHistorical(int,int)和getHistorical(int,int)来获得时间稍早的触点事件的坐标,二者是发生时间先后的关系。所以,我们应该先处理通过getHistoricalXX相关函数获得的事件信息,然后在处理当前的事件信息。

    for (int i = 0; i < historySize + 1; i++) {
        final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
        final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
     ...
    }
    
    • ACTION_UP(handleActionUp):如果mPattern不为空的话,会重置mPatternInProgress,取消动画,然后回调mOnPatternListener.onPatternDetected(final List<LockPatternView.Cell> pattern),这时候就开始图案解锁的验证了。

    • ACTION_CANCEL:重置pattern状态,回调mOnPatternListener.onPatternCleared()

    图案解锁验证

    // src/com/android/keyguard/KeyguardPatternView.java
        private class UnlockPatternListener implements LockPatternView.OnPatternListener {
        ...
            @Override
            public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
               if (DEBUG) Log.d(TAG, "onPatternDetected");
                mKeyguardUpdateMonitor.setCredentialAttempted();
                mLockPatternView.disableInput();
                if (mPendingLockCheck != null) {
                    mPendingLockCheck.cancel(false);
                }
    
                final int userId = KeyguardUpdateMonitor.getCurrentUser();
                if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                    mLockPatternView.enableInput();
                    onPatternChecked(userId, false, 0, false /* not valid - too short */);
                    return;
                }
    
                if (LatencyTracker.isEnabled(mContext)) {
                    LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
                    LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
                }
                mPendingLockCheck = LockPatternChecker.checkCredential(
                        mLockPatternUtils,
                        LockscreenCredential.createPattern(pattern),    // 这里跟进去,会发现将 pattern转化成了 byte[]
                        userId,
                        new LockPatternChecker.OnCheckCallback() {
    
                            @Override
                            public void onEarlyMatched() {
                                if (DEBUG) Log.d(TAG, "onEarlyMatched");
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL);
                                }
                                onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
                                        true /* isValidPattern */);
                            }
    
                            @Override
                            public void onChecked(boolean matched, int timeoutMs) {
                                if (DEBUG) Log.d(TAG, "onChecked matched:" + matched);
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL_UNLOCKED);
                                }
                                mLockPatternView.enableInput();
                                mPendingLockCheck = null;
                                if (!matched) {
                                    onPatternChecked(userId, false /* matched */, timeoutMs,
                                            true /* isValidPattern */);
                                }
                            }
    
                            @Override
                            public void onCancelled() {
                               if (DEBUG) Log.d(TAG, "onCancelled");
                                // We already got dismissed with the early matched callback, so we
                                // cancelled the check. However, we still need to note down the latency.
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL_UNLOCKED);
                                }
                            }
                        });
                if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                    mCallback.userActivity();
                    mCallback.onUserInput();
                }
            }
        ...
        }
     
    

    在绘制密码后手指抬起的时候,如果已存的有效点数达到4个及以上,就会使用LockPatternChecker.checkCredential 方法调用 task.execute() 启动一个AsyncTask, 并在doInBackground中调用LockPatternUtils.checkCredential 进行密码验证,此时pattern会被转化成字节形式(LockscreenCredential.createPattern(pattern) 这里跟进去,会发现将 pattern转化成了 byte[])

    // LockPatternUtils.java
    public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
            if (pattern == null) {
                return new byte[0];
            }
            final int patternSize = pattern.size();
    
            byte[] res = new byte[patternSize];
            for (int i = 0; i < patternSize; i++) {
                LockPatternView.Cell cell = pattern.get(i);
                res[i] = (byte) (cell.getRow() * 3 + cell.getColumn() + '1');
            }
            return res;
        }
    

    最终和密码锁PIN码锁一样,都是远程调用到LockSettingsService 的 checkCredential 接口进行验证。

    Keyguard接收用户输入的密码会通过Binder到framework层的LockSettingsService,LockSettingsService经过一系列调用会通过getGateKeeperService获取GateKeeperService然后调用verifyChallenge方法将密码继续忘底层传递,framework的调用栈如下:


    framework的调用栈.png

    验证流程:

    // LockSettingsService.java
        private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
                @ChallengeType int challengeType, long challenge, int userId,
                ICheckCredentialProgressCallback progressCallback,
                @Nullable ArrayList<PendingResetLockout> resetLockouts) {
    
            // 省略部分代码......
    
            if (credential == null || credential.isNone()) {
                throw new IllegalArgumentException("Credential can't be null or empty");
            }
            if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
                    Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
                Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
                return VerifyCredentialResponse.ERROR;
            }
            
            // response是验证响应,spBasedDoVerifyCredential发起验证,返回 response 响应
            VerifyCredentialResponse response = null;
            response = spBasedDoVerifyCredential(credential, challengeType, challenge,
                    userId, progressCallback, resetLockouts);
    
            // The user employs synthetic password based credential.
            if (response != null) {
                if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                    sendCredentialsOnUnlockIfRequired(credential, userId);
                }
                return response;
            }
            // 省略部分代码......
            return response;
        }
    

    这里先看verifyChallenge方法返回有三个状态,也就是response.getResponseCode():
      //密码匹配失败
      public static final int RESPONSE_ERROR = -1;
      //密码匹配成功
      public static final int RESPONSE_OK = 0;
      //重试
      public static final int RESPONSE_RETRY = 1;

    接下来我们看spBasedDoVerifyCredential()方法做基于 sp 的做验证凭证:

    // LockSettingsService.java
        private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
                @ChallengeType int challengeType, long challenge,
                int userId, ICheckCredentialProgressCallback progressCallback,
                @Nullable ArrayList<PendingResetLockout> resetLockouts) {
    
            // 判断是否具有生物识别
            final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
    
            // 省略部分代码......
    
            final AuthenticationResult authResult;
            VerifyCredentialResponse response;
            synchronized (mSpManager) {
                if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                    return null;
                }
                if (userId == USER_FRP) {
                    return mSpManager.verifyFrpCredential(getGateKeeperService(),
                            userCredential, progressCallback);
                }
    
                long handle = getSyntheticPasswordHandleLocked(userId);
                // 解开基于密码的合成密码
                authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
                        getGateKeeperService(), handle, userCredential, userId, progressCallback);
    
                response = authResult.gkResponse;
    
                // 省略部分代码......
            }
    
            // 省略部分代码......
            return response;
        }
    

    在这方法里首先进行了判断是不是生物解锁,然后在调用合成密码管理器SyntheticPasswordManager里的unwrapPasswordBasedSyntheticPassword()方法进行解密,并将锁屏密码往下传。

    // SyntheticPasswordManager.java
        public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
                long handle, @NonNull LockscreenCredential credential, int userId,
                ICheckCredentialProgressCallback progressCallback) {
    
            // 省略部分代码......
    
              else {
                byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
                GateKeeperResponse response;
                try {
                    Log.d("yexiao","yexiao:",new Throwable());
                    response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
                            pwd.passwordHandle, gkPwdToken);
                } catch (RemoteException e) {
                    Slog.e(TAG, "gatekeeper verify failed", e);
                    result.gkResponse = VerifyCredentialResponse.ERROR;
                    return result;
                }
    
                // 省略部分代码......
            }
    
            // 省略部分代码......
    
            return result;
        }
    
    

    unwrapPasswordBasedSyntheticPassword中的gatekeeper是LockSettingsService的getGateKeeperService方法获取的IGateKeeperService Binder代理端:

    // LockSettingsService.java
        protected synchronized IGateKeeperService getGateKeeperService() {
            if (mGateKeeperService != null) {
                return mGateKeeperService;
            }
    
            final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
            if (service != null) {
                try {
                    service.linkToDeath(new GateKeeperDiedRecipient(), 0);
                } catch (RemoteException e) {
                    Slog.w(TAG, " Unable to register death recipient", e);
                }
                mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
                return mGateKeeperService;
            }
    
            Slog.e(TAG, "Unable to acquire GateKeeperService");
            return null;
        }
    

    这里有个问题,我们发现IGateKeeperService的Binder实现端找不到,而且在Framework层也找不到在那里注册的service,为何能getService?

    其实IGateKeeperService这个AIDL文件的具体实现类不像传统的Framework Binder服务,它的实现端在native层,我们前面说了GateKeeper的架构,提到GateKeeper是一种C++的Binder服务,与java层接口相对应

    我们就先来来看看GateKeeper server端,目录system/core/gatekeeperd下的gatekeeperd.cpp类

    // gatekeeperd.cpp
    int main(int argc, char* argv[]) {
        ALOGI("Starting gatekeeperd...");
        if (argc < 2) {
            ALOGE("A directory must be specified!");
            return 1;
        }
        if (chdir(argv[1]) == -1) {
            ALOGE("chdir: %s: %s", argv[1], strerror(errno));
            return 1;
        }
    
        android::sp<android::IServiceManager> sm = android::defaultServiceManager();
        android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
        android::status_t ret = sm->addService(
                android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
        if (ret != android::OK) {
            ALOGE("Couldn't register binder service!");
            return -1;
        }
    
        /*
         * We're the only thread in existence, so we're just going to process
         * Binder transaction as a single-threaded program.
         */
        android::IPCThreadState::self()->joinThreadPool();
        return 0;
    }
    
    

    在main函数中,首先获取BpSeviceManager,然后创建GateKeeperProxy类,在调用addService函数将GateKeeperProxy注册到SeviceManager,名称为"android.service.gatekeeper.IGateKeeperService",前面我们在Framework层通过getService(Context.GATEKEEPER_SERVICE)获取的gatekeeper服务其实获取的就是这个服务,Context中定义的服务名称也是一样的。

    至于native层与Java层如何相互调用关联的,这里就省略了。

    前面解密过程调的verifyChallenge方法调到了gatekeeperd.cpp中的GateKeeperProxy类的同名verifyChallenge函数,但我们又发现这两个verifyChallenge方法参数并不一致,这无所谓的,Binder调用并不需要client端和server端参数一致,调用方法的匹配是通过Binder code来决定的。

    到这里,上层的锁屏密码就已经传递到了natice层,还记得前面说的gatekeeper架构吗,native层过了之后就该通过HIDL忘HAL层发送密码了,来看看GateKeeperProxy中的verifyChallenge具体实现:

    // gatekeeperd.cpp
        Status verifyChallenge(int32_t uid, int64_t challenge,
                               const std::vector<uint8_t>& enrolledPasswordHandle,
                               const std::vector<uint8_t>& providedPassword,
                               GKResponse* gkResponse) override {
    
            //省略掉一些权限相关检查 ......
            //省略一些数据类型转换.....
            Return<void> hwRes = hw_device->verify(
                    hw_uid, challenge, curPwdHandle, enteredPwd,
                    [&gkResponse](const GatekeeperResponse& rsp) {
                        if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
                            ALOGD("gatekeeperd verify process ok");
                            *gkResponse = GKResponse::ok(
                                    {rsp.data.begin(), rsp.data.end()},
                                    rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
                        } else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
                            ALOGD("gatekeeperd verify process retry timeout");
                            *gkResponse = GKResponse::retry(rsp.timeout);
                        } else {
                            ALOGD("gatekeeperd verify fail,maybe not match");
                            *gkResponse = GKResponse::error();
                        }
                    });
    
            if (!hwRes.isOk()) {
                LOG(ERROR) << "verify transaction failed";
                return GK_ERROR;
            }
            // 省略部分代码......
            return Status::ok();
        }
    

    到这里基本整个解锁流程结束了,native层也不在继续深入分析了。

    在整个操作过程中,mOnPatternListener 被用于通知LockPatternView进行安全锁提示内容和Pattern状态的刷新。

    我们可以对整个密码匹配的流程进行总结了:

    • 上层Keyguard接收用户的密码输入。
    • 收到密码后通过Binder将密码传递到LockSettingsService。
    • 在LockSettingsService中获取到实现在native层的GateKeeperService,调用其verifyChallenge函数。
    • verifyChallenge中调用HIDL服务IGatekeeper的verify函数继续向HAL中发送密码。
    • IGatekeeper中获取名为GATEKEEPER_HARDWARE_MODULE_ID的HAL模块,并打开其下的device,调用device的verify函数在TEE硬件中进最终密码匹配。

    参考:Android P keyguard 初始化,Pattern解锁等介绍SystemUI 之图案锁验证流程AndroidQ 锁屏密码验证解析

    相关文章

      网友评论

        本文标题:Android10.0 锁屏分析——KeyguardPatter

        本文链接:https://www.haomeiwen.com/subject/exiagrtx.html