1、gatekeeper是什么?
在android中,gatekeeper是密码锁或图案锁的一种服务. 主要支持的两个方法是:enroll(密码的录入)、verify(密码的验证).
调用流程:locksetting APP ----> IGatekeeperserivce ----> Hardware Gatekeeper HAL ----> Vendor Gatekeeper HAL ----> Gatekeeper TA
enroll录入密码时,locksetting APP将密码数据传送到TEE的gatekeeper TA, 在TA中先计算signature,计算方法为:HMAC(密码数据,hmackey)=signature,
然后再去填充password_handle结构体,最后再将password_handle返回给android,locksetting
APP中再将password_handle保存到文件中.
verify验证密码时,locksetting
APP将保存在文件中的password_handle和输入的密码数据一同传进TEE的gatekeeper
TA,在TA中先计算signature,计算方法为:HMAC(密码数据,hmackey)=signature.
然后再拿这个signature和password_handle中的signature相比较,如果一样,则返回authToken给android.
在android的IGatekeeperserivce中,将authToken发送给keystore存储内存中.
同时返回给locksetting APP结果failed或ok
2、gatekeeper的软件框图
(代码结构图)
3、enroll和verify的调用流程
4、重要的结构体
(1)、password_handle
在enroll的时候gatekeeper TA负责填充password_handle结构体,返回给android的locksetting保存到文件中.
(system/gatekeeper/include/gatekeeper/password_handle.h)
struct __attribute__ ((__packed__)) password_handle_t {
uint8_t version;
secure_id_t user_id;
uint64_t flags;
salt_t salt;
uint8_t signature[32];
bool hardware_backed;
};
gatekeeper TA是怎样填充password_handle结构体的?
enrolled_password_handle->version = handle_version
enrolled_password_handle->salt = salt
enrolled_password_handle->user_id = user_id
enrolled_password_handle->flags = flags
enrolled_password_handle->hardware_backed = gkbase->IsHardwareBacked()
enrolled_password_handle->signature
- handle_version:在tee中写死的2
- salt:每次enroll时,在tee中GetRandom随机生成
- user_id:第一次enroll时,在tee中GetRandom随机生成.其实就是SID
- flags : throttle flag,写死1. 就是是否开启,失败密码次数计数功能.
- hardware_backed:为1
- signature: 对密码进行hmac hash运算得到. 即 HMAC(data,key) = signature
(2)、authToken
在verify通过时候gatekeeper TA填充authToken,返回给android的IGatekeeperService程序,再发送给keystore保存到内存中. (authToken的详细介绍可以参考这篇文章)
(hardware/libhardware/include/hardware/hw_auth_token.h)
typedef struct __attribute__((__packed__)) {
uint8_t version; // Current version is 0
uint64_t challenge;
uint64_t user_id; // secure user ID, not Android user ID
uint64_t authenticator_id; // secure authenticator ID
uint32_t authenticator_type; // hw_authenticator_type_t, in network order
uint64_t timestamp; // in network order
uint8_t hmac[32];
} hw_auth_token_t;
typedef enum {
HW_AUTH_NONE = 0,
HW_AUTH_PASSWORD = 1 << 0,
HW_AUTH_FINGERPRINT = 1 << 1,
// Additional entries should be powers of 2.
HW_AUTH_ANY = UINT32_MAX,
} hw_authenticator_type_t;
- 质询 : challenge
- 用户SID :user_id
- 身份验证程序 ID (ASID) : authenticator_id, 身份验证程序类型 : authenticator_type,00-gatekeeper,01-指纹
5、技术的细节
(1)、failure_record :记录失败信息
verify失败后,会将failure_counter和当前的timestamp同时记录下来,secure_user_id用于索引.
struct __attribute__((packed)) failure_record_t {
uint64_t secure_user_id;
uint64_t last_checked_timestamp;
uint32_t failure_counter;
};
(2)、throttle : failed_counter和retry_time的规则
在verify的失败的时候,需要将失败的次数记录下来,通常的做法是将这个failed_count保存到RPMB中。
在verify成功的时候,再去清除这个数据.
另外,在verify失败的时候,还会根据failed_count值来计算retry_timeout值,retry_timeout最终返回给android侧,对应的也就是输错一次密码后,还需再等待多数秒才能进行下一次的输入. retry_timeout的计算规则是:
它的计算方式:
(a)、failure_counter为0-4次时,retry_timeout = 0
(b)、failure_counter为5次时,retry_timeout = 30s
©、failure_counter为6-9次时,retry_timeout = 0
(d)、failure_counter为10-29次时,retry_timeout = 30
(e)、failure_counter大于等于30次时,retry_timeout变得更大了,有个指数增长的过程
(根据failed_counter计算timeout的源码)
uint32_t GateKeeper::ComputeRetryTimeout(const failure_record_t *record) {
static const int failure_timeout_ms = 30000;
if (record->failure_counter == 0) return 0;
if (record->failure_counter > 0 && record->failure_counter <= 10) {
if (record->failure_counter % 5 == 0) {
return failure_timeout_ms;
} else {
return 0;
}
} else if (record->failure_counter < 30) {
return failure_timeout_ms;
} else if (record->failure_counter < 140) {
return failure_timeout_ms << ((record->failure_counter - 30) / 10);
}
return DAY_IN_MS;
}
由于Gatekeeper TA闭源,我们这里就贴下google的软实现(写得不是很好哦)
这种做法意味着,每次verify成功,都会对failed\_count操作两次,如果failed\_count是保存在RPMB或某个固定分区中,那么频繁的verify显然容易对这块分区或RPMB造成损坏.
在项目设计中,我们还是建议尽量减少RPMB的读写次数
(3)、timestamp
timestamp是从开机到现在的时间,单位为毫秒. 在TA中是uint64_t timestamp = GetMillisecondsSinceBoot()获取的.
timestamp的功能有两个:
a、在gatekeeper TA中的简单合法校验
在verify中,比对两个signature之前,会先检查RPMB中存储的的timestamp、根据RPMB中的failure_counter计算而来的retry_time. 然后进行简单的逻辑判断
if (timeout > 0) {
// we have a pending timeout
if (timestamp < last_checked + timeout && timestamp > last_checked) {
// attempt before timeout expired, return remaining time
response->SetRetryTimeout(timeout - (timestamp - last_checked));
return true;
} else if (timestamp <= last_checked) {
// device was rebooted or timer reset, don't count as new failure but
// reset timeout
record->last_checked_timestamp = timestamp;
if (!WriteFailureRecord(uid, record, secure)) {
response->error = ERROR_UNKNOWN;
return true;
}
response->SetRetryTimeout(timeout);
return true;
}
}
b、 在android中,会对timestamp进行检查
verify成功后,会将此时的timestamp填充到authToken结构体,返回给android。 android在使用该authToken时,会对timestamp进行检查.
6、关键函数的介绍
LockSetting
(1)、writeCredentialHash //将enroll_handle保存到文件
如果是password,保存到passwordFilename, patterFilename写入空
如果是patter,保存到patterFilename, passwordFilename写入空
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
public void writeCredentialHash(CredentialHash hash, int userId) {
byte[] patternHash = null;
byte[] passwordHash = null;
if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
passwordHash = hash.hash;
} else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
patternHash = hash.hash;
}
writeFile(getLockPasswordFilename(userId), passwordHash);
writeFile(getLockPatternFilename(userId), patternHash);
}
passwordFilename和patterFilename的文件名字分别是:“gatekeeper.password.key”、“gatekeeper.pattern.key”
frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
private static final String SYSTEM_DIRECTORY = "/system/";
private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
Vendor Gatekeeper Hal
(1)、enroll
(函数原型)
int (*enroll)(const struct gatekeeper_device *dev, uint32_t uid,
const uint8_t *current_password_handle, uint32_t current_password_handle_length,
const uint8_t *current_password, uint32_t current_password_length,
const uint8_t *desired_password, uint32_t desired_password_length,
uint8_t **enrolled_password_handle, uint32_t *enrolled_password_handle_length);
- current_password_handle : 输入参数,原来的包含signature的handle结构体,第一次录入密码时为空, 修改密码时使用
- current_password : 输入参数,原来的密码数据,第一次录入密码时为空, 修改密码时使用
- desired_password : 输入参数,录入密码数据(修改后的密码数据)
- enrolled_password_handle : 输出参数,
返回包含signature的handle结构体,交由android保存到文件中.
(2)、verify
(函数原型)
int (*verify)(const struct gatekeeper_device *dev, 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);
- enrolled_password_handle : 输入参数,
android中传来的从文件中读取的包含signature的handle结构体 - provided_password : 输入参数, 需要验证的密码数据
- auth_token : 输出参数 , 在gatekeeper TA
verify成功后会填充authToken结构体返回给android,如果verify失败,则authToken为NULL
作者:代码改变世界ctw
文章来源:CSDN
推荐阅读
更多物联网安全,PSA等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA技术交流群,请备注研究方向。