如何查找可用于存储新密钥对的空闲TPM句柄?
关于TPM空闲句柄判断与Go-TPM实践指南
1. 编程判断TPM句柄是否空闲
TPM句柄分临时句柄(transient,TPM自动分配,重启后失效)和持久句柄(persistent,用户指定,重启后保留)两类,判断逻辑不同:
- 临时句柄:无需手动判断,调用
TPM2_CreatePrimary或TPM2_Create时,TPM会自动分配空闲的临时句柄,无需用户干预。 - 持久句柄:用户需在
0x81000000 ~ 0x81FFFFFF的用户可用范围内选择,判断空闲的核心方法是调用TPM2_ReadPublic尝试读取该句柄的公钥信息:- 如果返回
TPM_RC_HANDLE错误,说明该句柄未绑定任何对象,处于空闲状态; - 如果成功返回公钥数据,说明句柄已被占用。
- 如果返回
另外,也可以通过TPM2_GetCapability查询TPM已注册的所有持久句柄,再从用户范围内筛选出未被占用的句柄,这种方式更高效,避免逐个尝试。
2. Go-TPM包的相关函数支持
Go-TPM(github.com/google/go-tpm/tpm2)提供了直接对应TPM命令的函数,可完成句柄检查与密钥持久化:
- 检查句柄空闲:使用
tpm2.ReadPublic函数,传入目标句柄,捕获TPM_RC_HANDLE错误来判断空闲:import ( "fmt" "io" "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil" ) func isHandleFree(rw io.ReadWriter, handle tpmutil.Handle) (bool, error) { _, _, err := tpm2.ReadPublic(rw, handle) if err != nil { if tpm2.RC(err) == tpm2.RCHandle { return true, nil } return false, fmt.Errorf("failed to read public: %v", err) } // 能读到公钥,说明句柄已被占用 return false, nil } - 获取已使用的持久句柄:用
tpm2.GetCapability查询TPM2_CAP_HANDLES类型中的TPM_HT_PERSISTENT类别,得到所有已注册的持久句柄:func getUsedPersistentHandles(rw io.ReadWriter) ([]tpmutil.Handle, error) { caps, moreData, err := tpm2.GetCapability(rw, tpm2.CapHandles, uint32(tpm2.HTPersistent), 0) if err != nil { return nil, err } if moreData { // 若数据未读完,需循环调用直到moreData为false,这里简化处理 return nil, fmt.Errorf("more persistent handles available, need to fetch more") } usedHandles := make([]tpmutil.Handle, 0) for _, cap := range caps { if h, ok := cap.(tpmutil.Handle); ok { usedHandles = append(usedHandles, h) } } return usedHandles, nil } - 持久化密钥到指定句柄:先用
CreatePrimary生成临时密钥句柄,再调用tpm2.EvictControl将其持久化到目标空闲句柄:func persistKey(rw io.ReadWriter, targetHandle tpmutil.Handle) error { // 先检查目标句柄是否空闲 free, err := isHandleFree(rw, targetHandle) if err != nil { return err } if !free { return fmt.Errorf("target handle %x is already in use", targetHandle) } // 生成临时主密钥(示例,实际可根据需求生成普通密钥) primaryHandle, _, err := tpm2.CreatePrimary(rw, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", nil) if err != nil { return fmt.Errorf("create primary failed: %v", err) } defer tpm2.FlushContext(rw, primaryHandle) // 持久化密钥到目标句柄 err = tpm2.EvictControl(rw, "", tpm2.HandleOwner, primaryHandle, targetHandle) if err != nil { return fmt.Errorf("evict control failed: %v", err) } return nil }
3. 选择TPM句柄的注意事项与最佳实践
- 优先使用临时句柄:如果密钥不需要跨TPM重启保留,直接用TPM自动分配的临时句柄,无需手动管理,避免持久句柄资源浪费。
- 严格限定持久句柄范围:仅使用
0x81000000 ~ 0x81FFFFFF的用户专属持久句柄,0x80000000 ~ 0x80FFFFFF是厂商预留范围,禁止使用。 - 避免硬编码句柄:不要直接写死
0x81010001这类句柄,而是通过GetCapability查询已使用句柄,动态筛选空闲值,避免冲突。 - 持久化前必须检查句柄:调用
EvictControl前,一定要用ReadPublic验证目标句柄空闲,否则会返回TPM_RC_HANDLE错误导致操作失败。 - 及时清理无用持久句柄:不再需要的持久密钥,调用
EvictControl传入相同的目标句柄即可删除,释放TPM资源。 - 注意TPM资源限制:TPM的持久句柄数量有限(通常几十到上百个),不要无限制创建持久密钥,避免耗尽资源。
内容的提问来源于stack exchange,提问作者Alireza Delkhahi
相关产品推荐
相关产品推荐

