]>
Commit | Line | Data |
---|---|---|
5e521624 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include "extract-word.h" | |
4 | #include "parse-util.h" | |
5 | #include "tpm2-util.h" | |
6 | ||
7 | #if HAVE_TPM2 | |
8 | #include "alloc-util.h" | |
9 | #include "dirent-util.h" | |
10 | #include "dlfcn-util.h" | |
11 | #include "fd-util.h" | |
12 | #include "format-table.h" | |
13 | #include "fs-util.h" | |
14 | #include "hexdecoct.h" | |
15 | #include "memory-util.h" | |
16 | #include "random-util.h" | |
17 | #include "time-util.h" | |
18 | ||
19 | static void *libtss2_esys_dl = NULL; | |
20 | static void *libtss2_rc_dl = NULL; | |
21 | static void *libtss2_mu_dl = NULL; | |
22 | ||
23 | TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; | |
24 | TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; | |
25 | void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; | |
26 | TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; | |
27 | void (*sym_Esys_Free)(void *ptr) = NULL; | |
07697bfe | 28 | TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData); |
5e521624 LP |
29 | TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; |
30 | TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; | |
31 | TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; | |
32 | TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; | |
33 | TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; | |
34 | TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; | |
35 | TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; | |
36 | TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; | |
37 | ||
38 | const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; | |
39 | ||
40 | TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; | |
41 | TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; | |
42 | TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; | |
43 | TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; | |
44 | ||
45 | int dlopen_tpm2(void) { | |
d32f7a8e ZJS |
46 | int r; |
47 | ||
48 | r = dlopen_many_sym_or_warn( | |
49 | &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, | |
50 | DLSYM_ARG(Esys_Create), | |
51 | DLSYM_ARG(Esys_CreatePrimary), | |
52 | DLSYM_ARG(Esys_Finalize), | |
53 | DLSYM_ARG(Esys_FlushContext), | |
54 | DLSYM_ARG(Esys_Free), | |
07697bfe | 55 | DLSYM_ARG(Esys_GetCapability), |
d32f7a8e ZJS |
56 | DLSYM_ARG(Esys_GetRandom), |
57 | DLSYM_ARG(Esys_Initialize), | |
58 | DLSYM_ARG(Esys_Load), | |
59 | DLSYM_ARG(Esys_PolicyGetDigest), | |
60 | DLSYM_ARG(Esys_PolicyPCR), | |
61 | DLSYM_ARG(Esys_StartAuthSession), | |
62 | DLSYM_ARG(Esys_Startup), | |
63 | DLSYM_ARG(Esys_Unseal)); | |
64 | if (r < 0) | |
65 | return r; | |
66 | ||
67 | r = dlopen_many_sym_or_warn( | |
68 | &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, | |
69 | DLSYM_ARG(Tss2_RC_Decode)); | |
70 | if (r < 0) | |
71 | return r; | |
72 | ||
73 | return dlopen_many_sym_or_warn( | |
74 | &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, | |
75 | DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal), | |
76 | DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal), | |
77 | DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), | |
78 | DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal)); | |
5e521624 LP |
79 | } |
80 | ||
81 | struct tpm2_context { | |
82 | ESYS_CONTEXT *esys_context; | |
83 | void *tcti_dl; | |
84 | TSS2_TCTI_CONTEXT *tcti_context; | |
85 | }; | |
86 | ||
87 | static void tpm2_context_destroy(struct tpm2_context *c) { | |
88 | assert(c); | |
89 | ||
90 | if (c->esys_context) | |
91 | sym_Esys_Finalize(&c->esys_context); | |
92 | ||
93 | c->tcti_context = mfree(c->tcti_context); | |
94 | ||
95 | if (c->tcti_dl) { | |
96 | dlclose(c->tcti_dl); | |
97 | c->tcti_dl = NULL; | |
98 | } | |
99 | } | |
100 | ||
101 | static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) { | |
102 | /* A wrapper around Esys_Finalize() for use with _cleanup_(). Only reasons we need this wrapper is | |
103 | * because the function itself warn logs if we'd pass a pointer to NULL, and we don't want that. */ | |
104 | if (*c) | |
105 | sym_Esys_Finalize(c); | |
106 | } | |
107 | ||
108 | static inline void Esys_Freep(void *p) { | |
109 | if (*(void**) p) | |
110 | sym_Esys_Free(*(void**) p); | |
111 | } | |
112 | ||
113 | static ESYS_TR flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) { | |
114 | TSS2_RC rc; | |
115 | ||
116 | if (!c || handle == ESYS_TR_NONE) | |
117 | return ESYS_TR_NONE; | |
118 | ||
119 | rc = sym_Esys_FlushContext(c, handle); | |
120 | if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called | |
121 | * in error paths, where we cannot do anything about failures anymore. And | |
122 | * when it is called in successful codepaths by this time we already did | |
123 | * what we wanted to do, and got the results we wanted so there's no | |
124 | * reason to make this fail more loudly than necessary. */ | |
125 | log_debug("Failed to get flush context of TPM, ignoring: %s", sym_Tss2_RC_Decode(rc)); | |
126 | ||
127 | return ESYS_TR_NONE; | |
128 | } | |
129 | ||
130 | static int tpm2_init(const char *device, struct tpm2_context *ret) { | |
131 | _cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL; | |
132 | _cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL; | |
133 | _cleanup_(dlclosep) void *dl = NULL; | |
134 | TSS2_RC rc; | |
135 | int r; | |
136 | ||
137 | r = dlopen_tpm2(); | |
138 | if (r < 0) | |
139 | return log_error_errno(r, "TPM2 support not installed: %m"); | |
140 | ||
141 | if (!device) | |
142 | device = secure_getenv("SYSTEMD_TPM2_DEVICE"); | |
143 | ||
144 | if (device) { | |
145 | const char *param, *driver, *fn; | |
146 | const TSS2_TCTI_INFO* info; | |
147 | TSS2_TCTI_INFO_FUNC func; | |
148 | size_t sz = 0; | |
149 | ||
150 | param = strchr(device, ':'); | |
151 | if (param) { | |
152 | driver = strndupa(device, param - device); | |
153 | param++; | |
154 | } else { | |
155 | driver = "device"; | |
156 | param = device; | |
157 | } | |
158 | ||
159 | fn = strjoina("libtss2-tcti-", driver, ".so.0"); | |
160 | ||
161 | dl = dlopen(fn, RTLD_NOW); | |
162 | if (!dl) | |
163 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror()); | |
164 | ||
165 | func = dlsym(dl, TSS2_TCTI_INFO_SYMBOL); | |
166 | if (!func) | |
167 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
168 | "Failed to find TCTI info symbol " TSS2_TCTI_INFO_SYMBOL ": %s", | |
169 | dlerror()); | |
170 | ||
171 | info = func(); | |
172 | if (!info) | |
173 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data."); | |
174 | ||
175 | ||
176 | log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version); | |
177 | ||
178 | rc = info->init(NULL, &sz, NULL); | |
179 | if (rc != TPM2_RC_SUCCESS) | |
180 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
181 | "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); | |
182 | ||
183 | tcti = malloc0(sz); | |
184 | if (!tcti) | |
185 | return log_oom(); | |
186 | ||
d2bf22fb | 187 | rc = info->init(tcti, &sz, param); |
5e521624 LP |
188 | if (rc != TPM2_RC_SUCCESS) |
189 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
190 | "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); | |
191 | } | |
192 | ||
193 | rc = sym_Esys_Initialize(&c, tcti, NULL); | |
194 | if (rc != TSS2_RC_SUCCESS) | |
195 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
196 | "Failed to initialize TPM context: %s", sym_Tss2_RC_Decode(rc)); | |
197 | ||
198 | rc = sym_Esys_Startup(c, TPM2_SU_CLEAR); | |
199 | if (rc == TPM2_RC_INITIALIZE) | |
200 | log_debug("TPM already started up."); | |
201 | else if (rc == TSS2_RC_SUCCESS) | |
202 | log_debug("TPM successfully started up."); | |
203 | else | |
204 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
205 | "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); | |
206 | ||
207 | *ret = (struct tpm2_context) { | |
208 | .esys_context = TAKE_PTR(c), | |
209 | .tcti_context = TAKE_PTR(tcti), | |
210 | .tcti_dl = TAKE_PTR(dl), | |
211 | }; | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static int tpm2_credit_random(ESYS_CONTEXT *c) { | |
217 | size_t rps, done = 0; | |
218 | TSS2_RC rc; | |
219 | int r; | |
220 | ||
221 | assert(c); | |
222 | ||
223 | /* Pulls some entropy from the TPM and adds it into the kernel RNG pool. That way we can say that the | |
224 | * key we will ultimately generate with the kernel random pool is at least as good as the TPM's RNG, | |
225 | * but likely better. Note that we don't trust the TPM RNG very much, hence do not actually credit | |
226 | * any entropy. */ | |
227 | ||
228 | for (rps = random_pool_size(); rps > 0;) { | |
229 | _cleanup_(Esys_Freep) TPM2B_DIGEST *buffer = NULL; | |
230 | ||
231 | rc = sym_Esys_GetRandom( | |
232 | c, | |
233 | ESYS_TR_NONE, | |
234 | ESYS_TR_NONE, | |
235 | ESYS_TR_NONE, | |
236 | MIN(rps, 32U), /* 32 is supposedly a safe choice, given that AES 256bit keys are this long, and TPM2 baseline requires support for those. */ | |
237 | &buffer); | |
238 | if (rc != TSS2_RC_SUCCESS) | |
239 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
240 | "Failed to acquire entropy from TPM: %s", sym_Tss2_RC_Decode(rc)); | |
241 | ||
242 | if (buffer->size == 0) | |
243 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
244 | "Zero-sized entropy returned from TPM."); | |
245 | ||
246 | r = random_write_entropy(-1, buffer->buffer, buffer->size, false); | |
247 | if (r < 0) | |
248 | return log_error_errno(r, "Failed wo write entropy to kernel: %m"); | |
249 | ||
250 | done += buffer->size; | |
251 | rps = LESS_BY(rps, buffer->size); | |
252 | } | |
253 | ||
254 | log_debug("Added %zu bytes of entropy to the kernel random pool.", done); | |
255 | return 0; | |
256 | } | |
257 | ||
258 | static int tpm2_make_primary( | |
259 | ESYS_CONTEXT *c, | |
2b92a672 LP |
260 | ESYS_TR *ret_primary, |
261 | TPMI_ALG_PUBLIC alg, | |
262 | TPMI_ALG_PUBLIC *ret_alg) { | |
5e521624 LP |
263 | |
264 | static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; | |
2b92a672 | 265 | static const TPM2B_PUBLIC primary_template_ecc = { |
5e521624 LP |
266 | .size = sizeof(TPMT_PUBLIC), |
267 | .publicArea = { | |
268 | .type = TPM2_ALG_ECC, | |
269 | .nameAlg = TPM2_ALG_SHA256, | |
270 | .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, | |
271 | .parameters = { | |
272 | .eccDetail = { | |
273 | .symmetric = { | |
274 | .algorithm = TPM2_ALG_AES, | |
275 | .keyBits.aes = 128, | |
276 | .mode.aes = TPM2_ALG_CFB, | |
277 | }, | |
278 | .scheme.scheme = TPM2_ALG_NULL, | |
279 | .curveID = TPM2_ECC_NIST_P256, | |
280 | .kdf.scheme = TPM2_ALG_NULL, | |
281 | }, | |
282 | }, | |
283 | }, | |
284 | }; | |
2b92a672 LP |
285 | static const TPM2B_PUBLIC primary_template_rsa = { |
286 | .size = sizeof(TPMT_PUBLIC), | |
287 | .publicArea = { | |
288 | .type = TPM2_ALG_RSA, | |
289 | .nameAlg = TPM2_ALG_SHA256, | |
290 | .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, | |
291 | .parameters = { | |
292 | .rsaDetail = { | |
293 | .symmetric = { | |
294 | .algorithm = TPM2_ALG_AES, | |
295 | .keyBits.aes = 128, | |
296 | .mode.aes = TPM2_ALG_CFB, | |
297 | }, | |
298 | .scheme.scheme = TPM2_ALG_NULL, | |
299 | .keyBits = 2048, | |
300 | }, | |
301 | }, | |
302 | }, | |
303 | }; | |
304 | ||
5e521624 LP |
305 | static const TPML_PCR_SELECTION creation_pcr = {}; |
306 | ESYS_TR primary = ESYS_TR_NONE; | |
307 | TSS2_RC rc; | |
2b92a672 | 308 | usec_t ts; |
5e521624 LP |
309 | |
310 | log_debug("Creating primary key on TPM."); | |
311 | ||
2b92a672 LP |
312 | /* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much |
313 | * faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's | |
314 | * try to use ECC first, and if that does not work, let's fall back to RSA. */ | |
5e521624 | 315 | |
2b92a672 LP |
316 | ts = now(CLOCK_MONOTONIC); |
317 | ||
318 | if (IN_SET(alg, 0, TPM2_ALG_ECC)) { | |
319 | rc = sym_Esys_CreatePrimary( | |
320 | c, | |
321 | ESYS_TR_RH_OWNER, | |
322 | ESYS_TR_PASSWORD, | |
323 | ESYS_TR_NONE, | |
324 | ESYS_TR_NONE, | |
325 | &primary_sensitive, | |
326 | &primary_template_ecc, | |
327 | NULL, | |
328 | &creation_pcr, | |
329 | &primary, | |
330 | NULL, | |
331 | NULL, | |
332 | NULL, | |
333 | NULL); | |
334 | ||
335 | if (rc != TSS2_RC_SUCCESS) { | |
336 | if (alg != 0) | |
337 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
338 | "Failed to generate ECC primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
339 | ||
340 | log_debug("Failed to generate ECC primary key in TPM, trying RSA: %s", sym_Tss2_RC_Decode(rc)); | |
341 | } else { | |
342 | log_debug("Successfully created ECC primary key on TPM."); | |
343 | alg = TPM2_ALG_ECC; | |
344 | } | |
345 | } | |
346 | ||
347 | if (IN_SET(alg, 0, TPM2_ALG_RSA)) { | |
348 | rc = sym_Esys_CreatePrimary( | |
349 | c, | |
350 | ESYS_TR_RH_OWNER, | |
351 | ESYS_TR_PASSWORD, | |
352 | ESYS_TR_NONE, | |
353 | ESYS_TR_NONE, | |
354 | &primary_sensitive, | |
355 | &primary_template_rsa, | |
356 | NULL, | |
357 | &creation_pcr, | |
358 | &primary, | |
359 | NULL, | |
360 | NULL, | |
361 | NULL, | |
362 | NULL); | |
363 | if (rc != TSS2_RC_SUCCESS) | |
364 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
365 | "Failed to generate RSA primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
366 | else if (alg == 0) { | |
367 | log_notice("TPM2 chip apparently does not support ECC primary keys, falling back to RSA. " | |
368 | "This likely means TPM2 operations will be relatively slow, please be patient."); | |
369 | alg = TPM2_ALG_RSA; | |
370 | } | |
371 | ||
372 | log_debug("Successfully created RSA primary key on TPM."); | |
373 | } | |
5e521624 | 374 | |
2b92a672 | 375 | log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); |
5e521624 LP |
376 | |
377 | *ret_primary = primary; | |
2b92a672 LP |
378 | if (ret_alg) |
379 | *ret_alg = alg; | |
380 | ||
5e521624 LP |
381 | return 0; |
382 | } | |
383 | ||
07697bfe LP |
384 | static int tpm2_get_best_pcr_bank( |
385 | ESYS_CONTEXT *c, | |
386 | TPMI_ALG_HASH *ret) { | |
387 | ||
388 | _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; | |
389 | TPMI_ALG_HASH hash = TPM2_ALG_SHA1; | |
390 | bool found = false; | |
391 | TPMI_YES_NO more; | |
392 | TSS2_RC rc; | |
393 | ||
394 | rc = sym_Esys_GetCapability( | |
395 | c, | |
396 | ESYS_TR_NONE, | |
397 | ESYS_TR_NONE, | |
398 | ESYS_TR_NONE, | |
399 | TPM2_CAP_PCRS, | |
400 | 0, | |
401 | 1, | |
402 | &more, | |
403 | &pcap); | |
404 | if (rc != TSS2_RC_SUCCESS) | |
405 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
406 | "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); | |
407 | ||
408 | assert(pcap->capability == TPM2_CAP_PCRS); | |
409 | ||
410 | for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { | |
411 | bool valid = true; | |
412 | ||
413 | /* As per | |
414 | * https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a | |
415 | * TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over | |
416 | * it. */ | |
417 | if (pcap->data.assignedPCR.pcrSelections[i].sizeofSelect < TPM2_PCRS_MAX/8) { | |
418 | log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.", | |
419 | strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash))); | |
420 | continue; | |
421 | } | |
422 | ||
423 | assert_cc(TPM2_PCRS_MAX % 8 == 0); | |
424 | ||
bdbb61f6 | 425 | /* It's not enough to check how many PCRs there are, we also need to check that the 24 are |
07697bfe LP |
426 | * enabled for this bank. Otherwise this TPM doesn't qualify. */ |
427 | for (size_t j = 0; j < TPM2_PCRS_MAX/8; j++) | |
428 | if (pcap->data.assignedPCR.pcrSelections[i].pcrSelect[j] != 0xFF) { | |
429 | valid = false; | |
430 | break; | |
431 | } | |
432 | ||
433 | if (!valid) { | |
434 | log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.", | |
435 | strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash))); | |
436 | continue; | |
437 | } | |
438 | ||
439 | if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { | |
440 | hash = TPM2_ALG_SHA256; | |
441 | found = true; | |
442 | break; | |
443 | } | |
444 | ||
445 | if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1) | |
446 | found = true; | |
447 | } | |
448 | ||
449 | if (!found) | |
450 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
451 | "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate."); | |
452 | ||
453 | if (hash == TPM2_ALG_SHA256) | |
454 | log_debug("TPM2 device supports SHA256 PCR banks, yay!"); | |
455 | else { | |
456 | assert(hash == TPM2_ALG_SHA1); | |
457 | log_debug("TPM2 device lacks support for SHA256 PCR banks, falling back to SHA1 banks."); | |
458 | } | |
459 | ||
460 | *ret = hash; | |
461 | return 0; | |
462 | } | |
463 | ||
5e521624 LP |
464 | static int tpm2_make_pcr_session( |
465 | ESYS_CONTEXT *c, | |
466 | uint32_t pcr_mask, | |
07697bfe | 467 | uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ |
5e521624 | 468 | ESYS_TR *ret_session, |
07697bfe LP |
469 | TPM2B_DIGEST **ret_policy_digest, |
470 | TPMI_ALG_HASH *ret_pcr_bank) { | |
5e521624 LP |
471 | |
472 | static const TPMT_SYM_DEF symmetric = { | |
473 | .algorithm = TPM2_ALG_AES, | |
474 | .keyBits = { | |
475 | .aes = 128 | |
476 | }, | |
477 | .mode = { | |
478 | .aes = TPM2_ALG_CFB, | |
479 | } | |
480 | }; | |
481 | TPML_PCR_SELECTION pcr_selection = { | |
482 | .count = 1, | |
bdbb61f6 | 483 | .pcrSelections[0].hash = TPM2_ALG_SHA256, /* overridden below, depending on TPM2 capabilities */ |
5e521624 LP |
484 | .pcrSelections[0].sizeofSelect = 3, |
485 | .pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF, | |
486 | .pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF, | |
487 | .pcrSelections[0].pcrSelect[2] = (pcr_mask >> 16) & 0xFF, | |
488 | }; | |
489 | _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; | |
490 | ESYS_TR session = ESYS_TR_NONE; | |
491 | TSS2_RC rc; | |
492 | int r; | |
493 | ||
494 | assert(c); | |
495 | ||
496 | log_debug("Starting authentication session."); | |
497 | ||
07697bfe LP |
498 | if (pcr_bank != UINT16_MAX) |
499 | pcr_selection.pcrSelections[0].hash = pcr_bank; | |
500 | else { | |
501 | /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect | |
502 | * that use that, but preferably use SHA256 */ | |
503 | r = tpm2_get_best_pcr_bank(c, &pcr_selection.pcrSelections[0].hash); | |
504 | if (r < 0) | |
505 | return r; | |
506 | } | |
507 | ||
5e521624 LP |
508 | rc = sym_Esys_StartAuthSession( |
509 | c, | |
510 | ESYS_TR_NONE, | |
511 | ESYS_TR_NONE, | |
512 | ESYS_TR_NONE, | |
513 | ESYS_TR_NONE, | |
514 | ESYS_TR_NONE, | |
515 | NULL, | |
516 | TPM2_SE_POLICY, | |
517 | &symmetric, | |
518 | TPM2_ALG_SHA256, | |
519 | &session); | |
520 | if (rc != TSS2_RC_SUCCESS) | |
521 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
522 | "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
523 | ||
524 | log_debug("Configuring PCR policy."); | |
525 | ||
526 | rc = sym_Esys_PolicyPCR( | |
527 | c, | |
528 | session, | |
529 | ESYS_TR_NONE, | |
530 | ESYS_TR_NONE, | |
531 | ESYS_TR_NONE, | |
532 | NULL, | |
533 | &pcr_selection); | |
534 | if (rc != TSS2_RC_SUCCESS) { | |
535 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
536 | "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); | |
537 | goto finish; | |
538 | } | |
539 | ||
540 | if (DEBUG_LOGGING || ret_policy_digest) { | |
541 | log_debug("Acquiring policy digest."); | |
542 | ||
543 | rc = sym_Esys_PolicyGetDigest( | |
544 | c, | |
545 | session, | |
546 | ESYS_TR_NONE, | |
547 | ESYS_TR_NONE, | |
548 | ESYS_TR_NONE, | |
549 | &policy_digest); | |
550 | ||
551 | if (rc != TSS2_RC_SUCCESS) { | |
552 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
553 | "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); | |
554 | goto finish; | |
555 | } | |
556 | ||
557 | if (DEBUG_LOGGING) { | |
558 | _cleanup_free_ char *h = NULL; | |
559 | ||
560 | h = hexmem(policy_digest->buffer, policy_digest->size); | |
561 | if (!h) { | |
562 | r = log_oom(); | |
563 | goto finish; | |
564 | } | |
565 | ||
566 | log_debug("Session policy digest: %s", h); | |
567 | } | |
568 | } | |
569 | ||
570 | if (ret_session) { | |
571 | *ret_session = session; | |
572 | session = ESYS_TR_NONE; | |
573 | } | |
574 | ||
575 | if (ret_policy_digest) | |
576 | *ret_policy_digest = TAKE_PTR(policy_digest); | |
577 | ||
07697bfe LP |
578 | if (ret_pcr_bank) |
579 | *ret_pcr_bank = pcr_selection.pcrSelections[0].hash; | |
580 | ||
5e521624 LP |
581 | r = 0; |
582 | ||
583 | finish: | |
584 | session = flush_context_verbose(c, session); | |
585 | return r; | |
586 | } | |
587 | ||
588 | int tpm2_seal( | |
589 | const char *device, | |
590 | uint32_t pcr_mask, | |
591 | void **ret_secret, | |
592 | size_t *ret_secret_size, | |
593 | void **ret_blob, | |
594 | size_t *ret_blob_size, | |
595 | void **ret_pcr_hash, | |
07697bfe | 596 | size_t *ret_pcr_hash_size, |
2b92a672 LP |
597 | uint16_t *ret_pcr_bank, |
598 | uint16_t *ret_primary_alg) { | |
5e521624 LP |
599 | |
600 | _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; | |
601 | _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; | |
602 | _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; | |
603 | _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; | |
604 | static const TPML_PCR_SELECTION creation_pcr = {}; | |
605 | _cleanup_(erase_and_freep) void *secret = NULL; | |
606 | _cleanup_free_ void *blob = NULL, *hash = NULL; | |
607 | TPM2B_SENSITIVE_CREATE hmac_sensitive; | |
608 | ESYS_TR primary = ESYS_TR_NONE; | |
2b92a672 | 609 | TPMI_ALG_PUBLIC primary_alg; |
5e521624 | 610 | TPM2B_PUBLIC hmac_template; |
07697bfe | 611 | TPMI_ALG_HASH pcr_bank; |
5e521624 LP |
612 | size_t k, blob_size; |
613 | usec_t start; | |
614 | TSS2_RC rc; | |
615 | int r; | |
616 | ||
617 | assert(ret_secret); | |
618 | assert(ret_secret_size); | |
619 | assert(ret_blob); | |
620 | assert(ret_blob_size); | |
621 | assert(ret_pcr_hash); | |
622 | assert(ret_pcr_hash_size); | |
07697bfe | 623 | assert(ret_pcr_bank); |
5e521624 LP |
624 | |
625 | assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */ | |
626 | ||
627 | /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that | |
628 | * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We | |
2b92a672 LP |
629 | * generate a "primary" key pair derived from that (ECC if possible, RSA as fallback). Given the seed |
630 | * remains fixed this will result in the same key pair whenever we specify the exact same parameters | |
631 | * for it. We then create a PCR-bound policy session, which calculates a hash on the current PCR | |
632 | * values of the indexes we specify. We then generate a randomized key on the host (which is the key | |
633 | * we actually enroll in the LUKS2 keyslots), which we upload into the TPM2, where it is encrypted | |
634 | * with the "primary" key, taking the PCR policy session into account. We then download the encrypted | |
635 | * key from the TPM2 ("sealing") and marshall it into binary form, which is ultimately placed in the | |
636 | * LUKS2 JSON header. | |
5e521624 LP |
637 | * |
638 | * The TPM2 "seed" key and "primary" keys never leave the TPM2 chip (and cannot be extracted at | |
639 | * all). The random key we enroll in LUKS2 we generate on the host using the Linux random device. It | |
640 | * is stored in the LUKS2 JSON only in encrypted form with the "primary" key of the TPM2 chip, thus | |
641 | * binding the unlocking to the TPM2 chip. */ | |
642 | ||
643 | start = now(CLOCK_MONOTONIC); | |
644 | ||
645 | r = tpm2_init(device, &c); | |
646 | if (r < 0) | |
647 | return r; | |
648 | ||
2b92a672 | 649 | r = tpm2_make_primary(c.esys_context, &primary, 0, &primary_alg); |
5e521624 LP |
650 | if (r < 0) |
651 | return r; | |
652 | ||
07697bfe | 653 | r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, NULL, &policy_digest, &pcr_bank); |
5e521624 LP |
654 | if (r < 0) |
655 | goto finish; | |
656 | ||
657 | /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the | |
658 | * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it | |
659 | * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ | |
660 | hmac_template = (TPM2B_PUBLIC) { | |
661 | .size = sizeof(TPMT_PUBLIC), | |
662 | .publicArea = { | |
663 | .type = TPM2_ALG_KEYEDHASH, | |
664 | .nameAlg = TPM2_ALG_SHA256, | |
665 | .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, | |
666 | .parameters = { | |
667 | .keyedHashDetail = { | |
668 | .scheme.scheme = TPM2_ALG_NULL, | |
669 | }, | |
670 | }, | |
671 | .unique = { | |
672 | .keyedHash = { | |
673 | .size = 32, | |
674 | }, | |
675 | }, | |
676 | .authPolicy = *policy_digest, | |
677 | }, | |
678 | }; | |
679 | ||
680 | hmac_sensitive = (TPM2B_SENSITIVE_CREATE) { | |
681 | .size = sizeof(hmac_sensitive.sensitive), | |
682 | .sensitive.data.size = 32, | |
683 | }; | |
684 | assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); | |
685 | ||
686 | (void) tpm2_credit_random(c.esys_context); | |
687 | ||
688 | log_debug("Generating secret key data."); | |
689 | ||
690 | r = genuine_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size, RANDOM_BLOCK); | |
691 | if (r < 0) { | |
692 | log_error_errno(r, "Failed to generate secret key: %m"); | |
693 | goto finish; | |
694 | } | |
695 | ||
696 | log_debug("Creating HMAC key."); | |
697 | ||
698 | rc = sym_Esys_Create( | |
699 | c.esys_context, | |
700 | primary, | |
701 | ESYS_TR_PASSWORD, | |
702 | ESYS_TR_NONE, | |
703 | ESYS_TR_NONE, | |
704 | &hmac_sensitive, | |
705 | &hmac_template, | |
706 | NULL, | |
707 | &creation_pcr, | |
708 | &private, | |
709 | &public, | |
710 | NULL, | |
711 | NULL, | |
712 | NULL); | |
713 | if (rc != TSS2_RC_SUCCESS) { | |
714 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
715 | "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
716 | goto finish; | |
717 | } | |
718 | ||
719 | secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); | |
720 | explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); | |
721 | if (!secret) { | |
722 | r = log_oom(); | |
723 | goto finish; | |
724 | } | |
725 | ||
726 | log_debug("Marshalling private and public part of HMAC key."); | |
727 | ||
728 | k = ALIGN8(sizeof(*private)) + ALIGN8(sizeof(*public)); /* Some roughly sensible start value */ | |
729 | for (;;) { | |
730 | _cleanup_free_ void *buf = NULL; | |
731 | size_t offset = 0; | |
732 | ||
733 | buf = malloc(k); | |
734 | if (!buf) { | |
735 | r = log_oom(); | |
736 | goto finish; | |
737 | } | |
738 | ||
739 | rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, buf, k, &offset); | |
740 | if (rc == TSS2_RC_SUCCESS) { | |
741 | rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, buf, k, &offset); | |
742 | if (rc == TSS2_RC_SUCCESS) { | |
743 | blob = TAKE_PTR(buf); | |
744 | blob_size = offset; | |
745 | break; | |
746 | } | |
747 | } | |
748 | if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) { | |
749 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
750 | "Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc)); | |
751 | goto finish; | |
752 | } | |
753 | ||
754 | if (k > SIZE_MAX / 2) { | |
755 | r = log_oom(); | |
756 | goto finish; | |
757 | } | |
758 | ||
759 | k *= 2; | |
760 | } | |
761 | ||
762 | hash = memdup(policy_digest->buffer, policy_digest->size); | |
763 | if (!hash) | |
764 | return log_oom(); | |
765 | ||
5291f26d ZJS |
766 | if (DEBUG_LOGGING) |
767 | log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); | |
5e521624 LP |
768 | |
769 | *ret_secret = TAKE_PTR(secret); | |
770 | *ret_secret_size = hmac_sensitive.sensitive.data.size; | |
771 | *ret_blob = TAKE_PTR(blob); | |
772 | *ret_blob_size = blob_size; | |
773 | *ret_pcr_hash = TAKE_PTR(hash); | |
774 | *ret_pcr_hash_size = policy_digest->size; | |
07697bfe | 775 | *ret_pcr_bank = pcr_bank; |
2b92a672 | 776 | *ret_primary_alg = primary_alg; |
5e521624 LP |
777 | |
778 | r = 0; | |
779 | ||
780 | finish: | |
781 | primary = flush_context_verbose(c.esys_context, primary); | |
782 | return r; | |
783 | } | |
784 | ||
785 | int tpm2_unseal( | |
786 | const char *device, | |
787 | uint32_t pcr_mask, | |
07697bfe | 788 | uint16_t pcr_bank, |
2b92a672 | 789 | uint16_t primary_alg, |
5e521624 LP |
790 | const void *blob, |
791 | size_t blob_size, | |
792 | const void *known_policy_hash, | |
793 | size_t known_policy_hash_size, | |
794 | void **ret_secret, | |
795 | size_t *ret_secret_size) { | |
796 | ||
797 | _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; | |
798 | ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_key = ESYS_TR_NONE; | |
799 | _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; | |
800 | _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; | |
801 | _cleanup_(erase_and_freep) char *secret = NULL; | |
802 | TPM2B_PRIVATE private = {}; | |
803 | TPM2B_PUBLIC public = {}; | |
804 | size_t offset = 0; | |
805 | TSS2_RC rc; | |
806 | usec_t start; | |
807 | int r; | |
808 | ||
809 | assert(blob); | |
810 | assert(blob_size > 0); | |
811 | assert(known_policy_hash_size == 0 || known_policy_hash); | |
812 | assert(ret_secret); | |
813 | assert(ret_secret_size); | |
814 | ||
815 | assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */ | |
816 | ||
1b30720c LP |
817 | r = dlopen_tpm2(); |
818 | if (r < 0) | |
819 | return log_error_errno(r, "TPM2 support is not installed."); | |
820 | ||
5e521624 LP |
821 | /* So here's what we do here: We connect to the TPM2 chip. As we do when sealing we generate a |
822 | * "primary" key on the TPM2 chip, with the same parameters as well as a PCR-bound policy | |
823 | * session. Given we pass the same parameters, this will result in the same "primary" key, and same | |
824 | * policy hash (the latter of course, only if the PCR values didn't change in between). We unmarshal | |
825 | * the encrypted key we stored in the LUKS2 JSON token header and upload it into the TPM2, where it | |
826 | * is decrypted if the seed and the PCR policy were right ("unsealing"). We then download the result, | |
827 | * and use it to unlock the LUKS2 volume. */ | |
828 | ||
829 | start = now(CLOCK_MONOTONIC); | |
830 | ||
831 | log_debug("Unmarshalling private part of HMAC key."); | |
832 | ||
833 | rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private); | |
834 | if (rc != TSS2_RC_SUCCESS) | |
835 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
836 | "Failed to unmarshal private key: %s", sym_Tss2_RC_Decode(rc)); | |
837 | ||
838 | log_debug("Unmarshalling public part of HMAC key."); | |
839 | ||
840 | rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public); | |
841 | if (rc != TSS2_RC_SUCCESS) | |
842 | return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
843 | "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); | |
844 | ||
845 | r = tpm2_init(device, &c); | |
846 | if (r < 0) | |
847 | return r; | |
848 | ||
07697bfe | 849 | r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, &session, &policy_digest, NULL); |
5e521624 LP |
850 | if (r < 0) |
851 | goto finish; | |
852 | ||
853 | /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not | |
854 | * wait until the TPM2 tells us to go away. */ | |
855 | if (known_policy_hash_size > 0 && | |
856 | memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) | |
857 | return log_error_errno(SYNTHETIC_ERRNO(EPERM), | |
858 | "Current policy digest does not match stored policy digest, cancelling TPM2 authentication attempt."); | |
859 | ||
2b92a672 | 860 | r = tpm2_make_primary(c.esys_context, &primary, primary_alg, NULL); |
5e521624 LP |
861 | if (r < 0) |
862 | return r; | |
863 | ||
864 | log_debug("Loading HMAC key into TPM."); | |
865 | ||
866 | rc = sym_Esys_Load( | |
867 | c.esys_context, | |
868 | primary, | |
869 | ESYS_TR_PASSWORD, | |
870 | ESYS_TR_NONE, | |
871 | ESYS_TR_NONE, | |
872 | &private, | |
873 | &public, | |
874 | &hmac_key); | |
875 | if (rc != TSS2_RC_SUCCESS) { | |
876 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
877 | "Failed to load HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
878 | goto finish; | |
879 | } | |
880 | ||
881 | log_debug("Unsealing HMAC key."); | |
882 | ||
883 | rc = sym_Esys_Unseal( | |
884 | c.esys_context, | |
885 | hmac_key, | |
886 | session, | |
887 | ESYS_TR_NONE, | |
888 | ESYS_TR_NONE, | |
889 | &unsealed); | |
890 | if (rc != TSS2_RC_SUCCESS) { | |
891 | r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), | |
892 | "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); | |
893 | goto finish; | |
894 | } | |
895 | ||
896 | secret = memdup(unsealed->buffer, unsealed->size); | |
897 | explicit_bzero_safe(unsealed->buffer, unsealed->size); | |
898 | if (!secret) { | |
899 | r = log_oom(); | |
900 | goto finish; | |
901 | } | |
902 | ||
5291f26d ZJS |
903 | if (DEBUG_LOGGING) |
904 | log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); | |
5e521624 LP |
905 | |
906 | *ret_secret = TAKE_PTR(secret); | |
907 | *ret_secret_size = unsealed->size; | |
908 | ||
909 | r = 0; | |
910 | ||
911 | finish: | |
912 | primary = flush_context_verbose(c.esys_context, primary); | |
913 | session = flush_context_verbose(c.esys_context, session); | |
914 | hmac_key = flush_context_verbose(c.esys_context, hmac_key); | |
915 | return r; | |
916 | } | |
917 | ||
918 | #endif | |
919 | ||
920 | int tpm2_list_devices(void) { | |
921 | #if HAVE_TPM2 | |
922 | _cleanup_(table_unrefp) Table *t = NULL; | |
923 | _cleanup_(closedirp) DIR *d = NULL; | |
924 | int r; | |
925 | ||
926 | r = dlopen_tpm2(); | |
927 | if (r < 0) | |
928 | return log_error_errno(r, "TPM2 support is not installed."); | |
929 | ||
930 | t = table_new("path", "device", "driver"); | |
931 | if (!t) | |
932 | return log_oom(); | |
933 | ||
934 | d = opendir("/sys/class/tpmrm"); | |
935 | if (!d) { | |
936 | log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open /sys/class/tpmrm: %m"); | |
937 | if (errno != ENOENT) | |
938 | return -errno; | |
939 | } else { | |
940 | for (;;) { | |
941 | _cleanup_free_ char *device_path = NULL, *device = NULL, *driver_path = NULL, *driver = NULL, *node = NULL; | |
942 | struct dirent *de; | |
943 | ||
944 | de = readdir_no_dot(d); | |
945 | if (!de) | |
946 | break; | |
947 | ||
948 | device_path = path_join("/sys/class/tpmrm", de->d_name, "device"); | |
949 | if (!device_path) | |
950 | return log_oom(); | |
951 | ||
952 | r = readlink_malloc(device_path, &device); | |
953 | if (r < 0) | |
954 | log_debug_errno(r, "Failed to read device symlink %s, ignoring: %m", device_path); | |
955 | else { | |
956 | driver_path = path_join(device_path, "driver"); | |
957 | if (!driver_path) | |
958 | return log_oom(); | |
959 | ||
960 | r = readlink_malloc(driver_path, &driver); | |
961 | if (r < 0) | |
962 | log_debug_errno(r, "Failed to read driver symlink %s, ignoring: %m", driver_path); | |
963 | } | |
964 | ||
965 | node = path_join("/dev", de->d_name); | |
966 | if (!node) | |
967 | return log_oom(); | |
968 | ||
969 | r = table_add_many( | |
970 | t, | |
971 | TABLE_PATH, node, | |
972 | TABLE_STRING, device ? last_path_component(device) : NULL, | |
973 | TABLE_STRING, driver ? last_path_component(driver) : NULL); | |
974 | if (r < 0) | |
975 | return table_log_add_error(r); | |
976 | } | |
977 | } | |
978 | ||
979 | if (table_get_rows(t) <= 1) { | |
980 | log_info("No suitable TPM2 devices found."); | |
981 | return 0; | |
982 | } | |
983 | ||
984 | r = table_print(t, stdout); | |
985 | if (r < 0) | |
986 | return log_error_errno(r, "Failed to show device table: %m"); | |
987 | ||
988 | return 0; | |
989 | #else | |
990 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
991 | "TPM2 not supported on this build."); | |
992 | #endif | |
993 | } | |
994 | ||
995 | int tpm2_find_device_auto( | |
996 | int log_level, /* log level when no device is found */ | |
997 | char **ret) { | |
998 | #if HAVE_TPM2 | |
999 | _cleanup_(closedirp) DIR *d = NULL; | |
1000 | int r; | |
1001 | ||
1002 | r = dlopen_tpm2(); | |
1003 | if (r < 0) | |
1004 | return log_error_errno(r, "TPM2 support is not installed."); | |
1005 | ||
1006 | d = opendir("/sys/class/tpmrm"); | |
1007 | if (!d) { | |
1008 | log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, | |
1009 | "Failed to open /sys/class/tpmrm: %m"); | |
1010 | if (errno != ENOENT) | |
1011 | return -errno; | |
1012 | } else { | |
1013 | _cleanup_free_ char *node = NULL; | |
1014 | ||
1015 | for (;;) { | |
1016 | struct dirent *de; | |
1017 | ||
1018 | de = readdir_no_dot(d); | |
1019 | if (!de) | |
1020 | break; | |
1021 | ||
1022 | if (node) | |
1023 | return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), | |
1024 | "More than one TPM2 (tpmrm) device found."); | |
1025 | ||
1026 | node = path_join("/dev", de->d_name); | |
1027 | if (!node) | |
1028 | return log_oom(); | |
1029 | } | |
1030 | ||
1031 | if (node) { | |
1032 | *ret = TAKE_PTR(node); | |
1033 | return 0; | |
1034 | } | |
1035 | } | |
1036 | ||
1037 | return log_full_errno(log_level, SYNTHETIC_ERRNO(ENODEV), "No TPM2 (tpmrm) device found."); | |
1038 | #else | |
1039 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
1040 | "TPM2 not supported on this build."); | |
1041 | #endif | |
1042 | } | |
1043 | ||
1044 | int tpm2_parse_pcrs(const char *s, uint32_t *ret) { | |
1045 | const char *p = s; | |
1046 | uint32_t mask = 0; | |
1047 | int r; | |
1048 | ||
d57f6340 LP |
1049 | assert(s); |
1050 | ||
1051 | if (isempty(s)) { | |
1052 | *ret = 0; | |
1053 | return 0; | |
1054 | } | |
1055 | ||
a1788a69 LP |
1056 | /* Parses a "," or "+" separated list of PCR indexes. We support "," since this is a list after all, |
1057 | * and most other tools expect comma separated PCR specifications. We also support "+" since in | |
1058 | * /etc/crypttab the "," is already used to separate options, hence a different separator is nice to | |
1059 | * avoid escaping. */ | |
5e521624 LP |
1060 | |
1061 | for (;;) { | |
1062 | _cleanup_free_ char *pcr = NULL; | |
1063 | unsigned n; | |
1064 | ||
a1788a69 | 1065 | r = extract_first_word(&p, &pcr, ",+", EXTRACT_DONT_COALESCE_SEPARATORS); |
5e521624 LP |
1066 | if (r == 0) |
1067 | break; | |
1068 | if (r < 0) | |
1069 | return log_error_errno(r, "Failed to parse PCR list: %s", s); | |
1070 | ||
1071 | r = safe_atou(pcr, &n); | |
1072 | if (r < 0) | |
1073 | return log_error_errno(r, "Failed to parse PCR number: %s", pcr); | |
1074 | if (n >= TPM2_PCRS_MAX) | |
1075 | return log_error_errno(SYNTHETIC_ERRNO(ERANGE), | |
1076 | "PCR number out of range (valid range 0…23): %u", n); | |
1077 | ||
1078 | mask |= UINT32_C(1) << n; | |
1079 | } | |
1080 | ||
1081 | *ret = mask; | |
1082 | return 0; | |
1083 | } | |
1084 | ||
1085 | int tpm2_make_luks2_json( | |
1086 | int keyslot, | |
1087 | uint32_t pcr_mask, | |
07697bfe | 1088 | uint16_t pcr_bank, |
2b92a672 | 1089 | uint16_t primary_alg, |
5e521624 LP |
1090 | const void *blob, |
1091 | size_t blob_size, | |
1092 | const void *policy_hash, | |
1093 | size_t policy_hash_size, | |
1094 | JsonVariant **ret) { | |
1095 | ||
1096 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL; | |
1097 | _cleanup_free_ char *keyslot_as_string = NULL; | |
1098 | JsonVariant* pcr_array[TPM2_PCRS_MAX]; | |
1099 | unsigned n_pcrs = 0; | |
1100 | int r; | |
1101 | ||
1102 | assert(blob || blob_size == 0); | |
1103 | assert(policy_hash || policy_hash_size == 0); | |
1104 | ||
1105 | if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) | |
1106 | return -ENOMEM; | |
1107 | ||
1108 | for (unsigned i = 0; i < ELEMENTSOF(pcr_array); i++) { | |
1109 | if ((pcr_mask & (UINT32_C(1) << i)) == 0) | |
1110 | continue; | |
1111 | ||
1112 | r = json_variant_new_integer(pcr_array + n_pcrs, i); | |
1113 | if (r < 0) { | |
1114 | json_variant_unref_many(pcr_array, n_pcrs); | |
1115 | return -ENOMEM; | |
1116 | } | |
1117 | ||
1118 | n_pcrs++; | |
1119 | } | |
1120 | ||
1121 | r = json_variant_new_array(&a, pcr_array, n_pcrs); | |
1122 | json_variant_unref_many(pcr_array, n_pcrs); | |
1123 | if (r < 0) | |
1124 | return -ENOMEM; | |
1125 | ||
1126 | r = json_build(&v, | |
1127 | JSON_BUILD_OBJECT( | |
1128 | JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-tpm2")), | |
1129 | JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), | |
1130 | JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), | |
1131 | JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)), | |
07697bfe | 1132 | JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))), |
2b92a672 | 1133 | JSON_BUILD_PAIR_CONDITION(!!tpm2_primary_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_primary_alg_to_string(primary_alg))), |
5e521624 LP |
1134 | JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)))); |
1135 | if (r < 0) | |
1136 | return r; | |
1137 | ||
1138 | if (ret) | |
1139 | *ret = TAKE_PTR(v); | |
1140 | ||
1141 | return keyslot; | |
1142 | } | |
07697bfe | 1143 | |
2b92a672 | 1144 | const char *tpm2_pcr_bank_to_string(uint16_t bank) { |
07697bfe LP |
1145 | /* For now, let's officially only support these two. We can extend this later on, should the need |
1146 | * arise. */ | |
07697bfe LP |
1147 | if (bank == TPM2_ALG_SHA256) |
1148 | return "sha256"; | |
1149 | if (bank == TPM2_ALG_SHA1) | |
1150 | return "sha1"; | |
1151 | return NULL; | |
1152 | } | |
1153 | ||
1154 | int tpm2_pcr_bank_from_string(const char *bank) { | |
1155 | if (streq_ptr(bank, "sha256")) | |
1156 | return TPM2_ALG_SHA256; | |
1157 | if (streq_ptr(bank, "sha1")) | |
1158 | return TPM2_ALG_SHA1; | |
1159 | return -EINVAL; | |
1160 | } | |
2b92a672 LP |
1161 | |
1162 | const char *tpm2_primary_alg_to_string(uint16_t alg) { | |
1163 | if (alg == TPM2_ALG_ECC) | |
1164 | return "ecc"; | |
1165 | if (alg == TPM2_ALG_RSA) | |
1166 | return "rsa"; | |
1167 | return NULL; | |
1168 | } | |
1169 | ||
1170 | int tpm2_primary_alg_from_string(const char *alg) { | |
1171 | if (streq_ptr(alg, "ecc")) | |
1172 | return TPM2_ALG_ECC; | |
1173 | if (streq_ptr(alg, "rsa")) | |
1174 | return TPM2_ALG_RSA; | |
1175 | return -EINVAL; | |
1176 | } |