0%

fbe与用户安全密码

[TOC]

1. fbe与用户安全密码

之前介绍了fde与用户安全密码的关系,发现其是相对简单的。而对fbe和metadata加密的方式,在调研过程中发现密码的存储与文件ce区的密钥的关系则复杂的多。

1.1. 用户ce区文件解密

这个过程伴随着一个函数向下执行的,即unlockUserKey函数,首先看下伴随这个过程的密钥来自哪里

1
2
3
4
5
6
unlockUserKey(userId, null, auth.deriveDiskEncryptionKey());
/** Unlock disk encryption */
1712 private void unlockUserKey(int userId, byte[] token, byte[] secret) throws RemoteException {
1713 final UserInfo userInfo = mUserManager.getUserInfo(userId);
1714 mStorageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
1715 }

以机主用户为例, 上述参数中userID为0, token 为空, serialNumber为0,未知的只有secret,来自deriveDiskEncryptionKey

1
2
3
4
5
6
7
8
9
10
11
12
13
      public byte[] deriveDiskEncryptionKey() {
179 return derivePassword(PERSONALIZATION_FBE_KEY);
180 }

160 private byte[] derivePassword(byte[] personalization) {
161 if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
162 return (new SP800Derive(syntheticPassword.getBytes()))
163 .withContext(personalization, PERSONALISATION_CONTEXT);
164 } else {
165 return SyntheticPasswordCrypto.personalisedHash(personalization,
166 syntheticPassword.getBytes());
167 }
168 }

关键参数只有一个syntheticPassword

1
2
3
4
5
        private void initialize(byte[] P0, byte[] P1) {
191 this.P1 = P1;
192 this.syntheticPassword = String.valueOf(HexEncoding.encode(
193 SyntheticPasswordCrypto.personalisedHash(
194 PERSONALIZATION_SP_SPLIT, P0, P1)));

这个密码生成的时候是一个随机的值?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
463 byte[] hash, byte[] credential, int userId) throws RemoteException {
464 AuthenticationToken result = AuthenticationToken.create();
...
478 saveEscrowData(result, userId);
479 return result;
480 }

protected static AuthenticationToken create() {
204 AuthenticationToken result = new AuthenticationToken(SYNTHETIC_PASSWORD_VERSION_V3);
205 result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
206 secureRandom(SYNTHETIC_PASSWORD_LENGTH));
207 return result;
208 }

还有一个地方表明这个是伴随用户密码解锁过程解出来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
979 byte[] applicationId, long sid, int userId) {
980 byte[] blob = loadState(SP_BLOB_NAME, handle, userId);

94 if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
995 secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
996 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
997 } else {
998 secret = decryptSPBlob(getHandleName(handle),
999 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
1000 }
// 来自secret,secret的解密又有keymaster gatekeeper的参与
if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
1007 if (!loadEscrowData(result, userId)) {
1008 Log.e(TAG, "User is not escrowable: " + userId);
1009 return null;
1010 }
1011 result.recreate(secret);
1012 } else {
1013 result.syntheticPassword = new String(secret);
1014 }

这个流程可以追溯到解锁后,解锁密码的保存时触发的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-> saveChosenPasswordnAndFinish(final String pin)    #ChooseLockPassword.java - MiuiSystemUI
\ - saveLockPassword(pin, mUserPassword, mRequestedQuality, mUserIdToSetPassword) # LockPatternUtils.java
\ - setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
requestedQuality, userHandle, allowUntrustedChange); #LockSettingService.java
\ - setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId,
allowUntrustedChange, /* isLockTiedToParent= */ false); #LockSettingService.java
\ - spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
requestedQuality, userId, allowUntrustedChange, isLockTiedToParent); #LockSettingService.java
\ - AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
getGateKeeperService(), handle, savedCredential, userId, null);
| - setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality, userId);
\ - unlockUserKey(userId, null, auth.deriveDiskEncryptionKey());
| - setUserKeyProtection(userId, credential, convertResponse(gkResponse));
\ - addUserKeyAuth(userId, token, secretFromCredential(credential)); #LockSettingService.java

上述过程只是解锁的一个流程,在用户密码校验的地方,有一处值得注意,即使用spBasedSetLockCredentialInternalLocked进行核对时,是有条件的
注意这个函数isSyntheticPasswordBasedCredentialLocked, 只有这个函数为true时,才会对secret加这一层封装。从之前的流程分析下来,这个封装的中间文件存在了
/data/system_de/0/spblob

1
2
3
4
5
6
7
8
9
     private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
...
2517 long handle = getSyntheticPasswordHandleLocked(userId);
2518 // This is a global setting
2519 long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY,
2520 SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT, UserHandle.USER_SYSTEM);
2521 return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE;
2522 }

初步流程分析下来,用户密码和ce区文件解锁密码之间的导出关系比较复杂,且需要多个中间参数和keymater的参与

上面的流程分析有点问题,unlockUserKey应该时UserController下发的
关注下面的函数:

1
2
3
4
     private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
1907 byte[] credential, boolean hasChallenge, long challenge,
1908 ICheckCredentialProgressCallback progressCallback) throws RemoteException {

这里如果isSyntheticPasswordBasedCredentialLocked返回false,再对整个解锁流程做一下梳理,密码输入后,会触发check的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+- onPatternDetected(List<Cell> pattern) #图案绘完 ConfirmLockPattern.java
\ +- startVerifyPattern(pattern)
\ - long challenge = getActivity().getIntent().getLongExtra(MiuiChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
| +- LockPatternChecker.verifyPattern(mLockPatternUtils, pattern, challenge, localUserId, mContext, onVerifyCallback) #challenge未知,由外部传入
\ +- LockPatternUtils.verifyPattern(patternCopy, challenge, userId) # LockPatternChecker.java
\ - verifyCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, challenge, userId); # LockPatternUtils.java
\ - getLockSettings().verifyCredential(credential, type, challenge, userId);
\ +- doVerifyCredential(credential, type, true, challenge, userId, null) # LockSettingService.java
\ +- response = spBasedDoVerifyCredential(credential, credentialType, hasChallenge, challenge, userId, progressCallback);
\ - spBasedDoVerifyCredential<credential, credentialType, hasChallenge, challenge, userId, progressCallback>
\ - long handle = getSyntheticPasswordHandleLocked(userId);
| - mSpManager.unwrapPasswordBasedSyntheticPassword( getGateKeeperService(), handle, userCredential, userId, progressCallback);
| +- response = verifyCredential(userId, storedHash, credentialToVerify, hasChallenge, challenge, progressCallback);
\ - CredentialHash storedHash = mStorage.readCredentialHash(userId) #从保存的密钥中间文件中构造hash
| - gateKeeperResponse = getGateKeeperService().verifyChallenge(userId, challenge, storedHash.hash, credential); #challenge由外部传入,storedHash读取/data/system/gatekeeper.pattern.key|gatekeeper.password.key,通过verifyChallenge返回了一个token
| - VerifyCredentialResponse response = convertResponse(gateKeeperResponse); #这里仍然有gatekeeper参与
| - unlockKeystore(credential, userId);
| - unlockUser(userId, response.getPayload(), secretFromCredential(credential)); #未知参数变成了token
\ - mActivityManager.unlockUser(userId, token, secret, listener);
\ - mUserController.unlockUser(userId, token, secret, listener) # ActivityManagerService.java
\ - unlockUserCleared(userId, token, secret, listener) #UserController.java
\ - storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
\- mVold.unlockUserKey(userId, serialNumber, encodeBytes(token), encodeBytes(secret)) #StorageManagrService

从上面的流程看,对于isSyntheticPasswordBasedCredentialLocked返回false的情况,需要关注的点是外部传入的chanllenge, 以及用户安全密码的中间文件storeHash和用户输入的安全密码,unlockKeyStore GateKeeper都需要能被调用到 system/security/keystore system/core/gatekeeperd

1.2. 小结

从上述流程分析来看,fde和fbe用户密码与直接解锁密码的逻辑并不相同:

  • fde的解锁方式上看,开机时CryptKeeper接管了keyguard的操作,用户密码与解锁fde磁盘加密主密钥直接相关,因此导出关系很单一。
  • 而fbe的解锁方式,开机后直接走的keyguard,keyguard的模块逻辑比较复杂,而且用户密码经过了多层加密,最终的用户密码到ce区的文件解锁密码关系要复杂的多。中间经过了多层中转,需要逐层分析。 需要通过设备调试,发现其中的中转关系

1.3. spblob方式流程跟进

从上面isSyntheticPasswordBasedCredentialLocked返回true的场景上,q的机型正是用的这种加密方式

1
2
3
4
774         long handle = getSyntheticPasswordHandleLocked(userId);
2775 AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
2776 getGateKeeperService(), handle, savedCredential, userId, null);

先看 getSyntheticPasswordHandleLocked函数

1
2
3
4
     private long getSyntheticPasswordHandleLocked(int userId) {
2566 return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY,
2567 SyntheticPasswordManager.DEFAULT_HANDLE, userId);
2568 }

SYNTHETIC_PASSWORD_HANDLE_KEY来自于initializeSyntheticPasswordLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

2533 protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
2534 byte[] credential, int credentialType, int requestedQuality,
2535 int userId) throws RemoteException {
2536 Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
2537 final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
2538 getGateKeeperService(), credentialHash, credential, userId);
2539 onAuthTokenKnownForUser(userId, auth);
// 创建password相关的handle和spblob中间文件
2544 long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
2545 credential, credentialType, auth, requestedQuality, userId);
2546 if (credential != null) {
2547 if (credentialHash == null) {
2548 // Since when initializing SP, we didn't provide an existing password handle
2549 // for it to migrate SID, we need to create a new SID for the user.
2550 mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
2551 }
2552 mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
2553 setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
2554 setKeystorePassword(auth.deriveKeyStorePassword(), userId);
2555 }
2560 fixateNewestUserKeyAuth(userId);
2561 setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId);
2562 return auth;
2563 }

public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
627 byte[] credential, int credentialType, AuthenticationToken authToken,
628 int requestedQuality, int userId)
29 throws RemoteException {
//可以看到handle是一个随机值,最终setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId); 到数据库中,再次取得时候,直接从数据库中getLong获取
long handle = generateHandle();
636 PasswordData pwd = PasswordData.create(credentialType);
637 byte[] pwdToken = computePasswordToken(credential, pwd);
...
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
676 createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
677 applicationId, sid, userId);
}

而unwrapPasswordBasedSyntheticPassword函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//参数为上面得handle 用户凭据  userId
AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
2776 getGateKeeperService(), handle, savedCredential, userId, null);

public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
861 long handle, byte[] credential, int userId,
862 ICheckCredentialProgressCallback progressCallback) throws RemoteException {
866 AuthenticationResult result = new AuthenticationResult();
//从中间文件解出 passworddata
867 PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
868 result.credentialType = pwd.passwordType;
869 byte[] pwdToken = computePasswordToken(credential, pwd);
// 这中间走的分支都需要debug才能知道
918 sid = sidFromPasswordHandle(pwd.passwordHandle);
919 applicationId = transformUnderSecdiscardable(pwdToken,
920 loadSecdiscardable(handle, userId));

。。。
// 需要参数 applicationId, sid
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
928 applicationId, sid, userId);
31 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);

// 最终要得到 authToken,进行验证得到的结果为gkResponse,然后再通过Spmanager的verifyChallenge验证challenge, 都为正确的,才说明密码是正确的。
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
2634 // perform verifyChallenge with synthetic password which generates the real GK auth
2635 // token and response for the current user
2636 response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
2637 challenge, userId);
2638 if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
2639 // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
2640 // match the recorded GK password handle.
2641 Slog.wtf(TAG, "verifyChallenge with SP failed.");
2642 return VerifyCredentialResponse.ERROR;
2643 }
2644 }

// 而文件密码与/data/system_de/0/spblob/下存储的文件,applicationId, 上面提到的handle有关
private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
979 byte[] applicationId, long sid, int userId) {
980 byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
981 if (blob == null) {
982 return null;
983 }
984 final byte version = blob[0];
985 if (version != SYNTHETIC_PASSWORD_VERSION_V3
986 && version != SYNTHETIC_PASSWORD_VERSION_V2
987 && version != SYNTHETIC_PASSWORD_VERSION_V1) {
988 throw new RuntimeException("Unknown blob version");
989 }
990 if (blob[1] != type) {
991 throw new RuntimeException("Invalid blob type");
992 }
993 final byte[] secret;
994 if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
// 解用户ce区文件的密码
995 secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
996 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
997 } else {
998 secret = decryptSPBlob(getHandleName(handle),
999 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
1000 }
1005 AuthenticationToken result = new AuthenticationToken(version);
// 这个地方也需要debug,应该是用的 SYNTHETIC_PASSWORD_TOKEN_BASED 方式

1006 if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
1007 if (!loadEscrowData(result, userId)) {
1008 Log.e(TAG, "User is not escrowable: " + userId);
1009 return null;
1010 }
1011 result.recreate(secret);
1012 } else {
1013 result.syntheticPassword = new String(secret);
1014 }
// spblob格式升级
1015 if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
1016 Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
1017 createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
1018 }
1019 return result;
1020 }

上述过程,很多地方需要通过debug才能确定,需要进一步调研。查看哪些是可以拆解出来的。可以通过native程序解出来。

在keyguard解锁后校验密码的流程走的spblob的方式,在gatekeeper和keymaster的基础上对密码又做了一层封装。将spblob中间文件删除后,重启手机,用户ce区文件不能解密,重建spblob后也无法解密。用户ce区解锁也是每次开机加载机主用户时执行的,并不是每次解锁都会进行unlock。用户密码更新后,ce区密码也会更新,但该密码只是用来加密文件主密钥的密码,文件主密钥并没变,所以用户密码改变不需要重新解锁用户ce区,只需要更新中间文件spblob和/data/misc/vold/user_keys/0/current下的中间文件即可。
需要进一步调试跟踪各中间参数是怎样生成的, 最终效果是能模拟用户密码是否输入正确及推导出加密ce区主密钥的密钥

1.4. debug 调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
01-09 15:05:25.848  2575  2717 E LockPatternUtils: zlg:
60167 01-09 15:05:25.848 2575 2717 E LockPatternUtils: java.lang.RuntimeException: checkCredential log
60168 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at com.android.internal.widget.LockPatternUtils.checkCredential(LockPatternUtils.java:393)
60169 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at com.android.internal.widget.LockPatternUtils.checkPattern(LockPatternUtils.java:447)
60170 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at com.android.keyguard.LockPatternChecker$1.checkPattern(LockPatternChecker.java:91)
60171 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at com.android.keyguard.LockPatternChecker$1.doInBackground(LockPatternChecker.java:49)
60172 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at com.android.keyguard.LockPatternChecker$1.doInBackground(LockPatternChecker.java:39)
60173 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at android.os.AsyncTask$3.call(AsyncTask.java:378)
60174 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at java.util.concurrent.FutureTask.run(FutureTask.java:266)
60175 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
60176 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
60177 01-09 15:05:25.848 2575 2717 E LockPatternUtils: |at java.lang.Thread.run(Thread.java:919)
60178 01-09 15:05:25.849 2115 2546 D LockSettingsService: spBasedDoVerifyCredential: user=0
60179 01-09 15:05:25.850 2115 2546 E LockSettingsService: zlg :
60180 01-09 15:05:25.850 2115 2546 E LockSettingsService: java.lang.RuntimeException: spBasedDoVerifyCredential log
60181 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at com.android.server.locksettings.LockSettingsService.spBasedDoVerifyCredential(LockSettingsService.java:2623)
60182 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at com.android.server.locksettings.LockSettingsService.doVerifyCredential(LockSettingsService.java:1875)
60183 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at com.android.server.locksettings.LockSettingsService.checkCredential(LockSettingsService.java:1840)
60184 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at com.android.internal.widget.ILockSettings$Stub.onTransact(ILockSettings.java:556)
60185 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at android.os.Binder.execTransactInternal(Binder.java:1021)
60186 01-09 15:05:25.850 2115 2546 E LockSettingsService: | at android.os.Binder.execTransact(Binder.java:994)

从上述流程看,是走的checkPattern
图案解锁方式, 最后到checkCredential处,没有challenge,即challenge为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
     public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
433 throws RequestThrottledException {
434 return checkPattern(pattern, userId, null /* progressCallback */);
435 }
436
437 /**
438 * Check to see if a pattern matches the saved pattern. If no pattern exists,
439 * always returns true.
440 * @param pattern The pattern to check.
441 * @return Whether the pattern matches the stored one.
442 */
443 public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
444 @Nullable CheckCredentialProgressCallback progressCallback)
445 throws RequestThrottledException {
446 throwIfCalledOnMainThread();
447 return checkCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, userId,
448 progressCallback);
449 }

1837 public VerifyCredentialResponse checkCredential(byte[] credential, int type, int userId,
1838 ICheckCredentialProgressCallback progressCallback) throws RemoteException {
1839 checkPasswordReadPermission(userId);
// 没有challenge
1840 VerifyCredentialResponse response = doVerifyCredential(credential, type,
1841 false, 0, userId, progressCallback);
1842 if ((response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) &&
1843 (userId == UserHandle.USER_OWNER)) {
1844 //TODO(b/127810705): Update to credentials to use byte[]
1845 String credentialString = credential == null ? null : new String(credential);
1846 retainPassword(credentialString);
1847 }
1848 return response;
1849 }

sid和applicationId是容易解出的, 根据上面debug的关键信息, 再跟一遍代码流程和参数的求解

1
2
3
4
5
6
7
8
9
10
11
12
/data/system_de/0/spblob/4497efca1dd620f6.secdis
applicationId = transformUnderSecdiscardable(pwdToken,
927 loadSecdiscardable(handle, userId));
private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
// pwdToken 和 secdis文件内容联合做sha1-512
1109 byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
1110 PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
1111 byte[] result = new byte[data.length + secdiscardable.length];
1112 System.arraycopy(data, 0, result, 0, data.length);
1113 System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
1114 return result;
1115 }
1
2
3
4
5
6
7
8
nativeSidFromPasswordHandle->sidFromPasswordHandle(pwd.passwordHandle);
2static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) {
33
34 jbyte* data = (jbyte*)env->GetPrimitiveArrayCritical(handleArray, NULL);
37 const gatekeeper::password_handle_t *handle =
38 reinterpret_cast<const gatekeeper::password_handle_t *>(data);
39 jlong sid = handle->user_id;
45}
1
2
3
4
5
LockSettingsService: zlg: credential: [B@3ada1b6 hasChallenge: false challenge: 0 handle: 4942682767425413366
SyntheticPasswordManager: zlg weaverSlot is INVALID_WEAVER_SLOT
SyntheticPasswordManager: zlg: unwrapSyntheticPasswordBlob version not V1
SyntheticPasswordManager: zlg: not unwrapSyntheticPasswordBlob type SYNTHETIC_PASSWORD_TOKEN_BASED
LockSettingsService: zlg Unlocking user 0 with secret only, length 32 secret: [B@8fb4f53
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    private String getHandleName(long handle) {
1182 return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
1183 }
public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
// handle信息其实就是/data/system_de/0/spblob/<handle>.spblob
// secret就是文件ce区主密钥的密钥, blob 是 /data/system_de/0/spblob/<handle>.spblob 读取的内容, applicationId是上面提到的sha1-512的值
secret = decryptSPBlob(getHandleName(handle),
1008 Arrays.copyOfRange(blob, 2, blob.length), applicationId);

public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
139 try {
// 调用 "android.security.keystore"的接口,这个地方注意KeyStore是java.Security.keyStore,是Java的公共接口
源码在libcore/ojluni/src/main/java/java/security/KeyStore.java

140 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
141 keyStore.load(null);
142
143 SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
144 byte[] intermediate = decrypt(decryptionKey, blob);
145 return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
146 }

1055 public final Key getKey(String alias, char[] password)
1056 throws KeyStoreException, NoSuchAlgorithmException,
1057 UnrecoverableKeyException
1058 {
1062 return keyStoreSpi.engineGetKey(alias, password);
1063 }
这个keyStore的调用有点深,需要进一步查看, 最终进入了AndroidKeyStoreSpi.java内部

KeyStore.getInstance("AndroidKeyStore")返回的的是AndroidKeyStoreSpi的实例,调用getKey方法,最终调到了AndroidKeyStoreSpiengineGetKey

ciper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+- secret = decryptSPBlob<getHandleName(handle), Arrays.copyOfRange(blob, 2, blob.length), applicationId>  #SyntheticPasswordManager.java
# handle blob applicationId 都可以解出
\ +- decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) # SyntheticPasswordCrypto.java
\ - KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); #调用java se的公共接口,最终访问到了AndroidKeyStoreSpi
| +- keyStore.load(null); # AndroidkeyStoreSpi.engineLoad(param);
\ - android.security.KeyStore.getInstance # 调用到了 native keyStore "android.security.keystore" 实例,构造AndroidKeyStoreSpi的mKeyStore 对象, 该对象持有remote 端, native的 keyStore service
| +- SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
\ +- AndroidkeyStoreSpi.engineGetKey(alias, password) # passwd 为null, alias为 synthetic_password_<handle>值 getHandleName方法
\ - String userKeyAlias = Credentials.USER_PRIVATE_KEY + alias; # USERKEY_synthetic_password_<handle>
| +- key = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, userKeyAlias, mUid);
\ - KeyCharacteristics keyCharacteristics = getKeyCharacteristics(keyStore, userKeyAlias, uid); # keyStore为上面实例的mKeyStore实例
| +- keyStore.getKeyCharacteristics(alias, null, null, uid, keyCharacteristics) # clientId为空 appid为空 android KeyStore.java
| \ - mBinder.getKeyCharacteristics(promise, alias, clientId, appId, uid); #key_store_service.cpp 这个clientId appId 是构造了两个KeymasterBlob[new byte[0]] 出来,native层也有相应的类, KeymasterBlob.h
| +- return loadAndroidKeyStoreSecretKeyFromKeystore(userKeyAlias, uid, keyCharacteristics)
\ - Integer keymasterAlgorithm = keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_ALGORITHM); #获得加密算法
| | - List<Integer> keymasterDigests = keyCharacteristics.getEnums(KeymasterDefs.KM_TAG_DIGEST);
| - keyAlgorithmString = fromKeymasterSecretKeyAlgorithm(keymasterAlgorithm, keymasterDigest); # 解出来为AES
| +- return new AndroidKeyStoreSecretKey(secretKeyAlias, uid, keyAlgorithmString);
| \ - AndroidKeyStoreKey(String alias, int uid, String algorithm) # mAlias mUid mAlgorithm # AndroidKeyStoreKey 构造函数
| +- byte[] intermediate = decrypt(decryptionKey, blob); # SyntheticPasswordCrypto.java 获得key后进行解密 blob为spblob读出的数据
\ - byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE); #初始化向量
| - byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length); # 密文
| - Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); # AES/GCM/NoPadding
\ - cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv)); #解密数据 128 GCMParameterSpec怎么构造? key为SecurityKey类型, C++怎么构造
| - return cipher.doFinal(ciphertext); # ciphertext是密文,解密出明文
| +- return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate); # intermediate为上面Cipher解出的明文
\ - byte[] keyHash = personalisedHash(personalisation, keyBytes); # APPLICATION_ID_PERSONALIZATION 和 APPLICATION_ID_PERSONALIZATION
| - SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH), AES); # javax.crypto.spec.SecretKeySpec
| - decrypt(key, ciphertext) #此处ciphertext为上面的解出的intermediate, 重复上面的流程

从keyStore的模型上看,涉及的模块较多,如果多机型是同样的解密算法的话,解密流程还是可以还原的。 但从实现上看,预估不同机型的不同平台上的解密插件是不同的,如果做定制的话,涉及的解析debug 转换java代码很多。 当前流程涉及到了很多java的sdk的类,这些类怎么转换成native的代码也是一个大问题。 而解密这块,目前涉及到AES/GCM/NoPadding的解密,native层可以使用openssl的库来完成。 ^a300bf

上述密码解出后,还有最后一步,即验证用户密码是否正确。
这个主要涉及到gatekeeper的校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
937 applicationId, sid, userId);
938
939 // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
940 result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);

43 public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
1044 @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
1045 byte[] spHandle = loadSyntheticPasswordHandle(userId);
1051 VerifyCredentialResponse result;
> 1052 GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
1053 spHandle, auth.deriveGkPassword());
1054 int responseCode = response.getResponseCode();
1055 if (responseCode == GateKeeperResponse.RESPONSE_OK) {
1056 result = new VerifyCredentialResponse(response.getPayload());
1057 if (response.getShouldReEnroll()) {
1058 response = gatekeeper.enroll(userId, spHandle,
1059 spHandle, auth.deriveGkPassword());
1060 if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
1061 spHandle = response.getPayload();
1062 saveSyntheticPasswordHandle(spHandle, userId);
1063 // Call self again to re-verify with updated handle
> 1064 return verifyChallenge(gatekeeper, auth, challenge, userId);
1065 }
1069 }
1070 } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
1071 result = new VerifyCredentialResponse(response.getTimeout());
1072 } else {
1073 result = VerifyCredentialResponse.ERROR;
1074 }
1075 return result;
1076 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
+++++++++++-- verifyChallenge --+++++++++
+- verifyChallenge(gatekeeper, result.authToken, 0L, userId); #SyntheticPasswordManager.java
\ - byte[] spHandle = loadSyntheticPasswordHandle(userId); # /data/system_de/0/spblob/*.handle
| - response = gatekeeper.verifyChallenge(userId, challenge, spHandle, auth.deriveGkPassword()); # chanllenge 为 0
\ +- auth.deriveGkPassword()
\ - derivePassword(PERSONALIZATION_SP_GK_AUTH)
\ - new SP800Derive(syntheticPassword.getBytes().withContext(PERSONALIZATION_SP_GK_AUTH, PERSONALISATION_CONTEXT);
\ - Mac m = Mac.getInstance("HmacSHA256"); m.init(new SecretKeySpec(syntheticPassword, m.getAlgorithm())); # javax.crypto.Mac
\ - spi.engineInit(key, params); #javax/crypto/Mac.java
| \ - init(key, params) #AndroidKeyStoreHmacSpi.java params为空
| - ensureKeystoreOperationInitialized()
\ - mKeyStore.begin(mKey.getAlias(), KeymasterDefs.KM_PURPOSE_SIGN, true,keymasterArgs, ... )
\ - mBinder.begin(promise, getToken(), alias, purpose, pruneable, args, entropy, uid) # /system/core/keystore
| - mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, mOperationToken)); #后面update会用到,保存keystore和oprationtoken
| - withContext(PERSONALIZATION_SP_GK_AUTH, PERSONALISATION_CONTEXT); #SP800Derive
\ - m.update(label);
| \ - engineUpdate(new byte[] {input}, 0, 1);
\ - mChunkedStreamer.update(input, offset, len)
\ - mKeyStoreStream.update(chunk)
\ - mKeyStore.update(mOperationToken, null, input)
\ - mBinder.update(promise, token, arguments, input)
| - m.doFinal() #SP800Derive.java
\ - spi.engineDoFinal()
\ - engineDoFinal()
\ - return result = mChunkedStreamer.doFinal(null, 0, 0, null, null);
\ - output = update(input, inputOffset, inputLength)
\ - mKeyStoreStream.update(chunk)
\ - mKeyStore.update(mOperationToken, null, input)
\ - mBinder.update(promise, token, arguments, input)
| - mKeyStoreStream.finish(signature, additionalEntropy)
\ - mKeyStore.finish(mOperationToken, null, signature, additionalEntropy)
\ - mBinder.finish(promise, token, arguments, signature, entropy);
| - spi.engineReset()
\ - resetWhilePreservingInitState()
| - verifyChallenge(uid, challenge, (uint8_t *) currentPasswordHandle,
currentPasswordHandleSize, (uint8_t *) currentPassword, currentPasswordSize,
&out, &outSize, &request_reenroll); # system/core/gatekeeperd/IGateKeeperService.cpp
\ - verifyChallenge(uint32_t uid, uint64_t challenge,
const uint8_t *enrolled_password_handle, uint32_t enrolled_password_handle_length,
const uint8_t *provided_password, uint32_t provided_password_length,
uint8_t **auth_token, uint32_t *auth_token_length, bool *request_reenroll) #gatekeeperd.cpp 返回结果为auth_token, 如执行成功, reply->writeInt32(GATEKEEPER_RESPONSE_OK); reply->writeInt32(request_reenroll ? 1 : 0);
| - result = new VerifyCredentialResponse(response.getPayload()); # 执行成功,校验成功满足 GATEKEEPER_RESPONSE_OK 条件
\ - mResponseCode = RESPONSE_OK; # VerifyCredentialResponse payload构造函数
| - response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK # 校验成功, 解锁成功

经过debug, Mac.getInstance(“HmacSHA256”),并init后得到的并不是AndroidKeyStoreHmacSpi的实例,而是OpenSSLMac,代码位于/external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/

1
01-09 17:41:15.323  2608  2833 E zlg:    : getMac: spi com.android.org.conscrypt.OpenSSLMac.HmacSHA256 getProvider: com.android.org.conscrypt.OpenSSLProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
+++++++++++-- verifyChallenge --+++++++++
+- verifyChallenge(gatekeeper, result.authToken, 0L, userId); #SyntheticPasswordManager.java
\ - byte[] spHandle = loadSyntheticPasswordHandle(userId); # /data/system_de/0/spblob/*.handle
| - response = gatekeeper.verifyChallenge(userId, challenge, spHandle, auth.deriveGkPassword()); # chanllenge 为 0
\ +- auth.deriveGkPassword()
\ - derivePassword(PERSONALIZATION_SP_GK_AUTH)
\ - new SP800Derive(syntheticPassword.getBytes().withContext(PERSONALIZATION_SP_GK_AUTH, PERSONALISATION_CONTEXT);
\ - Mac m = Mac.getInstance("HmacSHA256"); m.init(new SecretKeySpec(syntheticPassword, m.getAlgorithm())); # javax.crypto.Mac
\ - super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.SIZE_BYTES); #OpenSSLMac.java
\ - NativeCrypto.EVP_get_digestbyname("sha256") # libopenssl
| - spi.engineInit(key, params); #javax/crypto/Mac.java
\ - resetContext(); #OpenSSLMac.java
\ - new NativeRef.HMAC_CTX(NativeCrypto.HMAC_CTX_new()) #libopenssl
| - NativeCrypto.HMAC_Init_ex(ctxLocal, keyBytes, evp_md)
| - withContext(PERSONALIZATION_SP_GK_AUTH, PERSONALISATION_CONTEXT); #SP800Derive
\ - m.update(label);
| \ - engineUpdate(singleByte, 0, 1); # OpenSSLMac.java
\ - NativeCrypto.HMAC_Update(ctxLocal, input, offset, len) #OpenSSLMac.java
| - m.doFinal() #SP800Derive.java
\ - spi.engineDoFinal()
\ - engineDoFinal() #OpenSSLMac.java
\ - output = NativeCrypto.HMAC_Final(ctxLocal); # libopenssl
| - resetContext();
| - verifyChallenge(uid, challenge, (uint8_t *) currentPasswordHandle,
currentPasswordHandleSize, (uint8_t *) currentPassword, currentPasswordSize,
&out, &outSize, &request_reenroll); # system/core/gatekeeperd/IGateKeeperService.cpp
\ - verifyChallenge(uint32_t uid, uint64_t challenge,
const uint8_t *enrolled_password_handle, uint32_t enrolled_password_handle_length,
const uint8_t *provided_password, uint32_t provided_password_length,
uint8_t **auth_token, uint32_t *auth_token_length, bool *request_reenroll) #gatekeeperd.cpp 返回结果为auth_token, 如执行成功, reply->writeInt32(GATEKEEPER_RESPONSE_OK); reply->writeInt32(request_reenroll ? 1 : 0);
| - result = new VerifyCredentialResponse(response.getPayload()); # 执行成功,校验成功满足 GATEKEEPER_RESPONSE_OK 条件
\ - mResponseCode = RESPONSE_OK; # VerifyCredentialResponse payload构造函数
| - response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK # 校验成功, 解锁成功

从上述实现看, OpenSSLMac 的实现相对比较简单,都是直接调到native 的libopenssl库中,而AndroidKeyStoreHmacSpi的实现方式则复杂很多,本身上层代码经过了很复杂的处理,最后又通过keystore进行加解密。

1.4.1. Cipher 封装

测试下来,Cipher 的AES解密成功的条件是gatekeeper需要先

1
2
gatekeeper.verifyChallenge(fakeUid(userId), 0L,
898 pwd.passwordHandle, gkPwdToken);

这里有必要对Cipher AES的Android封装过程调研一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
byte[] intermediate = decrypt(decryptionKey, blob);
return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
}

private static byte[] decrypt(SecretKey key, byte[] blob)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
return cipher.doFinal(ciphertext);
}

前面的过程如keystore的getInstace 和 load函数大部分都调研完了,需要注意的是Cipher的这个地方还没看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
+ - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
\ - javax.crypto.Cipher.createCipher(trans, null)
\ - cipherSpiAndProvider = tryCombinations(null /*params*/, null, tokenizedTransformation);
\ - CipherSpiAndProvider sap = tryTransformWithProvider(null, tokenizedTransformation, transform.needToSet, service); #获得对应的Provider,这个应该最终指向了 android.security.keystore.AndroidKeyStoreBCWorkaroundProvider
\ - Cipher spi = sap.cipherSpi # 指向AndroidKeyStoreCipherSpiBase.java
| - cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv)); #SyntheticPasswordCrypto.java
\ - init(opmode, key, params, JceSecurity.RANDOM); #Cipher.java
\ - chooseProvider(InitType.ALGORITHM_PARAM_SPEC, DECRYPT_MODE, key, params, null, random);
\ - spiAndProviderUpdater.updateAndGetSpiAndProvider(initParams, spi, provider);
\ - tryTransformWithProvider(initParams, tokenizedTransformation, transform.needToSet, service);
\ - spi.engineSetMode(tokenizedTransformation[1]);
| - spi.engineSetPadding(tokenizedTransformation[2]);
| - spi.engineInit(initParams.opmode, initParams.key, initParams.spec, initParams.random);
\ - init(opmode, key, random);
| - initAlgorithmSpecificParameters(params);
| - ensureKeystoreOperationInitialized();
\ - byte[] additionalEntropy = EmptyArray.BYTE;
| - KeymasterArguments keymasterInputArgs = new KeymasterArguments();
| - purpose = KeymasterDefs.KM_PURPOSE_DECRYPT;
| - mKeyStore.begin(mKey.getAlias(), purpose, true, keymasterInputArgs, additionalEntropy, mKey.getUid()); #调进native的keystore中
| - cipher.doFinal(ciphertext) #SyntheticPasswordCrypto.java
\ - updateProviderIfNeeded();
\ - spiAndProviderUpdater.updateAndGetSpiAndProvider(null, spi, provider);
\ - return new CipherSpiAndProvider(sap.cipherSpi, sap.provider);
| - return spi.engineDoFinal(input, 0, input.length);
\ - ensureKeystoreOperationInitialized()
\ - byte[] additionalEntropy = EmptyArray.BYTE;
| - KeymasterArguments keymasterInputArgs = new KeymasterArguments();
| - purpose = KeymasterDefs.KM_PURPOSE_DECRYPT;
| - OperationResult opResult = mKeyStore.begin(mKey.getAlias(), purpose, true, keymasterInputArgs, additionalEntropy, mKey.getUid()); #调进native的keystore中
| - loadAlgorithmSpecificParametersFromBeginResult(opResult.outParams)
\ - byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
| - if (mIvRequired) mIv = returnedIv # mIvRequired应该是false,没看到赋值的地方,可以加log验证
| - mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); #mKeyStore为android KeyStore的instance,其对端即为native的keystore
\ - return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(keyStore, operationToken)); # 用到了keystore返回的参数token
|- mAdditionalAuthenticationDataStreamer = createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token);
\ - return new KeyStoreCryptoOperationChunkedStreamer(
new AdditionalAuthenticationDataStream(keyStore, operationToken));
| - flushAAD()
\ - output = mAdditionalAuthenticationDataStreamer.doFinal(EmptyArray.BYTE, 0 , 0 ,null, null);
\ - output = update(input, inputOffset, inputLength);
\ - mKeyStoreStream.update(chunk) #经过了复杂的流程,最终调到这里
\ - keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
| - mKeyStore.update(mOperationToken, keymasterArgs, null); #AdditionalAuthenticationDataStream # - 1.
\ - output = new OperationResult(result.resultCode, result.token, result.operationHandle, input.length, result.output, result.outParams);
| - output = ArrayUtils.concat(output, flush()); #flush又是一个复杂的函数
| - OperationResult opResult = mKeyStoreStream.finish(null, null);
\ - return new OperationResult(KeyStore.NO_ERROR, mOperationToken, 0 ,0 , EmptyArray.BYTE, new KeymasterArguments());
| - return ArrayUtils.concat(output, opResult.output);
| - output = mMainDataStreamer.doFinal(input, offset, len, null, EmptyArray.BYTE);
\ - output = update(input, inputOffset, inputLength);
\ - mKeyStoreStream.update(chunk) #经过了复杂的流程,最终调到这里
\ - return mKeyStore.update(mOperationToken, null, input); # - 2.
| - output = ArrayUtils.concat(output, flush()); #flush又是一个复杂的函数
| - OperationResult opResult = mKeyStoreStream.finish(null, EmptyArray.BYTE);
\ - opResult = mKeyStore.finish(mOperationToken, null, null, EmptyArray.BYTE); # - 3.
| - return ArrayUtils.concat(output, opResult.output);
| - return output

从上面的流程看,经历的过程很复杂,而经过native keystore的操作有

  1. mKeyStore.update(mOperationToken, keymasterArgs, null);
  2. mKeyStore.update(mOperationToken, null, input);
  3. mKeyStore.finish(mOperationToken, null, null, EmptyArray.BYTE);
    最终finish 结束了访问, 获得了结果, 而再keystore update 和 finish前 Java层又经过了一系列的转换数据的操作。包括数据分片的处理

gatekeeperd 守护进程会向 Android 框架 API 授予访问 HAL 的权限,并且会参与向 Keystore 报告设备身份验证的活动。gatekeeperd 守护进程会在自己的进程中运行,与系统服务器隔离开来。

LockSettingsService 会通过 Binder 发出一个请求,该请求会到达 Android 操作系统中的 gatekeeperd 守护进程。gatekeeperd 守护进程会发出一个请求,该请求会到达此守护进程在 TEE 中的副本 (Gatekeeper):

gatekeeper

每次密码验证成功时生成的身份验证令牌 (AuthToken)
用户安全 ID (SID)

每当用户注册新密码时,如果未提供之前的密码,系统就会使用加密伪随机数生成器 (PRNG) 生成一个用户 SID。这称为“不可信”重新注册,在正常情况下,Android 框架不允许进行这种操作。如果用户提供了之前的有效密码,便会发生“可信”重新注册;在这种情况下,用户 SID 会迁移到新密码句柄,从而保留绑定到它的密钥。

注册密码时,用户 SID 会随密码句柄中的密码一起接受 HMAC 处理。
用户 SID 会写入到 verify 函数返回的 AuthToken 中,并且会同所有与身份验证绑定的 Keystore 密钥相关联(如需详细了解 AuthToken 格式和 Keystore,请参阅身份验证)。由于对 enroll 函数的不可信调用会更改用户 SID,因此此类调用会使绑定到相应密码的密钥无法再使用。攻击者在控制 Android 操作系统后可以更改设备密码,但在此过程中,他们需要破坏掉受 Root 保护的敏感密钥。

1.4.1.1. 身份验证

用户设置凭据并收到用户 SID 后,便可以开始进行身份验证,身份验证从用户提供 PIN 码、解锁图案、密码或指纹开始。所有 TEE 组件都共用一个密钥来验证对方的消息。

身份验证

对于 PIN 码、解锁图案或密码,LockSettingsService 会向 gatekeeperd 发出请求。守护进程将数据发至其副本,后者生成 AuthToken:对于 PIN 码/解锁图案/密码身份验证,gatekeeperd 将 PIN 码、解锁图案或密码哈希发送到 TEE 中的 Gatekeeper。如果 TEE 中的身份验证成功,TEE 中的 Gatekeeper 会将包含相应用户 SID(已使用 AuthToken HMAC 密钥签名)的 AuthToken 发送到它在 Android 操作系统中的副本。
守护进程收到经过签名的 AuthToken,并通过 Keystore 服务 Binder 接口的扩展程序将 AuthToken 传递给 Keystore 服务。(gatekeeperd 还会在设备被重新锁定以及设备密码发生变化时通知 Keystore 服务。)
Keystore 服务将 AuthToken 传递给 Keymaster,并使用与 Gatekeeper 和支持的生物识别 TEE 组件共用的密钥来验证这些 AuthToken。Keymaster 会将令牌中的时间戳视为最后一次身份验证的时间,并根据该时间戳做出密钥发布决定(以允许应用使用相应密钥)。

debug验证时,keystore解密的工作前需要gatekeeper先通过对用户密码的验证,验证正确后,gatekeeperd 守护进程会向 Android 框架 API 授予访问 HAL 的权限,并且会参与向 Keystore 报告设备身份验证的活动。keystore才可以使用,而在apk demo中碰到了android.security.KeyStoreException: Signature/MAC verification failed 的问题,在手机解锁后执行也报这样的错误,可能与gatekeeper的授权有关。因为gatekeeper的访问不在public api中,需要进一步在编译系统中构建应用验证。如果没有验证用户安全密码,则会报android.security.keystore.KeyPermanentlyInvalidatedException: Key permanently invalidated的异常。