Android10.0 锁屏分析——KeyguardPatternView图案锁分析

简介: Android10.0 锁屏分析——KeyguardPatternView图案锁分析

学习笔记:

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


image.png

image.png



通过前面锁屏加载流程可以知道在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;
    }

几种事件类型:


image.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的调用栈如下:


image.png


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硬件中进最终密码匹配。
相关文章
|
27天前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统无疑是主角。它们各自拥有独特的特点和优势,为开发者提供了不同的开发环境和工具。本文将深入浅出地探讨安卓和iOS开发环境的主要差异,包括开发工具、编程语言、用户界面设计、性能优化以及市场覆盖等方面,旨在帮助初学者更好地理解两大平台的开发特点,并为他们选择合适的开发路径提供参考。通过比较分析,我们将揭示不同环境下的开发实践,以及如何根据项目需求和目标受众来选择最合适的开发平台。
34 2
|
8天前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
|
15天前
|
安全 Linux Android开发
探索安卓与iOS的安全性差异:技术深度分析
本文深入探讨了安卓(Android)和iOS两个主流操作系统平台在安全性方面的不同之处。通过比较它们在架构设计、系统更新机制、应用程序生态和隐私保护策略等方面的差异,揭示了每个平台独特的安全优势及潜在风险。此外,文章还讨论了用户在使用这些设备时可以采取的一些最佳实践,以增强个人数据的安全。
|
1月前
|
IDE 开发工具 Android开发
安卓与iOS开发环境对比分析
本文将探讨安卓和iOS这两大移动操作系统在开发环境上的差异,从工具、语言、框架到生态系统等多个角度进行比较。我们将深入了解各自的优势和劣势,并尝试为开发者提供一些实用的建议,以帮助他们根据自己的需求选择最适合的开发平台。
29 1
|
2月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
【8月更文挑战第20天】在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着重要的位置。本文将深入探讨这两种操作系统的开发环境,从编程语言到开发工具,从用户界面设计到性能优化,以及市场趋势对开发者选择的影响。我们旨在为读者提供一个全面的比较视角,帮助理解不同平台的优势与挑战,并为那些站在选择十字路口的开发者提供有价值的参考信息。
|
2月前
|
开发框架 Android开发 Swift
安卓与iOS应用开发对比分析
【8月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文将深入探讨这两大操作系统在开发环境、编程语言、用户界面设计、性能优化及市场分布等方面的差异和特点。通过比较分析,旨在为开发者提供一个宏观的视角,帮助他们根据项目需求和目标受众选择最合适的开发平台。同时,文章还将讨论跨平台开发框架的利与弊,以及它们如何影响着移动应用的开发趋势。
|
2月前
|
安全 搜索推荐 Android开发
安卓与iOS应用开发的对比分析
【8月更文挑战第20天】在移动应用开发领域,安卓和iOS两大平台各领风骚。本文通过深入探讨两者的开发环境、编程语言、用户界面设计、应用市场及分发机制等方面的差异,揭示了各自的优势和挑战。旨在为开发者提供决策支持,同时帮助理解为何某些应用可能优先选择在一个平台上发布。
30 2
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的大舞台上,安卓与iOS两大操作系统各占半壁江山。本文将深入浅出地比较两者的开发环境,从开发工具、编程语言到用户界面设计等多个维度进行剖析,旨在为初入行的开发者们提供一盏明灯,帮助他们选择适合自己的开发路径。通过实例分析和数据支持,我们将揭示这两个平台的独特优势和潜在挑战,以及它们如何影响应用的性能和用户体验。
59 1
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文通过浅显的语言和直观的比喻,探讨了这两大操作系统在开发环境上的差异与特点,旨在为初入行的开发者们提供一个清晰的指南。我们将从开发工具、编程语言、用户界面设计以及生态系统四个方面进行比较,帮助读者理解每个平台的优势与局限。
|
2月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的大潮中,安卓和iOS两大平台各自展现出独特的开发环境和生态系统。本文将从开发者的角度出发,深入探讨这两个平台在编程语言、开发工具、用户界面设计以及市场分布等方面的不同特点。通过比较分析,旨在为移动应用开发者提供一份实用的参考,帮助他们在项目初期做出更加明智的平台选择。
下一篇
无影云桌面