summaryrefslogtreecommitdiff
path: root/src/cryptsetup/cryptsetup-tpm2.c
blob: 8757212969169d3518918f867027bf9bcd57ead1 (plain)
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "cryptsetup-tpm2.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "parse-util.h"
#include "random-util.h"
#include "tpm2-util.h"

int acquire_tpm2_key(
                const char *volume_name,
                const char *device,
                uint32_t pcr_mask,
                uint16_t pcr_bank,
                uint16_t primary_alg,
                const char *key_file,
                size_t key_file_size,
                uint64_t key_file_offset,
                const void *key_data,
                size_t key_data_size,
                const void *policy_hash,
                size_t policy_hash_size,
                void **ret_decrypted_key,
                size_t *ret_decrypted_key_size) {

        _cleanup_free_ void *loaded_blob = NULL;
        _cleanup_free_ char *auto_device = NULL;
        size_t blob_size;
        const void *blob;
        int r;

        if (!device) {
                r = tpm2_find_device_auto(LOG_DEBUG, &auto_device);
                if (r == -ENODEV)
                        return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
                if (r < 0)
                        return r;

                device = auto_device;
        }

        if (key_data) {
                blob = key_data;
                blob_size = key_data_size;
        } else {
                _cleanup_free_ char *bindname = NULL;

                /* If we read the salt via AF_UNIX, make this client recognizable */
                if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0)
                        return log_oom();

                r = read_full_file_full(
                                AT_FDCWD, key_file,
                                key_file_offset == 0 ? UINT64_MAX : key_file_offset,
                                key_file_size == 0 ? SIZE_MAX : key_file_size,
                                READ_FULL_FILE_CONNECT_SOCKET,
                                bindname,
                                (char**) &loaded_blob, &blob_size);
                if (r < 0)
                        return r;

                blob = loaded_blob;
        }

        return tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
}

int find_tpm2_auto_data(
                struct crypt_device *cd,
                uint32_t search_pcr_mask,
                int start_token,
                uint32_t *ret_pcr_mask,
                uint16_t *ret_pcr_bank,
                uint16_t *ret_primary_alg,
                void **ret_blob,
                size_t *ret_blob_size,
                void **ret_policy_hash,
                size_t *ret_policy_hash_size,
                int *ret_keyslot,
                int *ret_token) {

        _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
        size_t blob_size = 0, policy_hash_size = 0;
        int r, keyslot = -1, token = -1;
        uint32_t pcr_mask = 0;
        uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
        uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */

        assert(cd);

        for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
                _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
                JsonVariant *w, *e;
                int ks;

                r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
                if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
                        continue;
                if (r < 0)
                        return log_error_errno(r, "Failed to read JSON token data off disk: %m");

                ks = cryptsetup_get_keyslot_from_token(v);
                if (ks < 0) {
                        /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
                         * us, but by the LUKS2 spec */
                        log_warning_errno(ks, "Failed to extract keyslot index from TPM2 JSON data token %i, skipping: %m", token);
                        continue;
                }

                w = json_variant_by_key(v, "tpm2-pcrs");
                if (!w || !json_variant_is_array(w))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                               "TPM2 token data lacks 'tpm2-pcrs' field.");

                assert(pcr_mask == 0);
                JSON_VARIANT_ARRAY_FOREACH(e, w) {
                        uintmax_t u;

                        if (!json_variant_is_number(e))
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                       "TPM2 PCR is not a number.");

                        u = json_variant_unsigned(e);
                        if (u >= TPM2_PCRS_MAX)
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                       "TPM2 PCR number out of range.");

                        pcr_mask |= UINT32_C(1) << u;
                }

                if (search_pcr_mask != UINT32_MAX &&
                    search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */
                        continue;

                assert(keyslot < 0);
                keyslot = ks;

                assert(pcr_bank == UINT16_MAX);
                assert(primary_alg == TPM2_ALG_ECC);

                /* The bank field is optional, since it was added in systemd 250 only. Before the bank was
                 * hardcoded to SHA256. */
                w = json_variant_by_key(v, "tpm2-pcr-bank");
                if (w) {
                        /* The PCR bank field is optional */

                        if (!json_variant_is_string(w))
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                       "TPM2 PCR bank is not a string.");

                        r = tpm2_pcr_bank_from_string(json_variant_string(w));
                        if (r < 0)
                                return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w));

                        pcr_bank = r;
                }

                /* The primary key algorithm field is optional, since it was also added in systemd 250
                 * only. Before the algorithm was hardcoded to ECC. */
                w = json_variant_by_key(v, "tpm2-primary-alg");
                if (w) {
                        /* The primary key algorithm is optional */

                        if (!json_variant_is_string(w))
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                       "TPM2 primary key algorithm is not a string.");

                        r = tpm2_primary_alg_from_string(json_variant_string(w));
                        if (r < 0)
                                return log_error_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));

                        primary_alg = r;
                }

                assert(!blob);
                w = json_variant_by_key(v, "tpm2-blob");
                if (!w || !json_variant_is_string(w))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                               "TPM2 token data lacks 'tpm2-blob' field.");

                r = unbase64mem(json_variant_string(w), SIZE_MAX, &blob, &blob_size);
                if (r < 0)
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                               "Invalid base64 data in 'tpm2-blob' field.");

                assert(!policy_hash);
                w = json_variant_by_key(v, "tpm2-policy-hash");
                if (!w || !json_variant_is_string(w))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                               "TPM2 token data lacks 'tpm2-policy-hash' field.");

                r = unhexmem(json_variant_string(w), SIZE_MAX, &policy_hash, &policy_hash_size);
                if (r < 0)
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                               "Invalid base64 data in 'tpm2-policy-hash' field.");

                break;
        }

        if (!blob)
                return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
                                       "No valid TPM2 token data found.");

        if (start_token <= 0)
                log_info("Automatically discovered security TPM2 token unlocks volume.");

        *ret_pcr_mask = pcr_mask;
        *ret_blob = TAKE_PTR(blob);
        *ret_blob_size = blob_size;
        *ret_policy_hash = TAKE_PTR(policy_hash);
        *ret_policy_hash_size = policy_hash_size;
        *ret_keyslot = keyslot;
        *ret_token = token;
        *ret_pcr_bank = pcr_bank;
        *ret_primary_alg = primary_alg;

        return 0;
}