|
| 1 | ++++ |
| 2 | +date = '2026-05-05T16:49:51+08:00' |
| 3 | +draft = false |
| 4 | +title = '简单了解和手动使用 TPM' |
| 5 | +tags = ['Linux', 'Arch Linux'] |
| 6 | ++++ |
| 7 | + |
| 8 | + |
| 9 | +最近疯狂迷恋 tpm,这个小玩意究竟是怎么实现基于设备的安全的? |
| 10 | + |
| 11 | +## tpm 的相关属性 |
| 12 | + |
| 13 | +第一步,我们先看下 tpm 有什么变量是可以被改变的: |
| 14 | + |
| 15 | +```bash |
| 16 | +sudo tpm2_getcap properties-variable |
| 17 | +``` |
| 18 | + |
| 19 | +### 第一组:永久属性组 (PT -> Property) |
| 20 | + |
| 21 | +```bash |
| 22 | +TPM2_PT_PERMANENT: |
| 23 | + ownerAuthSet: 0 # 所有者鉴权密码 |
| 24 | + endorsementAuthSet: 0 # 背书鉴权密码 |
| 25 | + lockoutAuthSet: 0 # 锁定鉴权密码 |
| 26 | + reserved1: 0 |
| 27 | + disableClear: 0 |
| 28 | + inLockout: 0 |
| 29 | + tpmGeneratedEPS: 1 |
| 30 | + reserved2: 0 |
| 31 | +``` |
| 32 | + |
| 33 | +### 第二组:启动时状态 |
| 34 | + |
| 35 | +```bash |
| 36 | +TPM2_PT_STARTUP_CLEAR: |
| 37 | + phEnable: 1 # 平台层级,也就是你可以在系统里面清除tpm所有信息 |
| 38 | + shEnable: 1 # 存储层级,其实跟owner是一样的 |
| 39 | + ehEnable: 1 # 背书层级 |
| 40 | + phEnableNV: 1 |
| 41 | + reserved1: 0 |
| 42 | + orderly: 1 |
| 43 | +``` |
| 44 | + |
| 45 | +### 第三组:句柄资源 (HR -> Handle Resource) |
| 46 | + |
| 47 | +```bash |
| 48 | +TPM2_PT_HR_NV_INDEX: 0x5 |
| 49 | +TPM2_PT_HR_LOADED: 0x0 # 当前已加载密钥 |
| 50 | +TPM2_PT_HR_LOADED_AVAIL: 0x3 |
| 51 | +TPM2_PT_HR_ACTIVE: 0x0 # 已激活的会话 |
| 52 | +TPM2_PT_HR_ACTIVE_AVAIL: 0x40 |
| 53 | +TPM2_PT_HR_TRANSIENT_AVAIL: 0x3 # 临时会话,从存储读入内存 |
| 54 | +TPM2_PT_HR_PERSISTENT: 0x0 # 可以永久存储的槽位 |
| 55 | +TPM2_PT_HR_PERSISTENT_AVAIL: 0x2 |
| 56 | +``` |
| 57 | + |
| 58 | +### 第四组:锁定相关配置 |
| 59 | + |
| 60 | +```bash |
| 61 | +TPM2_PT_LOCKOUT_COUNTER: 0x0 |
| 62 | +TPM2_PT_MAX_AUTH_FAIL: 0xC8 |
| 63 | +TPM2_PT_LOCKOUT_INTERVAL: 0x0 |
| 64 | +TPM2_PT_LOCKOUT_RECOVERY: 0x0 |
| 65 | +``` |
| 66 | + |
| 67 | +### 第五组:杂项 |
| 68 | + |
| 69 | +```bash |
| 70 | +TPM2_PT_NV_COUNTERS: 0x0 # 非易失标志位 |
| 71 | +TPM2_PT_NV_COUNTERS_AVAIL: 0x8 |
| 72 | +TPM2_PT_ALGORITHM_SET: 0xFFFFFFFF |
| 73 | +TPM2_PT_LOADED_CURVES: 0x3 |
| 74 | +TPM2_PT_AUDIT_COUNTER_0: 0x0 # 审计日志 |
| 75 | +TPM2_PT_AUDIT_COUNTER_1: 0x0 |
| 76 | +``` |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +### 不同层级 |
| 81 | + |
| 82 | + |
| 83 | +**Owner Hierarchy**: 你平时用来创建密钥、存储数据的权限。 |
| 84 | + |
| 85 | +**Endorsement Hierarchy**: 涉及设备隐私和证书的权限。 |
| 86 | + |
| 87 | +**Lockout Hierarchy**: “锁定控制”权限,用于控制锁定策略: |
| 88 | + |
| 89 | +- 允许多少次普通密码尝试后进入锁定模式 (TPM2_PT_MAX_AUTH_FAIL) |
| 90 | +- 被记录下连续失败后,过多久次数才能减一 (TPM2_PT_LOCKOUT_INTERVAL) |
| 91 | +- 进入锁定模式后,过多久才能解锁 (TPM2_PT_LOCKOUT_RECOVERY) |
| 92 | + |
| 93 | +> ⚠️注意:如果你是在验证以上三种层级的管理密码,比如用 tpm2_clear -C o -c -p <password>的时候,只有一次机会,失败立即进入锁定模式。 |
| 94 | +> |
| 95 | +> 你将被限制到 TPM2_PT_LOCKOUT_RECOVERY 的时间秒数之后才能解锁。 |
| 96 | +> 这两个时间设成 0,永远解不了,只能清除。 |
| 97 | +
|
| 98 | +**Platform Hierarchy**: 唯一不受 DA 限制的密码尝试。但是就算你拿到了权限,除了能够清除所有的密钥和密码,没有任何别的权限。 |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +### 初始化自己的 tpm 配置 |
| 103 | + |
| 104 | +**清除**:`tpm2_clear` |
| 105 | + |
| 106 | +有的平台可以直接用`tpm2_clear -c p`清掉,有的不行只能到 bios 里面清。 |
| 107 | + |
| 108 | +**修改锁定设置**:`tpm2_dictionarylockout` (也叫 DA,dictionary attack) |
| 109 | + |
| 110 | +```bash |
| 111 | +# 分别对应上面的“锁定层级”的三个变量,下例为windows标准设置 |
| 112 | +sudo tpm2_dictionarylockout -s -n 32 -t 7200 -l 86400 |
| 113 | +``` |
| 114 | + |
| 115 | +**修改密码**:`tpm2_changeauth` |
| 116 | + |
| 117 | +```bash |
| 118 | +sudo tpm2_changeauth -c lockout <new_password> |
| 119 | +``` |
| 120 | + |
| 121 | +这里有个彩蛋,windows 一开始看到新的 tpm,跟我们一样改完了,直接把钥匙扔了,惊喜吧? |
| 122 | + |
| 123 | +所以你只能重置,然后自己设个密码,好在 windows 不会改你的密码回去。 |
| 124 | + |
| 125 | +--- |
| 126 | +## 开始使用 |
| 127 | + |
| 128 | +创建主密钥,得到一个 tpm 的内存快照,这个主密钥,相比于密钥的说法,其实更是一个 token,因为是由 TPM 这一个生命周期的种子派生的,所以每次用同样的输入得到同样的输出,这里不是指的 ctx 的 hash 相同,而是每次读取的公钥相同。 |
| 129 | + |
| 130 | +如果还原了 tpm,签名会变,ctx 永远不再可用。 |
| 131 | + |
| 132 | +```bash |
| 133 | +# 创一个新的 |
| 134 | +❯ tpm2_createprimary -C o -c primary2.ctx |
| 135 | +name-alg: |
| 136 | + value: sha256 |
| 137 | + raw: 0xb |
| 138 | +attributes: |
| 139 | + value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt |
| 140 | + raw: 0x30072 |
| 141 | +type: |
| 142 | + value: rsa |
| 143 | + raw: 0x1 |
| 144 | +exponent: 65537 |
| 145 | +bits: 2048 |
| 146 | +scheme: |
| 147 | + value: null |
| 148 | + raw: 0x10 |
| 149 | +scheme-halg: |
| 150 | + value: (null) |
| 151 | + raw: 0x0 |
| 152 | +sym-alg: |
| 153 | + value: aes |
| 154 | + raw: 0x6 |
| 155 | +sym-mode: |
| 156 | + value: cfb |
| 157 | + raw: 0x43 |
| 158 | +sym-keybits: 128 |
| 159 | +rsa: c26b4cbadd50e487645f22d72f378953282d9a95036f09815fd91b2fd360f85517e730b19c1ba5668452b4ceca94a38aa6883ba6204f1ea4f72bd644190186fd1806f486e0e9de1ebe4fd64f618a0aac2d120e766bd0575f19b0ecce6ff55df47bacdffb8a431ac8d75cadff88ec62f149ae99ea210da4ba315b2d849b1e2b9196f7baac40ee34382f063e69af448b0c5812319b1efaa7cdffe50d3e4c396e427b270c95676b48168b2b90de555dd1cb46cbdeaed6c4f5c8466fa6a977af67a70efdf673a05b3fe7577bfb7d55991a0b7e94e917fc2533975c13c26231b3d2f6e990c395f963728f3dcaecd8f656f4a5a709fbd5c7edbda773ce960d0c9076c3 |
| 160 | + |
| 161 | +# 读一个之前创的 |
| 162 | +❯ tpm2_readpublic -c primary.ctx |
| 163 | +name: 000bf2c2daef8da6f674f7cdd47edb780e14baec9ed14a212904d79ca92c93e05c7c |
| 164 | +qualified name: 000b8a569c59019bf73a23befe7aeb9e54c74470737c0288f63d2481324388e1edfb |
| 165 | +name-alg: |
| 166 | + value: sha256 |
| 167 | + raw: 0xb |
| 168 | +attributes: |
| 169 | + value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt |
| 170 | + raw: 0x30072 |
| 171 | +type: |
| 172 | + value: rsa |
| 173 | + raw: 0x1 |
| 174 | +exponent: 65537 |
| 175 | +bits: 2048 |
| 176 | +scheme: |
| 177 | + value: null |
| 178 | + raw: 0x10 |
| 179 | +scheme-halg: |
| 180 | + value: (null) |
| 181 | + raw: 0x0 |
| 182 | +sym-alg: |
| 183 | + value: aes |
| 184 | + raw: 0x6 |
| 185 | +sym-mode: |
| 186 | + value: cfb |
| 187 | + raw: 0x43 |
| 188 | +sym-keybits: 128 |
| 189 | +rsa: c26b4cbadd50e487645f22d72f378953282d9a95036f09815fd91b2fd360f85517e730b19c1ba5668452b4ceca94a38aa6883ba6204f1ea4f72bd644190186fd1806f486e0e9de1ebe4fd64f618a0aac2d120e766bd0575f19b0ecce6ff55df47bacdffb8a431ac8d75cadff88ec62f149ae99ea210da4ba315b2d849b1e2b9196f7baac40ee34382f063e69af448b0c5812319b1efaa7cdffe50d3e4c396e427b270c95676b48168b2b90de555dd1cb46cbdeaed6c4f5c8466fa6a977af67a70efdf673a05b3fe7577bfb7d55991a0b7e94e917fc2533975c13c26231b3d2f6e990c395f963728f3dcaecd8f656f4a5a709fbd5c7edbda773ce960d0c9076c3 |
| 190 | + |
| 191 | +``` |
| 192 | + |
| 193 | +### 加密文字 |
| 194 | + |
| 195 | +在主密钥下面创建一个子密钥: |
| 196 | + |
| 197 | +```bash |
| 198 | +echo -n "hello" | tpm2_create -C primary.ctx \ |
| 199 | + -u key.pub -r key.priv \ |
| 200 | + -i- \ |
| 201 | + -p "123456" |
| 202 | +``` |
| 203 | + |
| 204 | +生成一个 tpm 的内存快照: |
| 205 | + |
| 206 | +```bash |
| 207 | +tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx |
| 208 | +``` |
| 209 | + |
| 210 | +把它持久化到 tpm 的对象里面: |
| 211 | + |
| 212 | +```bash |
| 213 | +sudo tpm2_evictcontrol -C o -c key.ctx 0x81010002 |
| 214 | +``` |
| 215 | + |
| 216 | +--- |
| 217 | +> 有点乱?总结一下 |
| 218 | +
|
| 219 | +**命令**说明: |
| 220 | +tpm2_create 指的是创建一个"密封信件",这个信件有公钥来标识身份,有加密 blob 来给 tpm 读。 |
| 221 | +tpm2_load 则是让 tpm 读取这个密封信件,他生成一个内存快照 (token),这个东西可以持久化来被 tpm 引用(成为永久句柄)。 |
| 222 | +tpm2_evictcontrol 如果-c 加的是文件,然后再跟上地址,语义就是把这个文件持久化到 tpm 的一个句柄中;如果-c 加的是一个地址,那么则是把这个持久句柄驱逐。 |
| 223 | + |
| 224 | +对这里出现的**文件**进行说明: |
| 225 | + |
| 226 | +- key.pub 可以开放给别人看 |
| 227 | +- key.priv 是被 tpm 加密的对象,只有 tpm 才能解密 |
| 228 | +- key.ctx 是 tpm 的内存快照,和 0x 开头的是同一个类型的东西,都可以被`-c`加载 |
| 229 | + |
| 230 | +--- |
| 231 | + |
| 232 | +所以我们给他解个密: |
| 233 | + |
| 234 | +```bash |
| 235 | +❯ tpm2_unseal -c key.ctx -p 123456 |
| 236 | +hello% |
| 237 | +❯ tpm2_unseal -c 0x81010002 -p 123456 |
| 238 | +hello% |
| 239 | +``` |
| 240 | + |
| 241 | +### 验证签名 |
| 242 | +既然懂了一些,我们就不要用 rsa 了,用 ecc,只需把所有的创建密钥的过程加`-G ecc`就行。 |
| 243 | + |
| 244 | +如果要删除之前的,善用 evictcontrol。 |
| 245 | + |
| 246 | +重新创建 ecc 的主密钥: |
| 247 | +```bash |
| 248 | +tpm2_createprimary -C o -G ecc -c ecc_primary.ctx |
| 249 | +``` |
| 250 | + |
| 251 | +创建一个签名的对象: |
| 252 | + |
| 253 | +```bash |
| 254 | +tpm2_create \ |
| 255 | + -C ecc_primary.ctx \ |
| 256 | + -G ecc256:ecdsa \ |
| 257 | + -u ecc.pub \ |
| 258 | + -r ecc.priv \ |
| 259 | + -p 123456 |
| 260 | +``` |
| 261 | + |
| 262 | +加载: |
| 263 | + |
| 264 | +```bash |
| 265 | +tpm2_load \ |
| 266 | + -C ecc_primary.ctx \ |
| 267 | + -u ecc.pub \ |
| 268 | + -r ecc.priv \ |
| 269 | + -c ecc.ctx |
| 270 | +``` |
| 271 | + |
| 272 | +持久化: |
| 273 | +```bash |
| 274 | +sudo tpm2_evictcontrol -C o -c ecc.ctx 0x81020000 |
| 275 | +``` |
| 276 | + |
| 277 | +生成一个固定的挑战,用于验证(其实真正的过程中,每次都是不同的): |
| 278 | + |
| 279 | +```bash |
| 280 | +head -c 32 /dev/urandom > challenge.bin |
| 281 | +``` |
| 282 | + |
| 283 | +用 tpm 自带的工具验证: |
| 284 | + |
| 285 | +```bash |
| 286 | +❯ tpm2_sign \ |
| 287 | + -c 0x81020000 \ |
| 288 | + -g sha256 \ |
| 289 | + -s ecdsa \ |
| 290 | + -o sig.tss \ |
| 291 | + -p 123456 \ |
| 292 | + challenge.bin |
| 293 | +❯ tpm2_verifysignature \ |
| 294 | + -c 0x81020000 \ |
| 295 | + -g sha256 \ |
| 296 | + -s sig.tss \ |
| 297 | + -m challenge.bin |
| 298 | +# 返回0,通过 |
| 299 | +``` |
| 300 | + |
| 301 | +--- |
| 302 | + |
| 303 | +## 整活 |
| 304 | +接下来把这个接入 pam。 |
| 305 | + |
| 306 | +导出公钥: |
| 307 | +```bash |
| 308 | +tpm2_readpublic -c 0x81020000 -f pem -o /etc/tpm-ecc.pub.pem |
| 309 | +chmod 644 /etc/tpm-ecc.pub.pem |
| 310 | +``` |
| 311 | + |
| 312 | +用我这个[项目](https://github.com/minortex/tpm_auth)试试,仅供娱乐: |
| 313 | +```bash |
| 314 | +#%PAM-1.0 |
| 315 | + |
| 316 | +auth required pam_faillock.so preauth |
| 317 | +auth [success=3 default=ignore] pam_tpm_ecc.so key_handle=0x81020000 pubkey=/etc/tpm-ecc.pub.pem |
| 318 | +-auth [success=2 default=ignore] pam_systemd_home.so |
| 319 | +auth [success=1 default=bad] pam_unix.so try_first_pass nullok |
| 320 | +auth [default=die] pam_faillock.so authfail |
| 321 | +auth optional pam_permit.so |
| 322 | +auth required pam_env.so |
| 323 | +auth required pam_faillock.so authsucc |
| 324 | +``` |
| 325 | + |
| 326 | +### polkit |
| 327 | + |
| 328 | +这玩意,搞那么复杂的权限管理,还用模板,没折腾死我。 |
| 329 | + |
| 330 | +他每次授权的时候启动一个 helper,这个 helper 有 root 权限,但是启动的时候被 systemd 限制了一大堆特权,我们得用 drop-in 给他加回来: |
| 331 | + |
| 332 | +```bash |
| 333 | +sudo systemctl edit polkit-agent-helper@ |
| 334 | +``` |
| 335 | + |
| 336 | +```ini |
| 337 | +# Editing /etc/systemd/system/polkit-agent-helper@.service.d/override.conf |
| 338 | +[Service] |
| 339 | +DeviceAllow=/dev/tpmrm0 rw |
| 340 | +BindPaths=/dev/tpmrm0 |
| 341 | +``` |
| 342 | + |
| 343 | +## 彩蛋 |
| 344 | +**一发入魄**: |
| 345 | +```bash |
| 346 | +❯ sudo tpm2_dictionarylockout -s -n 10 -t 600 -l 60 |
| 347 | +WARNING:esys:src/tss2-esys/api/Esys_DictionaryAttackParameters.c:310:Esys_DictionaryAttackParameters_Finish() Received TPM Error |
| 348 | +ERROR:esys:src/tss2-esys/api/Esys_DictionaryAttackParameters.c:108:Esys_DictionaryAttackParameters() Esys Finish ErrorCode (0x0000098e) |
| 349 | +ERROR: Esys_DictionaryAttackParameters(0x98E) - tpm:session(1):the authorization HMAC check failed and DA counter incremented |
| 350 | +ERROR: Failed DictionaryLockout Setup |
| 351 | +ERROR: Unable to run tpm2_dictionarylockout |
| 352 | +❯ sudo tpm2_dictionarylockout -s -n 10 -t 600 -l 60 |
| 353 | +WARNING:esys:src/tss2-esys/api/Esys_DictionaryAttackParameters.c:310:Esys_DictionaryAttackParameters_Finish() Received TPM Error |
| 354 | +ERROR:esys:src/tss2-esys/api/Esys_DictionaryAttackParameters.c:108:Esys_DictionaryAttackParameters() Esys Finish ErrorCode (0x00000921) |
| 355 | +ERROR: Esys_DictionaryAttackParameters(0x921) - tpm:warn(2.0): authorizations for objects subject to DA protection are not allowed at this time because the TPM is in DA lockout mode |
| 356 | +ERROR: Failed DictionaryLockout Setup |
| 357 | +ERROR: Unable to run tpm2_dictionarylockout |
| 358 | +``` |
0 commit comments